Widgets

Pullable

Pengantar

Widget Pullable menambahkan fungsionalitas tarik-untuk-refresh dan muat-lebih-banyak ke konten scrollable apa pun. Widget ini membungkus child widget Anda dengan perilaku refresh dan paginasi berbasis gesture, mendukung beberapa gaya animasi header.

Dibangun di atas paket pull_to_refresh_flutter3, Pullable menyediakan API yang bersih dengan konstruktor bernama untuk konfigurasi umum.

Pullable(
  onRefresh: () async {
    // Fetch fresh data
    await fetchData();
  },
  child: ListView(
    children: items.map((item) => ListTile(title: Text(item))).toList(),
  ),
)

Penggunaan Dasar

Bungkus widget scrollable apa pun dengan Pullable:

Pullable(
  onRefresh: () async {
    await loadLatestPosts();
  },
  child: ListView.builder(
    itemCount: posts.length,
    itemBuilder: (context, index) => PostCard(post: posts[index]),
  ),
)

Ketika pengguna menarik ke bawah pada daftar, callback onRefresh akan dipanggil. Indikator refresh secara otomatis selesai ketika callback selesai.

Konstruktor

Pullable menyediakan konstruktor bernama untuk konfigurasi umum:

Konstruktor Gaya Header Deskripsi
Pullable() Water Drop Konstruktor default
Pullable.classicHeader() Classic Gaya tarik-untuk-refresh klasik
Pullable.waterDropHeader() Water Drop Animasi tetesan air
Pullable.materialClassicHeader() Material Classic Gaya klasik Material Design
Pullable.waterDropMaterialHeader() Water Drop Material Gaya tetesan air Material
Pullable.bezierHeader() Bezier Animasi kurva Bezier
Pullable.noBounce() Dapat dikonfigurasi Pantulan berkurang dengan ClampingScrollPhysics
Pullable.custom() Widget kustom Gunakan widget header/footer Anda sendiri
Pullable.builder() Dapat dikonfigurasi Kontrol penuh PullableConfig

Contoh

// Classic header
Pullable.classicHeader(
  onRefresh: () async => await refreshData(),
  child: myListView,
)

// Material header
Pullable.materialClassicHeader(
  onRefresh: () async => await refreshData(),
  child: myListView,
)

// No bounce effect
Pullable.noBounce(
  onRefresh: () async => await refreshData(),
  headerType: PullableHeaderType.classic,
  child: myListView,
)

// Custom header widget
Pullable.custom(
  customHeader: MyCustomRefreshHeader(),
  onRefresh: () async => await refreshData(),
  child: myListView,
)

PullableConfig

Untuk kontrol yang lebih detail, gunakan PullableConfig dengan konstruktor Pullable.builder():

Pullable.builder(
  config: PullableConfig(
    enablePullDown: true,
    enablePullUp: true,
    headerType: PullableHeaderType.materialClassic,
    onRefresh: () async => await refreshData(),
    onLoading: () async => await loadMoreData(),
    refreshCompleteDelay: Duration(milliseconds: 500),
    loadCompleteDelay: Duration(milliseconds: 300),
    physics: BouncingScrollPhysics(),
  ),
  child: myListView,
)

Semua Opsi Konfigurasi

Properti Tipe Default Deskripsi
enablePullDown bool true Mengaktifkan tarik-turun-untuk-refresh
enablePullUp bool false Mengaktifkan tarik-naik-untuk-muat-lebih
physics ScrollPhysics? null Fisika scroll kustom
onRefresh Future<void> Function()? null Callback refresh
onLoading Future<void> Function()? null Callback muat-lebih
headerType PullableHeaderType waterDrop Gaya animasi header
customHeader Widget? null Widget header kustom
customFooter Widget? null Widget footer kustom
refreshCompleteDelay Duration Duration.zero Jeda sebelum refresh selesai
loadCompleteDelay Duration Duration.zero Jeda sebelum loading selesai
enableOverScroll bool true Mengizinkan efek over-scroll
cacheExtent double? null Jangkauan cache scroll
semanticChildCount int? null Jumlah child semantik
dragStartBehavior DragStartBehavior start Cara gesture tarik dimulai

Gaya Header

Pilih dari lima animasi header bawaan:

enum PullableHeaderType {
  classic,           // Classic pull indicator
  waterDrop,         // Water drop animation (default)
  materialClassic,   // Material Design classic
  waterDropMaterial,  // Material water drop
  bezier,            // Bezier curve animation
}

Atur gaya melalui konstruktor atau config:

// Via named constructor
Pullable.bezierHeader(
  onRefresh: () async => await refreshData(),
  child: myListView,
)

// Via config
Pullable.builder(
  config: PullableConfig(
    headerType: PullableHeaderType.bezier,
    onRefresh: () async => await refreshData(),
  ),
  child: myListView,
)

Tarik Atas untuk Muat Lebih Banyak

Aktifkan paginasi dengan pemuatan tarik-atas:

Pullable.builder(
  config: PullableConfig(
    enablePullDown: true,
    enablePullUp: true,
    onRefresh: () async {
      // Reset to page 1
      page = 1;
      items = await fetchItems(page: page);
      setState(() {});
    },
    onLoading: () async {
      // Load next page
      page++;
      List<Item> more = await fetchItems(page: page);
      items.addAll(more);
      setState(() {});
    },
  ),
  child: ListView.builder(
    itemCount: items.length,
    itemBuilder: (context, index) => ItemTile(item: items[index]),
  ),
)

Header dan Footer Kustom

Sediakan widget header dan footer Anda sendiri:

Pullable.custom(
  customHeader: Container(
    height: 60,
    alignment: Alignment.center,
    child: CircularProgressIndicator(),
  ),
  customFooter: Container(
    height: 40,
    alignment: Alignment.center,
    child: Text("Loading more..."),
  ),
  enablePullUp: true,
  onRefresh: () async => await refreshData(),
  onLoading: () async => await loadMore(),
  child: myListView,
)

Controller

Gunakan RefreshController untuk kontrol programatik:

final RefreshController _controller = RefreshController();

Pullable(
  controller: _controller,
  onRefresh: () async => await refreshData(),
  child: myListView,
)

// Trigger refresh programmatically
_controller.triggerRefresh();

// Trigger loading programmatically
_controller.triggerLoading();

// Check state
bool refreshing = _controller.isRefreshing;
bool loading = _controller.isLoading;

Metode Extension pada RefreshController

Metode/Getter Tipe Kembalian Deskripsi
triggerRefresh() void Memicu refresh secara manual
triggerLoading() void Memicu muat-lebih secara manual
isRefreshing bool Apakah refresh sedang aktif
isLoading bool Apakah loading sedang aktif

Metode Extension

Widget apa pun dapat dibungkus dengan tarik-untuk-refresh menggunakan extension .pullable():

ListView(
  children: items.map((item) => ListTile(title: Text(item.name))).toList(),
).pullable(
  onRefresh: () async {
    await fetchItems();
  },
)

Dengan config kustom:

myListView.pullable(
  onRefresh: () async => await refreshData(),
  pullableConfig: PullableConfig(
    headerType: PullableHeaderType.classic,
    enablePullUp: true,
    onLoading: () async => await loadMore(),
  ),
)

Integrasi CollectionView

CollectionView menyediakan varian pullable dengan paginasi bawaan:

CollectionView.pullable

CollectionView<User>.pullable(
  data: (iteration) async => api.getUsers(page: iteration),
  builder: (context, item) => UserTile(user: item.data),
  onRefresh: () => print('Refreshed!'),
  headerStyle: 'WaterDropHeader',
)

CollectionView.pullableSeparated

CollectionView<User>.pullableSeparated(
  data: (iteration) async => api.getUsers(page: iteration),
  builder: (context, item) => UserTile(user: item.data),
  separatorBuilder: (context, index) => Divider(),
)

CollectionView.pullableGrid

CollectionView<Product>.pullableGrid(
  data: (iteration) async => api.getProducts(page: iteration),
  builder: (context, item) => ProductCard(product: item.data),
  crossAxisCount: 2,
  mainAxisSpacing: 8,
  crossAxisSpacing: 8,
)

Parameter Khusus Pullable

Parameter Tipe Deskripsi
data Function(int iteration) Callback data berpaginasi (iterasi dimulai dari 1)
onRefresh Function()? Callback setelah refresh
beforeRefresh Function()? Hook sebelum refresh dimulai
afterRefresh Function(dynamic)? Hook setelah refresh dengan data
headerStyle String? Nama tipe header (misalnya, 'WaterDropHeader', 'ClassicHeader')
footerLoadingIcon Widget? Indikator loading kustom untuk footer

Contoh

Daftar Berpaginasi dengan Refresh

class _PostListState extends NyState<PostListPage> {
  List<Post> posts = [];
  int page = 1;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Pullable.builder(
        config: PullableConfig(
          enablePullDown: true,
          enablePullUp: true,
          headerType: PullableHeaderType.materialClassic,
          onRefresh: () async {
            page = 1;
            posts = await api<PostApiService>((request) => request.getPosts(page: page));
            setState(() {});
          },
          onLoading: () async {
            page++;
            List<Post> more = await api<PostApiService>((request) => request.getPosts(page: page));
            posts.addAll(more);
            setState(() {});
          },
        ),
        child: ListView.builder(
          itemCount: posts.length,
          itemBuilder: (context, index) => PostCard(post: posts[index]),
        ),
      ),
    );
  }
}

Refresh Sederhana dengan Extension

ListView(
  children: notifications
    .map((n) => ListTile(
      title: Text(n.title),
      subtitle: Text(n.body),
    ))
    .toList(),
).pullable(
  onRefresh: () async {
    notifications = await fetchNotifications();
    setState(() {});
  },
)