flutter_custom_refresh_indicator

repository·master·Indexed 20 days ago

https://github.com/gonuit/flutter-custom-refresh-indicator

A Flutter package for creating highly customizable refresh indicator widgets. It provides CustomMaterialIndicator for simple Material replacements and CustomRefreshIndicator for complex, fully animated custom designs. The library includes an IndicatorController to manage states (idle, dragging, armed, loading, etc.), control trigger behavior via IndicatorTrigger and IndicatorTriggerMode, and fine-tune timing using RefreshIndicatorDurations.

Tokens
5.8K
Snippets
10
Records
19
Agent score
69%

What's inside flutter_custom_refresh_indicator

  1. Understand Indicator States

    master

    The IndicatorController provides access to the current state of the refresh process. You can use these states to drive your custom animations:

    StateValue RangeDescription
    idle0.0Default state; indicator is not visible.
    dragging0.0 to 1.0User is pulling down, but hasn't reached the threshold.
    armedAt or above 1.0Threshold reached. Releasing will trigger onRefresh.
    cancelingAnimates to 0.0Pull-down stopped before threshold; indicator retracts.
    loadingSteady at 1.0onRefresh callback is currently active.
    completeSteady at 1.0Refresh is finished; stays visible if completeDuration is set.
    finalizing1.0 to 0.0Refresh finished; indicator is animating back to initial state.
  2. Create a fully custom indicator with CustomRefreshIndicator

    master

    To build a completely unique refresh experience, use CustomRefreshIndicator. You wrap your scrollable list and provide a builder function. The builder receives an IndicatorController, which you can use to drive animations or UI changes based on the current pull distance and state.

    CustomRefreshIndicator(
      onRefresh: onRefresh, // Your refresh logic
      builder: (context, child, controller) {
        // Place your custom indicator here.
        return MyIndicator(
          child: child,
          controller: controller,
        );
      },
      child: ListView.builder(
        itemBuilder: (_, index) => Text('Item $index'),
      ),
    )
  3. Quick start with CustomMaterialIndicator

    master

    If you only need to replace the content of the standard Material refresh indicator while keeping its basic behavior, use CustomMaterialIndicator. It supports horizontal lists and triggering from both edges (via the trigger argument).

    CustomMaterialIndicator(
      onRefresh: onRefresh, // Your refresh logic
      backgroundColor: Colors.white,
      indicatorBuilder: (context, controller) {
        return Padding(
          padding: const EdgeInsets.all(6.0),
          child: CircularProgressIndicator(
            color: Colors.redAccent,
            value: controller.state.isLoading ? null : math.min(controller.value, 1.0),
          ),
        );
      },
      child: child,
    )
  4. Configure trigger and triggerMode

    master

    The CustomRefreshIndicator allows you to control which edges of a list can initiate a refresh and how the scroll position affects activation.

    trigger (IndicatorTrigger)

    Determines which edge of the list can initiate the pull-to-refresh gesture. This is useful for handling reversed lists.

    • leadingEdge: Initiated from the leading edge (top for standard, bottom for reversed).
    • trailingEdge: Initiated from the trailing edge (bottom for standard, top for reversed).
    • bothEdges: Can be triggered from both ends.

    triggerMode (IndicatorTriggerMode)

    Controls activation relative to the scrollable's position when a drag starts.

    • anywhere: Refresh can be triggered from any position in the scrollable.
    • onEdge: Refresh only triggers if the scrollable is at the edge when the drag begins (Default: onEdge).
  5. How the IndicatorController and builder work together

    master

    The IndicatorController is the bridge between the scroll gestures and your custom UI.

    1. Value: The controller.value is an animation value. 0.0 is idle, 1.0 is the point where the indicator is considered 'armed', and values up to 1.5 are used for overshoot/settling.
    2. State: The controller.state tracks the lifecycle: idle, dragging, armed, settling, loading, canceling, finalizing, or complete.
    3. Edge: The controller.edge tells you if the interaction is happening at the leading or trailing edge.

    When autoRebuild is true, the builder function is automatically called whenever these properties change, allowing you to create smooth, reactive animations.

  6. Use CustomRefreshIndicator to create custom pull-to-refresh UI

    master

    The CustomRefreshIndicator widget allows you to build highly customized pull-to-refresh animations. It wraps a scrollable widget (like ListView) and provides an IndicatorBuilder to render your custom UI based on the current scroll progress and state.

    To use it, you must provide:

    1. child: The scrollable widget.
    2. onRefresh: An AsyncCallback that performs the refresh logic.
    3. builder: A function that takes the BuildContext, the child, and an IndicatorController to build your indicator widget.

    By default, the widget automatically rebuilds whenever the controller's state or value changes (controlled by the autoRebuild parameter).

    CustomRefreshIndicator(
      onRefresh: () async {
        // Perform refresh logic here
        await Future.delayed(Duration(seconds: 2));
      },
      builder: (context, child, controller) {
        return Stack(
          children: [
            child,
            // Use controller.value (0.0 to 1.5) to drive animations
            Positioned(
              top: controller.value * 50,
              child: CircularProgressIndicator(),
            ),
          ],
        );
      },
      child: ListView.builder(
        itemCount: 20,
        itemBuilder: (context, index) => ListTile(title: Text('Item $index')),
      ),
    )
  7. React to state changes with onStateChanged

    master

    Use the onStateChanged callback in CustomRefreshIndicator to execute logic when the indicator transitions between different states (e.g., from dragging to armed). The callback provides an IndicatorStateChange object which includes a didChange method to check for specific transitions.

    Common use cases include playing sounds, triggering animations, or resetting UI elements when the indicator returns to the idle state.

    CustomRefreshIndicator(
      onRefresh: onRefresh,
      // Track state changes with the onStateChanged callback.
      onStateChanged: (IndicatorStateChange change) {
        // When transitioning from dragging to armed state, do something:
        if (change.didChange(from: IndicatorState.dragging, to: IndicatorState.armed)) {
          // Handle the armed state, e.g., play a sound, start an animation, etc.
        }
        // When returning to the idle state from any other state, do something else:
        else if (change.didChange(to: IndicatorState.idle)) {
          // Reset any animations, update UI elements, etc.
        }
      },
    )
  8. CustomRefreshIndicator Parameters

    master

    The CustomRefreshIndicator widget accepts several parameters to control its behavior, timing, and appearance:

    Basic

    • child (Widget): The scrollable content (e.g., ListView).
    • builder (IndicatorBuilder): Function returning the indicator widget.
    • onRefresh (AsyncCallback): The logic to execute when refresh is triggered. Must return a Future.
    • controller (IndicatorController?): Manages state and interaction.

    Timing and Durations (RefreshIndicatorDurations)

    • cancelDuration: Duration to hide the indicator after canceling.
    • settleDuration: Duration for the indicator to settle after release.
    • finalizeDuration: Duration to hide the indicator after refreshing.
    • completeDuration: (Optional) How long the indicator stays in the complete state after onRefresh finishes. If omitted, it skips complete and goes straight to finalizing.

    Trigger Behavior

    • offsetToArmed (double?): Pixel distance required to trigger refresh.
    • containerExtentPercentageToArmed (double?): Percentage of container extent required to arm.
    • trigger (IndicatorTrigger): Defines which edge (leading, trailing, or both) triggers the refresh.
    • triggerMode (IndicatorTriggerMode): Configures the condition for triggering.

    Other

    • onStateChanged (OnStateChanged?): Callback when the indicator state changes.
    • notificationPredicate (ScrollNotificationPredicate): Determines which ScrollNotifications trigger the indicator.
    • leadingScrollIndicatorVisible (bool): Visibility of the leading indicator.
    • trailingScrollIndicatorVisible (bool): Visibility of the trailing indicator.
    • autoRebuild (bool): Whether to automatically rebuild the indicator on controller updates.
  9. Access indicator state via IndicatorController

    master

    The IndicatorController provides real-time access to the indicator's current status, position, and movement details. This is essential for building highly customized UI that reacts to the user's physical drag actions.

    Core Properties

    • state (IndicatorState): The current lifecycle state of the indicator.
    • edge (IndicatorEdge?): Which end the gesture started from (start or end). Useful when trigger is set to bothEdges.
    • side (IndicatorSide): Where the indicator is positioned (top, bottom, left, right, or none).
    • direction (AxisDirection): The axis direction of the scrollable content.
    • scrollingDirection (ScrollDirection): The current direction the user is scrolling.
    • dragDetails (DragUpdateDetails?): Detailed pointer information during a drag.

    dragDetails Sub-properties

    When dragDetails is available, you can access:

    • globalPosition: The global pointer position.
    • delta: The distance moved since the last update.
    • primaryDelta: The distance moved along the primary axis (e.g., vertical movement in a vertical list).
  10. Configure RefreshIndicatorDurations

    master

    Use the RefreshIndicatorDurations class to customize the timing of different animation phases in the refresh indicator. This allows you to control how quickly the indicator cancels, settles into a loading state, or finalizes after a refresh is complete.

    Available Durations

    PropertyDescriptionDefault
    cancelDurationHow long it takes to hide the indicator when dragging is stopped before the onRefresh callback is triggered.300ms
    settleDurationThe time taken to settle the pointer to the target location (value 1.0) after releasing the pointer in the armed state. The state changes to IndicatorState.loading during this phase.150ms
    finalizeDurationHow long it takes to hide the pointer after the onRefresh function completes. The value decreases from 1.0 to 0.0 and the state changes to IndicatorState.finalizing.100ms
    completeDurationHow long the indicator remains at value 1.0 in the IndicatorState.complete state after onRefresh completes.null (no delay)
    const durations = RefreshIndicatorDurations(
      cancelDuration: Duration(milliseconds: 500),
      settleDuration: Duration(milliseconds: 200),
      finalizeDuration: Duration(milliseconds: 150),
      completeDuration: Duration(seconds: 1),
    );
  11. Transform IndicatorController animations

    master

    Because IndicatorController is an Animation<double>, you can derive new animations to drive your custom indicator's UI (e.g., scaling, rotating, or opacity) without manually calculating values.

    • normalize(): Returns an Animation<double> mapped to the range [0.0, 1.0]. This is the most common way to drive custom UI.
    • clamp(min, max): Returns a ClampedAnimation that restricts the controller's value to a specific range.
    • transform(min, max): Returns a TransformedAnimation that maps the controller's internal range (minValue to maxValue) to a new range [min, max].
    // Map the indicator progress to a scale between 1.0 and 2.0
    Animation<double> scaleAnimation = controller.transform(1.0, 2.0);
    
    // Map the indicator progress to a 0.0 to 1.0 range for opacity
    Animation<double> opacityAnimation = controller.normalize();
  12. Use CustomMaterialIndicator for Material-style refresh

    master

    The CustomMaterialIndicator widget replicates the standard Material refresh indicator behavior but allows for extensive customization of its appearance, displacement, and container properties.

    You can use the default constructor for a standard Material look or the .adaptive() constructor to automatically switch between CupertinoActivityIndicator (iOS/macOS) and RefreshProgressIndicator (Android/others) based on the platform.

    Key customization options include:

    • displacement: The distance from the edge where the indicator settles.
    • edgeOffset: The offset where the indicator starts to appear on drag.
    • indicatorBuilder: A function to provide a custom widget for the indicator.
    • scrollableBuilder: A builder to construct the scrollable widget (e.g., a ListView).
    CustomMaterialIndicator(
      onRefresh: () async {
        // Your refresh logic here
      },
      child: ListView.builder(
        itemCount: 20,
        itemBuilder: (context, index) => ListTile(title: Text('Item $index')),
      ),
    );