SoFunction
Updated on 2025-04-06

Android refresh loading framework details

This article shares the specific code of Android refresh loading framework for your reference. The specific content is as follows

1. Define an interface to control pull-down and pull-up

public interface Pullable {

  /**
    * Is it possible to pull down
    */
  boolean canPullDown();

  /**
    * Is it possible to pull up
    */
  boolean canPullUp();
}

2. Define a refresh loading layout

public class PullToRefreshLayout extends RelativeLayout {

  /**
    * head
    */
  private View headView;//Head view  private ImageView headIv;//Head icon  private TextView headTv;//Initial text  private int headHeight;//Height  private float headBorder;//Head critical  /**
    * pull
    */
  private View pullView;//Pull view  private int pullHeight;//Pull height  private int pullWidth;//Width
  /**
    * Final
    */
  private View footView;//Tail view  private ImageView footIv;//Tail icon  private TextView footTv;//The end text  private int footHeight;//Tail height  private float footBorder;//Tail critical
  /**
    * state
    */
  public static final int INIT = 0;//initial  public static final int RELEASE_TO_REFRESH = 1;//Release refresh  public static final int REFRESHING = 2;//Refreshing  public static final int RELEASE_TO_LOAD = 3;//Release load  public static final int LOADING = 4;//Loading  public static final int DONE = 5;//Finish  private int state = INIT;

  /**
    * Interface
    */
  private OnRefreshListener mListener;

  private float downY;//Y coordinate when pressed  private float lastY;//Previous Y coordinate  private float pullDownY = 0;// Pull down offset  private float pullUpY = 0;//Pull-up offset  private int offset;//Offset
  /**
    * Animation
    */
  private RotateAnimation rotateAnimation;

  /**
    * Thread
    */
  private Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      (msg);
      if (msg != null) {
        switch () {
          case 1:
            ();
            break;
          case 2:
            ();
            break;
          default:
            break;
        }
        pullDownY = 0;
        pullUpY = 0;
        requestLayout();
        state = INIT;
        refreshViewByState();
        isTouch = true;
      }
    }
  };

  /**
    * First execution layout
    */
  private boolean isLayout = false;
  /**
    * Slide during refresh
    */
  private boolean isTouch = false;
  /**
    * The ratio of the sliding distance between the finger and the sliding distance between the pull-down head will change with the tangent function.
    */
  private float radio = 2;
  /**
    * Filter multi-touch
    */
  private int mEvents;
  /**
    * These two variables are used to control the direction of pull. If there is no control, it cannot be pulled up when the situation meets the situation.
    */
  private boolean canPullDown = true;
  private boolean canPullUp = true;

  public void setOnRefreshListener(OnRefreshListener listener) {
    mListener = listener;
  }

  public PullToRefreshLayout(Context context) {
    super(context);
    initView(context);
  }

  public PullToRefreshLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    initView(context);
  }

  public PullToRefreshLayout(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    initView(context);
  }

  private void initView(Context context) {
    rotateAnimation = (RotateAnimation) (context, );
  }

  private void refreshViewByState() {
    switch (state) {
      case INIT:
        // Initial state of pull-down layout        ("Drag down to refresh the displayed picture");
        ("Drive down to refresh");
        // Initial state of pull-up layout        ("Pull up to load the displayed picture");
        ("Pull-up Loading");
        break;
      case RELEASE_TO_REFRESH:
        // Release refresh status        ("Release refreshed image");
        ("Release Refresh");
        break;
      case REFRESHING:
        // Refreshing status        ("Refreshing the displayed image");
        ("Refreshing");
        break;
      case RELEASE_TO_LOAD:
        // Release the loading state        ("Release the picture loaded");
        ("Release Loading");
        break;
      case LOADING:
        // Loading status        ("The displayed picture is loading");
        ("Loading");
        break;
      case DONE:
        // After refreshing or loading, do nothing        break;
    }
  }

  /**
    * No limit on pull-up or pull-down
    */
  private void releasePull() {
    canPullDown = true;
    canPullUp = true;
  }
  
  @Override
  public boolean dispatchTouchEvent(MotionEvent ev) {
    switch (()) {
      case MotionEvent.ACTION_DOWN:
        downY = ();
        lastY = downY;
        mEvents = 0;
        releasePull();
        if (state != REFRESHING && state != LOADING) {
          isTouch = true;
        }
        break;
      case MotionEvent.ACTION_POINTER_DOWN:
      case MotionEvent.ACTION_POINTER_UP:
        // Filter multi-touch        mEvents = -1;
        break;
      case MotionEvent.ACTION_MOVE:
        if (mEvents == 0) {
          if (pullDownY > 0 || (((Pullable) pullView).canPullDown()
              && canPullDown && state != LOADING && state != REFRESHING)) {
            // Can be pulled down, but cannot be pulled down when loading            // Reduce the actual sliding distance, causing the feeling of pulling with force            pullDownY = pullDownY + (() - lastY) / radio;
            if (pullDownY < 0) {
              pullDownY = 0;
              canPullDown = false;
              canPullUp = true;
            }
            if (pullDownY > getMeasuredHeight()) {
              pullDownY = getMeasuredHeight();
            }
            if (state == REFRESHING) {
              // Touch to move while refreshing              isTouch = false;
            }
          } else if (pullUpY < 0
              || (((Pullable) pullView).canPullUp() && canPullUp && state != REFRESHING && state != LOADING)) {
            // Can be pulled up, but cannot be pulled up when refreshing            pullUpY = pullUpY + (() - lastY) / radio;

            if (pullUpY > 0) {
              pullUpY = 0;
              canPullDown = true;
              canPullUp = false;
            }
            if (pullUpY < -getMeasuredHeight()) {
              pullUpY = -getMeasuredHeight();
            }
            if (state == LOADING) {
              // Touch and move while loading              isTouch = false;
            }
          }
        }
        if (isTouch) {
          lastY = ();
          if (pullDownY > 0 || pullUpY < 0) {
            requestLayout();
          }
          if (pullDownY > 0) {
            if (pullDownY <= headBorder && (state == RELEASE_TO_REFRESH || state == DONE)) {
              // If the pull-down distance does not reach the refresh distance and the current state is releasing refresh, change the state to pull-down refresh              state = INIT;
              refreshViewByState();
            }
            if (pullDownY >= headBorder && state == INIT) {
              // If the pull-down distance reaches the refresh distance and the current state is the initial state refresh, change the state to release refresh              state = RELEASE_TO_REFRESH;
              refreshViewByState();
            }
          } else if (pullUpY < 0) {
            // The following is to judge the loading of pullup, the same as above, note that pullUpY is a negative value            if (-pullUpY <= footBorder && (state == RELEASE_TO_LOAD || state == DONE)) {
              state = INIT;
              refreshViewByState();
            }
            // Pull-up operation            if (-pullUpY >= footBorder && state == INIT) {
              state = RELEASE_TO_LOAD;
              refreshViewByState();
            }
          }
          // Because refresh and load operations cannot be performed at the same time, pullDownY and pullUpY will not be 0 at the same time, so here (pullDownY +          // (pullUpY)) can not distinguish the current status          if ((pullDownY + (pullUpY)) > 8) {
            // Prevent long press and click events from accidentally triggering during pull-down            (MotionEvent.ACTION_CANCEL);
          }
        }
        break;
      case MotionEvent.ACTION_UP:
        if (pullDownY > headBorder || -pullUpY > footBorder) {
          // Pull down when refreshing (pull up when loading), and after release, pull down head (pull up head) will not be hidden          isTouch = false;
        }
        if (state == RELEASE_TO_REFRESH) {
          state = REFRESHING;
          refreshViewByState();
          // Refresh operation          if (mListener != null) {
            canPullDown = false;
            pullDownY = headBorder;
            pullUpY = 0;
            requestLayout();
            (rotateAnimation);
            (this);
          }
        } else if (state == RELEASE_TO_LOAD) {
          state = LOADING;
          refreshViewByState();
          // Loading operation          if (mListener != null) {
            canPullUp = false;
            pullDownY = 0;
            pullUpY = -footBorder;
            requestLayout();
            (rotateAnimation);
            (this);
          }
        } else {
          pullDownY = 0;
          pullUpY = 0;
          requestLayout();
        }
      default:
        break;
    }
    // Event distribution is handed over to the parent class    (ev);
    return true;
  }

  public void hideHeadView() {
    (1);
  }

  public void hideFootView() {
    (2);
  }

  private void initView() {
    // Initialize the drop-down layout    headIv = (ImageView) (.iv_head);
    headTv = (TextView) (.tv_head);
    //Initialize the pull-up layout    footIv = (ImageView) (.iv_foot);
    footTv = (TextView) (.tv_foot);
    refreshViewByState();
  }

  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    if (!isLayout) {
      // This is the first time I come in to do some initialization      headView = getChildAt(0);
      pullView = getChildAt(1);
      footView = getChildAt(2);
      headBorder = ((ViewGroup) headView).getChildAt(0).getMeasuredHeight();
      footBorder = ((ViewGroup) footView).getChildAt(0).getMeasuredHeight();
      headHeight = ();
      pullHeight = ();
      footHeight = ();
      pullWidth = ();
      initView();
      isLayout = true;
    }
    // Change the layout of the subcontrol, use (pullDownY + pullUpY) as the offset, so that the current state can be distinguished without distinguishing    offset = (int) (pullDownY + pullUpY);
    (0, -headHeight + offset, pullWidth, offset);
    (0, offset, pullWidth, pullHeight + offset);
    (0, pullHeight + offset, pullWidth, pullHeight + footHeight + offset);
  }

  public interface OnRefreshListener {
    void onRefresh(PullToRefreshLayout pullToRefreshLayout);

    void onLoadMore(PullToRefreshLayout pullToRefreshLayout);
  }

}

3. Custom View
ListView

public class PullableListView extends ListView implements Pullable {

  public PullableListView(Context context) {
    super(context);
  }

  public PullableListView(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  public PullableListView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
  }

  @Override
  public boolean canPullDown() {
    if (getCount() == 0) {
      // When there is no item, you can also pull down and refresh      return false;
    } else if (getFirstVisiblePosition() == 0 && getChildAt(0).getTop() >= 0) {
      // Slide to the top of the ListView      return true;
    } else
      return false;
  }

  @Override
  public boolean canPullUp() {
    if (getCount() == 0) {
      // You can also load when there is no item      return false;
    } else if (getLastVisiblePosition() == (getCount() - 1)) {
      // Slide to the bottom      if (getChildAt(getLastVisiblePosition() - getFirstVisiblePosition()) != null
          && getChildAt(getLastVisiblePosition() - getFirstVisiblePosition())
          .getBottom() <= getMeasuredHeight())
        return true;
    }
    return false;
  }
}

GridView

public class PullableGridView extends GridView implements Pullable {

  public PullableGridView(Context context) {
    super(context);
  }

  public PullableGridView(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  public PullableGridView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
  }

  @Override
  public boolean canPullDown() {
    if (getCount() == 0) {
      // When there is no item, you can also pull down and refresh      return false;
    } else if (getFirstVisiblePosition() == 0
        && getChildAt(0).getTop() >= 0) {
      // Slide to the top      return true;
    } else
      return false;
  }

  @Override
  public boolean canPullUp() {
    if (getCount() == 0) {
      // You can also load when there is no item      return false;
    } else if (getLastVisiblePosition() == (getCount() - 1)) {
      // Slide to the bottom      if (getChildAt(getLastVisiblePosition() - getFirstVisiblePosition()) != null
          && getChildAt(
          getLastVisiblePosition()
              - getFirstVisiblePosition()).getBottom() <= getMeasuredHeight())
        return true;
    }
    return false;
  }

}

RecyclerView

public class PullableRecyclerView extends RecyclerView implements Pullable {

  public PullableRecyclerView(Context context) {
    super(context);
  }

  public PullableRecyclerView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
  }

  public PullableRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
  }

  @Override
  public boolean canPullDown() {
     layoutManager = getLayoutManager();
    if (layoutManager instanceof LinearLayoutManager) {
      LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
      if (() == 0) {
        return false;
      } else if (() == 0 && (0).getTop() >= 0) {
        return true;
      } else {
        return false;
      }
    } else if (layoutManager instanceof StaggeredGridLayoutManager) {
      StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
      if (() == 0) {
        return false;
      } else {
        int[] firstVisibleItems = null;
        firstVisibleItems = (firstVisibleItems);
        if (firstVisibleItems != null &&  > 0) {
          if (() + firstVisibleItems[0] == ()) {
            return true;
          }
        }
      }
    }
    return false;
  }

  @Override
  public boolean canPullUp() {
     layoutManager = getLayoutManager();
    if (layoutManager instanceof LinearLayoutManager) {
      LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
      if (() == 0) {
        return false;
      } else if (() == (() - 1)) {
        if ((() - ()) != null
            && (() - ()).getBottom() <= getMeasuredHeight()) {
          return true;
        }
      }
    } else if (layoutManager instanceof StaggeredGridLayoutManager) {
      StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
      if (() == 0) {
        return false;
      } else {
        int[] lastPositions = new int[()];
        lastPositions = (lastPositions);
        if (findMax(lastPositions) >= () - 1) {
          return true;
        }
      }
    }
    return false;
  }

  private int findMax(int[] lastPositions) {
    int max = lastPositions[0];
    for (int value : lastPositions) {
      if (value > max) {
        max = value;
      }
    }
    return max;
  }
}

ExpandableListView

public class PullableExpandableListView extends ExpandableListView implements
    Pullable {

  public PullableExpandableListView(Context context) {
    super(context);
  }

  public PullableExpandableListView(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  public PullableExpandableListView(Context context, AttributeSet attrs,
                   int defStyle) {
    super(context, attrs, defStyle);
  }

  @Override
  public boolean canPullDown() {
    if (getCount() == 0) {
      // When there is no item, you can also pull down and refresh      return false;
    } else if (getFirstVisiblePosition() == 0
        &amp;&amp; getChildAt(0).getTop() &gt;= 0) {
      // Slide to the top      return true;
    } else
      return false;
  }

  @Override
  public boolean canPullUp() {
    if (getCount() == 0) {
      // You can also load when there is no item      return false;
    } else if (getLastVisiblePosition() == (getCount() - 1)) {
      // Slide to the bottom      if (getChildAt(getLastVisiblePosition() - getFirstVisiblePosition()) != null
          &amp;&amp; getChildAt(
          getLastVisiblePosition()
              - getFirstVisiblePosition()).getBottom() &lt;= getMeasuredHeight())
        return true;
    }
    return false;
  }

}

ScrollView

public class PullableScrollView extends ScrollView implements Pullable {

  public PullableScrollView(Context context) {
    super(context);
  }

  public PullableScrollView(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  public PullableScrollView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
  }

  @Override
  public boolean canPullDown() {
    if (getScrollY() == 0)
      return true;
    else
      return false;
  }

  @Override
  public boolean canPullUp() {
    if (getScrollY() >= (getChildAt(0).getHeight() - getMeasuredHeight()))
      return true;
    else
      return false;
  }

}

WebView

 public class PullableWebView extends WebView implements Pullable {

  public PullableWebView(Context context) {
    super(context);
  }

  public PullableWebView(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  public PullableWebView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
  }

  @Override
  public boolean canPullDown() {
    if (getScrollY() == 0)
      return true;
    else
      return false;
  }

  @Override
  public boolean canPullUp() {
    if (getScrollY() >= getContentHeight() * getScale()
        - getMeasuredHeight())
      return true;
    else
      return false;
  }
}

ImageView

public class PullableImageView extends ImageView implements Pullable
{

  public PullableImageView(Context context)
  {
    super(context);
  }

  public PullableImageView(Context context, AttributeSet attrs)
  {
    super(context, attrs);
  }

  public PullableImageView(Context context, AttributeSet attrs, int defStyle)
  {
    super(context, attrs, defStyle);
  }

  @Override
  public boolean canPullDown()
  {
    return true;
  }

  @Override
  public boolean canPullUp()
  {
    return true;
  }

}

TextView

public class PullableTextView extends TextView implements Pullable {

  public PullableTextView(Context context) {
    super(context);
  }

  public PullableTextView(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  public PullableTextView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
  }

  @Override
  public boolean canPullDown() {
    return true;
  }

  @Override
  public boolean canPullUp() {
    return true;
  }

}

4. Use example (taking ListView as an example)

<
  android:
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <include layout="@layout/head" />

  <
    android:
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

  <include layout="@layout/foot" />
  
</>

head

<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns:andro
  android:
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@android:color/darker_gray">

  <RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:paddingBottom="20dp"
    android:paddingTop="20dp">

    <RelativeLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_centerInParent="true">

      <ImageView
        android:
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:layout_centerVertical="true"
        android:layout_marginLeft="60dp"
        android:scaleType="fitXY" />

      <TextView
        android:
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textColor="@android:color/white"
        android:textSize="16sp" />
      
    </RelativeLayout>
    
  </RelativeLayout>
  
</RelativeLayout>

foot

<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns:andro
  android:
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@android:color/darker_gray">

  <RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentTop="true"
    android:paddingBottom="20dp"
    android:paddingTop="20dp">

    <RelativeLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_centerInParent="true">

      <ImageView
        android:
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:layout_centerVertical="true"
        android:layout_marginLeft="60dp"
        android:scaleType="fitXY" />

      <TextView
        android:
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textColor="@android:color/white"
        android:textSize="16sp" />
    </RelativeLayout>
    
  </RelativeLayout>
  
</RelativeLayout>

4. Pay attention

There is no difference between custom View and normal View usage
To implement refresh loading, you must make (onRefreshListener);

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.