SoFunction
Updated on 2025-03-11

Android development tutorial to implement listview pull-down refresh and pull-up refresh effects


public class PullToLoadListView extends ListView implements OnScrollListener {

 private static final String TAG = ();

 private static final int STATE_NON = 0;
 private static final int STATE_PULL_TO_REFRESH = 1;
 private static final int STATE_RELEASE_TO_REFRESH = 2;
 private static final int STATE_REFRESHING = 3;

 private int state;

 private int firstVisibleItem;
 private int lastVisisibleItem;

 private float prevY = 0;

 private View headerView;
 private View footerView;

 // header widgets
 private ProgressBar headerProgressBar;
 private ImageView headerImageArrow;
 private TextView headerText;
 private RotateAnimation headerArrowAnim;
 private RotateAnimation headerArrowReverseAnim;
 // footer widgets
 private ProgressBar footerProgressBar;
 private TextView footerText;

 private boolean headerIsHanding = false;
 private boolean footerIsHanding = false;

 private int headerHeight;
 private int footerHeight;

 private ResetAnimation resetAnim;

 private OnLoadingListener onLoadingListener;

 private OnScrollListener onScrollListener;

 public PullToLoadListView(Context context) {
  super(context);
  init(context);
 }

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

 private void init(Context context) {
  state = STATE_NON;
  firstVisibleItem = 0;
  lastVisisibleItem = 0;

  LayoutInflater inflater = (context);
  headerView = (.view_pull_header, null);
  footerView = (.view_pull_footer, null);

  headerProgressBar = (ProgressBar) ();
  headerImageArrow = (ImageView) ();
  headerText = (TextView) ();
  headerArrowAnim = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
  (300);
  (true);
  headerArrowReverseAnim = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
  (300);
  (true);

  footerProgressBar = (ProgressBar) ();
  footerText = (TextView) ();

  measureView(headerView);
  measureView(footerView);
  headerHeight = ();
  footerHeight = ();
  (0, -1 * (), 0, 0);
  (0, -1 * (), 0, 0);
  ();
  ();
  addHeaderView(headerView, null, false);
  addFooterView(footerView, null, false);

  (this);
 }

 private void measureView(View view) {
   lp = ();
  if(lp == null) {
   lp = new (.MATCH_PARENT, .WRAP_CONTENT);
  }
  int childWidthSpec = (0, 0, );
  int childHeightSpec;
  if( > 0) {
   childHeightSpec = (0, );
  } else {
   childHeightSpec = (0, );
  }
  (childWidthSpec, childHeightSpec);
 }

 private void resetHeader() {
  //  (0, -1 * headerHeight, 0, 0);
  resetAnim = new ResetAnimation(headerView, headerHeight, ());
  ();
 }

 private void resetFooter() {
  resetAnim = new ResetAnimation(footerView, footerHeight, ());
  ();
 }

 private void changeHeaderViewByState(int state) {
  if( == state) {
   return ;
  }
  int prevState = ;
   = state;

  switch(state) {
  case STATE_NON:
   ();
   ();
   ();
   ("Pull Down To Refresh");
   break;
  case STATE_PULL_TO_REFRESH:
   ();
   ();
   ("Pull Down To Refresh");
   if(prevState == STATE_RELEASE_TO_REFRESH) {
    (headerArrowReverseAnim);
   } else {
    ();
   }
   break;
  case STATE_RELEASE_TO_REFRESH:
   ();
   ();
   (headerArrowAnim);
   ("Release To Refresh");
   break;
  case STATE_REFRESHING:
   ();
   ();
   ();
   ("Refreshing");
   break;
  default:
   break;
  }
 }

 private void changeFooterViewByState(int state) {
  if( == state) {
   return ;
  }
   = state;

  switch(state) {
  case STATE_NON:
   ();
   ("Pull Up To Refresh");
   break;
  case STATE_PULL_TO_REFRESH:
   ();
   ("Pull Up To Refresh");
   break;
  case STATE_RELEASE_TO_REFRESH:
   ();
   ("Release To Refresh");
   break;
  case STATE_REFRESHING:
   ();
   ("Refreshing");
   break;
  default:
   break;
  }
 }

 @Override
 public void setOnScrollListener(OnScrollListener l) {
   = l;
 }

 public void setOnLoadingListener(OnLoadingListener onLoadingListener) {
   = onLoadingListener;
 }

 public void loadCompleted() {
  if(headerIsHanding) {
   changeHeaderViewByState(STATE_NON);
   resetHeader();
   headerIsHanding = false;
  }
  if(footerIsHanding) {
   changeFooterViewByState(STATE_NON);
   resetFooter();
   footerIsHanding = false;
  }
 }

 private void handleMoveHeaderEvent(MotionEvent ev) {
  headerIsHanding = true;
  float tempY = ();
  float vector = tempY - prevY;
  vector /= 2;
  prevY = tempY;
  if(vector > 0) {
   int newPadding = (int) (() + vector);
   newPadding = (newPadding, headerHeight / 2);
   (0, newPadding, 0, 0);
   if(state != STATE_REFRESHING) {
    if(newPadding > 0) {
     changeHeaderViewByState(STATE_RELEASE_TO_REFRESH);
    } else {
     changeHeaderViewByState(STATE_PULL_TO_REFRESH);
    }
   }
  } else {
   if(state == STATE_RELEASE_TO_REFRESH || state == STATE_PULL_TO_REFRESH) {
    int newPadding = (int) (() + vector);
    newPadding = (newPadding, -1 * headerHeight);
    (0, newPadding, 0, 0);
    if(newPadding <= -1 * headerHeight) {
     changeHeaderViewByState(STATE_NON);
     headerIsHanding = false;
    } else if(newPadding <= 0) {
     changeHeaderViewByState(STATE_PULL_TO_REFRESH);
    } else {

    }
   }
  }
 }

 private void handleMoveFooterEvent(MotionEvent ev) {
  footerIsHanding = true;
  float tempY = ();
  float vector = tempY - prevY;
  vector /= 2;
  prevY = tempY;
  if(vector < 0) {
   int newPadding = (int) (() - vector);
   if(newPadding > 0) {
    newPadding = 0;
   }
   (0, newPadding, 0, 0);
   if(state != STATE_REFRESHING) {
    if(newPadding < 0) {
     changeFooterViewByState(STATE_PULL_TO_REFRESH);
    } else {
     changeFooterViewByState(STATE_RELEASE_TO_REFRESH);
    }
   }
  } else {
   int newPadding = (int) (() - vector);
   newPadding = (newPadding, footerHeight);
   (0, newPadding, 0, 0);
   if(newPadding <= -1 * footerHeight) {
    changeFooterViewByState(STATE_NON);
    footerIsHanding = false;
   } else if(newPadding < 0) {
    changeFooterViewByState(STATE_PULL_TO_REFRESH);
   }
  }
 }

 @Override
 public boolean onTouchEvent(MotionEvent ev) {
  switch(()) {
  case MotionEvent.ACTION_DOWN:
   prevY = ();
   break;
  case MotionEvent.ACTION_UP:
   if(state == STATE_RELEASE_TO_REFRESH) {
    if(headerIsHanding) {
     changeHeaderViewByState(STATE_REFRESHING);
     if(onLoadingListener != null) {
      ();
     }
    }
    if(footerIsHanding) {
     changeFooterViewByState(STATE_REFRESHING);
     if(onLoadingListener != null) {
      ();
     }
    }
   } else if(state == STATE_PULL_TO_REFRESH) {
    if(headerIsHanding) {
     changeHeaderViewByState(STATE_NON);
     resetHeader();
     headerIsHanding = false;
    }
    if(footerIsHanding) {
     changeFooterViewByState(STATE_NON);
     resetFooter();
     footerIsHanding = false;
    }
   } else if(state == STATE_NON) {
    headerIsHanding = false;
    footerIsHanding = false;
   } else {
    // state == STATE_REFRESHING
    // ignore
   }
   break;
  case MotionEvent.ACTION_MOVE:
   if(resetAnim == null || !) {

    if(state != STATE_REFRESHING) {
     Adapter adapter = getAdapter();
     if(adapter == null) {
      handleMoveHeaderEvent(ev);
     } else {
      final int count = ();
      if(count <= 0) {
       handleMoveHeaderEvent(ev);
      } else {
       float tempY = ();
       float vector = tempY - prevY;
       if(firstVisibleItem == 0 && lastVisisibleItem == count - 1) {
        if(headerIsHanding) {
         handleMoveHeaderEvent(ev);
        } else if(footerIsHanding) {
         handleMoveFooterEvent(ev);
        } else {
         if(vector > 0) {
          handleMoveHeaderEvent(ev);
         } else if(vector < 0) {
          handleMoveFooterEvent(ev);
         } else {
          // ignore vector == 0
         }
        }
       } else if(firstVisibleItem == 0 && vector > 0) {
        handleMoveHeaderEvent(ev);
       } else if(lastVisisibleItem == count - 1 && vector < 0) {
        handleMoveFooterEvent(ev);
       } else {
        // ignore
       }
      }
     }
    }
   }
   break;
  default:
   break;
  }
  return (ev);
 }

 @Override
 public void onScrollStateChanged(AbsListView view, int scrollState) {
  if(onScrollListener != null) {
   (view, scrollState);
  }
 }

 @Override
 public void onScroll(AbsListView view, int firstVisibleItem,
   int visibleItemCount, int totalItemCount) {
   = firstVisibleItem;
   = firstVisibleItem + visibleItemCount - 1;
  if(onScrollListener != null) {
   (view, firstVisibleItem, visibleItemCount, totalItemCount);
  }
 }

 static class ResetAnimation extends Thread {

  static final int DURATION = 600;

  static final int INTERVAL = 5;

  View view;
  int orignalHeight;
  int paddingTop;

  boolean run = false;

  ResetAnimation(View view, int orignalHeight, int paddingTop) {
    = view;
    = orignalHeight;
    = paddingTop;
  }

  public void run() {
   run = true;
   int total = orignalHeight * 2 + paddingTop;
   int timeTotal = DURATION / INTERVAL;
   int piece = total / timeTotal;
   int time = 0;
   final View view = ;
   final int paddingTop = ;
   do {
    final int nextPaddingTop = paddingTop - time * piece;
    (new Runnable() {
     public void run() {
      (0, nextPaddingTop, 0, 0);
      ();
     }
    });
    try {
     sleep(INTERVAL);
    } catch (InterruptedException e) {
     ();
    }
    time ++;
   } while(time < timeTotal);
   run = false;
  }
 }

 public interface OnLoadingListener {

  public void onLoadNew();

  public void onLoadMore();
 }

}