Android 向下刷新列表

11年前

Android PullToRefresh为 Android 应用提供一个向下滑动即刷新列表的功能。

  项目如图:

效果如图:


包含测试文件就2个目标文件!

主要类如下:

public class PullToRefreshListView extends ListView implements OnScrollListener  {     private static final int TAP_TO_REFRESH = 1;   private static final int PULL_TO_REFRESH = 2;   private static final int RELEASE_TO_REFRESH = 3;   private static final int REFRESHING = 4;     private static final String TAG = "PullToRefreshListView";     private OnRefreshListener mOnRefreshListener;     /**    * 收到通知的监听器每次滚动列表。    */   private OnScrollListener mOnScrollListener;   private LayoutInflater mInflater;     private RelativeLayout mRefreshView;   private TextView mRefreshViewText;   private ImageView mRefreshViewImage;   private ProgressBar mRefreshViewProgress;   private TextView mRefreshViewLastUpdated;     private int mCurrentScrollState;   private int mRefreshState;     private RotateAnimation mFlipAnimation;   private RotateAnimation mReverseFlipAnimation;     private int mRefreshViewHeight;   private int mRefreshOriginalTopPadding;   private int mLastMotionY;     private boolean mBounceHack;     public PullToRefreshListView(Context context)   {    super(context);    init(context);   }     public PullToRefreshListView(Context context, AttributeSet attrs)   {    super(context, attrs);    init(context);   }     public PullToRefreshListView(Context context, AttributeSet attrs,     int defStyle)   {    super(context, attrs, defStyle);    init(context);   }     private void init(Context context)   {    //我们需要在代码中加载所有的动画,而不是通过XML    mFlipAnimation = new RotateAnimation(0, -180,      RotateAnimation.RELATIVE_TO_SELF, 0.5f,      RotateAnimation.RELATIVE_TO_SELF, 0.5f);    mFlipAnimation.setInterpolator(new LinearInterpolator());    mFlipAnimation.setDuration(250);    mFlipAnimation.setFillAfter(true);    mReverseFlipAnimation = new RotateAnimation(-180, 0,      RotateAnimation.RELATIVE_TO_SELF, 0.5f,      RotateAnimation.RELATIVE_TO_SELF, 0.5f);    mReverseFlipAnimation.setInterpolator(new LinearInterpolator());    mReverseFlipAnimation.setDuration(250);    mReverseFlipAnimation.setFillAfter(true);      mInflater = (LayoutInflater) context      .getSystemService(Context.LAYOUT_INFLATER_SERVICE);      mRefreshView = (RelativeLayout) mInflater.inflate(      R.layout.pull_to_refresh_header, this, false);    mRefreshViewText = (TextView) mRefreshView      .findViewById(R.id.pull_to_refresh_text);    mRefreshViewImage = (ImageView) mRefreshView      .findViewById(R.id.pull_to_refresh_image);    mRefreshViewProgress = (ProgressBar) mRefreshView      .findViewById(R.id.pull_to_refresh_progress);    mRefreshViewLastUpdated = (TextView) mRefreshView      .findViewById(R.id.pull_to_refresh_updated_at);      mRefreshViewImage.setMinimumHeight(50);    mRefreshView.setOnClickListener(new OnClickRefreshListener());    mRefreshOriginalTopPadding = mRefreshView.getPaddingTop();      mRefreshState = TAP_TO_REFRESH;      addHeaderView(mRefreshView);      super.setOnScrollListener(this);      measureView(mRefreshView);    mRefreshViewHeight = mRefreshView.getMeasuredHeight();   }     @Override   protected void onAttachedToWindow()   {    super.onAttachedToWindow();    setSelection(1);   }     @Override   public void setAdapter(ListAdapter adapter)   {    super.setAdapter(adapter);      setSelection(1);   }     /**    * 设置将接收通知的侦听器,每次滚动列表。    * @param l    *            The scroll listener.    */   @Override   public void setOnScrollListener(AbsListView.OnScrollListener l)   {    mOnScrollListener = l;   }     /**    * 注册一个回调函数被调用时,此列表应被刷新。    * @param onRefreshListener    *            The callback to run.    */   public void setOnRefreshListener(OnRefreshListener onRefreshListener)   {    mOnRefreshListener = onRefreshListener;   }     /**    * 设置文本表示当列表的最后更新时间。    * @param lastUpdated    *            Last updated at.    */   public void setLastUpdated(CharSequence lastUpdated)   {    if (lastUpdated != null)    {     mRefreshViewLastUpdated.setVisibility(View.VISIBLE);     mRefreshViewLastUpdated.setText(lastUpdated);    } else    {     mRefreshViewLastUpdated.setVisibility(View.GONE);    }   }     @Override   public boolean onTouchEvent(MotionEvent event)   {    final int y = (int) event.getY();    mBounceHack = false;      switch (event.getAction())    {    case MotionEvent.ACTION_UP:     if (!isVerticalScrollBarEnabled())     {      setVerticalScrollBarEnabled(true);     }     if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING)     {      if ((mRefreshView.getBottom() >= mRefreshViewHeight || mRefreshView        .getTop() >= 0) && mRefreshState == RELEASE_TO_REFRESH)      {       // Initiate the refresh       mRefreshState = REFRESHING;       prepareForRefresh();       onRefresh();      } else if (mRefreshView.getBottom() < mRefreshViewHeight        || mRefreshView.getTop() <= 0)      {       // Abort refresh and scroll down below the refresh view       resetHeader();       setSelection(1);      }     }     break;    case MotionEvent.ACTION_DOWN:     mLastMotionY = y;     break;    case MotionEvent.ACTION_MOVE:     applyHeaderPadding(event);     break;    }    return super.onTouchEvent(event);   }     private void applyHeaderPadding(MotionEvent ev)   {    // getHistorySize has been available since API 1    int pointerCount = ev.getHistorySize();      for (int p = 0; p < pointerCount; p++)    {     if (mRefreshState == RELEASE_TO_REFRESH)     {      if (isVerticalFadingEdgeEnabled())      {       setVerticalScrollBarEnabled(false);      }        int historicalY = (int) ev.getHistoricalY(p);        // Calculate the padding to apply, we divide by 1.7 to      // simulate a more resistant effect during pull.      int topPadding = (int) (((historicalY - mLastMotionY) - mRefreshViewHeight) / 1.7);        mRefreshView.setPadding(mRefreshView.getPaddingLeft(),        topPadding, mRefreshView.getPaddingRight(),        mRefreshView.getPaddingBottom());     }    }   }     /**    *设置的头部填充返回到原来的大小。    */   private void resetHeaderPadding()   {    mRefreshView.setPadding(mRefreshView.getPaddingLeft(),      mRefreshOriginalTopPadding, mRefreshView.getPaddingRight(),      mRefreshView.getPaddingBottom());   }     /**    * 复位到原来的状态的标头。    */   private void resetHeader()   {    if (mRefreshState != TAP_TO_REFRESH)    {     mRefreshState = TAP_TO_REFRESH;       resetHeaderPadding();       // 将刷新视图文本拉标签     mRefreshViewText.setText(R.string.pull_to_refresh_tap_label);     // Replace refresh drawable with arrow drawable     mRefreshViewImage       .setImageResource(R.drawable.ic_pulltorefresh_arrow);     //清除旋转动画     mRefreshViewImage.clearAnimation();     // 隐藏进度条和箭头。     mRefreshViewImage.setVisibility(View.GONE);     mRefreshViewProgress.setVisibility(View.GONE);    }   }     private void measureView(View child)   {    ViewGroup.LayoutParams p = child.getLayoutParams();    if (p == null)    {     p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,       ViewGroup.LayoutParams.WRAP_CONTENT);    }      int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);    int lpHeight = p.height;    int childHeightSpec;    if (lpHeight > 0)    {     childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,       MeasureSpec.EXACTLY);    } else    {     childHeightSpec = MeasureSpec.makeMeasureSpec(0,       MeasureSpec.UNSPECIFIED);    }    child.measure(childWidthSpec, childHeightSpec);   }     @Override   public void onScroll(AbsListView view, int firstVisibleItem,     int visibleItemCount, int totalItemCount)   {    // 当刷新的观点是完全可见的,改变的文字说:“放开刷新...”翻转的箭头绘制的。    if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL      && mRefreshState != REFRESHING)    {     if (firstVisibleItem == 0)     {      mRefreshViewImage.setVisibility(View.VISIBLE);      if ((mRefreshView.getBottom() >= mRefreshViewHeight + 20 || mRefreshView        .getTop() >= 0) && mRefreshState != RELEASE_TO_REFRESH)      {       mRefreshViewText         .setText(R.string.pull_to_refresh_release_label);       mRefreshViewImage.clearAnimation();       mRefreshViewImage.startAnimation(mFlipAnimation);       mRefreshState = RELEASE_TO_REFRESH;      } else if (mRefreshView.getBottom() < mRefreshViewHeight + 20        && mRefreshState != PULL_TO_REFRESH)      {       mRefreshViewText         .setText(R.string.pull_to_refresh_pull_label);       if (mRefreshState != TAP_TO_REFRESH)       {        mRefreshViewImage.clearAnimation();        mRefreshViewImage.startAnimation(mReverseFlipAnimation);       }       mRefreshState = PULL_TO_REFRESH;      }     } else     {      mRefreshViewImage.setVisibility(View.GONE);      resetHeader();     }    } else if (mCurrentScrollState == SCROLL_STATE_FLING      && firstVisibleItem == 0 && mRefreshState != REFRESHING)    {     setSelection(1);     mBounceHack = true;    } else if (mBounceHack && mCurrentScrollState == SCROLL_STATE_FLING)    {     setSelection(1);    }      if (mOnScrollListener != null)    {     mOnScrollListener.onScroll(view, firstVisibleItem,       visibleItemCount, totalItemCount);    }   }     @Override   public void onScrollStateChanged(AbsListView view, int scrollState)   {    mCurrentScrollState = scrollState;      if (mCurrentScrollState == SCROLL_STATE_IDLE)    {     mBounceHack = false;    }      if (mOnScrollListener != null)    {     mOnScrollListener.onScrollStateChanged(view, scrollState);    }   }     public void prepareForRefresh()   {    resetHeaderPadding();      mRefreshViewImage.setVisibility(View.GONE);    // 我们需要这个技巧,否则它会保持以前的drawable。    mRefreshViewImage.setImageDrawable(null);    mRefreshViewProgress.setVisibility(View.VISIBLE);      // 将刷新视图文本清爽的标签    mRefreshViewText.setText(R.string.pull_to_refresh_refreshing_label);      mRefreshState = REFRESHING;   }     public void onRefresh()   {    Log.d(TAG, "onRefresh");      if (mOnRefreshListener != null)    {     mOnRefreshListener.onRefresh();    }   }     /**    * 重置列表刷新后到正常状态。    *     * @param lastUpdated    *            Last updated at.    */   public void onRefreshComplete(CharSequence lastUpdated)   {    setLastUpdated(lastUpdated);    onRefreshComplete();   }     /**    * 重置列表刷新后到正常状态。    */   public void onRefreshComplete()   {    Log.d(TAG, "onRefreshComplete");      resetHeader();      // 如果刷新视图是可见的,当加载完成,向下滚动到下一个项目。    if (mRefreshView.getBottom() > 0)    {     invalidateViews();     setSelection(1);    }   }     /**    * 点击刷新视图时调用。这主要是用于当有几个项目在列表中,这是不可能的拖动列表。    */   private class OnClickRefreshListener implements OnClickListener   {      @Override    public void onClick(View v)    {     if (mRefreshState != REFRESHING)     {      prepareForRefresh();      onRefresh();     }    }     }     /**    *当列表被刷新的回调被调用的接口定义。    */   public interface OnRefreshListener   {    /**     * 列表被刷新时调用的     * <p>     * A call to {@link PullToRefreshListView #onRefreshComplete()} is     * expected to indicate that the refresh has completed.     */    public void onRefresh();   }  }

测试代码如下:

public class PullToRefreshActivity extends ListActivity  {   private LinkedList<String> mListItems;     /** Called when the activity is first created. */   @Override   public void onCreate(Bundle savedInstanceState)   {    super.onCreate(savedInstanceState);    setContentView(R.layout.pull_to_refresh);      // 设置一个监听器时要调用的列表被刷新。    ((PullToRefreshListView) getListView())      .setOnRefreshListener(new OnRefreshListener()      {       @Override       public void onRefresh()       {        // 请刷新列表。        new GetDataTask().execute();       }      });      mListItems = new LinkedList<String>();    mListItems.addAll(Arrays.asList(mStrings));      ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,      android.R.layout.simple_list_item_1, mListItems);      setListAdapter(adapter);   }     private class GetDataTask extends AsyncTask<Void, Void, String[]>   {      @Override    protected String[] doInBackground(Void... params)    {     //后台作业。     try     {      Thread.sleep(2000);     } catch (InterruptedException e)     {      ;     }     return mStrings;    }      @Override    protected void onPostExecute(String[] result)    {     mListItems.addFirst("Added after refresh...");       // 刷新完成     ((PullToRefreshListView) getListView()).onRefreshComplete();       super.onPostExecute(result);    }   }     private String[] mStrings =   { "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance",     "Ackawi", "Acorn", "Adelost", "Affidelice au Chablis",     "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre",     "Allgauer Emmentaler" };  }