关于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;??
- ????}??
- ??
- ????/**?
- ?????*?设置标签显示何时最后被刷新?
- ?????*??
- [email protected]?
- ?????*????????????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,该方法设置最后更新时间?
- ?????*??
- [email protected]?
- ?????*????????????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做复位处理。