android UI进阶之实现listview的下拉加载

来源:互联网 发布:网络直播的弊端 编辑:程序博客网 时间:2024/04/28 10:02

 

关于listview的操作五花八门,有下拉刷新,分级显示,分页列表,逐页加载等,以后会陆续和大家分享这些技术,今天讲下下拉加载这个功能的实现。

最初的下拉加载应该是ios上的效果,现在很多应用如新浪微博等都加入了这个操作。即下拉listview刷新列表,这无疑是一个非常友好的操作。今天就和大家分享下这个操作的实现。

先看下运行效果:


   

 

   


 

代码参考国外朋友Johan Nilsson的实现,http://johannilsson.com/2011/03/13/android-pull-to-refresh-update.html。

主要原理为监听触摸和滑动操作,在listview头部加载一个视图。那要做的其实很简单:1.写好加载到listview头部的view 2.重写listview,实现onTouchEvent方法和onScroll方法,监听滑动状态。计算headview全部显示出来即可实行加载动作,加载完成即刷新列表。重新隐藏headview。

首先写下headview的xml代码:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"      android:layout_width="fill_parent"      android:layout_height="fill_parent"      android:paddingTop="10dip"      android:paddingBottom="15dip"      android:gravity="center"          android:id="@+id/pull_to_refresh_header"      >      <ProgressBar           android:id="@+id/pull_to_refresh_progress"          android:indeterminate="true"          android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:layout_marginLeft="30dip"          android:layout_marginRight="20dip"          android:layout_marginTop="10dip"          android:visibility="gone"          android:layout_centerVertical="true"          style="?android:attr/progressBarStyleSmall"          />      <ImageView          android:id="@+id/pull_to_refresh_image"          android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:layout_marginLeft="30dip"          android:layout_marginRight="20dip"          android:visibility="gone"          android:layout_gravity="center"          android:gravity="center"          android:src="@drawable/ic_pulltorefresh_arrow"          />      <TextView          android:id="@+id/pull_to_refresh_text"          android:textAppearance="?android:attr/textAppearanceMedium"          android:textStyle="bold"          android:paddingTop="5dip"          android:layout_width="fill_parent"          android:layout_height="wrap_content"          android:layout_gravity="center"          android:gravity="center"          />      <TextView          android:id="@+id/pull_to_refresh_updated_at"          android:layout_below="@+id/pull_to_refresh_text"          android:visibility="gone"          android:textAppearance="?android:attr/textAppearanceSmall"          android:layout_width="fill_parent"          android:layout_height="wrap_content"          android:layout_gravity="center"          android:gravity="center"          />  </RelativeLayout> 


 

代码比较简单,即headview包括一个进度条一个箭头和两段文字(一个显示加载状态,另一个显示最后刷新时间,本例就不设置了)。

而后重写listview,代码如下:

package com.notice.pullrefresh;      import android.content.Context;  import android.util.AttributeSet;  import android.view.LayoutInflater;  import android.view.MotionEvent;  import android.view.View;  import android.view.ViewGroup;  import android.view.animation.LinearInterpolator;  import android.view.animation.RotateAnimation;  import android.widget.AbsListView;  import android.widget.AbsListView.OnScrollListener;  import android.widget.ImageView;  import android.widget.ListAdapter;  import android.widget.ListView;  import android.widget.ProgressBar;  import android.widget.RelativeLayout;  import android.widget.TextView;         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 OnRefreshListener mOnRefreshListener;          // 监听对listview的滑动动作      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);      }        /**      * 初始化控件和箭头动画(这里直接在代码中初始化动画而不是通过xml)      */      private void init(Context context) {          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);          mRefreshOriginalTopPadding = mRefreshView.getPaddingTop();            mRefreshState = TAP_TO_REFRESH;                    //为listview头部增加一个view          addHeaderView(mRefreshView);            super.setOnScrollListener(this);            measureView(mRefreshView);          mRefreshViewHeight = mRefreshView.getMeasuredHeight();      }        @Override      protected void onAttachedToWindow() {          setSelection(1);      }        @Override      public void setAdapter(ListAdapter adapter) {          super.setAdapter(adapter);            setSelection(1);      }        /**      * 设置滑动监听器      *       */      @Override      public void setOnScrollListener(AbsListView.OnScrollListener l) {          mOnScrollListener = l;      }        /**      * 注册一个list需要刷新时的回调接口      *       */      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) {                      // 把状态设置为正在刷新                          mRefreshState = REFRESHING;                      // 准备刷新                          prepareForRefresh();                      // 刷新                          onRefresh();                      } else if (mRefreshView.getBottom() < mRefreshViewHeight                              || mRefreshView.getTop() <= 0) {                      // 中止刷新                          resetHeader();                          setSelection(1);                      }                  }                  break;              case MotionEvent.ACTION_DOWN:              // 获得按下y轴位置                  mLastMotionY = y;                  break;              case MotionEvent.ACTION_MOVE:              // 计算边距                  applyHeaderPadding(event);                  break;          }          return super.onTouchEvent(event);      }        // 获得header的边距      private void applyHeaderPadding(MotionEvent ev) {            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);                    // 计算申请的边距,除以1.7使得拉动效果更好                  int topPadding = (int) (((historicalY - mLastMotionY)                          - mRefreshViewHeight) / 1.7);                    mRefreshView.setPadding(                          mRefreshView.getPaddingLeft(),                          topPadding,                          mRefreshView.getPaddingRight(),                          mRefreshView.getPaddingBottom());              }          }      }        /**      * 将head的边距重置为初始的数值      */      private void resetHeaderPadding() {          mRefreshView.setPadding(                  mRefreshView.getPaddingLeft(),                  mRefreshOriginalTopPadding,                  mRefreshView.getPaddingRight(),                  mRefreshView.getPaddingBottom());      }        /**      * 重置header为之前的状态      */      private void resetHeader() {          if (mRefreshState != TAP_TO_REFRESH) {              mRefreshState = TAP_TO_REFRESH;                resetHeaderPadding();                // 将刷新图标换成箭头              mRefreshViewImage.setImageResource(R.drawable.ic_pulltorefresh_arrow);              // 清除动画              mRefreshViewImage.clearAnimation();              // 隐藏图标和进度条              mRefreshViewImage.setVisibility(View.GONE);              mRefreshViewProgress.setVisibility(View.GONE);          }      }        // 估算headview的width和height      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) {            // 在refreshview完全可见时,设置文字为松开刷新,同时翻转箭头          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("松开加载...");                      mRefreshViewImage.clearAnimation();                      mRefreshViewImage.startAnimation(mFlipAnimation);                      mRefreshState = RELEASE_TO_REFRESH;                  } else if (mRefreshView.getBottom() < mRefreshViewHeight + 20                          && mRefreshState != PULL_TO_REFRESH) {                      mRefreshViewText.setText("下拉刷新...");                      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();// 恢复header的边距            mRefreshViewImage.setVisibility(View.GONE);          // 注意加上,否则仍然显示之前的图片          mRefreshViewImage.setImageDrawable(null);          mRefreshViewProgress.setVisibility(View.VISIBLE);            // 设置文字          mRefreshViewText.setText("加载中...");            mRefreshState = REFRESHING;      }        public void onRefresh() {            if (mOnRefreshListener != null) {              mOnRefreshListener.onRefresh();          }      }        /**      * 重置listview为普通的listview,该方法设置最后更新时间      *       * @param lastUpdated      *            Last updated at.      */      public void onRefreshComplete(CharSequence lastUpdated) {          setLastUpdated(lastUpdated);          onRefreshComplete();      }        /**      * 重置listview为普通的listview,不设置最后更新时间      */      public void onRefreshComplete() {                    resetHeader();            // 如果refreshview在加载结束后可见,下滑到下一个条目          if (mRefreshView.getBottom() > 0) {              invalidateViews();              setSelection(1);          }      }            /**      * 刷新监听器接口      */      public interface OnRefreshListener {          /**          * list需要被刷新时调用          */          public void onRefresh();      }  }  

 

相信我注释已经写的比较详细了,主要注意onTouchEvent和onScroll方法,在这里面计算头部边距,从而通过用户的手势实现“下拉刷新”到“松开加载”以及“加载”三个状态的切换。其中还有一系列和header有关的方法,用来设置header的显示以及取得header的边距。于此同时,代码留出了接口以供调用。

那么现在写一个测试Activity来试验下效果:

package com.notice.pullrefresh;    import java.util.Arrays;  import java.util.LinkedList;    import android.app.ListActivity;  import android.os.AsyncTask;  import android.os.Bundle;  import android.widget.ArrayAdapter;    import com.notice.pullrefresh.PullToRefreshListView.OnRefreshListener;      public class PullrefreshActivity extends ListActivity {      private LinkedList<String> mListItems;      ArrayAdapter<String> adapter;        /** Called when the activity is first created. */      @Override      public void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.pull_to_refresh);            // list需要刷新时调用          ((PullToRefreshListView) getListView())                  .setOnRefreshListener(new OnRefreshListener() {                      @Override                      public void onRefresh() {                          // 在这执行后台工作                          new GetDataTask().execute();                      }                  });                mListItems = new LinkedList<String>();          mListItems.addAll(Arrays.asList(mStrings));            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) {                  e.printStackTrace();              }              return mStrings;          }            @Override          protected void onPostExecute(String[] result) {              // 下拉后增加的内容              mListItems.addFirst("Added after refresh...");                // 刷新完成调用该方法复位              ((PullToRefreshListView) getListView()).onRefreshComplete();                super.onPostExecute(result);          }      }        private String[] mStrings = { "normal data1", "normal data2",              "nomal data3", "normal data4", "norma data5", "normal data6" };  }  

代码通过asyncTask实现一个异步操作,并通过设置onRefreshListener监听器调用onRefresh方法实现下拉时刷新,并在刷新完成后调用onRefreshComplete做复位处理。

今天就和大家分享这些,有问题欢迎留言交流。


 

原创粉丝点击