1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/geekharmony-hmosapp1

Клонировать/Скачать
README6.md 21 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 06.06.2025 17:52 d53c9aa

loading_more_list

Быстрая поддержка списков, таблиц, водопадных потоков и загрузки дополнительных данных при прокрутке вниз.

LoadingMoreList LoadingMoreGrid
LoadingMoreWaterFlow LoadingMoreCustomIndicator

Установка

ohpm install @candies/loading_more_list

Состояния списка

IndicatorStatus

У нас есть 7 различных состояний для одного списка.

export enum IndicatorStatus {
  none, // Инициализированное состояние
  loadingMoreBusying, // Загрузка дополнительных данных
  fullScreenBusying, // Полноэкранный прогресс загрузки до первой загрузки данных
  loadingMoreError, // Ошибка загрузки дополнительных данных
  fullScreenError, // Полноэкранный прогресс загрузки с ошибкой
  noMoreLoad, // Больше данных нет
  empty // Список пуст
}
```Эти состояния можно разделить на три основных сценария:

1. Инициализированное состояние
* `none`

2. Состояние до первой загрузки данных
* `fullScreenBusying`
* `fullScreenError`
* `empty`

3. Состояния при наличии данных
* `loadingMoreBusying`
* `loadingMoreError`
* `noMoreLoad`

Эти три состояния рисуются путем добавления элемента в конец данных.

Ключевые методы находятся в `LoadingMoreBase` - `totalCount` и `getData`.

```typescript
lastItemIsLoadingMoreItem: boolean = true;

totalCount(): number {
  return this.length + (this.lastItemIsLoadingMoreItem ? 1 : 0);
}

Подготовка данных

LoadingMoreBase

Для реализации источника данных с подгрузкой необходимо наследовать LoadingMoreBase<T>. Перегрузите метод loadData для загрузки данных. Не забудьте устанавливать hasMore в false, когда данных больше нет.

Вот пример источника данных:

  • Перегрузите метод refresh для инициализации начальных значений. Этот метод можно использовать для обновления всего списка при выполнении действия "отскролить вниз".
  • Перегрузите метод loadData для предоставления логики загрузки данных и проверки наличия дополнительных данных. Если загрузка прошла успешно, используйте this.addAll для добавления новых данных и верните true. Если загрузка не удалась, верните false.
import {
  LoadingMoreBase,
} from '@candies/loading_more_list'
import { FeedList, TuChongSource } from './TuChongSource';
import http from '@ohos.net.http';

export class TuChongRepository extends LoadingMoreBase<FeedList> {
  public hasMore: boolean = true;
  page: number = 1;
}
``````## Использование

### Импорт ссылок
```typescript
import {
  LoadingMoreList,
  LoadingMoreBase,
  IndicatorWidget,
  IndicatorStatus,
} from '@candies/loading_more_list'

LoadingMoreList

LoadingMoreList — это наш компонент для загрузки дополнительных элементов. Его параметры следующие:

/// Список, может быть List, Grid или WaterFlow
@BuilderParam
private builder: () => void;
/// Источник данных для списка
@Link sourceList: LoadingMoreBase<any>;
/// Конструктор состояния индикатора, только для [IndicatorStatus.fullScreenBusying,IndicatorStatus.fullScreenError,IndicatorStatus.empty]
@BuilderParam
indicatorBuilder?: ($$: IndicatorParam) => void = this.buildIndicator;

Примеры

Подготовим простой источник данных.

import {
  LoadingMoreBase,
} from '@candies/loading_more_list'
``````typescript
export class ListData extends LoadingMoreBase<number> {
  hasMore: boolean = true;
  pageSize: number = 10;
  maxCount: number = 20;

  public async refresh(notifyStateChanged: boolean = false): Promise<boolean> {
    this.hasMore = true;
    return super.refresh(notifyStateChanged);
  }

  async loadData(isLoadMoreAction: boolean): Promise<boolean> {
    // Моделируем задержку запроса на 1 секунду
    return new Promise<boolean>((resolve) => {
      setTimeout(() => {
          var length = this.length;
          let list = [];
     
          for (let index = length; index < length + this.pageSize; index++) {
            list.push(index);
          }
          this.addAll(list);
        
        if (this.length >= this.maxCount) {
          this.hasMore = false;
        }
        resolve(this.isSuccess);
      }, 1000);
    });
  }
}

Список (List)

Мы должны учитывать только случаи, когда элементы списка превышают его длину. Если при построении элементов списка обнаруживается, что это LoadingMoreItem, то можно использовать следующий метод для создания соответствующего состояния UI.

if (this.listData.isLoadingMoreItem(item))
  IndicatorWidget({
    indicatorStatus: this.listData.getLoadingMoreItemStatus(item),
    sourceList: this.listData,
  })

Полный код представлен ниже:

import { LoadingMoreList, IndicatorWidget } from '@candies/loading_more_list'
import { ListData } from '../data/ListData';
@Entry
@Component
struct ЗагрузкаДополнительныхЭлементовDemo {
  @State listData: ListData = new ListData();
}
```  @Builder
  построитьСписок() {
    Список() {
      ЛениваяПоследовательность(this.listData, (элемент, индекс) => {
        ЭлементСписка() {
          // индекс == this.listData.length
          если (this.listData.загрузкаДополнительныхЭлементов(элемент))
            ИндикаторВиджет({
              статусИндикатора: this.listData.статусЗагрузкиДополнительныхЭлементов(элемент),
              исходныйСписок: this.listData,
            })
          иначе
            Текст(`${элемент}`,).выравнивание(Выравнивание.Центр).высота(100)
        }.ширина('100%')
      },
        (элемент, индекс) => {
          вернуть `${элемент}`
        }
      )
    }
    .растяжение(1)
    .наДостижениеКонца(() => {
      this.listData.загрузкаДополнительныхЭлементов();
    })
  }    })
  }

  построить() {
    Навигация() {
      ЗагрузкаДополнительныхЭлементов({
        исходныйСписок: this.listData,
        построитель: () => this.построитьСписок(),
      })
    }
    .заголовок('ЗагрузкаДополнительныхЭлементовDemo').режимЗаголовкаНавигации(НавигацияЗаголовокРежим.Мини)
  }
}

Когда в Список вызывается наДостижениеКонца обратный вызов, вы можете вручную вызвать метод загрузкаДополнительныхЭлементов. Конечно, вы также можете не вызывать его вручную, так как ЗагрузкаДополнительныхБаза уже автоматически вызывает его. Разница заключается в том, что если загрузка дополнительных элементов не удалась, вы добавили этот вручную вызов, вы можете использовать прокрутку вверх, чтобы снова вызвать загрузку дополнительных элементов. В противном случае, вы можете использовать, например, нажатие, чтобы снова вызвать загрузкаДополнительныхЭлементов.

Таблица (Grid)

Аналогично списку, но обратите внимание, что построение последнего элемента немного отличается. Вам нужно установить columnStart и columnEnd для последнего элемента GridItem, чтобы сделать его кросс-колонкой, что позволит ему занимать всю строку (конечно, это обычно, вы можете настроить его в зависимости от ваших потребностей).

import { ЗагрузкаДополнительныхЭлементов, ЗагрузкаДополнительныхБаза, ИндикаторВиджет } from '@candies/загрузка_дополнительных_элементов'
import { ListData } from '../data/ListData';
```@Entry
@Component
struct ЗагрузкаДополнительныхЭлементовGridDemo {
  @State listData: ListData = new ListData();

  передПоявлением() {
    this.listData.размерСтраницы = 50;
    this.listData.максимальноеКоличество = 100;
  }

```typescript
  @Builder
  buildList() {
    Grid() {
      LazyForEach(this.listData, (item, index) => {
        // index == this.listData.length
        if (this.listData.isLoadingMoreItem(item))
          GridItem() {
            IndicatorWidget({
              indicatorStatus: this.listData.getLoadingMoreItemStatus(item),
              sourceList: this.listData,
            })
          }
          // loading more item занимает одну строку, вы можете определить это в зависимости от вашего случая
          .columnStart(0).columnEnd(4)
        else
          GridItem() {
            Text(`${item}`,).align(Alignment.Center)
          }.height(100).width('100%')
      },
        (item, index) => {
          return `${item}`
        }
      )
    }
    .flexGrow(1)
    .columnsTemplate('1fr 1fr 1fr 1fr 1fr')
    .columnsGap(10)
    .rowsGap(10)
  }
  // api10
  // .onReachEnd(() => {
  //   this.listData.loadMore();
  //
  // })
}

build() {
  Navigation() {
    LoadingMoreList({
      sourceList: this.listData,
      builder: () => this.buildList(),
    })
  }
  .title('Загрузка дополнительных элементов').titleMode(NavigationTitleMode.Mini)
}

Водопад (WaterFlow)

Официальный водопад предоставляет footer обратный вызов, который можно использовать для создания стиля последнего элемента.

Сначала нам нужно установить lastItemIsLoadingMoreItem в false.

this.listData.lastItemIsLoadingMoreItem = false;

Затем используем footer обратный вызов для создания компонента для загрузки дополнительных элементов.```typescript @Builder buildFooter() { if (!this.listData.hasMore) { IndicatorWidget({ indicatorStatus: IndicatorStatus.noMoreLoad, }); } else if (this.listData.indicatorStatus === IndicatorStatus.loadingMoreError) { IndicatorWidget({ indicatorStatus: IndicatorStatus.loadingMoreError, sourceList: this.listData, }); } else { IndicatorWidget({ indicatorStatus: IndicatorStatus.loadingMoreBusying, }); } }

import { LoadingMoreList, IndicatorWidget, IndicatorStatus } from '@candies/loading_more_list'
import { TuChongRepository } from '../data/TuChongRepository';
import { FeedList } from '../data/TuChongSource';

@Entry
@Component
struct LoadingMoreWaterFlowDemo {
  @State listData: TuChongRepository = new TuChongRepository();

  aboutToAppear() {
    this.listData.lastItemIsLoadingMoreItem = false;
  }

  @Builder
  buildFooter() {
    if (!this.listData.hasMore) {
      IndicatorWidget({
        indicatorStatus: IndicatorStatus.noMoreLoad,
      });
    } else if (this.listData.indicatorStatus === IndicatorStatus.loadingMoreError) {
      IndicatorWidget({
        indicatorStatus: IndicatorStatus.loadingMoreError,
        sourceList: this.listData,
      });
    } else {
      IndicatorWidget({
        indicatorStatus: IndicatorStatus.loadingMoreBusying,
      });
    }
  }
}
```  @Builder
  buildList() {
    WaterFlow({
      footer: () => this.buildFooter()
    }) {
      LazyForEach(this.listData, (item, index) => {
        FlowItem() {
          TuChongImageListItem({ item: item, index: index })
        }
      },
        (item, index) => {
          var feedList = item as FeedList;
          if ('post_id' in feedList) {
            return `${feedList.post_id}`;
          }
          return `${item}`;
        }
      )
    }
    .columnsTemplate("1fr 1fr")
    .columnsGap(10)
    .rowsGap(5)
    .flexGrow(1)
    .onReachEnd(() => {
      this.listData.loadMore();
    })
  }```markdown
build() {
  Navigation() {
    LoadingMoreList({
      sourceList: this.listData,
      builder: () => this.buildList(),
    })
  }
  .title('LoadingMoreWaterFlowDemo').titleMode(NavigationTitleMode.Mini)
}
}

@Component
struct TuChongImageListItem {
  item: FeedList;
  index: number;

  hasImage(): boolean {
    return this.item.images.length !== 0;
  }

  imageUrl(): string {
    if (!this.hasImage()) {
      return '';
    }

    return `https://photo.tuchong.com/${this.item.images[0].user_id}/f/${this.item.images[0].img_id}.jpg`;
  }

  avatarUrl() {
    return this.item.site.icon;
  }

  imageTitle() {
    if (!this.hasImage()) {
      return this.item.title;
    }

    return this.item.images[0].title;
  }
}
```  imageDescription() {
    if (!this.hasImage()) {
      return this.item.content;
    }

    return this.item.images![0].description;
  }

  aboutToAppear() {
    // вывод в консоль this.imageUrl
  }

  getAspectRatio(): number {
    return this.item.images[0].width / this.item.images![0].height;
  }

  build() {
    Column() {
      if (this.hasImage())
        Image(this.imageUrl())
          .objectFit(ImageFit.Fill)
          .autoResize(true)
          .width('100%')
          .aspectRatio(this.getAspectRatio())
          .backgroundColor('#22808080')
      Divider()
    }
  }
}
```

### Структурированный компонент состояния

Если мы не создаем структурированный компонент состояния, по умолчанию предоставляется `IndicatorWidget`, который создает `UI` для различных состояний.

Мы можем создать собственный компонент состояния `CustomIndicatorWidget` с помощью обратного вызова `indicatorBuilder` и обработки последнего элемента, чтобы создать пользовательский эффект состояния.

Полный код представлен ниже:

```typescript
import { LoadingMoreList, LoadingMoreBase, IndicatorStatus, IndicatorParam, } from '@candies/loading_more_list'
import { ListData } from '../data/ListData';

@Entry
@Component
struct LoadingMoreCustomIndicatorDemo {
  @State listData: ListData = new ListData();
}
```  @Builder
  buildList() {
    List() {
      LazyForEach(this.listData, (item, index) => {
        ListItem() {
          // index равно this.listData.length
          if (this.listData.isLoadingMoreItem(item))
            CustomIndicatorWidget({
              indicatorStatus: this.listData.getLoadingMoreItemStatus(item),
              sourceList: this.listData,
            })
          else
            Text(`${item}`,).align(Alignment.Center).height(100)
        }.width('100%')
      },
        (item, index) => {
          return `${item}`
        }
      )
    }
    .flexGrow(1)
    .onReachEnd(() => {
      this.listData.loadMore();    })
  }

  @Builder
  buildIndicator($$: IndicatorParam) {
    CustomIndicatorWidget({ indicatorStatus: $$.indicatorStatus, sourceList: $$.sourceList, })
  }

  build() {
    Navigation() {
      LoadingMoreList({
        sourceList: this.listData,
        builder: () => this.buildList(),
        indicatorBuilder: ($$: IndicatorParam) => this.buildIndicator(
          { indicatorStatus: $$.indicatorStatus, sourceList: $$.sourceList, }
        ),
      })
    }
    .title('Загрузка дополнительных элементов с пользовательским индикатором').titleMode(NavigationTitleMode.Mini)
  }
}@Component
export struct CustomIndicatorWidget {
  /// Источник списка на основе [LoadingMoreBase].
  indicatorStatus: IndicatorStatus;
  sourceList: LoadingMoreBase<any>;

```markdown
  build() {
    if (this.indicatorStatus == IndicatorStatus.none)
      Column()
    else if (this.indicatorStatus == IndicatorStatus.fullScreenBusying)
      Row() {
        Text('Загрузка... Не торопитесь'),
        LoadingProgress().width(50).height(50).margin({ left: 10 })
      }.justifyContent(FlexAlign.Center).width('100%').height('100%')
    else if (this.indicatorStatus == IndicatorStatus.fullScreenError)
      Row() {
        Text('Похоже, произошла ошибка? Нажмите для обновления')
      }.justifyContent(FlexAlign.Center)
      .width('100%').height('100%').onClick((event) => {
        this.sourceList.errorRefresh();
      })
    else if (this.indicatorStatus == IndicatorStatus.empty)
      Row() {
        Text('Здесь только воздух!')
      }.justifyContent(FlexAlign.Center).width('100%').height('100%')
    else if (this.indicatorStatus == IndicatorStatus.loadingMoreBusying)
      Row() {
        Text('Загрузка... Не тяните слишком сильно'),
        LoadingProgress().width(40).height(40).margin({ left: 10 })
      }.justifyContent(FlexAlign.Center).width('100%').height(50).backgroundColor('#22808080')
    else if (this.indicatorStatus == IndicatorStatus.loadingMoreError)
      Row() {
        Text('Сетевая связь нестабильна? Нажмите для повторной загрузки!')
      }
      .justifyContent(FlexAlign.Center)
      .width('100%')
      .height(50)
      .backgroundColor('#22808080')
      .onClick((event) => {
        this.sourceList.errorRefresh();
      })
    else if (this.indicatorStatus == IndicatorStatus.noMoreLoad)
      Row() {
        Text('Я уже на пределе, не тяните больше!')
      }.justifyContent(FlexAlign.Center).width('100%').backgroundColor('#22808080').height(50)
    else
      Column()
  }
}
```

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/geekharmony-hmosapp1.git
git@api.gitlife.ru:oschina-mirror/geekharmony-hmosapp1.git
oschina-mirror
geekharmony-hmosapp1
geekharmony-hmosapp1
master