下拉列表,滑动到底部时自动刷新:此博客均为网络转载货收藏,如有侵权,请告知,及时删除此文

下拉刷新最主要的流程是:
(1). 下拉,显示提示头部界面(HeaderView),这个过程提示用户”下拉刷新”
(2). 下拉到一定程度,超出了刷新最基本的下拉界限,我们认为达到了刷新的条件,提示用户可以”松手刷新”了,效果上允许用户继续下拉
(3). 用户松手,可能用户下拉远远不止提示头部界面,所以这一步,先反弹回仅显示提示头部界面,然后提示用户”正在加载”。
(4). 加载完成后,隐藏提示头部界面。

那么让我们看看怎么才能实现呢???
第一步:既然是要显示listview ,那么就应该有个listview 的容器pulldown.xml

<?xml version="1.0" encoding="utf-8"?>
<com.solo.pulldown.PullDownView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/pull_down_view"android:layout_width="fill_parent"android:layout_height="fill_parent"android:background="@android:color/white"></com.solo.pulldown.PullDownView>

第二步:自定义一个listview中显示的item对象pulldown_item.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@android:id/text1"android:layout_width="fill_parent"android:layout_height="wrap_content"android:textAppearance="?android:attr/textAppearanceLarge"android:gravity="center_vertical"android:paddingLeft="6dip"android:minHeight="?android:attr/listPreferredItemHeight"android:textColor="@android:color/black"
/>

第三步:定义一个header的xml布局文件pulldown_header.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="wrap_content"android:paddingBottom="10dp"android:paddingTop="10dp" ><ImageViewandroid:id="@+id/pulldown_header_arrow"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentLeft="true"android:layout_marginLeft="20dp"android:scaleType="centerCrop"android:src="@drawable/z_arrow_down"android:visibility="invisible" /><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignBottom="@+id/pulldown_header_arrow"android:layout_alignTop="@+id/pulldown_header_arrow"android:layout_centerHorizontal="true"android:gravity="center_vertical"android:orientation="vertical" ><TextViewandroid:id="@+id/pulldown_header_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:text="加载中..." /><TextViewandroid:id="@+id/pulldown_header_date"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:text="更新于:"android:visibility="gone" /></LinearLayout><ProgressBarandroid:id="@+id/pulldown_header_loading"style="@android:style/Widget.ProgressBar.Small.Inverse"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginLeft="20dp" /></RelativeLayout>

第四步:如果需要向上拉更新更多的话,那就定义一个底部的footer的布局文件,在此为方便起见,只定义一个progressbar跟textview,更加复杂的显示,就交给你们了~~~~~pulldown_footer.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="wrap_content"android:paddingBottom="10dp"android:paddingTop="10dp" ><TextViewandroid:id="@+id/pulldown_footer_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:text="更多"android:textSize="15dp" /><ProgressBarandroid:id="@+id/pulldown_footer_loading"style="@android:style/Widget.ProgressBar.Small.Inverse"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginLeft="20dp"android:visibility="gone" /></RelativeLayout>

第五步:那么主要的文件这才登场:::::::重写listview这个文件主要任务是提供触摸的事件的处理方法。

/*** <p>一个可以监听ListView是否滚动到最顶部或最底部的自定义控件</p>* 只能监听由触摸产生的,如果是ListView本身Flying导致的,则不能监听</br>* 如果加以改进,可以实现监听scroll滚动的具体位置等*/public class ScrollOverListView extends ListView {private int mLastY;private int mTopPosition;private int mBottomPosition;public ScrollOverListView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init();}public ScrollOverListView(Context context, AttributeSet attrs) {super(context, attrs);init();}public ScrollOverListView(Context context) {super(context);init();}private void init(){mTopPosition = 0;mBottomPosition = 0;}@Overridepublic boolean onTouchEvent(MotionEvent ev) {final int action = ev.getAction();final int y = (int) ev.getRawY();switch(action){case MotionEvent.ACTION_DOWN:{mLastY = y;final boolean isHandled = mOnScrollOverListener.onMotionDown(ev);if (isHandled) {mLastY = y;return isHandled;}break;}case MotionEvent.ACTION_MOVE:{final int childCount = getChildCount();if(childCount == 0) return super.onTouchEvent(ev);final int itemCount = getAdapter().getCount() - mBottomPosition;final int deltaY = y - mLastY;//DLog.d("lastY=%d y=%d", mLastY, y);final int firstTop = getChildAt(0).getTop();final int listPadding = getListPaddingTop();final int lastBottom = getChildAt(childCount - 1).getBottom();final int end = getHeight() - getPaddingBottom();final int firstVisiblePosition = getFirstVisiblePosition();final boolean isHandleMotionMove = mOnScrollOverListener.onMotionMove(ev, deltaY);if(isHandleMotionMove){mLastY = y;return true;}//DLog.d("firstVisiblePosition=%d firstTop=%d listPaddingTop=%d deltaY=%d", firstVisiblePosition, firstTop, listPadding, deltaY);if (firstVisiblePosition <= mTopPosition && firstTop >= listPadding && deltaY > 0) {final boolean isHandleOnListViewTopAndPullDown;isHandleOnListViewTopAndPullDown = mOnScrollOverListener.onListViewTopAndPullDown(deltaY);if(isHandleOnListViewTopAndPullDown){mLastY = y;return true;}}// DLog.d("lastBottom=%d end=%d deltaY=%d", lastBottom, end, deltaY);if (firstVisiblePosition + childCount >= itemCount && lastBottom <= end && deltaY < 0) {final boolean isHandleOnListViewBottomAndPullDown;isHandleOnListViewBottomAndPullDown = mOnScrollOverListener.onListViewBottomAndPullUp(deltaY);if(isHandleOnListViewBottomAndPullDown){mLastY = y;return true;}}break;}case MotionEvent.ACTION_UP:{final boolean isHandlerMotionUp = mOnScrollOverListener.onMotionUp(ev);if (isHandlerMotionUp) {mLastY = y;return true;}break;}}mLastY = y;return super.onTouchEvent(ev);}/**空的*/private OnScrollOverListener mOnScrollOverListener = new OnScrollOverListener(){@Overridepublic boolean onListViewTopAndPullDown(int delta) {return false;}@Overridepublic boolean onListViewBottomAndPullUp(int delta) {return false;}@Overridepublic boolean onMotionDown(MotionEvent ev) {return false;}@Overridepublic boolean onMotionMove(MotionEvent ev, int delta) {return false;}@Overridepublic boolean onMotionUp(MotionEvent ev) {return false;}};// =============================== public method ===============================/*** 可以自定义其中一个条目为头部,头部触发的事件将以这个为准,默认为第一个** @param index 正数第几个,必须在条目数范围之内*/public void setTopPosition(int index){if(getAdapter() == null)throw new NullPointerException("You must set adapter before setTopPosition!");if(index < 0)throw new IllegalArgumentException("Top position must > 0");mTopPosition = index;}/*** 可以自定义其中一个条目为尾部,尾部触发的事件将以这个为准,默认为最后一个** @param index 倒数第几个,必须在条目数范围之内*/public void setBottomPosition(int index){if(getAdapter() == null)throw new NullPointerException("You must set adapter before setBottonPosition!");if(index < 0)throw new IllegalArgumentException("Bottom position must > 0");mBottomPosition = index;}/*** 设置这个Listener可以监听是否到达顶端,或者是否到达低端等事件</br>** @see OnScrollOverListener*/public void setOnScrollOverListener(OnScrollOverListener onScrollOverListener){mOnScrollOverListener = onScrollOverListener;}/*** 滚动监听接口</br>* @see ScrollOverListView#setOnScrollOverListener(OnScrollOverListener)**/public interface OnScrollOverListener {/*** 到达最顶部触发** @param delta 手指点击移动产生的偏移量* @return*/boolean onListViewTopAndPullDown(int delta);/*** 到达最底部触发** @param delta 手指点击移动产生的偏移量* @return*/boolean onListViewBottomAndPullUp(int delta);/*** 手指触摸按下触发,相当于{@link MotionEvent#ACTION_DOWN}** @return 返回true表示自己处理* @see View#onTouchEvent(MotionEvent)*/boolean onMotionDown(MotionEvent ev);/*** 手指触摸移动触发,相当于{@link MotionEvent#ACTION_MOVE}** @return 返回true表示自己处理* @see View#onTouchEvent(MotionEvent)*/boolean onMotionMove(MotionEvent ev, int delta);/*** 手指触摸后提起触发,相当于{@link MotionEvent#ACTION_UP}** @return 返回true表示自己处理* @see View#onTouchEvent(MotionEvent)*/boolean onMotionUp(MotionEvent ev);}}

第六步:下拉刷新控件,真正实现下拉刷新的是这个控件,而上面的那个ScrollOverListView只是提供触摸的事件等

/*** 下拉刷新控件</br>* 真正实现下拉刷新的是这个控件,* ScrollOverListView只是提供触摸的事件等*/
public class PullDownView extends LinearLayout implements OnScrollOverListener{private static final String TAG = "PullDownView";private static final int START_PULL_DEVIATION = 50; // 移动误差private static final int AUTO_INCREMENTAL = 10;     // 自增量,用于回弹private static final int WHAT_DID_LOAD_DATA = 1;    // Handler what 数据加载完毕private static final int WHAT_ON_REFRESH = 2;       // Handler what 刷新中private static final int WHAT_DID_REFRESH = 3;      // Handler what 已经刷新完private static final int WHAT_SET_HEADER_HEIGHT = 4;// Handler what 设置高度private static final int WHAT_DID_MORE = 5;         // Handler what 已经获取完更多private static final int DEFAULT_HEADER_VIEW_HEIGHT = 105;  // 头部文件原本的高度private static SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd HH:mm");private View mHeaderView;private LayoutParams mHeaderViewParams;private TextView mHeaderViewDateView;private TextView mHeaderTextView;private ImageView mHeaderArrowView;private View mHeaderLoadingView;private View mFooterView;private TextView mFooterTextView;private View mFooterLoadingView;private ScrollOverListView mListView;private OnPullDownListener mOnPullDownListener;private RotateAnimation mRotateOTo180Animation;private RotateAnimation mRotate180To0Animation;private int mHeaderIncremental; // 增量private float mMotionDownLastY; // 按下时候的Y轴坐标private boolean mIsDown;            // 是否按下private boolean mIsRefreshing;      // 是否下拉刷新中private boolean mIsFetchMoreing;    // 是否获取更多中private boolean mIsPullUpDone;      // 是否回推完成private boolean mEnableAutoFetchMore;   // 是否允许自动获取更多// 头部文件的状态private static final int HEADER_VIEW_STATE_IDLE = 0;            // 空闲private static final int HEADER_VIEW_STATE_NOT_OVER_HEIGHT = 1; // 没有超过默认高度private static final int HEADER_VIEW_STATE_OVER_HEIGHT = 2;     // 超过默认高度private int mHeaderViewState = HEADER_VIEW_STATE_IDLE;public PullDownView(Context context, AttributeSet attrs) {super(context, attrs);initHeaderViewAndFooterViewAndListView(context);}public PullDownView(Context context) {super(context);initHeaderViewAndFooterViewAndListView(context);}/** ==================================* Public method* 外部使用,具体就是用这几个就可以了** ==================================*//*** 刷新事件接口*/public interface OnPullDownListener {void onRefresh();void onMore();}/*** 通知加载完了数据,要放在Adapter.notifyDataSetChanged后面* 当你加载完数据的时候,调用这个notifyDidLoad()* 才会隐藏头部,并初始化数据等*/public void notifyDidLoad() {mUIHandler.sendEmptyMessage(WHAT_DID_LOAD_DATA);}/*** 通知已经刷新完了,要放在Adapter.notifyDataSetChanged后面* 当你执行完刷新任务之后,调用这个notifyDidRefresh()* 才会隐藏掉头部文件等操作*/public void notifyDidRefresh() {mUIHandler.sendEmptyMessage(WHAT_DID_REFRESH);}/*** 通知已经获取完更多了,要放在Adapter.notifyDataSetChanged后面* 当你执行完更多任务之后,调用这个notyfyDidMore()* 才会隐藏加载圈等操作*/public void notifyDidMore() {mUIHandler.sendEmptyMessage(WHAT_DID_MORE);}/*** 设置监听器* @param listener*/public void setOnPullDownListener(OnPullDownListener listener){mOnPullDownListener = listener;}/*** 获取内嵌的listview* @return ScrollOverListView*/public ListView getListView(){return mListView;}/*** 是否开启自动获取更多* 自动获取更多,将会隐藏footer,并在到达底部的时候自动刷新* @param index 倒数第几个触发*/public void enableAutoFetchMore(boolean enable, int index){if(enable){mListView.setBottomPosition(index);mFooterLoadingView.setVisibility(View.VISIBLE);}else{mFooterTextView.setText("更多");mFooterLoadingView.setVisibility(View.GONE);}mEnableAutoFetchMore = enable;}/** ==================================* Private method* 具体实现下拉刷新等操作** ==================================*//*** 初始化界面*/private void initHeaderViewAndFooterViewAndListView(Context context){setOrientation(LinearLayout.VERTICAL);//setDrawingCacheEnabled(false);/** 自定义头部文件* 放在这里是因为考虑到很多界面都需要使用* 如果要修改,和它相关的设置都要更改*/mHeaderView = LayoutInflater.from(context).inflate(R.layout.pulldown_header, null);mHeaderViewParams = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);addView(mHeaderView, 0, mHeaderViewParams);mHeaderTextView = (TextView) mHeaderView.findViewById(R.id.pulldown_header_text);mHeaderArrowView = (ImageView) mHeaderView.findViewById(R.id.pulldown_header_arrow);mHeaderLoadingView = mHeaderView.findViewById(R.id.pulldown_header_loading);// 注意,图片旋转之后,再执行旋转,坐标会重新开始计算mRotateOTo180Animation = new RotateAnimation(0, 180,Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);mRotateOTo180Animation.setDuration(250);mRotateOTo180Animation.setFillAfter(true);mRotate180To0Animation = new RotateAnimation(180, 0,Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);mRotate180To0Animation.setDuration(250);mRotate180To0Animation.setFillAfter(true);/*** 自定义脚部文件*/mFooterView = LayoutInflater.from(context).inflate(R.layout.pulldown_footer, null);mFooterTextView = (TextView) mFooterView.findViewById(R.id.pulldown_footer_text);mFooterLoadingView = mFooterView.findViewById(R.id.pulldown_footer_loading);mFooterView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {if(!mIsFetchMoreing){mIsFetchMoreing = true;mFooterLoadingView.setVisibility(View.VISIBLE);mOnPullDownListener.onMore();}}});/** ScrollOverListView 同样是考虑到都是使用,所以放在这里* 同时因为,需要它的监听事件*/mListView = new ScrollOverListView(context);mListView.setOnScrollOverListener(this);mListView.setCacheColorHint(0);addView(mListView, LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);// 空的listenermOnPullDownListener = new OnPullDownListener() {@Overridepublic void onRefresh() {}@Overridepublic void onMore() {}};}/*** 在下拉和回推的时候检查头部文件的状态</br>* 如果超过了默认高度,就显示松开可以刷新,* 否则显示下拉可以刷新*/private void checkHeaderViewState(){if(mHeaderViewParams.height >= DEFAULT_HEADER_VIEW_HEIGHT){if(mHeaderViewState == HEADER_VIEW_STATE_OVER_HEIGHT) return;mHeaderViewState = HEADER_VIEW_STATE_OVER_HEIGHT;mHeaderTextView.setText("松开可以刷新");mHeaderArrowView.startAnimation(mRotateOTo180Animation);}else{if(mHeaderViewState == HEADER_VIEW_STATE_NOT_OVER_HEIGHT|| mHeaderViewState == HEADER_VIEW_STATE_IDLE) return;mHeaderViewState = HEADER_VIEW_STATE_NOT_OVER_HEIGHT;mHeaderTextView.setText("下拉可以刷新");mHeaderArrowView.startAnimation(mRotate180To0Animation);}}private void setHeaderHeight(final int height){mHeaderIncremental = height;mHeaderViewParams.height = height;mHeaderView.setLayoutParams(mHeaderViewParams);}/*** 自动隐藏动画*/class HideHeaderViewTask extends TimerTask{@Overridepublic void run() {if(mIsDown) {cancel();return;}mHeaderIncremental -= AUTO_INCREMENTAL;if(mHeaderIncremental > 0){mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);}else{mHeaderIncremental = 0;mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);cancel();}}}/*** 自动显示动画*/class ShowHeaderViewTask extends TimerTask{@Overridepublic void run() {if(mIsDown) {cancel();return;}mHeaderIncremental -= AUTO_INCREMENTAL;if(mHeaderIncremental > DEFAULT_HEADER_VIEW_HEIGHT){mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);}else{mHeaderIncremental = DEFAULT_HEADER_VIEW_HEIGHT;mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);if(!mIsRefreshing){mIsRefreshing = true;mUIHandler.sendEmptyMessage(WHAT_ON_REFRESH);}cancel();}}}private Handler mUIHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case WHAT_DID_LOAD_DATA:{mHeaderViewParams.height = 0;mHeaderLoadingView.setVisibility(View.GONE);mHeaderTextView.setText("下拉可以刷新");mHeaderViewDateView = (TextView) mHeaderView.findViewById(R.id.pulldown_header_date);mHeaderViewDateView.setVisibility(View.VISIBLE);mHeaderViewDateView.setText("更新于:" + dateFormat.format(new Date(System.currentTimeMillis())));mHeaderArrowView.setVisibility(View.VISIBLE);showFooterView();return;}case WHAT_ON_REFRESH:{// 要清除掉动画,否则无法隐藏
                    mHeaderArrowView.clearAnimation();mHeaderArrowView.setVisibility(View.INVISIBLE);mHeaderLoadingView.setVisibility(View.VISIBLE);mOnPullDownListener.onRefresh();return;}case WHAT_DID_REFRESH :{mIsRefreshing = false;mHeaderViewState = HEADER_VIEW_STATE_IDLE;mHeaderArrowView.setVisibility(View.VISIBLE);mHeaderLoadingView.setVisibility(View.GONE);mHeaderViewDateView.setText("更新于:" + dateFormat.format(new Date(System.currentTimeMillis())));setHeaderHeight(0);showFooterView();return;}case WHAT_SET_HEADER_HEIGHT :{setHeaderHeight(mHeaderIncremental);return;}case WHAT_DID_MORE :{mIsFetchMoreing = false;mFooterTextView.setText("更多");mFooterLoadingView.setVisibility(View.GONE);}}}};/*** 显示脚步脚部文件*/private void showFooterView(){if(mListView.getFooterViewsCount() == 0 && isFillScreenItem()){mListView.addFooterView(mFooterView);mListView.setAdapter(mListView.getAdapter());}}/*** 条目是否填满整个屏幕*/private boolean isFillScreenItem(){final int firstVisiblePosition = mListView.getFirstVisiblePosition();final int lastVisiblePostion = mListView.getLastVisiblePosition() - mListView.getFooterViewsCount();final int visibleItemCount = lastVisiblePostion - firstVisiblePosition + 1;final int totalItemCount = mListView.getCount() - mListView.getFooterViewsCount();if(visibleItemCount < totalItemCount) return true;return false;}/** ==================================* 实现 OnScrollOverListener接口*** ==================================*/@Overridepublic boolean onListViewTopAndPullDown(int delta) {if(mIsRefreshing || mListView.getCount() - mListView.getFooterViewsCount() == 0) return false;int absDelta = Math.abs(delta);final int i = (int) Math.ceil((double)absDelta / 2);mHeaderIncremental += i;if(mHeaderIncremental >= 0){ // && mIncremental <= mMaxHeight
            setHeaderHeight(mHeaderIncremental);checkHeaderViewState();}return true;}@Overridepublic boolean onListViewBottomAndPullUp(int delta) {if(!mEnableAutoFetchMore || mIsFetchMoreing) return false;// 数量充满屏幕才触发if(isFillScreenItem()){mIsFetchMoreing = true;mFooterTextView.setText("加载更多中...");mFooterLoadingView.setVisibility(View.VISIBLE);mOnPullDownListener.onMore();return true;}return false;}@Overridepublic boolean onMotionDown(MotionEvent ev) {mIsDown = true;mIsPullUpDone = false;mMotionDownLastY = ev.getRawY();return false;}@Overridepublic boolean onMotionMove(MotionEvent ev, int delta) {//当头部文件回推消失的时候,不允许滚动if(mIsPullUpDone) return true;// 如果开始按下到滑动距离不超过误差值,则不滑动final int absMotionY = (int) Math.abs(ev.getRawY() - mMotionDownLastY);if(absMotionY < START_PULL_DEVIATION) return true;final int absDelta = Math.abs(delta);final int i = (int) Math.ceil((double)absDelta / 2);// onTopDown在顶部,并上回推和onTopUp相对if(mHeaderViewParams.height > 0 && delta < 0){mHeaderIncremental -= i;if(mHeaderIncremental > 0){setHeaderHeight(mHeaderIncremental);checkHeaderViewState();}else{mHeaderViewState = HEADER_VIEW_STATE_IDLE;mHeaderIncremental = 0;setHeaderHeight(mHeaderIncremental);mIsPullUpDone = true;}return true;}return false;}@Overridepublic boolean onMotionUp(MotionEvent ev) {mIsDown = false;// 避免和点击事件冲突if(mHeaderViewParams.height > 0){// 判断头文件拉动的距离与设定的高度,小了就隐藏,多了就固定高度int x = mHeaderIncremental - DEFAULT_HEADER_VIEW_HEIGHT;Timer timer = new Timer(true);if(x < 0){timer.scheduleAtFixedRate(new HideHeaderViewTask(), 0, 10);}else{timer.scheduleAtFixedRate(new ShowHeaderViewTask(), 0, 10);}return true;}return false;}}

第七步:这个java文件就是封装数据,然后调用上两个文件就好了。废话不多说,上代码:::::

public class PullDownActivity extends Activity implements OnPullDownListener, OnItemClickListener{private static final int WHAT_DID_LOAD_DATA = 0;private static final int WHAT_DID_REFRESH = 1;private static final int WHAT_DID_MORE = 2;private ListView mListView;private ArrayAdapter<String> mAdapter;private PullDownView mPullDownView;private List<String> mStrings = new ArrayList<String>();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.pulldown);/** 1.使用PullDownView* 2.设置OnPullDownListener* 3.从mPullDownView里面获取ListView*/mPullDownView = (PullDownView) findViewById(R.id.pull_down_view);mPullDownView.setOnPullDownListener(this);mListView = mPullDownView.getListView();mListView.setOnItemClickListener(this);mAdapter = new ArrayAdapter<String>(this, R.layout.pulldown_item, mStrings);mListView.setAdapter(mAdapter);mPullDownView.enableAutoFetchMore(true, 1);loadData();}private void loadData(){new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}List<String> strings = new ArrayList<String>();for (String body : mStringArray) {strings.add(body);}Message msg = mUIHandler.obtainMessage(WHAT_DID_LOAD_DATA);msg.obj = strings;msg.sendToTarget();}}).start();}@Overridepublic void onRefresh() {new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}Message msg = mUIHandler.obtainMessage(WHAT_DID_REFRESH);msg.obj = "After refresh " + System.currentTimeMillis();msg.sendToTarget();}}).start();}@Overridepublic void onMore() {new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}Message msg = mUIHandler.obtainMessage(WHAT_DID_MORE);msg.obj = "After more " + System.currentTimeMillis();msg.sendToTarget();}}).start();}private Handler mUIHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case WHAT_DID_LOAD_DATA:{if(msg.obj != null){List<String> strings = (List<String>) msg.obj;if(!strings.isEmpty()){mStrings.addAll(strings);mAdapter.notifyDataSetChanged();}}// 诉它数据加载完毕;
                    mPullDownView.notifyDidLoad();break;}case WHAT_DID_REFRESH :{String body = (String) msg.obj;mStrings.add(0, body);mAdapter.notifyDataSetChanged();// 告诉它更新完毕
                    mPullDownView.notifyDidRefresh();break;}case WHAT_DID_MORE:{String body = (String) msg.obj;mStrings.add(body);mAdapter.notifyDataSetChanged();// 告诉它获取更多完毕
                    mPullDownView.notifyDidMore();break;}}}};@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {Toast.makeText(this, "啊,你点中我了 " + position, Toast.LENGTH_SHORT).show();}// 模拟数据private String[] mStringArray = {"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", "Alverca", "Ambert", "American Cheese"};}

整个程序就这么完成了,总之,可以使用前两个封装好的文件,然后,调用就好了~~~~没什么太难的地方

本博客中内容均为网络转载或收藏,如有侵权,告之即删除

下拉列表滑动到底部自动加载的功能:供参考

下拉刷新最主要的流程是:
(1). 下拉,显示提示头部界面(HeaderView),这个过程提示用户”下拉刷新”
(2). 下拉到一定程度,超出了刷新最基本的下拉界限,我们认为达到了刷新的条件,提示用户可以”松手刷新”了,效果上允许用户继续下拉
(3). 用户松手,可能用户下拉远远不止提示头部界面,所以这一步,先反弹回仅显示提示头部界面,然后提示用户”正在加载”。
(4). 加载完成后,隐藏提示头部界面。

那么让我们看看怎么才能实现呢???
第一步:既然是要显示listview ,那么就应该有个listview 的容器pulldown.xml

<?xml version="1.0" encoding="utf-8"?>
<com.solo.pulldown.PullDownView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/pull_down_view"android:layout_width="fill_parent"android:layout_height="fill_parent"android:background="@android:color/white"></com.solo.pulldown.PullDownView>

第二步:自定义一个listview中显示的item对象pulldown_item.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@android:id/text1"android:layout_width="fill_parent"android:layout_height="wrap_content"android:textAppearance="?android:attr/textAppearanceLarge"android:gravity="center_vertical"android:paddingLeft="6dip"android:minHeight="?android:attr/listPreferredItemHeight"android:textColor="@android:color/black"
/>

第三步:定义一个header的xml布局文件pulldown_header.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="wrap_content"android:paddingBottom="10dp"android:paddingTop="10dp" ><ImageViewandroid:id="@+id/pulldown_header_arrow"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentLeft="true"android:layout_marginLeft="20dp"android:scaleType="centerCrop"android:src="@drawable/z_arrow_down"android:visibility="invisible" /><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignBottom="@+id/pulldown_header_arrow"android:layout_alignTop="@+id/pulldown_header_arrow"android:layout_centerHorizontal="true"android:gravity="center_vertical"android:orientation="vertical" ><TextViewandroid:id="@+id/pulldown_header_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:text="加载中..." /><TextViewandroid:id="@+id/pulldown_header_date"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:text="更新于:"android:visibility="gone" /></LinearLayout><ProgressBarandroid:id="@+id/pulldown_header_loading"style="@android:style/Widget.ProgressBar.Small.Inverse"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginLeft="20dp" /></RelativeLayout>

第四步:如果需要向上拉更新更多的话,那就定义一个底部的footer的布局文件,在此为方便起见,只定义一个progressbar跟textview,更加复杂的显示,就交给你们了~~~~~pulldown_footer.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="wrap_content"android:paddingBottom="10dp"android:paddingTop="10dp" ><TextViewandroid:id="@+id/pulldown_footer_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:text="更多"android:textSize="15dp" /><ProgressBarandroid:id="@+id/pulldown_footer_loading"style="@android:style/Widget.ProgressBar.Small.Inverse"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginLeft="20dp"android:visibility="gone" /></RelativeLayout>

第五步:那么主要的文件这才登场:::::::重写listview这个文件主要任务是提供触摸的事件的处理方法。

/*** <p>一个可以监听ListView是否滚动到最顶部或最底部的自定义控件</p>* 只能监听由触摸产生的,如果是ListView本身Flying导致的,则不能监听</br>* 如果加以改进,可以实现监听scroll滚动的具体位置等*/public class ScrollOverListView extends ListView {private int mLastY;private int mTopPosition;private int mBottomPosition;public ScrollOverListView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init();}public ScrollOverListView(Context context, AttributeSet attrs) {super(context, attrs);init();}public ScrollOverListView(Context context) {super(context);init();}private void init(){mTopPosition = 0;mBottomPosition = 0;}@Overridepublic boolean onTouchEvent(MotionEvent ev) {final int action = ev.getAction();final int y = (int) ev.getRawY();switch(action){case MotionEvent.ACTION_DOWN:{mLastY = y;final boolean isHandled = mOnScrollOverListener.onMotionDown(ev);if (isHandled) {mLastY = y;return isHandled;}break;}case MotionEvent.ACTION_MOVE:{final int childCount = getChildCount();if(childCount == 0) return super.onTouchEvent(ev);final int itemCount = getAdapter().getCount() - mBottomPosition;final int deltaY = y - mLastY;//DLog.d("lastY=%d y=%d", mLastY, y);final int firstTop = getChildAt(0).getTop();final int listPadding = getListPaddingTop();final int lastBottom = getChildAt(childCount - 1).getBottom();final int end = getHeight() - getPaddingBottom();final int firstVisiblePosition = getFirstVisiblePosition();final boolean isHandleMotionMove = mOnScrollOverListener.onMotionMove(ev, deltaY);if(isHandleMotionMove){mLastY = y;return true;}//DLog.d("firstVisiblePosition=%d firstTop=%d listPaddingTop=%d deltaY=%d", firstVisiblePosition, firstTop, listPadding, deltaY);if (firstVisiblePosition <= mTopPosition && firstTop >= listPadding && deltaY > 0) {final boolean isHandleOnListViewTopAndPullDown;isHandleOnListViewTopAndPullDown = mOnScrollOverListener.onListViewTopAndPullDown(deltaY);if(isHandleOnListViewTopAndPullDown){mLastY = y;return true;}}// DLog.d("lastBottom=%d end=%d deltaY=%d", lastBottom, end, deltaY);if (firstVisiblePosition + childCount >= itemCount && lastBottom <= end && deltaY < 0) {final boolean isHandleOnListViewBottomAndPullDown;isHandleOnListViewBottomAndPullDown = mOnScrollOverListener.onListViewBottomAndPullUp(deltaY);if(isHandleOnListViewBottomAndPullDown){mLastY = y;return true;}}break;}case MotionEvent.ACTION_UP:{final boolean isHandlerMotionUp = mOnScrollOverListener.onMotionUp(ev);if (isHandlerMotionUp) {mLastY = y;return true;}break;}}mLastY = y;return super.onTouchEvent(ev);}/**空的*/private OnScrollOverListener mOnScrollOverListener = new OnScrollOverListener(){@Overridepublic boolean onListViewTopAndPullDown(int delta) {return false;}@Overridepublic boolean onListViewBottomAndPullUp(int delta) {return false;}@Overridepublic boolean onMotionDown(MotionEvent ev) {return false;}@Overridepublic boolean onMotionMove(MotionEvent ev, int delta) {return false;}@Overridepublic boolean onMotionUp(MotionEvent ev) {return false;}};// =============================== public method ===============================/*** 可以自定义其中一个条目为头部,头部触发的事件将以这个为准,默认为第一个** @param index 正数第几个,必须在条目数范围之内*/public void setTopPosition(int index){if(getAdapter() == null)throw new NullPointerException("You must set adapter before setTopPosition!");if(index < 0)throw new IllegalArgumentException("Top position must > 0");mTopPosition = index;}/*** 可以自定义其中一个条目为尾部,尾部触发的事件将以这个为准,默认为最后一个** @param index 倒数第几个,必须在条目数范围之内*/public void setBottomPosition(int index){if(getAdapter() == null)throw new NullPointerException("You must set adapter before setBottonPosition!");if(index < 0)throw new IllegalArgumentException("Bottom position must > 0");mBottomPosition = index;}/*** 设置这个Listener可以监听是否到达顶端,或者是否到达低端等事件</br>** @see OnScrollOverListener*/public void setOnScrollOverListener(OnScrollOverListener onScrollOverListener){mOnScrollOverListener = onScrollOverListener;}/*** 滚动监听接口</br>* @see ScrollOverListView#setOnScrollOverListener(OnScrollOverListener)**/public interface OnScrollOverListener {/*** 到达最顶部触发** @param delta 手指点击移动产生的偏移量* @return*/boolean onListViewTopAndPullDown(int delta);/*** 到达最底部触发** @param delta 手指点击移动产生的偏移量* @return*/boolean onListViewBottomAndPullUp(int delta);/*** 手指触摸按下触发,相当于{@link MotionEvent#ACTION_DOWN}** @return 返回true表示自己处理* @see View#onTouchEvent(MotionEvent)*/boolean onMotionDown(MotionEvent ev);/*** 手指触摸移动触发,相当于{@link MotionEvent#ACTION_MOVE}** @return 返回true表示自己处理* @see View#onTouchEvent(MotionEvent)*/boolean onMotionMove(MotionEvent ev, int delta);/*** 手指触摸后提起触发,相当于{@link MotionEvent#ACTION_UP}** @return 返回true表示自己处理* @see View#onTouchEvent(MotionEvent)*/boolean onMotionUp(MotionEvent ev);}}

第六步:下拉刷新控件,真正实现下拉刷新的是这个控件,而上面的那个ScrollOverListView只是提供触摸的事件等

/*** 下拉刷新控件</br>* 真正实现下拉刷新的是这个控件,* ScrollOverListView只是提供触摸的事件等*/
public class PullDownView extends LinearLayout implements OnScrollOverListener{private static final String TAG = "PullDownView";private static final int START_PULL_DEVIATION = 50; // 移动误差private static final int AUTO_INCREMENTAL = 10;     // 自增量,用于回弹private static final int WHAT_DID_LOAD_DATA = 1;    // Handler what 数据加载完毕private static final int WHAT_ON_REFRESH = 2;       // Handler what 刷新中private static final int WHAT_DID_REFRESH = 3;      // Handler what 已经刷新完private static final int WHAT_SET_HEADER_HEIGHT = 4;// Handler what 设置高度private static final int WHAT_DID_MORE = 5;         // Handler what 已经获取完更多private static final int DEFAULT_HEADER_VIEW_HEIGHT = 105;  // 头部文件原本的高度private static SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd HH:mm");private View mHeaderView;private LayoutParams mHeaderViewParams;private TextView mHeaderViewDateView;private TextView mHeaderTextView;private ImageView mHeaderArrowView;private View mHeaderLoadingView;private View mFooterView;private TextView mFooterTextView;private View mFooterLoadingView;private ScrollOverListView mListView;private OnPullDownListener mOnPullDownListener;private RotateAnimation mRotateOTo180Animation;private RotateAnimation mRotate180To0Animation;private int mHeaderIncremental; // 增量private float mMotionDownLastY; // 按下时候的Y轴坐标private boolean mIsDown;            // 是否按下private boolean mIsRefreshing;      // 是否下拉刷新中private boolean mIsFetchMoreing;    // 是否获取更多中private boolean mIsPullUpDone;      // 是否回推完成private boolean mEnableAutoFetchMore;   // 是否允许自动获取更多// 头部文件的状态private static final int HEADER_VIEW_STATE_IDLE = 0;            // 空闲private static final int HEADER_VIEW_STATE_NOT_OVER_HEIGHT = 1; // 没有超过默认高度private static final int HEADER_VIEW_STATE_OVER_HEIGHT = 2;     // 超过默认高度private int mHeaderViewState = HEADER_VIEW_STATE_IDLE;public PullDownView(Context context, AttributeSet attrs) {super(context, attrs);initHeaderViewAndFooterViewAndListView(context);}public PullDownView(Context context) {super(context);initHeaderViewAndFooterViewAndListView(context);}/** ==================================* Public method* 外部使用,具体就是用这几个就可以了** ==================================*//*** 刷新事件接口*/public interface OnPullDownListener {void onRefresh();void onMore();}/*** 通知加载完了数据,要放在Adapter.notifyDataSetChanged后面* 当你加载完数据的时候,调用这个notifyDidLoad()* 才会隐藏头部,并初始化数据等*/public void notifyDidLoad() {mUIHandler.sendEmptyMessage(WHAT_DID_LOAD_DATA);}/*** 通知已经刷新完了,要放在Adapter.notifyDataSetChanged后面* 当你执行完刷新任务之后,调用这个notifyDidRefresh()* 才会隐藏掉头部文件等操作*/public void notifyDidRefresh() {mUIHandler.sendEmptyMessage(WHAT_DID_REFRESH);}/*** 通知已经获取完更多了,要放在Adapter.notifyDataSetChanged后面* 当你执行完更多任务之后,调用这个notyfyDidMore()* 才会隐藏加载圈等操作*/public void notifyDidMore() {mUIHandler.sendEmptyMessage(WHAT_DID_MORE);}/*** 设置监听器* @param listener*/public void setOnPullDownListener(OnPullDownListener listener){mOnPullDownListener = listener;}/*** 获取内嵌的listview* @return ScrollOverListView*/public ListView getListView(){return mListView;}/*** 是否开启自动获取更多* 自动获取更多,将会隐藏footer,并在到达底部的时候自动刷新* @param index 倒数第几个触发*/public void enableAutoFetchMore(boolean enable, int index){if(enable){mListView.setBottomPosition(index);mFooterLoadingView.setVisibility(View.VISIBLE);}else{mFooterTextView.setText("更多");mFooterLoadingView.setVisibility(View.GONE);}mEnableAutoFetchMore = enable;}/** ==================================* Private method* 具体实现下拉刷新等操作** ==================================*//*** 初始化界面*/private void initHeaderViewAndFooterViewAndListView(Context context){setOrientation(LinearLayout.VERTICAL);//setDrawingCacheEnabled(false);/** 自定义头部文件* 放在这里是因为考虑到很多界面都需要使用* 如果要修改,和它相关的设置都要更改*/mHeaderView = LayoutInflater.from(context).inflate(R.layout.pulldown_header, null);mHeaderViewParams = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);addView(mHeaderView, 0, mHeaderViewParams);mHeaderTextView = (TextView) mHeaderView.findViewById(R.id.pulldown_header_text);mHeaderArrowView = (ImageView) mHeaderView.findViewById(R.id.pulldown_header_arrow);mHeaderLoadingView = mHeaderView.findViewById(R.id.pulldown_header_loading);// 注意,图片旋转之后,再执行旋转,坐标会重新开始计算mRotateOTo180Animation = new RotateAnimation(0, 180,Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);mRotateOTo180Animation.setDuration(250);mRotateOTo180Animation.setFillAfter(true);mRotate180To0Animation = new RotateAnimation(180, 0,Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);mRotate180To0Animation.setDuration(250);mRotate180To0Animation.setFillAfter(true);/*** 自定义脚部文件*/mFooterView = LayoutInflater.from(context).inflate(R.layout.pulldown_footer, null);mFooterTextView = (TextView) mFooterView.findViewById(R.id.pulldown_footer_text);mFooterLoadingView = mFooterView.findViewById(R.id.pulldown_footer_loading);mFooterView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {if(!mIsFetchMoreing){mIsFetchMoreing = true;mFooterLoadingView.setVisibility(View.VISIBLE);mOnPullDownListener.onMore();}}});/** ScrollOverListView 同样是考虑到都是使用,所以放在这里* 同时因为,需要它的监听事件*/mListView = new ScrollOverListView(context);mListView.setOnScrollOverListener(this);mListView.setCacheColorHint(0);addView(mListView, LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);// 空的listenermOnPullDownListener = new OnPullDownListener() {@Overridepublic void onRefresh() {}@Overridepublic void onMore() {}};}/*** 在下拉和回推的时候检查头部文件的状态</br>* 如果超过了默认高度,就显示松开可以刷新,* 否则显示下拉可以刷新*/private void checkHeaderViewState(){if(mHeaderViewParams.height >= DEFAULT_HEADER_VIEW_HEIGHT){if(mHeaderViewState == HEADER_VIEW_STATE_OVER_HEIGHT) return;mHeaderViewState = HEADER_VIEW_STATE_OVER_HEIGHT;mHeaderTextView.setText("松开可以刷新");mHeaderArrowView.startAnimation(mRotateOTo180Animation);}else{if(mHeaderViewState == HEADER_VIEW_STATE_NOT_OVER_HEIGHT|| mHeaderViewState == HEADER_VIEW_STATE_IDLE) return;mHeaderViewState = HEADER_VIEW_STATE_NOT_OVER_HEIGHT;mHeaderTextView.setText("下拉可以刷新");mHeaderArrowView.startAnimation(mRotate180To0Animation);}}private void setHeaderHeight(final int height){mHeaderIncremental = height;mHeaderViewParams.height = height;mHeaderView.setLayoutParams(mHeaderViewParams);}/*** 自动隐藏动画*/class HideHeaderViewTask extends TimerTask{@Overridepublic void run() {if(mIsDown) {cancel();return;}mHeaderIncremental -= AUTO_INCREMENTAL;if(mHeaderIncremental > 0){mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);}else{mHeaderIncremental = 0;mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);cancel();}}}/*** 自动显示动画*/class ShowHeaderViewTask extends TimerTask{@Overridepublic void run() {if(mIsDown) {cancel();return;}mHeaderIncremental -= AUTO_INCREMENTAL;if(mHeaderIncremental > DEFAULT_HEADER_VIEW_HEIGHT){mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);}else{mHeaderIncremental = DEFAULT_HEADER_VIEW_HEIGHT;mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);if(!mIsRefreshing){mIsRefreshing = true;mUIHandler.sendEmptyMessage(WHAT_ON_REFRESH);}cancel();}}}private Handler mUIHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case WHAT_DID_LOAD_DATA:{mHeaderViewParams.height = 0;mHeaderLoadingView.setVisibility(View.GONE);mHeaderTextView.setText("下拉可以刷新");mHeaderViewDateView = (TextView) mHeaderView.findViewById(R.id.pulldown_header_date);mHeaderViewDateView.setVisibility(View.VISIBLE);mHeaderViewDateView.setText("更新于:" + dateFormat.format(new Date(System.currentTimeMillis())));mHeaderArrowView.setVisibility(View.VISIBLE);showFooterView();return;}case WHAT_ON_REFRESH:{// 要清除掉动画,否则无法隐藏
                    mHeaderArrowView.clearAnimation();mHeaderArrowView.setVisibility(View.INVISIBLE);mHeaderLoadingView.setVisibility(View.VISIBLE);mOnPullDownListener.onRefresh();return;}case WHAT_DID_REFRESH :{mIsRefreshing = false;mHeaderViewState = HEADER_VIEW_STATE_IDLE;mHeaderArrowView.setVisibility(View.VISIBLE);mHeaderLoadingView.setVisibility(View.GONE);mHeaderViewDateView.setText("更新于:" + dateFormat.format(new Date(System.currentTimeMillis())));setHeaderHeight(0);showFooterView();return;}case WHAT_SET_HEADER_HEIGHT :{setHeaderHeight(mHeaderIncremental);return;}case WHAT_DID_MORE :{mIsFetchMoreing = false;mFooterTextView.setText("更多");mFooterLoadingView.setVisibility(View.GONE);}}}};/*** 显示脚步脚部文件*/private void showFooterView(){if(mListView.getFooterViewsCount() == 0 && isFillScreenItem()){mListView.addFooterView(mFooterView);mListView.setAdapter(mListView.getAdapter());}}/*** 条目是否填满整个屏幕*/private boolean isFillScreenItem(){final int firstVisiblePosition = mListView.getFirstVisiblePosition();final int lastVisiblePostion = mListView.getLastVisiblePosition() - mListView.getFooterViewsCount();final int visibleItemCount = lastVisiblePostion - firstVisiblePosition + 1;final int totalItemCount = mListView.getCount() - mListView.getFooterViewsCount();if(visibleItemCount < totalItemCount) return true;return false;}/** ==================================* 实现 OnScrollOverListener接口*** ==================================*/@Overridepublic boolean onListViewTopAndPullDown(int delta) {if(mIsRefreshing || mListView.getCount() - mListView.getFooterViewsCount() == 0) return false;int absDelta = Math.abs(delta);final int i = (int) Math.ceil((double)absDelta / 2);mHeaderIncremental += i;if(mHeaderIncremental >= 0){ // && mIncremental <= mMaxHeight
            setHeaderHeight(mHeaderIncremental);checkHeaderViewState();}return true;}@Overridepublic boolean onListViewBottomAndPullUp(int delta) {if(!mEnableAutoFetchMore || mIsFetchMoreing) return false;// 数量充满屏幕才触发if(isFillScreenItem()){mIsFetchMoreing = true;mFooterTextView.setText("加载更多中...");mFooterLoadingView.setVisibility(View.VISIBLE);mOnPullDownListener.onMore();return true;}return false;}@Overridepublic boolean onMotionDown(MotionEvent ev) {mIsDown = true;mIsPullUpDone = false;mMotionDownLastY = ev.getRawY();return false;}@Overridepublic boolean onMotionMove(MotionEvent ev, int delta) {//当头部文件回推消失的时候,不允许滚动if(mIsPullUpDone) return true;// 如果开始按下到滑动距离不超过误差值,则不滑动final int absMotionY = (int) Math.abs(ev.getRawY() - mMotionDownLastY);if(absMotionY < START_PULL_DEVIATION) return true;final int absDelta = Math.abs(delta);final int i = (int) Math.ceil((double)absDelta / 2);// onTopDown在顶部,并上回推和onTopUp相对if(mHeaderViewParams.height > 0 && delta < 0){mHeaderIncremental -= i;if(mHeaderIncremental > 0){setHeaderHeight(mHeaderIncremental);checkHeaderViewState();}else{mHeaderViewState = HEADER_VIEW_STATE_IDLE;mHeaderIncremental = 0;setHeaderHeight(mHeaderIncremental);mIsPullUpDone = true;}return true;}return false;}@Overridepublic boolean onMotionUp(MotionEvent ev) {mIsDown = false;// 避免和点击事件冲突if(mHeaderViewParams.height > 0){// 判断头文件拉动的距离与设定的高度,小了就隐藏,多了就固定高度int x = mHeaderIncremental - DEFAULT_HEADER_VIEW_HEIGHT;Timer timer = new Timer(true);if(x < 0){timer.scheduleAtFixedRate(new HideHeaderViewTask(), 0, 10);}else{timer.scheduleAtFixedRate(new ShowHeaderViewTask(), 0, 10);}return true;}return false;}}

第七步:这个java文件就是封装数据,然后调用上两个文件就好了。废话不多说,上代码:::::

public class PullDownActivity extends Activity implements OnPullDownListener, OnItemClickListener{private static final int WHAT_DID_LOAD_DATA = 0;private static final int WHAT_DID_REFRESH = 1;private static final int WHAT_DID_MORE = 2;private ListView mListView;private ArrayAdapter<String> mAdapter;private PullDownView mPullDownView;private List<String> mStrings = new ArrayList<String>();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.pulldown);/** 1.使用PullDownView* 2.设置OnPullDownListener* 3.从mPullDownView里面获取ListView*/mPullDownView = (PullDownView) findViewById(R.id.pull_down_view);mPullDownView.setOnPullDownListener(this);mListView = mPullDownView.getListView();mListView.setOnItemClickListener(this);mAdapter = new ArrayAdapter<String>(this, R.layout.pulldown_item, mStrings);mListView.setAdapter(mAdapter);mPullDownView.enableAutoFetchMore(true, 1);loadData();}private void loadData(){new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}List<String> strings = new ArrayList<String>();for (String body : mStringArray) {strings.add(body);}Message msg = mUIHandler.obtainMessage(WHAT_DID_LOAD_DATA);msg.obj = strings;msg.sendToTarget();}}).start();}@Overridepublic void onRefresh() {new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}Message msg = mUIHandler.obtainMessage(WHAT_DID_REFRESH);msg.obj = "After refresh " + System.currentTimeMillis();msg.sendToTarget();}}).start();}@Overridepublic void onMore() {new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}Message msg = mUIHandler.obtainMessage(WHAT_DID_MORE);msg.obj = "After more " + System.currentTimeMillis();msg.sendToTarget();}}).start();}private Handler mUIHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case WHAT_DID_LOAD_DATA:{if(msg.obj != null){List<String> strings = (List<String>) msg.obj;if(!strings.isEmpty()){mStrings.addAll(strings);mAdapter.notifyDataSetChanged();}}// 诉它数据加载完毕;
                    mPullDownView.notifyDidLoad();break;}case WHAT_DID_REFRESH :{String body = (String) msg.obj;mStrings.add(0, body);mAdapter.notifyDataSetChanged();// 告诉它更新完毕
                    mPullDownView.notifyDidRefresh();break;}case WHAT_DID_MORE:{String body = (String) msg.obj;mStrings.add(body);mAdapter.notifyDataSetChanged();// 告诉它获取更多完毕
                    mPullDownView.notifyDidMore();break;}}}};@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {Toast.makeText(this, "啊,你点中我了 " + position, Toast.LENGTH_SHORT).show();}// 模拟数据private String[] mStringArray = {"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", "Alverca", "Ambert", "American Cheese"};}

整个程序就这么完成了,总之,可以使用前两个封装好的文件,然后,调用就好了~~~~没什么太难的地方

新浪微博就是使用这种方式的典型。

当用户从网络上读取微博的时候,如果一下子全部加载用户未读的微博这将耗费比较长的时间,造成不好的用户体验,同时一屏的内容也不足以显示如此多的内容。这时候,我们就需要用到另一个功能,那就是listview的分页了,其实这个分页可以做成客户端的分页,也可以做成服务器端的分页(点击加载时,从服务器对应的加载第N页就好了!!!)。通过分页分次加载数据,用户看多少就去加载多少。
通常这也分为两种方式,一种是设置一个按钮,用户点击即加载。另一种是当用户滑动到底部时自动加载。今天我就和大家分享一下滑动到底端时自动加载这个功能的实现。
效果图如下所示:

下拉刷新最主要的流程是:
(1). 下拉,显示提示头部界面(HeaderView),这个过程提示用户”下拉刷新”
(2). 下拉到一定程度,超出了刷新最基本的下拉界限,我们认为达到了刷新的条件,提示用户可以”松手刷新”了,效果上允许用户继续下拉
(3). 用户松手,可能用户下拉远远不止提示头部界面,所以这一步,先反弹回仅显示提示头部界面,然后提示用户”正在加载”。
(4). 加载完成后,隐藏提示头部界面。

那么让我们看看怎么才能实现呢???
第一步:既然是要显示listview ,那么就应该有个listview 的容器pulldown.xml

<?xml version="1.0" encoding="utf-8"?>
<com.solo.pulldown.PullDownView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/pull_down_view"android:layout_width="fill_parent"android:layout_height="fill_parent"android:background="@android:color/white"></com.solo.pulldown.PullDownView>

第二步:自定义一个listview中显示的item对象pulldown_item.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@android:id/text1"android:layout_width="fill_parent"android:layout_height="wrap_content"android:textAppearance="?android:attr/textAppearanceLarge"android:gravity="center_vertical"android:paddingLeft="6dip"android:minHeight="?android:attr/listPreferredItemHeight"android:textColor="@android:color/black"
/>

第三步:定义一个header的xml布局文件pulldown_header.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="wrap_content"android:paddingBottom="10dp"android:paddingTop="10dp" ><ImageViewandroid:id="@+id/pulldown_header_arrow"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentLeft="true"android:layout_marginLeft="20dp"android:scaleType="centerCrop"android:src="@drawable/z_arrow_down"android:visibility="invisible" /><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignBottom="@+id/pulldown_header_arrow"android:layout_alignTop="@+id/pulldown_header_arrow"android:layout_centerHorizontal="true"android:gravity="center_vertical"android:orientation="vertical" ><TextViewandroid:id="@+id/pulldown_header_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:text="加载中..." /><TextViewandroid:id="@+id/pulldown_header_date"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:text="更新于:"android:visibility="gone" /></LinearLayout><ProgressBarandroid:id="@+id/pulldown_header_loading"style="@android:style/Widget.ProgressBar.Small.Inverse"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginLeft="20dp" /></RelativeLayout>

第四步:如果需要向上拉更新更多的话,那就定义一个底部的footer的布局文件,在此为方便起见,只定义一个progressbar跟textview,更加复杂的显示,就交给你们了~~~~~pulldown_footer.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="wrap_content"android:paddingBottom="10dp"android:paddingTop="10dp" ><TextViewandroid:id="@+id/pulldown_footer_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:text="更多"android:textSize="15dp" /><ProgressBarandroid:id="@+id/pulldown_footer_loading"style="@android:style/Widget.ProgressBar.Small.Inverse"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginLeft="20dp"android:visibility="gone" /></RelativeLayout>

第五步:那么主要的文件这才登场:::::::重写listview这个文件主要任务是提供触摸的事件的处理方法。

/*** <p>一个可以监听ListView是否滚动到最顶部或最底部的自定义控件</p>* 只能监听由触摸产生的,如果是ListView本身Flying导致的,则不能监听</br>* 如果加以改进,可以实现监听scroll滚动的具体位置等*/public class ScrollOverListView extends ListView {private int mLastY;private int mTopPosition;private int mBottomPosition;public ScrollOverListView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init();}public ScrollOverListView(Context context, AttributeSet attrs) {super(context, attrs);init();}public ScrollOverListView(Context context) {super(context);init();}private void init(){mTopPosition = 0;mBottomPosition = 0;}@Overridepublic boolean onTouchEvent(MotionEvent ev) {final int action = ev.getAction();final int y = (int) ev.getRawY();switch(action){case MotionEvent.ACTION_DOWN:{mLastY = y;final boolean isHandled = mOnScrollOverListener.onMotionDown(ev);if (isHandled) {mLastY = y;return isHandled;}break;}case MotionEvent.ACTION_MOVE:{final int childCount = getChildCount();if(childCount == 0) return super.onTouchEvent(ev);final int itemCount = getAdapter().getCount() - mBottomPosition;final int deltaY = y - mLastY;//DLog.d("lastY=%d y=%d", mLastY, y);final int firstTop = getChildAt(0).getTop();final int listPadding = getListPaddingTop();final int lastBottom = getChildAt(childCount - 1).getBottom();final int end = getHeight() - getPaddingBottom();final int firstVisiblePosition = getFirstVisiblePosition();final boolean isHandleMotionMove = mOnScrollOverListener.onMotionMove(ev, deltaY);if(isHandleMotionMove){mLastY = y;return true;}//DLog.d("firstVisiblePosition=%d firstTop=%d listPaddingTop=%d deltaY=%d", firstVisiblePosition, firstTop, listPadding, deltaY);if (firstVisiblePosition <= mTopPosition && firstTop >= listPadding && deltaY > 0) {final boolean isHandleOnListViewTopAndPullDown;isHandleOnListViewTopAndPullDown = mOnScrollOverListener.onListViewTopAndPullDown(deltaY);if(isHandleOnListViewTopAndPullDown){mLastY = y;return true;}}// DLog.d("lastBottom=%d end=%d deltaY=%d", lastBottom, end, deltaY);if (firstVisiblePosition + childCount >= itemCount && lastBottom <= end && deltaY < 0) {final boolean isHandleOnListViewBottomAndPullDown;isHandleOnListViewBottomAndPullDown = mOnScrollOverListener.onListViewBottomAndPullUp(deltaY);if(isHandleOnListViewBottomAndPullDown){mLastY = y;return true;}}break;}case MotionEvent.ACTION_UP:{final boolean isHandlerMotionUp = mOnScrollOverListener.onMotionUp(ev);if (isHandlerMotionUp) {mLastY = y;return true;}break;}}mLastY = y;return super.onTouchEvent(ev);}/**空的*/private OnScrollOverListener mOnScrollOverListener = new OnScrollOverListener(){@Overridepublic boolean onListViewTopAndPullDown(int delta) {return false;}@Overridepublic boolean onListViewBottomAndPullUp(int delta) {return false;}@Overridepublic boolean onMotionDown(MotionEvent ev) {return false;}@Overridepublic boolean onMotionMove(MotionEvent ev, int delta) {return false;}@Overridepublic boolean onMotionUp(MotionEvent ev) {return false;}};// =============================== public method ===============================/*** 可以自定义其中一个条目为头部,头部触发的事件将以这个为准,默认为第一个** @param index 正数第几个,必须在条目数范围之内*/public void setTopPosition(int index){if(getAdapter() == null)throw new NullPointerException("You must set adapter before setTopPosition!");if(index < 0)throw new IllegalArgumentException("Top position must > 0");mTopPosition = index;}/*** 可以自定义其中一个条目为尾部,尾部触发的事件将以这个为准,默认为最后一个** @param index 倒数第几个,必须在条目数范围之内*/public void setBottomPosition(int index){if(getAdapter() == null)throw new NullPointerException("You must set adapter before setBottonPosition!");if(index < 0)throw new IllegalArgumentException("Bottom position must > 0");mBottomPosition = index;}/*** 设置这个Listener可以监听是否到达顶端,或者是否到达低端等事件</br>** @see OnScrollOverListener*/public void setOnScrollOverListener(OnScrollOverListener onScrollOverListener){mOnScrollOverListener = onScrollOverListener;}/*** 滚动监听接口</br>* @see ScrollOverListView#setOnScrollOverListener(OnScrollOverListener)**/public interface OnScrollOverListener {/*** 到达最顶部触发** @param delta 手指点击移动产生的偏移量* @return*/boolean onListViewTopAndPullDown(int delta);/*** 到达最底部触发** @param delta 手指点击移动产生的偏移量* @return*/boolean onListViewBottomAndPullUp(int delta);/*** 手指触摸按下触发,相当于{@link MotionEvent#ACTION_DOWN}** @return 返回true表示自己处理* @see View#onTouchEvent(MotionEvent)*/boolean onMotionDown(MotionEvent ev);/*** 手指触摸移动触发,相当于{@link MotionEvent#ACTION_MOVE}** @return 返回true表示自己处理* @see View#onTouchEvent(MotionEvent)*/boolean onMotionMove(MotionEvent ev, int delta);/*** 手指触摸后提起触发,相当于{@link MotionEvent#ACTION_UP}** @return 返回true表示自己处理* @see View#onTouchEvent(MotionEvent)*/boolean onMotionUp(MotionEvent ev);}}

第六步:下拉刷新控件,真正实现下拉刷新的是这个控件,而上面的那个ScrollOverListView只是提供触摸的事件等

/*** 下拉刷新控件</br>* 真正实现下拉刷新的是这个控件,* ScrollOverListView只是提供触摸的事件等*/
public class PullDownView extends LinearLayout implements OnScrollOverListener{private static final String TAG = "PullDownView";private static final int START_PULL_DEVIATION = 50; // 移动误差private static final int AUTO_INCREMENTAL = 10;     // 自增量,用于回弹private static final int WHAT_DID_LOAD_DATA = 1;    // Handler what 数据加载完毕private static final int WHAT_ON_REFRESH = 2;       // Handler what 刷新中private static final int WHAT_DID_REFRESH = 3;      // Handler what 已经刷新完private static final int WHAT_SET_HEADER_HEIGHT = 4;// Handler what 设置高度private static final int WHAT_DID_MORE = 5;         // Handler what 已经获取完更多private static final int DEFAULT_HEADER_VIEW_HEIGHT = 105;  // 头部文件原本的高度private static SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd HH:mm");private View mHeaderView;private LayoutParams mHeaderViewParams;private TextView mHeaderViewDateView;private TextView mHeaderTextView;private ImageView mHeaderArrowView;private View mHeaderLoadingView;private View mFooterView;private TextView mFooterTextView;private View mFooterLoadingView;private ScrollOverListView mListView;private OnPullDownListener mOnPullDownListener;private RotateAnimation mRotateOTo180Animation;private RotateAnimation mRotate180To0Animation;private int mHeaderIncremental; // 增量private float mMotionDownLastY; // 按下时候的Y轴坐标private boolean mIsDown;            // 是否按下private boolean mIsRefreshing;      // 是否下拉刷新中private boolean mIsFetchMoreing;    // 是否获取更多中private boolean mIsPullUpDone;      // 是否回推完成private boolean mEnableAutoFetchMore;   // 是否允许自动获取更多// 头部文件的状态private static final int HEADER_VIEW_STATE_IDLE = 0;            // 空闲private static final int HEADER_VIEW_STATE_NOT_OVER_HEIGHT = 1; // 没有超过默认高度private static final int HEADER_VIEW_STATE_OVER_HEIGHT = 2;     // 超过默认高度private int mHeaderViewState = HEADER_VIEW_STATE_IDLE;public PullDownView(Context context, AttributeSet attrs) {super(context, attrs);initHeaderViewAndFooterViewAndListView(context);}public PullDownView(Context context) {super(context);initHeaderViewAndFooterViewAndListView(context);}/** ==================================* Public method* 外部使用,具体就是用这几个就可以了** ==================================*//*** 刷新事件接口*/public interface OnPullDownListener {void onRefresh();void onMore();}/*** 通知加载完了数据,要放在Adapter.notifyDataSetChanged后面* 当你加载完数据的时候,调用这个notifyDidLoad()* 才会隐藏头部,并初始化数据等*/public void notifyDidLoad() {mUIHandler.sendEmptyMessage(WHAT_DID_LOAD_DATA);}/*** 通知已经刷新完了,要放在Adapter.notifyDataSetChanged后面* 当你执行完刷新任务之后,调用这个notifyDidRefresh()* 才会隐藏掉头部文件等操作*/public void notifyDidRefresh() {mUIHandler.sendEmptyMessage(WHAT_DID_REFRESH);}/*** 通知已经获取完更多了,要放在Adapter.notifyDataSetChanged后面* 当你执行完更多任务之后,调用这个notyfyDidMore()* 才会隐藏加载圈等操作*/public void notifyDidMore() {mUIHandler.sendEmptyMessage(WHAT_DID_MORE);}/*** 设置监听器* @param listener*/public void setOnPullDownListener(OnPullDownListener listener){mOnPullDownListener = listener;}/*** 获取内嵌的listview* @return ScrollOverListView*/public ListView getListView(){return mListView;}/*** 是否开启自动获取更多* 自动获取更多,将会隐藏footer,并在到达底部的时候自动刷新* @param index 倒数第几个触发*/public void enableAutoFetchMore(boolean enable, int index){if(enable){mListView.setBottomPosition(index);mFooterLoadingView.setVisibility(View.VISIBLE);}else{mFooterTextView.setText("更多");mFooterLoadingView.setVisibility(View.GONE);}mEnableAutoFetchMore = enable;}/** ==================================* Private method* 具体实现下拉刷新等操作** ==================================*//*** 初始化界面*/private void initHeaderViewAndFooterViewAndListView(Context context){setOrientation(LinearLayout.VERTICAL);//setDrawingCacheEnabled(false);/** 自定义头部文件* 放在这里是因为考虑到很多界面都需要使用* 如果要修改,和它相关的设置都要更改*/mHeaderView = LayoutInflater.from(context).inflate(R.layout.pulldown_header, null);mHeaderViewParams = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);addView(mHeaderView, 0, mHeaderViewParams);mHeaderTextView = (TextView) mHeaderView.findViewById(R.id.pulldown_header_text);mHeaderArrowView = (ImageView) mHeaderView.findViewById(R.id.pulldown_header_arrow);mHeaderLoadingView = mHeaderView.findViewById(R.id.pulldown_header_loading);// 注意,图片旋转之后,再执行旋转,坐标会重新开始计算mRotateOTo180Animation = new RotateAnimation(0, 180,Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);mRotateOTo180Animation.setDuration(250);mRotateOTo180Animation.setFillAfter(true);mRotate180To0Animation = new RotateAnimation(180, 0,Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);mRotate180To0Animation.setDuration(250);mRotate180To0Animation.setFillAfter(true);/*** 自定义脚部文件*/mFooterView = LayoutInflater.from(context).inflate(R.layout.pulldown_footer, null);mFooterTextView = (TextView) mFooterView.findViewById(R.id.pulldown_footer_text);mFooterLoadingView = mFooterView.findViewById(R.id.pulldown_footer_loading);mFooterView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {if(!mIsFetchMoreing){mIsFetchMoreing = true;mFooterLoadingView.setVisibility(View.VISIBLE);mOnPullDownListener.onMore();}}});/** ScrollOverListView 同样是考虑到都是使用,所以放在这里* 同时因为,需要它的监听事件*/mListView = new ScrollOverListView(context);mListView.setOnScrollOverListener(this);mListView.setCacheColorHint(0);addView(mListView, LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);// 空的listenermOnPullDownListener = new OnPullDownListener() {@Overridepublic void onRefresh() {}@Overridepublic void onMore() {}};}/*** 在下拉和回推的时候检查头部文件的状态</br>* 如果超过了默认高度,就显示松开可以刷新,* 否则显示下拉可以刷新*/private void checkHeaderViewState(){if(mHeaderViewParams.height >= DEFAULT_HEADER_VIEW_HEIGHT){if(mHeaderViewState == HEADER_VIEW_STATE_OVER_HEIGHT) return;mHeaderViewState = HEADER_VIEW_STATE_OVER_HEIGHT;mHeaderTextView.setText("松开可以刷新");mHeaderArrowView.startAnimation(mRotateOTo180Animation);}else{if(mHeaderViewState == HEADER_VIEW_STATE_NOT_OVER_HEIGHT|| mHeaderViewState == HEADER_VIEW_STATE_IDLE) return;mHeaderViewState = HEADER_VIEW_STATE_NOT_OVER_HEIGHT;mHeaderTextView.setText("下拉可以刷新");mHeaderArrowView.startAnimation(mRotate180To0Animation);}}private void setHeaderHeight(final int height){mHeaderIncremental = height;mHeaderViewParams.height = height;mHeaderView.setLayoutParams(mHeaderViewParams);}/*** 自动隐藏动画*/class HideHeaderViewTask extends TimerTask{@Overridepublic void run() {if(mIsDown) {cancel();return;}mHeaderIncremental -= AUTO_INCREMENTAL;if(mHeaderIncremental > 0){mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);}else{mHeaderIncremental = 0;mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);cancel();}}}/*** 自动显示动画*/class ShowHeaderViewTask extends TimerTask{@Overridepublic void run() {if(mIsDown) {cancel();return;}mHeaderIncremental -= AUTO_INCREMENTAL;if(mHeaderIncremental > DEFAULT_HEADER_VIEW_HEIGHT){mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);}else{mHeaderIncremental = DEFAULT_HEADER_VIEW_HEIGHT;mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);if(!mIsRefreshing){mIsRefreshing = true;mUIHandler.sendEmptyMessage(WHAT_ON_REFRESH);}cancel();}}}private Handler mUIHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case WHAT_DID_LOAD_DATA:{mHeaderViewParams.height = 0;mHeaderLoadingView.setVisibility(View.GONE);mHeaderTextView.setText("下拉可以刷新");mHeaderViewDateView = (TextView) mHeaderView.findViewById(R.id.pulldown_header_date);mHeaderViewDateView.setVisibility(View.VISIBLE);mHeaderViewDateView.setText("更新于:" + dateFormat.format(new Date(System.currentTimeMillis())));mHeaderArrowView.setVisibility(View.VISIBLE);showFooterView();return;}case WHAT_ON_REFRESH:{// 要清除掉动画,否则无法隐藏
                    mHeaderArrowView.clearAnimation();mHeaderArrowView.setVisibility(View.INVISIBLE);mHeaderLoadingView.setVisibility(View.VISIBLE);mOnPullDownListener.onRefresh();return;}case WHAT_DID_REFRESH :{mIsRefreshing = false;mHeaderViewState = HEADER_VIEW_STATE_IDLE;mHeaderArrowView.setVisibility(View.VISIBLE);mHeaderLoadingView.setVisibility(View.GONE);mHeaderViewDateView.setText("更新于:" + dateFormat.format(new Date(System.currentTimeMillis())));setHeaderHeight(0);showFooterView();return;}case WHAT_SET_HEADER_HEIGHT :{setHeaderHeight(mHeaderIncremental);return;}case WHAT_DID_MORE :{mIsFetchMoreing = false;mFooterTextView.setText("更多");mFooterLoadingView.setVisibility(View.GONE);}}}};/*** 显示脚步脚部文件*/private void showFooterView(){if(mListView.getFooterViewsCount() == 0 && isFillScreenItem()){mListView.addFooterView(mFooterView);mListView.setAdapter(mListView.getAdapter());}}/*** 条目是否填满整个屏幕*/private boolean isFillScreenItem(){final int firstVisiblePosition = mListView.getFirstVisiblePosition();final int lastVisiblePostion = mListView.getLastVisiblePosition() - mListView.getFooterViewsCount();final int visibleItemCount = lastVisiblePostion - firstVisiblePosition + 1;final int totalItemCount = mListView.getCount() - mListView.getFooterViewsCount();if(visibleItemCount < totalItemCount) return true;return false;}/** ==================================* 实现 OnScrollOverListener接口*** ==================================*/@Overridepublic boolean onListViewTopAndPullDown(int delta) {if(mIsRefreshing || mListView.getCount() - mListView.getFooterViewsCount() == 0) return false;int absDelta = Math.abs(delta);final int i = (int) Math.ceil((double)absDelta / 2);mHeaderIncremental += i;if(mHeaderIncremental >= 0){ // && mIncremental <= mMaxHeight
            setHeaderHeight(mHeaderIncremental);checkHeaderViewState();}return true;}@Overridepublic boolean onListViewBottomAndPullUp(int delta) {if(!mEnableAutoFetchMore || mIsFetchMoreing) return false;// 数量充满屏幕才触发if(isFillScreenItem()){mIsFetchMoreing = true;mFooterTextView.setText("加载更多中...");mFooterLoadingView.setVisibility(View.VISIBLE);mOnPullDownListener.onMore();return true;}return false;}@Overridepublic boolean onMotionDown(MotionEvent ev) {mIsDown = true;mIsPullUpDone = false;mMotionDownLastY = ev.getRawY();return false;}@Overridepublic boolean onMotionMove(MotionEvent ev, int delta) {//当头部文件回推消失的时候,不允许滚动if(mIsPullUpDone) return true;// 如果开始按下到滑动距离不超过误差值,则不滑动final int absMotionY = (int) Math.abs(ev.getRawY() - mMotionDownLastY);if(absMotionY < START_PULL_DEVIATION) return true;final int absDelta = Math.abs(delta);final int i = (int) Math.ceil((double)absDelta / 2);// onTopDown在顶部,并上回推和onTopUp相对if(mHeaderViewParams.height > 0 && delta < 0){mHeaderIncremental -= i;if(mHeaderIncremental > 0){setHeaderHeight(mHeaderIncremental);checkHeaderViewState();}else{mHeaderViewState = HEADER_VIEW_STATE_IDLE;mHeaderIncremental = 0;setHeaderHeight(mHeaderIncremental);mIsPullUpDone = true;}return true;}return false;}@Overridepublic boolean onMotionUp(MotionEvent ev) {mIsDown = false;// 避免和点击事件冲突if(mHeaderViewParams.height > 0){// 判断头文件拉动的距离与设定的高度,小了就隐藏,多了就固定高度int x = mHeaderIncremental - DEFAULT_HEADER_VIEW_HEIGHT;Timer timer = new Timer(true);if(x < 0){timer.scheduleAtFixedRate(new HideHeaderViewTask(), 0, 10);}else{timer.scheduleAtFixedRate(new ShowHeaderViewTask(), 0, 10);}return true;}return false;}}

第七步:这个java文件就是封装数据,然后调用上两个文件就好了。废话不多说,上代码:::::

public class PullDownActivity extends Activity implements OnPullDownListener, OnItemClickListener{private static final int WHAT_DID_LOAD_DATA = 0;private static final int WHAT_DID_REFRESH = 1;private static final int WHAT_DID_MORE = 2;private ListView mListView;private ArrayAdapter<String> mAdapter;private PullDownView mPullDownView;private List<String> mStrings = new ArrayList<String>();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.pulldown);/** 1.使用PullDownView* 2.设置OnPullDownListener* 3.从mPullDownView里面获取ListView*/mPullDownView = (PullDownView) findViewById(R.id.pull_down_view);mPullDownView.setOnPullDownListener(this);mListView = mPullDownView.getListView();mListView.setOnItemClickListener(this);mAdapter = new ArrayAdapter<String>(this, R.layout.pulldown_item, mStrings);mListView.setAdapter(mAdapter);mPullDownView.enableAutoFetchMore(true, 1);loadData();}private void loadData(){new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}List<String> strings = new ArrayList<String>();for (String body : mStringArray) {strings.add(body);}Message msg = mUIHandler.obtainMessage(WHAT_DID_LOAD_DATA);msg.obj = strings;msg.sendToTarget();}}).start();}@Overridepublic void onRefresh() {new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}Message msg = mUIHandler.obtainMessage(WHAT_DID_REFRESH);msg.obj = "After refresh " + System.currentTimeMillis();msg.sendToTarget();}}).start();}@Overridepublic void onMore() {new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}Message msg = mUIHandler.obtainMessage(WHAT_DID_MORE);msg.obj = "After more " + System.currentTimeMillis();msg.sendToTarget();}}).start();}private Handler mUIHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case WHAT_DID_LOAD_DATA:{if(msg.obj != null){List<String> strings = (List<String>) msg.obj;if(!strings.isEmpty()){mStrings.addAll(strings);mAdapter.notifyDataSetChanged();}}// 诉它数据加载完毕;
                    mPullDownView.notifyDidLoad();break;}case WHAT_DID_REFRESH :{String body = (String) msg.obj;mStrings.add(0, body);mAdapter.notifyDataSetChanged();// 告诉它更新完毕
                    mPullDownView.notifyDidRefresh();break;}case WHAT_DID_MORE:{String body = (String) msg.obj;mStrings.add(body);mAdapter.notifyDataSetChanged();// 告诉它获取更多完毕
                    mPullDownView.notifyDidMore();break;}}}};@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {Toast.makeText(this, "啊,你点中我了 " + position, Toast.LENGTH_SHORT).show();}// 模拟数据private String[] mStringArray = {"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", "Alverca", "Ambert", "American Cheese"};}

整个程序就这么完成了,总之,可以使用前两个封装好的文件,然后,调用就好了~~~~没什么太难的地方

本博客中内容均为网络转载或收藏,如有侵权,告之即删除。

新浪微博就是使用这种方式的典型。

当用户从网络上读取微博的时候,如果一下子全部加载用户未读的微博这将耗费比较长的时间,造成不好的用户体验,同时一屏的内容也不足以显示如此多的内容。这时候,我们就需要用到另一个功能,那就是listview的分页了,其实这个分页可以做成客户端的分页,也可以做成服务器端的分页(点击加载时,从服务器对应的加载第N页就好了!!!)。通过分页分次加载数据,用户看多少就去加载多少。
通常这也分为两种方式,一种是设置一个按钮,用户点击即加载。另一种是当用户滑动到底部时自动加载。今天我就和大家分享一下滑动到底端时自动加载这个功能的实现。
效果图如下所示:

下拉刷新最主要的流程是:
(1). 下拉,显示提示头部界面(HeaderView),这个过程提示用户”下拉刷新”
(2). 下拉到一定程度,超出了刷新最基本的下拉界限,我们认为达到了刷新的条件,提示用户可以”松手刷新”了,效果上允许用户继续下拉
(3). 用户松手,可能用户下拉远远不止提示头部界面,所以这一步,先反弹回仅显示提示头部界面,然后提示用户”正在加载”。
(4). 加载完成后,隐藏提示头部界面。

那么让我们看看怎么才能实现呢???
第一步:既然是要显示listview ,那么就应该有个listview 的容器pulldown.xml

<?xml version="1.0" encoding="utf-8"?>
<com.solo.pulldown.PullDownView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/pull_down_view"android:layout_width="fill_parent"android:layout_height="fill_parent"android:background="@android:color/white"></com.solo.pulldown.PullDownView>

第二步:自定义一个listview中显示的item对象pulldown_item.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@android:id/text1"android:layout_width="fill_parent"android:layout_height="wrap_content"android:textAppearance="?android:attr/textAppearanceLarge"android:gravity="center_vertical"android:paddingLeft="6dip"android:minHeight="?android:attr/listPreferredItemHeight"android:textColor="@android:color/black"
/>

第三步:定义一个header的xml布局文件pulldown_header.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="wrap_content"android:paddingBottom="10dp"android:paddingTop="10dp" ><ImageViewandroid:id="@+id/pulldown_header_arrow"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentLeft="true"android:layout_marginLeft="20dp"android:scaleType="centerCrop"android:src="@drawable/z_arrow_down"android:visibility="invisible" /><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignBottom="@+id/pulldown_header_arrow"android:layout_alignTop="@+id/pulldown_header_arrow"android:layout_centerHorizontal="true"android:gravity="center_vertical"android:orientation="vertical" ><TextViewandroid:id="@+id/pulldown_header_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:text="加载中..." /><TextViewandroid:id="@+id/pulldown_header_date"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:text="更新于:"android:visibility="gone" /></LinearLayout><ProgressBarandroid:id="@+id/pulldown_header_loading"style="@android:style/Widget.ProgressBar.Small.Inverse"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginLeft="20dp" /></RelativeLayout>

第四步:如果需要向上拉更新更多的话,那就定义一个底部的footer的布局文件,在此为方便起见,只定义一个progressbar跟textview,更加复杂的显示,就交给你们了~~~~~pulldown_footer.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="wrap_content"android:paddingBottom="10dp"android:paddingTop="10dp" ><TextViewandroid:id="@+id/pulldown_footer_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:text="更多"android:textSize="15dp" /><ProgressBarandroid:id="@+id/pulldown_footer_loading"style="@android:style/Widget.ProgressBar.Small.Inverse"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginLeft="20dp"android:visibility="gone" /></RelativeLayout>

第五步:那么主要的文件这才登场:::::::重写listview这个文件主要任务是提供触摸的事件的处理方法。

/*** <p>一个可以监听ListView是否滚动到最顶部或最底部的自定义控件</p>* 只能监听由触摸产生的,如果是ListView本身Flying导致的,则不能监听</br>* 如果加以改进,可以实现监听scroll滚动的具体位置等*/public class ScrollOverListView extends ListView {private int mLastY;private int mTopPosition;private int mBottomPosition;public ScrollOverListView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init();}public ScrollOverListView(Context context, AttributeSet attrs) {super(context, attrs);init();}public ScrollOverListView(Context context) {super(context);init();}private void init(){mTopPosition = 0;mBottomPosition = 0;}@Overridepublic boolean onTouchEvent(MotionEvent ev) {final int action = ev.getAction();final int y = (int) ev.getRawY();switch(action){case MotionEvent.ACTION_DOWN:{mLastY = y;final boolean isHandled = mOnScrollOverListener.onMotionDown(ev);if (isHandled) {mLastY = y;return isHandled;}break;}case MotionEvent.ACTION_MOVE:{final int childCount = getChildCount();if(childCount == 0) return super.onTouchEvent(ev);final int itemCount = getAdapter().getCount() - mBottomPosition;final int deltaY = y - mLastY;//DLog.d("lastY=%d y=%d", mLastY, y);final int firstTop = getChildAt(0).getTop();final int listPadding = getListPaddingTop();final int lastBottom = getChildAt(childCount - 1).getBottom();final int end = getHeight() - getPaddingBottom();final int firstVisiblePosition = getFirstVisiblePosition();final boolean isHandleMotionMove = mOnScrollOverListener.onMotionMove(ev, deltaY);if(isHandleMotionMove){mLastY = y;return true;}//DLog.d("firstVisiblePosition=%d firstTop=%d listPaddingTop=%d deltaY=%d", firstVisiblePosition, firstTop, listPadding, deltaY);if (firstVisiblePosition <= mTopPosition && firstTop >= listPadding && deltaY > 0) {final boolean isHandleOnListViewTopAndPullDown;isHandleOnListViewTopAndPullDown = mOnScrollOverListener.onListViewTopAndPullDown(deltaY);if(isHandleOnListViewTopAndPullDown){mLastY = y;return true;}}// DLog.d("lastBottom=%d end=%d deltaY=%d", lastBottom, end, deltaY);if (firstVisiblePosition + childCount >= itemCount && lastBottom <= end && deltaY < 0) {final boolean isHandleOnListViewBottomAndPullDown;isHandleOnListViewBottomAndPullDown = mOnScrollOverListener.onListViewBottomAndPullUp(deltaY);if(isHandleOnListViewBottomAndPullDown){mLastY = y;return true;}}break;}case MotionEvent.ACTION_UP:{final boolean isHandlerMotionUp = mOnScrollOverListener.onMotionUp(ev);if (isHandlerMotionUp) {mLastY = y;return true;}break;}}mLastY = y;return super.onTouchEvent(ev);}/**空的*/private OnScrollOverListener mOnScrollOverListener = new OnScrollOverListener(){@Overridepublic boolean onListViewTopAndPullDown(int delta) {return false;}@Overridepublic boolean onListViewBottomAndPullUp(int delta) {return false;}@Overridepublic boolean onMotionDown(MotionEvent ev) {return false;}@Overridepublic boolean onMotionMove(MotionEvent ev, int delta) {return false;}@Overridepublic boolean onMotionUp(MotionEvent ev) {return false;}};// =============================== public method ===============================/*** 可以自定义其中一个条目为头部,头部触发的事件将以这个为准,默认为第一个** @param index 正数第几个,必须在条目数范围之内*/public void setTopPosition(int index){if(getAdapter() == null)throw new NullPointerException("You must set adapter before setTopPosition!");if(index < 0)throw new IllegalArgumentException("Top position must > 0");mTopPosition = index;}/*** 可以自定义其中一个条目为尾部,尾部触发的事件将以这个为准,默认为最后一个** @param index 倒数第几个,必须在条目数范围之内*/public void setBottomPosition(int index){if(getAdapter() == null)throw new NullPointerException("You must set adapter before setBottonPosition!");if(index < 0)throw new IllegalArgumentException("Bottom position must > 0");mBottomPosition = index;}/*** 设置这个Listener可以监听是否到达顶端,或者是否到达低端等事件</br>** @see OnScrollOverListener*/public void setOnScrollOverListener(OnScrollOverListener onScrollOverListener){mOnScrollOverListener = onScrollOverListener;}/*** 滚动监听接口</br>* @see ScrollOverListView#setOnScrollOverListener(OnScrollOverListener)**/public interface OnScrollOverListener {/*** 到达最顶部触发** @param delta 手指点击移动产生的偏移量* @return*/boolean onListViewTopAndPullDown(int delta);/*** 到达最底部触发** @param delta 手指点击移动产生的偏移量* @return*/boolean onListViewBottomAndPullUp(int delta);/*** 手指触摸按下触发,相当于{@link MotionEvent#ACTION_DOWN}** @return 返回true表示自己处理* @see View#onTouchEvent(MotionEvent)*/boolean onMotionDown(MotionEvent ev);/*** 手指触摸移动触发,相当于{@link MotionEvent#ACTION_MOVE}** @return 返回true表示自己处理* @see View#onTouchEvent(MotionEvent)*/boolean onMotionMove(MotionEvent ev, int delta);/*** 手指触摸后提起触发,相当于{@link MotionEvent#ACTION_UP}** @return 返回true表示自己处理* @see View#onTouchEvent(MotionEvent)*/boolean onMotionUp(MotionEvent ev);}}

第六步:下拉刷新控件,真正实现下拉刷新的是这个控件,而上面的那个ScrollOverListView只是提供触摸的事件等

/*** 下拉刷新控件</br>* 真正实现下拉刷新的是这个控件,* ScrollOverListView只是提供触摸的事件等*/
public class PullDownView extends LinearLayout implements OnScrollOverListener{private static final String TAG = "PullDownView";private static final int START_PULL_DEVIATION = 50; // 移动误差private static final int AUTO_INCREMENTAL = 10;     // 自增量,用于回弹private static final int WHAT_DID_LOAD_DATA = 1;    // Handler what 数据加载完毕private static final int WHAT_ON_REFRESH = 2;       // Handler what 刷新中private static final int WHAT_DID_REFRESH = 3;      // Handler what 已经刷新完private static final int WHAT_SET_HEADER_HEIGHT = 4;// Handler what 设置高度private static final int WHAT_DID_MORE = 5;         // Handler what 已经获取完更多private static final int DEFAULT_HEADER_VIEW_HEIGHT = 105;  // 头部文件原本的高度private static SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd HH:mm");private View mHeaderView;private LayoutParams mHeaderViewParams;private TextView mHeaderViewDateView;private TextView mHeaderTextView;private ImageView mHeaderArrowView;private View mHeaderLoadingView;private View mFooterView;private TextView mFooterTextView;private View mFooterLoadingView;private ScrollOverListView mListView;private OnPullDownListener mOnPullDownListener;private RotateAnimation mRotateOTo180Animation;private RotateAnimation mRotate180To0Animation;private int mHeaderIncremental; // 增量private float mMotionDownLastY; // 按下时候的Y轴坐标private boolean mIsDown;            // 是否按下private boolean mIsRefreshing;      // 是否下拉刷新中private boolean mIsFetchMoreing;    // 是否获取更多中private boolean mIsPullUpDone;      // 是否回推完成private boolean mEnableAutoFetchMore;   // 是否允许自动获取更多// 头部文件的状态private static final int HEADER_VIEW_STATE_IDLE = 0;            // 空闲private static final int HEADER_VIEW_STATE_NOT_OVER_HEIGHT = 1; // 没有超过默认高度private static final int HEADER_VIEW_STATE_OVER_HEIGHT = 2;     // 超过默认高度private int mHeaderViewState = HEADER_VIEW_STATE_IDLE;public PullDownView(Context context, AttributeSet attrs) {super(context, attrs);initHeaderViewAndFooterViewAndListView(context);}public PullDownView(Context context) {super(context);initHeaderViewAndFooterViewAndListView(context);}/** ==================================* Public method* 外部使用,具体就是用这几个就可以了** ==================================*//*** 刷新事件接口*/public interface OnPullDownListener {void onRefresh();void onMore();}/*** 通知加载完了数据,要放在Adapter.notifyDataSetChanged后面* 当你加载完数据的时候,调用这个notifyDidLoad()* 才会隐藏头部,并初始化数据等*/public void notifyDidLoad() {mUIHandler.sendEmptyMessage(WHAT_DID_LOAD_DATA);}/*** 通知已经刷新完了,要放在Adapter.notifyDataSetChanged后面* 当你执行完刷新任务之后,调用这个notifyDidRefresh()* 才会隐藏掉头部文件等操作*/public void notifyDidRefresh() {mUIHandler.sendEmptyMessage(WHAT_DID_REFRESH);}/*** 通知已经获取完更多了,要放在Adapter.notifyDataSetChanged后面* 当你执行完更多任务之后,调用这个notyfyDidMore()* 才会隐藏加载圈等操作*/public void notifyDidMore() {mUIHandler.sendEmptyMessage(WHAT_DID_MORE);}/*** 设置监听器* @param listener*/public void setOnPullDownListener(OnPullDownListener listener){mOnPullDownListener = listener;}/*** 获取内嵌的listview* @return ScrollOverListView*/public ListView getListView(){return mListView;}/*** 是否开启自动获取更多* 自动获取更多,将会隐藏footer,并在到达底部的时候自动刷新* @param index 倒数第几个触发*/public void enableAutoFetchMore(boolean enable, int index){if(enable){mListView.setBottomPosition(index);mFooterLoadingView.setVisibility(View.VISIBLE);}else{mFooterTextView.setText("更多");mFooterLoadingView.setVisibility(View.GONE);}mEnableAutoFetchMore = enable;}/** ==================================* Private method* 具体实现下拉刷新等操作** ==================================*//*** 初始化界面*/private void initHeaderViewAndFooterViewAndListView(Context context){setOrientation(LinearLayout.VERTICAL);//setDrawingCacheEnabled(false);/** 自定义头部文件* 放在这里是因为考虑到很多界面都需要使用* 如果要修改,和它相关的设置都要更改*/mHeaderView = LayoutInflater.from(context).inflate(R.layout.pulldown_header, null);mHeaderViewParams = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);addView(mHeaderView, 0, mHeaderViewParams);mHeaderTextView = (TextView) mHeaderView.findViewById(R.id.pulldown_header_text);mHeaderArrowView = (ImageView) mHeaderView.findViewById(R.id.pulldown_header_arrow);mHeaderLoadingView = mHeaderView.findViewById(R.id.pulldown_header_loading);// 注意,图片旋转之后,再执行旋转,坐标会重新开始计算mRotateOTo180Animation = new RotateAnimation(0, 180,Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);mRotateOTo180Animation.setDuration(250);mRotateOTo180Animation.setFillAfter(true);mRotate180To0Animation = new RotateAnimation(180, 0,Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);mRotate180To0Animation.setDuration(250);mRotate180To0Animation.setFillAfter(true);/*** 自定义脚部文件*/mFooterView = LayoutInflater.from(context).inflate(R.layout.pulldown_footer, null);mFooterTextView = (TextView) mFooterView.findViewById(R.id.pulldown_footer_text);mFooterLoadingView = mFooterView.findViewById(R.id.pulldown_footer_loading);mFooterView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {if(!mIsFetchMoreing){mIsFetchMoreing = true;mFooterLoadingView.setVisibility(View.VISIBLE);mOnPullDownListener.onMore();}}});/** ScrollOverListView 同样是考虑到都是使用,所以放在这里* 同时因为,需要它的监听事件*/mListView = new ScrollOverListView(context);mListView.setOnScrollOverListener(this);mListView.setCacheColorHint(0);addView(mListView, LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);// 空的listenermOnPullDownListener = new OnPullDownListener() {@Overridepublic void onRefresh() {}@Overridepublic void onMore() {}};}/*** 在下拉和回推的时候检查头部文件的状态</br>* 如果超过了默认高度,就显示松开可以刷新,* 否则显示下拉可以刷新*/private void checkHeaderViewState(){if(mHeaderViewParams.height >= DEFAULT_HEADER_VIEW_HEIGHT){if(mHeaderViewState == HEADER_VIEW_STATE_OVER_HEIGHT) return;mHeaderViewState = HEADER_VIEW_STATE_OVER_HEIGHT;mHeaderTextView.setText("松开可以刷新");mHeaderArrowView.startAnimation(mRotateOTo180Animation);}else{if(mHeaderViewState == HEADER_VIEW_STATE_NOT_OVER_HEIGHT|| mHeaderViewState == HEADER_VIEW_STATE_IDLE) return;mHeaderViewState = HEADER_VIEW_STATE_NOT_OVER_HEIGHT;mHeaderTextView.setText("下拉可以刷新");mHeaderArrowView.startAnimation(mRotate180To0Animation);}}private void setHeaderHeight(final int height){mHeaderIncremental = height;mHeaderViewParams.height = height;mHeaderView.setLayoutParams(mHeaderViewParams);}/*** 自动隐藏动画*/class HideHeaderViewTask extends TimerTask{@Overridepublic void run() {if(mIsDown) {cancel();return;}mHeaderIncremental -= AUTO_INCREMENTAL;if(mHeaderIncremental > 0){mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);}else{mHeaderIncremental = 0;mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);cancel();}}}/*** 自动显示动画*/class ShowHeaderViewTask extends TimerTask{@Overridepublic void run() {if(mIsDown) {cancel();return;}mHeaderIncremental -= AUTO_INCREMENTAL;if(mHeaderIncremental > DEFAULT_HEADER_VIEW_HEIGHT){mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);}else{mHeaderIncremental = DEFAULT_HEADER_VIEW_HEIGHT;mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);if(!mIsRefreshing){mIsRefreshing = true;mUIHandler.sendEmptyMessage(WHAT_ON_REFRESH);}cancel();}}}private Handler mUIHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case WHAT_DID_LOAD_DATA:{mHeaderViewParams.height = 0;mHeaderLoadingView.setVisibility(View.GONE);mHeaderTextView.setText("下拉可以刷新");mHeaderViewDateView = (TextView) mHeaderView.findViewById(R.id.pulldown_header_date);mHeaderViewDateView.setVisibility(View.VISIBLE);mHeaderViewDateView.setText("更新于:" + dateFormat.format(new Date(System.currentTimeMillis())));mHeaderArrowView.setVisibility(View.VISIBLE);showFooterView();return;}case WHAT_ON_REFRESH:{// 要清除掉动画,否则无法隐藏
                    mHeaderArrowView.clearAnimation();mHeaderArrowView.setVisibility(View.INVISIBLE);mHeaderLoadingView.setVisibility(View.VISIBLE);mOnPullDownListener.onRefresh();return;}case WHAT_DID_REFRESH :{mIsRefreshing = false;mHeaderViewState = HEADER_VIEW_STATE_IDLE;mHeaderArrowView.setVisibility(View.VISIBLE);mHeaderLoadingView.setVisibility(View.GONE);mHeaderViewDateView.setText("更新于:" + dateFormat.format(new Date(System.currentTimeMillis())));setHeaderHeight(0);showFooterView();return;}case WHAT_SET_HEADER_HEIGHT :{setHeaderHeight(mHeaderIncremental);return;}case WHAT_DID_MORE :{mIsFetchMoreing = false;mFooterTextView.setText("更多");mFooterLoadingView.setVisibility(View.GONE);}}}};/*** 显示脚步脚部文件*/private void showFooterView(){if(mListView.getFooterViewsCount() == 0 && isFillScreenItem()){mListView.addFooterView(mFooterView);mListView.setAdapter(mListView.getAdapter());}}/*** 条目是否填满整个屏幕*/private boolean isFillScreenItem(){final int firstVisiblePosition = mListView.getFirstVisiblePosition();final int lastVisiblePostion = mListView.getLastVisiblePosition() - mListView.getFooterViewsCount();final int visibleItemCount = lastVisiblePostion - firstVisiblePosition + 1;final int totalItemCount = mListView.getCount() - mListView.getFooterViewsCount();if(visibleItemCount < totalItemCount) return true;return false;}/** ==================================* 实现 OnScrollOverListener接口*** ==================================*/@Overridepublic boolean onListViewTopAndPullDown(int delta) {if(mIsRefreshing || mListView.getCount() - mListView.getFooterViewsCount() == 0) return false;int absDelta = Math.abs(delta);final int i = (int) Math.ceil((double)absDelta / 2);mHeaderIncremental += i;if(mHeaderIncremental >= 0){ // && mIncremental <= mMaxHeight
            setHeaderHeight(mHeaderIncremental);checkHeaderViewState();}return true;}@Overridepublic boolean onListViewBottomAndPullUp(int delta) {if(!mEnableAutoFetchMore || mIsFetchMoreing) return false;// 数量充满屏幕才触发if(isFillScreenItem()){mIsFetchMoreing = true;mFooterTextView.setText("加载更多中...");mFooterLoadingView.setVisibility(View.VISIBLE);mOnPullDownListener.onMore();return true;}return false;}@Overridepublic boolean onMotionDown(MotionEvent ev) {mIsDown = true;mIsPullUpDone = false;mMotionDownLastY = ev.getRawY();return false;}@Overridepublic boolean onMotionMove(MotionEvent ev, int delta) {//当头部文件回推消失的时候,不允许滚动if(mIsPullUpDone) return true;// 如果开始按下到滑动距离不超过误差值,则不滑动final int absMotionY = (int) Math.abs(ev.getRawY() - mMotionDownLastY);if(absMotionY < START_PULL_DEVIATION) return true;final int absDelta = Math.abs(delta);final int i = (int) Math.ceil((double)absDelta / 2);// onTopDown在顶部,并上回推和onTopUp相对if(mHeaderViewParams.height > 0 && delta < 0){mHeaderIncremental -= i;if(mHeaderIncremental > 0){setHeaderHeight(mHeaderIncremental);checkHeaderViewState();}else{mHeaderViewState = HEADER_VIEW_STATE_IDLE;mHeaderIncremental = 0;setHeaderHeight(mHeaderIncremental);mIsPullUpDone = true;}return true;}return false;}@Overridepublic boolean onMotionUp(MotionEvent ev) {mIsDown = false;// 避免和点击事件冲突if(mHeaderViewParams.height > 0){// 判断头文件拉动的距离与设定的高度,小了就隐藏,多了就固定高度int x = mHeaderIncremental - DEFAULT_HEADER_VIEW_HEIGHT;Timer timer = new Timer(true);if(x < 0){timer.scheduleAtFixedRate(new HideHeaderViewTask(), 0, 10);}else{timer.scheduleAtFixedRate(new ShowHeaderViewTask(), 0, 10);}return true;}return false;}}

第七步:这个java文件就是封装数据,然后调用上两个文件就好了。废话不多说,上代码:::::

public class PullDownActivity extends Activity implements OnPullDownListener, OnItemClickListener{private static final int WHAT_DID_LOAD_DATA = 0;private static final int WHAT_DID_REFRESH = 1;private static final int WHAT_DID_MORE = 2;private ListView mListView;private ArrayAdapter<String> mAdapter;private PullDownView mPullDownView;private List<String> mStrings = new ArrayList<String>();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.pulldown);/** 1.使用PullDownView* 2.设置OnPullDownListener* 3.从mPullDownView里面获取ListView*/mPullDownView = (PullDownView) findViewById(R.id.pull_down_view);mPullDownView.setOnPullDownListener(this);mListView = mPullDownView.getListView();mListView.setOnItemClickListener(this);mAdapter = new ArrayAdapter<String>(this, R.layout.pulldown_item, mStrings);mListView.setAdapter(mAdapter);mPullDownView.enableAutoFetchMore(true, 1);loadData();}private void loadData(){new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}List<String> strings = new ArrayList<String>();for (String body : mStringArray) {strings.add(body);}Message msg = mUIHandler.obtainMessage(WHAT_DID_LOAD_DATA);msg.obj = strings;msg.sendToTarget();}}).start();}@Overridepublic void onRefresh() {new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}Message msg = mUIHandler.obtainMessage(WHAT_DID_REFRESH);msg.obj = "After refresh " + System.currentTimeMillis();msg.sendToTarget();}}).start();}@Overridepublic void onMore() {new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}Message msg = mUIHandler.obtainMessage(WHAT_DID_MORE);msg.obj = "After more " + System.currentTimeMillis();msg.sendToTarget();}}).start();}private Handler mUIHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case WHAT_DID_LOAD_DATA:{if(msg.obj != null){List<String> strings = (List<String>) msg.obj;if(!strings.isEmpty()){mStrings.addAll(strings);mAdapter.notifyDataSetChanged();}}// 诉它数据加载完毕;
                    mPullDownView.notifyDidLoad();break;}case WHAT_DID_REFRESH :{String body = (String) msg.obj;mStrings.add(0, body);mAdapter.notifyDataSetChanged();// 告诉它更新完毕
                    mPullDownView.notifyDidRefresh();break;}case WHAT_DID_MORE:{String body = (String) msg.obj;mStrings.add(body);mAdapter.notifyDataSetChanged();// 告诉它获取更多完毕
                    mPullDownView.notifyDidMore();break;}}}};@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {Toast.makeText(this, "啊,你点中我了 " + position, Toast.LENGTH_SHORT).show();}// 模拟数据private String[] mStringArray = {"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", "Alverca", "Ambert", "American Cheese"};}

整个程序就这么完成了,总之,可以使用前两个封装好的文件,然后,调用就好了~~~~没什么太难的地方

本博客中内容均为网络转载或收藏,如有侵权,告之即删除。

新浪微博就是使用这种方式的典型。

当用户从网络上读取微博的时候,如果一下子全部加载用户未读的微博这将耗费比较长的时间,造成不好的用户体验,同时一屏的内容也不足以显示如此多的内容。这时候,我们就需要用到另一个功能,那就是listview的分页了,其实这个分页可以做成客户端的分页,也可以做成服务器端的分页(点击加载时,从服务器对应的加载第N页就好了!!!)。通过分页分次加载数据,用户看多少就去加载多少。
通常这也分为两种方式,一种是设置一个按钮,用户点击即加载。另一种是当用户滑动到底部时自动加载。今天我就和大家分享一下滑动到底端时自动加载这个功能的实现。
效果图如下所示:

下拉刷新最主要的流程是:
(1). 下拉,显示提示头部界面(HeaderView),这个过程提示用户”下拉刷新”
(2). 下拉到一定程度,超出了刷新最基本的下拉界限,我们认为达到了刷新的条件,提示用户可以”松手刷新”了,效果上允许用户继续下拉
(3). 用户松手,可能用户下拉远远不止提示头部界面,所以这一步,先反弹回仅显示提示头部界面,然后提示用户”正在加载”。
(4). 加载完成后,隐藏提示头部界面。

那么让我们看看怎么才能实现呢???
第一步:既然是要显示listview ,那么就应该有个listview 的容器pulldown.xml

<?xml version="1.0" encoding="utf-8"?>
<com.solo.pulldown.PullDownView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/pull_down_view"android:layout_width="fill_parent"android:layout_height="fill_parent"android:background="@android:color/white"></com.solo.pulldown.PullDownView>

第二步:自定义一个listview中显示的item对象pulldown_item.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@android:id/text1"android:layout_width="fill_parent"android:layout_height="wrap_content"android:textAppearance="?android:attr/textAppearanceLarge"android:gravity="center_vertical"android:paddingLeft="6dip"android:minHeight="?android:attr/listPreferredItemHeight"android:textColor="@android:color/black"
/>

第三步:定义一个header的xml布局文件pulldown_header.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="wrap_content"android:paddingBottom="10dp"android:paddingTop="10dp" ><ImageViewandroid:id="@+id/pulldown_header_arrow"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentLeft="true"android:layout_marginLeft="20dp"android:scaleType="centerCrop"android:src="@drawable/z_arrow_down"android:visibility="invisible" /><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignBottom="@+id/pulldown_header_arrow"android:layout_alignTop="@+id/pulldown_header_arrow"android:layout_centerHorizontal="true"android:gravity="center_vertical"android:orientation="vertical" ><TextViewandroid:id="@+id/pulldown_header_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:text="加载中..." /><TextViewandroid:id="@+id/pulldown_header_date"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:text="更新于:"android:visibility="gone" /></LinearLayout><ProgressBarandroid:id="@+id/pulldown_header_loading"style="@android:style/Widget.ProgressBar.Small.Inverse"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginLeft="20dp" /></RelativeLayout>

第四步:如果需要向上拉更新更多的话,那就定义一个底部的footer的布局文件,在此为方便起见,只定义一个progressbar跟textview,更加复杂的显示,就交给你们了~~~~~pulldown_footer.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="wrap_content"android:paddingBottom="10dp"android:paddingTop="10dp" ><TextViewandroid:id="@+id/pulldown_footer_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:text="更多"android:textSize="15dp" /><ProgressBarandroid:id="@+id/pulldown_footer_loading"style="@android:style/Widget.ProgressBar.Small.Inverse"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginLeft="20dp"android:visibility="gone" /></RelativeLayout>

第五步:那么主要的文件这才登场:::::::重写listview这个文件主要任务是提供触摸的事件的处理方法。

/*** <p>一个可以监听ListView是否滚动到最顶部或最底部的自定义控件</p>* 只能监听由触摸产生的,如果是ListView本身Flying导致的,则不能监听</br>* 如果加以改进,可以实现监听scroll滚动的具体位置等*/public class ScrollOverListView extends ListView {private int mLastY;private int mTopPosition;private int mBottomPosition;public ScrollOverListView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init();}public ScrollOverListView(Context context, AttributeSet attrs) {super(context, attrs);init();}public ScrollOverListView(Context context) {super(context);init();}private void init(){mTopPosition = 0;mBottomPosition = 0;}@Overridepublic boolean onTouchEvent(MotionEvent ev) {final int action = ev.getAction();final int y = (int) ev.getRawY();switch(action){case MotionEvent.ACTION_DOWN:{mLastY = y;final boolean isHandled = mOnScrollOverListener.onMotionDown(ev);if (isHandled) {mLastY = y;return isHandled;}break;}case MotionEvent.ACTION_MOVE:{final int childCount = getChildCount();if(childCount == 0) return super.onTouchEvent(ev);final int itemCount = getAdapter().getCount() - mBottomPosition;final int deltaY = y - mLastY;//DLog.d("lastY=%d y=%d", mLastY, y);final int firstTop = getChildAt(0).getTop();final int listPadding = getListPaddingTop();final int lastBottom = getChildAt(childCount - 1).getBottom();final int end = getHeight() - getPaddingBottom();final int firstVisiblePosition = getFirstVisiblePosition();final boolean isHandleMotionMove = mOnScrollOverListener.onMotionMove(ev, deltaY);if(isHandleMotionMove){mLastY = y;return true;}//DLog.d("firstVisiblePosition=%d firstTop=%d listPaddingTop=%d deltaY=%d", firstVisiblePosition, firstTop, listPadding, deltaY);if (firstVisiblePosition <= mTopPosition && firstTop >= listPadding && deltaY > 0) {final boolean isHandleOnListViewTopAndPullDown;isHandleOnListViewTopAndPullDown = mOnScrollOverListener.onListViewTopAndPullDown(deltaY);if(isHandleOnListViewTopAndPullDown){mLastY = y;return true;}}// DLog.d("lastBottom=%d end=%d deltaY=%d", lastBottom, end, deltaY);if (firstVisiblePosition + childCount >= itemCount && lastBottom <= end && deltaY < 0) {final boolean isHandleOnListViewBottomAndPullDown;isHandleOnListViewBottomAndPullDown = mOnScrollOverListener.onListViewBottomAndPullUp(deltaY);if(isHandleOnListViewBottomAndPullDown){mLastY = y;return true;}}break;}case MotionEvent.ACTION_UP:{final boolean isHandlerMotionUp = mOnScrollOverListener.onMotionUp(ev);if (isHandlerMotionUp) {mLastY = y;return true;}break;}}mLastY = y;return super.onTouchEvent(ev);}/**空的*/private OnScrollOverListener mOnScrollOverListener = new OnScrollOverListener(){@Overridepublic boolean onListViewTopAndPullDown(int delta) {return false;}@Overridepublic boolean onListViewBottomAndPullUp(int delta) {return false;}@Overridepublic boolean onMotionDown(MotionEvent ev) {return false;}@Overridepublic boolean onMotionMove(MotionEvent ev, int delta) {return false;}@Overridepublic boolean onMotionUp(MotionEvent ev) {return false;}};// =============================== public method ===============================/*** 可以自定义其中一个条目为头部,头部触发的事件将以这个为准,默认为第一个** @param index 正数第几个,必须在条目数范围之内*/public void setTopPosition(int index){if(getAdapter() == null)throw new NullPointerException("You must set adapter before setTopPosition!");if(index < 0)throw new IllegalArgumentException("Top position must > 0");mTopPosition = index;}/*** 可以自定义其中一个条目为尾部,尾部触发的事件将以这个为准,默认为最后一个** @param index 倒数第几个,必须在条目数范围之内*/public void setBottomPosition(int index){if(getAdapter() == null)throw new NullPointerException("You must set adapter before setBottonPosition!");if(index < 0)throw new IllegalArgumentException("Bottom position must > 0");mBottomPosition = index;}/*** 设置这个Listener可以监听是否到达顶端,或者是否到达低端等事件</br>** @see OnScrollOverListener*/public void setOnScrollOverListener(OnScrollOverListener onScrollOverListener){mOnScrollOverListener = onScrollOverListener;}/*** 滚动监听接口</br>* @see ScrollOverListView#setOnScrollOverListener(OnScrollOverListener)**/public interface OnScrollOverListener {/*** 到达最顶部触发** @param delta 手指点击移动产生的偏移量* @return*/boolean onListViewTopAndPullDown(int delta);/*** 到达最底部触发** @param delta 手指点击移动产生的偏移量* @return*/boolean onListViewBottomAndPullUp(int delta);/*** 手指触摸按下触发,相当于{@link MotionEvent#ACTION_DOWN}** @return 返回true表示自己处理* @see View#onTouchEvent(MotionEvent)*/boolean onMotionDown(MotionEvent ev);/*** 手指触摸移动触发,相当于{@link MotionEvent#ACTION_MOVE}** @return 返回true表示自己处理* @see View#onTouchEvent(MotionEvent)*/boolean onMotionMove(MotionEvent ev, int delta);/*** 手指触摸后提起触发,相当于{@link MotionEvent#ACTION_UP}** @return 返回true表示自己处理* @see View#onTouchEvent(MotionEvent)*/boolean onMotionUp(MotionEvent ev);}}

第六步:下拉刷新控件,真正实现下拉刷新的是这个控件,而上面的那个ScrollOverListView只是提供触摸的事件等

/*** 下拉刷新控件</br>* 真正实现下拉刷新的是这个控件,* ScrollOverListView只是提供触摸的事件等*/
public class PullDownView extends LinearLayout implements OnScrollOverListener{private static final String TAG = "PullDownView";private static final int START_PULL_DEVIATION = 50; // 移动误差private static final int AUTO_INCREMENTAL = 10;     // 自增量,用于回弹private static final int WHAT_DID_LOAD_DATA = 1;    // Handler what 数据加载完毕private static final int WHAT_ON_REFRESH = 2;       // Handler what 刷新中private static final int WHAT_DID_REFRESH = 3;      // Handler what 已经刷新完private static final int WHAT_SET_HEADER_HEIGHT = 4;// Handler what 设置高度private static final int WHAT_DID_MORE = 5;         // Handler what 已经获取完更多private static final int DEFAULT_HEADER_VIEW_HEIGHT = 105;  // 头部文件原本的高度private static SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd HH:mm");private View mHeaderView;private LayoutParams mHeaderViewParams;private TextView mHeaderViewDateView;private TextView mHeaderTextView;private ImageView mHeaderArrowView;private View mHeaderLoadingView;private View mFooterView;private TextView mFooterTextView;private View mFooterLoadingView;private ScrollOverListView mListView;private OnPullDownListener mOnPullDownListener;private RotateAnimation mRotateOTo180Animation;private RotateAnimation mRotate180To0Animation;private int mHeaderIncremental; // 增量private float mMotionDownLastY; // 按下时候的Y轴坐标private boolean mIsDown;            // 是否按下private boolean mIsRefreshing;      // 是否下拉刷新中private boolean mIsFetchMoreing;    // 是否获取更多中private boolean mIsPullUpDone;      // 是否回推完成private boolean mEnableAutoFetchMore;   // 是否允许自动获取更多// 头部文件的状态private static final int HEADER_VIEW_STATE_IDLE = 0;            // 空闲private static final int HEADER_VIEW_STATE_NOT_OVER_HEIGHT = 1; // 没有超过默认高度private static final int HEADER_VIEW_STATE_OVER_HEIGHT = 2;     // 超过默认高度private int mHeaderViewState = HEADER_VIEW_STATE_IDLE;public PullDownView(Context context, AttributeSet attrs) {super(context, attrs);initHeaderViewAndFooterViewAndListView(context);}public PullDownView(Context context) {super(context);initHeaderViewAndFooterViewAndListView(context);}/** ==================================* Public method* 外部使用,具体就是用这几个就可以了** ==================================*//*** 刷新事件接口*/public interface OnPullDownListener {void onRefresh();void onMore();}/*** 通知加载完了数据,要放在Adapter.notifyDataSetChanged后面* 当你加载完数据的时候,调用这个notifyDidLoad()* 才会隐藏头部,并初始化数据等*/public void notifyDidLoad() {mUIHandler.sendEmptyMessage(WHAT_DID_LOAD_DATA);}/*** 通知已经刷新完了,要放在Adapter.notifyDataSetChanged后面* 当你执行完刷新任务之后,调用这个notifyDidRefresh()* 才会隐藏掉头部文件等操作*/public void notifyDidRefresh() {mUIHandler.sendEmptyMessage(WHAT_DID_REFRESH);}/*** 通知已经获取完更多了,要放在Adapter.notifyDataSetChanged后面* 当你执行完更多任务之后,调用这个notyfyDidMore()* 才会隐藏加载圈等操作*/public void notifyDidMore() {mUIHandler.sendEmptyMessage(WHAT_DID_MORE);}/*** 设置监听器* @param listener*/public void setOnPullDownListener(OnPullDownListener listener){mOnPullDownListener = listener;}/*** 获取内嵌的listview* @return ScrollOverListView*/public ListView getListView(){return mListView;}/*** 是否开启自动获取更多* 自动获取更多,将会隐藏footer,并在到达底部的时候自动刷新* @param index 倒数第几个触发*/public void enableAutoFetchMore(boolean enable, int index){if(enable){mListView.setBottomPosition(index);mFooterLoadingView.setVisibility(View.VISIBLE);}else{mFooterTextView.setText("更多");mFooterLoadingView.setVisibility(View.GONE);}mEnableAutoFetchMore = enable;}/** ==================================* Private method* 具体实现下拉刷新等操作** ==================================*//*** 初始化界面*/private void initHeaderViewAndFooterViewAndListView(Context context){setOrientation(LinearLayout.VERTICAL);//setDrawingCacheEnabled(false);/** 自定义头部文件* 放在这里是因为考虑到很多界面都需要使用* 如果要修改,和它相关的设置都要更改*/mHeaderView = LayoutInflater.from(context).inflate(R.layout.pulldown_header, null);mHeaderViewParams = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);addView(mHeaderView, 0, mHeaderViewParams);mHeaderTextView = (TextView) mHeaderView.findViewById(R.id.pulldown_header_text);mHeaderArrowView = (ImageView) mHeaderView.findViewById(R.id.pulldown_header_arrow);mHeaderLoadingView = mHeaderView.findViewById(R.id.pulldown_header_loading);// 注意,图片旋转之后,再执行旋转,坐标会重新开始计算mRotateOTo180Animation = new RotateAnimation(0, 180,Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);mRotateOTo180Animation.setDuration(250);mRotateOTo180Animation.setFillAfter(true);mRotate180To0Animation = new RotateAnimation(180, 0,Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);mRotate180To0Animation.setDuration(250);mRotate180To0Animation.setFillAfter(true);/*** 自定义脚部文件*/mFooterView = LayoutInflater.from(context).inflate(R.layout.pulldown_footer, null);mFooterTextView = (TextView) mFooterView.findViewById(R.id.pulldown_footer_text);mFooterLoadingView = mFooterView.findViewById(R.id.pulldown_footer_loading);mFooterView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {if(!mIsFetchMoreing){mIsFetchMoreing = true;mFooterLoadingView.setVisibility(View.VISIBLE);mOnPullDownListener.onMore();}}});/** ScrollOverListView 同样是考虑到都是使用,所以放在这里* 同时因为,需要它的监听事件*/mListView = new ScrollOverListView(context);mListView.setOnScrollOverListener(this);mListView.setCacheColorHint(0);addView(mListView, LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);// 空的listenermOnPullDownListener = new OnPullDownListener() {@Overridepublic void onRefresh() {}@Overridepublic void onMore() {}};}/*** 在下拉和回推的时候检查头部文件的状态</br>* 如果超过了默认高度,就显示松开可以刷新,* 否则显示下拉可以刷新*/private void checkHeaderViewState(){if(mHeaderViewParams.height >= DEFAULT_HEADER_VIEW_HEIGHT){if(mHeaderViewState == HEADER_VIEW_STATE_OVER_HEIGHT) return;mHeaderViewState = HEADER_VIEW_STATE_OVER_HEIGHT;mHeaderTextView.setText("松开可以刷新");mHeaderArrowView.startAnimation(mRotateOTo180Animation);}else{if(mHeaderViewState == HEADER_VIEW_STATE_NOT_OVER_HEIGHT|| mHeaderViewState == HEADER_VIEW_STATE_IDLE) return;mHeaderViewState = HEADER_VIEW_STATE_NOT_OVER_HEIGHT;mHeaderTextView.setText("下拉可以刷新");mHeaderArrowView.startAnimation(mRotate180To0Animation);}}private void setHeaderHeight(final int height){mHeaderIncremental = height;mHeaderViewParams.height = height;mHeaderView.setLayoutParams(mHeaderViewParams);}/*** 自动隐藏动画*/class HideHeaderViewTask extends TimerTask{@Overridepublic void run() {if(mIsDown) {cancel();return;}mHeaderIncremental -= AUTO_INCREMENTAL;if(mHeaderIncremental > 0){mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);}else{mHeaderIncremental = 0;mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);cancel();}}}/*** 自动显示动画*/class ShowHeaderViewTask extends TimerTask{@Overridepublic void run() {if(mIsDown) {cancel();return;}mHeaderIncremental -= AUTO_INCREMENTAL;if(mHeaderIncremental > DEFAULT_HEADER_VIEW_HEIGHT){mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);}else{mHeaderIncremental = DEFAULT_HEADER_VIEW_HEIGHT;mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);if(!mIsRefreshing){mIsRefreshing = true;mUIHandler.sendEmptyMessage(WHAT_ON_REFRESH);}cancel();}}}private Handler mUIHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case WHAT_DID_LOAD_DATA:{mHeaderViewParams.height = 0;mHeaderLoadingView.setVisibility(View.GONE);mHeaderTextView.setText("下拉可以刷新");mHeaderViewDateView = (TextView) mHeaderView.findViewById(R.id.pulldown_header_date);mHeaderViewDateView.setVisibility(View.VISIBLE);mHeaderViewDateView.setText("更新于:" + dateFormat.format(new Date(System.currentTimeMillis())));mHeaderArrowView.setVisibility(View.VISIBLE);showFooterView();return;}case WHAT_ON_REFRESH:{// 要清除掉动画,否则无法隐藏
                    mHeaderArrowView.clearAnimation();mHeaderArrowView.setVisibility(View.INVISIBLE);mHeaderLoadingView.setVisibility(View.VISIBLE);mOnPullDownListener.onRefresh();return;}case WHAT_DID_REFRESH :{mIsRefreshing = false;mHeaderViewState = HEADER_VIEW_STATE_IDLE;mHeaderArrowView.setVisibility(View.VISIBLE);mHeaderLoadingView.setVisibility(View.GONE);mHeaderViewDateView.setText("更新于:" + dateFormat.format(new Date(System.currentTimeMillis())));setHeaderHeight(0);showFooterView();return;}case WHAT_SET_HEADER_HEIGHT :{setHeaderHeight(mHeaderIncremental);return;}case WHAT_DID_MORE :{mIsFetchMoreing = false;mFooterTextView.setText("更多");mFooterLoadingView.setVisibility(View.GONE);}}}};/*** 显示脚步脚部文件*/private void showFooterView(){if(mListView.getFooterViewsCount() == 0 && isFillScreenItem()){mListView.addFooterView(mFooterView);mListView.setAdapter(mListView.getAdapter());}}/*** 条目是否填满整个屏幕*/private boolean isFillScreenItem(){final int firstVisiblePosition = mListView.getFirstVisiblePosition();final int lastVisiblePostion = mListView.getLastVisiblePosition() - mListView.getFooterViewsCount();final int visibleItemCount = lastVisiblePostion - firstVisiblePosition + 1;final int totalItemCount = mListView.getCount() - mListView.getFooterViewsCount();if(visibleItemCount < totalItemCount) return true;return false;}/** ==================================* 实现 OnScrollOverListener接口*** ==================================*/@Overridepublic boolean onListViewTopAndPullDown(int delta) {if(mIsRefreshing || mListView.getCount() - mListView.getFooterViewsCount() == 0) return false;int absDelta = Math.abs(delta);final int i = (int) Math.ceil((double)absDelta / 2);mHeaderIncremental += i;if(mHeaderIncremental >= 0){ // && mIncremental <= mMaxHeight
            setHeaderHeight(mHeaderIncremental);checkHeaderViewState();}return true;}@Overridepublic boolean onListViewBottomAndPullUp(int delta) {if(!mEnableAutoFetchMore || mIsFetchMoreing) return false;// 数量充满屏幕才触发if(isFillScreenItem()){mIsFetchMoreing = true;mFooterTextView.setText("加载更多中...");mFooterLoadingView.setVisibility(View.VISIBLE);mOnPullDownListener.onMore();return true;}return false;}@Overridepublic boolean onMotionDown(MotionEvent ev) {mIsDown = true;mIsPullUpDone = false;mMotionDownLastY = ev.getRawY();return false;}@Overridepublic boolean onMotionMove(MotionEvent ev, int delta) {//当头部文件回推消失的时候,不允许滚动if(mIsPullUpDone) return true;// 如果开始按下到滑动距离不超过误差值,则不滑动final int absMotionY = (int) Math.abs(ev.getRawY() - mMotionDownLastY);if(absMotionY < START_PULL_DEVIATION) return true;final int absDelta = Math.abs(delta);final int i = (int) Math.ceil((double)absDelta / 2);// onTopDown在顶部,并上回推和onTopUp相对if(mHeaderViewParams.height > 0 && delta < 0){mHeaderIncremental -= i;if(mHeaderIncremental > 0){setHeaderHeight(mHeaderIncremental);checkHeaderViewState();}else{mHeaderViewState = HEADER_VIEW_STATE_IDLE;mHeaderIncremental = 0;setHeaderHeight(mHeaderIncremental);mIsPullUpDone = true;}return true;}return false;}@Overridepublic boolean onMotionUp(MotionEvent ev) {mIsDown = false;// 避免和点击事件冲突if(mHeaderViewParams.height > 0){// 判断头文件拉动的距离与设定的高度,小了就隐藏,多了就固定高度int x = mHeaderIncremental - DEFAULT_HEADER_VIEW_HEIGHT;Timer timer = new Timer(true);if(x < 0){timer.scheduleAtFixedRate(new HideHeaderViewTask(), 0, 10);}else{timer.scheduleAtFixedRate(new ShowHeaderViewTask(), 0, 10);}return true;}return false;}}

第七步:这个java文件就是封装数据,然后调用上两个文件就好了。废话不多说,上代码:::::

public class PullDownActivity extends Activity implements OnPullDownListener, OnItemClickListener{private static final int WHAT_DID_LOAD_DATA = 0;private static final int WHAT_DID_REFRESH = 1;private static final int WHAT_DID_MORE = 2;private ListView mListView;private ArrayAdapter<String> mAdapter;private PullDownView mPullDownView;private List<String> mStrings = new ArrayList<String>();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.pulldown);/** 1.使用PullDownView* 2.设置OnPullDownListener* 3.从mPullDownView里面获取ListView*/mPullDownView = (PullDownView) findViewById(R.id.pull_down_view);mPullDownView.setOnPullDownListener(this);mListView = mPullDownView.getListView();mListView.setOnItemClickListener(this);mAdapter = new ArrayAdapter<String>(this, R.layout.pulldown_item, mStrings);mListView.setAdapter(mAdapter);mPullDownView.enableAutoFetchMore(true, 1);loadData();}private void loadData(){new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}List<String> strings = new ArrayList<String>();for (String body : mStringArray) {strings.add(body);}Message msg = mUIHandler.obtainMessage(WHAT_DID_LOAD_DATA);msg.obj = strings;msg.sendToTarget();}}).start();}@Overridepublic void onRefresh() {new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}Message msg = mUIHandler.obtainMessage(WHAT_DID_REFRESH);msg.obj = "After refresh " + System.currentTimeMillis();msg.sendToTarget();}}).start();}@Overridepublic void onMore() {new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}Message msg = mUIHandler.obtainMessage(WHAT_DID_MORE);msg.obj = "After more " + System.currentTimeMillis();msg.sendToTarget();}}).start();}private Handler mUIHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case WHAT_DID_LOAD_DATA:{if(msg.obj != null){List<String> strings = (List<String>) msg.obj;if(!strings.isEmpty()){mStrings.addAll(strings);mAdapter.notifyDataSetChanged();}}// 诉它数据加载完毕;
                    mPullDownView.notifyDidLoad();break;}case WHAT_DID_REFRESH :{String body = (String) msg.obj;mStrings.add(0, body);mAdapter.notifyDataSetChanged();// 告诉它更新完毕
                    mPullDownView.notifyDidRefresh();break;}case WHAT_DID_MORE:{String body = (String) msg.obj;mStrings.add(body);mAdapter.notifyDataSetChanged();// 告诉它获取更多完毕
                    mPullDownView.notifyDidMore();break;}}}};@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {Toast.makeText(this, "啊,你点中我了 " + position, Toast.LENGTH_SHORT).show();}// 模拟数据private String[] mStringArray = {"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", "Alverca", "Ambert", "American Cheese"};}

整个程序就这么完成了,总之,可以使用前两个封装好的文件,然后,调用就好了~~~~没什么太难的地方

下拉刷新最主要的流程是:
(1). 下拉,显示提示头部界面(HeaderView),这个过程提示用户”下拉刷新”
(2). 下拉到一定程度,超出了刷新最基本的下拉界限,我们认为达到了刷新的条件,提示用户可以”松手刷新”了,效果上允许用户继续下拉
(3). 用户松手,可能用户下拉远远不止提示头部界面,所以这一步,先反弹回仅显示提示头部界面,然后提示用户”正在加载”。
(4). 加载完成后,隐藏提示头部界面。

那么让我们看看怎么才能实现呢???
第一步:既然是要显示listview ,那么就应该有个listview 的容器pulldown.xml

<?xml version="1.0" encoding="utf-8"?>
<com.solo.pulldown.PullDownView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/pull_down_view"android:layout_width="fill_parent"android:layout_height="fill_parent"android:background="@android:color/white"></com.solo.pulldown.PullDownView>

第二步:自定义一个listview中显示的item对象pulldown_item.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@android:id/text1"android:layout_width="fill_parent"android:layout_height="wrap_content"android:textAppearance="?android:attr/textAppearanceLarge"android:gravity="center_vertical"android:paddingLeft="6dip"android:minHeight="?android:attr/listPreferredItemHeight"android:textColor="@android:color/black"
/>

第三步:定义一个header的xml布局文件pulldown_header.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="wrap_content"android:paddingBottom="10dp"android:paddingTop="10dp" ><ImageViewandroid:id="@+id/pulldown_header_arrow"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentLeft="true"android:layout_marginLeft="20dp"android:scaleType="centerCrop"android:src="@drawable/z_arrow_down"android:visibility="invisible" /><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignBottom="@+id/pulldown_header_arrow"android:layout_alignTop="@+id/pulldown_header_arrow"android:layout_centerHorizontal="true"android:gravity="center_vertical"android:orientation="vertical" ><TextViewandroid:id="@+id/pulldown_header_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:text="加载中..." /><TextViewandroid:id="@+id/pulldown_header_date"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:text="更新于:"android:visibility="gone" /></LinearLayout><ProgressBarandroid:id="@+id/pulldown_header_loading"style="@android:style/Widget.ProgressBar.Small.Inverse"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginLeft="20dp" /></RelativeLayout>

第四步:如果需要向上拉更新更多的话,那就定义一个底部的footer的布局文件,在此为方便起见,只定义一个progressbar跟textview,更加复杂的显示,就交给你们了~~~~~pulldown_footer.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="wrap_content"android:paddingBottom="10dp"android:paddingTop="10dp" ><TextViewandroid:id="@+id/pulldown_footer_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:text="更多"android:textSize="15dp" /><ProgressBarandroid:id="@+id/pulldown_footer_loading"style="@android:style/Widget.ProgressBar.Small.Inverse"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginLeft="20dp"android:visibility="gone" /></RelativeLayout>

第五步:那么主要的文件这才登场:::::::重写listview这个文件主要任务是提供触摸的事件的处理方法。

/*** <p>一个可以监听ListView是否滚动到最顶部或最底部的自定义控件</p>* 只能监听由触摸产生的,如果是ListView本身Flying导致的,则不能监听</br>* 如果加以改进,可以实现监听scroll滚动的具体位置等*/public class ScrollOverListView extends ListView {private int mLastY;private int mTopPosition;private int mBottomPosition;public ScrollOverListView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init();}public ScrollOverListView(Context context, AttributeSet attrs) {super(context, attrs);init();}public ScrollOverListView(Context context) {super(context);init();}private void init(){mTopPosition = 0;mBottomPosition = 0;}@Overridepublic boolean onTouchEvent(MotionEvent ev) {final int action = ev.getAction();final int y = (int) ev.getRawY();switch(action){case MotionEvent.ACTION_DOWN:{mLastY = y;final boolean isHandled = mOnScrollOverListener.onMotionDown(ev);if (isHandled) {mLastY = y;return isHandled;}break;}case MotionEvent.ACTION_MOVE:{final int childCount = getChildCount();if(childCount == 0) return super.onTouchEvent(ev);final int itemCount = getAdapter().getCount() - mBottomPosition;final int deltaY = y - mLastY;//DLog.d("lastY=%d y=%d", mLastY, y);final int firstTop = getChildAt(0).getTop();final int listPadding = getListPaddingTop();final int lastBottom = getChildAt(childCount - 1).getBottom();final int end = getHeight() - getPaddingBottom();final int firstVisiblePosition = getFirstVisiblePosition();final boolean isHandleMotionMove = mOnScrollOverListener.onMotionMove(ev, deltaY);if(isHandleMotionMove){mLastY = y;return true;}//DLog.d("firstVisiblePosition=%d firstTop=%d listPaddingTop=%d deltaY=%d", firstVisiblePosition, firstTop, listPadding, deltaY);if (firstVisiblePosition <= mTopPosition && firstTop >= listPadding && deltaY > 0) {final boolean isHandleOnListViewTopAndPullDown;isHandleOnListViewTopAndPullDown = mOnScrollOverListener.onListViewTopAndPullDown(deltaY);if(isHandleOnListViewTopAndPullDown){mLastY = y;return true;}}// DLog.d("lastBottom=%d end=%d deltaY=%d", lastBottom, end, deltaY);if (firstVisiblePosition + childCount >= itemCount && lastBottom <= end && deltaY < 0) {final boolean isHandleOnListViewBottomAndPullDown;isHandleOnListViewBottomAndPullDown = mOnScrollOverListener.onListViewBottomAndPullUp(deltaY);if(isHandleOnListViewBottomAndPullDown){mLastY = y;return true;}}break;}case MotionEvent.ACTION_UP:{final boolean isHandlerMotionUp = mOnScrollOverListener.onMotionUp(ev);if (isHandlerMotionUp) {mLastY = y;return true;}break;}}mLastY = y;return super.onTouchEvent(ev);}/**空的*/private OnScrollOverListener mOnScrollOverListener = new OnScrollOverListener(){@Overridepublic boolean onListViewTopAndPullDown(int delta) {return false;}@Overridepublic boolean onListViewBottomAndPullUp(int delta) {return false;}@Overridepublic boolean onMotionDown(MotionEvent ev) {return false;}@Overridepublic boolean onMotionMove(MotionEvent ev, int delta) {return false;}@Overridepublic boolean onMotionUp(MotionEvent ev) {return false;}};// =============================== public method ===============================/*** 可以自定义其中一个条目为头部,头部触发的事件将以这个为准,默认为第一个** @param index 正数第几个,必须在条目数范围之内*/public void setTopPosition(int index){if(getAdapter() == null)throw new NullPointerException("You must set adapter before setTopPosition!");if(index < 0)throw new IllegalArgumentException("Top position must > 0");mTopPosition = index;}/*** 可以自定义其中一个条目为尾部,尾部触发的事件将以这个为准,默认为最后一个** @param index 倒数第几个,必须在条目数范围之内*/public void setBottomPosition(int index){if(getAdapter() == null)throw new NullPointerException("You must set adapter before setBottonPosition!");if(index < 0)throw new IllegalArgumentException("Bottom position must > 0");mBottomPosition = index;}/*** 设置这个Listener可以监听是否到达顶端,或者是否到达低端等事件</br>** @see OnScrollOverListener*/public void setOnScrollOverListener(OnScrollOverListener onScrollOverListener){mOnScrollOverListener = onScrollOverListener;}/*** 滚动监听接口</br>* @see ScrollOverListView#setOnScrollOverListener(OnScrollOverListener)**/public interface OnScrollOverListener {/*** 到达最顶部触发** @param delta 手指点击移动产生的偏移量* @return*/boolean onListViewTopAndPullDown(int delta);/*** 到达最底部触发** @param delta 手指点击移动产生的偏移量* @return*/boolean onListViewBottomAndPullUp(int delta);/*** 手指触摸按下触发,相当于{@link MotionEvent#ACTION_DOWN}** @return 返回true表示自己处理* @see View#onTouchEvent(MotionEvent)*/boolean onMotionDown(MotionEvent ev);/*** 手指触摸移动触发,相当于{@link MotionEvent#ACTION_MOVE}** @return 返回true表示自己处理* @see View#onTouchEvent(MotionEvent)*/boolean onMotionMove(MotionEvent ev, int delta);/*** 手指触摸后提起触发,相当于{@link MotionEvent#ACTION_UP}** @return 返回true表示自己处理* @see View#onTouchEvent(MotionEvent)*/boolean onMotionUp(MotionEvent ev);}}

第六步:下拉刷新控件,真正实现下拉刷新的是这个控件,而上面的那个ScrollOverListView只是提供触摸的事件等

/*** 下拉刷新控件</br>* 真正实现下拉刷新的是这个控件,* ScrollOverListView只是提供触摸的事件等*/
public class PullDownView extends LinearLayout implements OnScrollOverListener{private static final String TAG = "PullDownView";private static final int START_PULL_DEVIATION = 50; // 移动误差private static final int AUTO_INCREMENTAL = 10;     // 自增量,用于回弹private static final int WHAT_DID_LOAD_DATA = 1;    // Handler what 数据加载完毕private static final int WHAT_ON_REFRESH = 2;       // Handler what 刷新中private static final int WHAT_DID_REFRESH = 3;      // Handler what 已经刷新完private static final int WHAT_SET_HEADER_HEIGHT = 4;// Handler what 设置高度private static final int WHAT_DID_MORE = 5;         // Handler what 已经获取完更多private static final int DEFAULT_HEADER_VIEW_HEIGHT = 105;  // 头部文件原本的高度private static SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd HH:mm");private View mHeaderView;private LayoutParams mHeaderViewParams;private TextView mHeaderViewDateView;private TextView mHeaderTextView;private ImageView mHeaderArrowView;private View mHeaderLoadingView;private View mFooterView;private TextView mFooterTextView;private View mFooterLoadingView;private ScrollOverListView mListView;private OnPullDownListener mOnPullDownListener;private RotateAnimation mRotateOTo180Animation;private RotateAnimation mRotate180To0Animation;private int mHeaderIncremental; // 增量private float mMotionDownLastY; // 按下时候的Y轴坐标private boolean mIsDown;            // 是否按下private boolean mIsRefreshing;      // 是否下拉刷新中private boolean mIsFetchMoreing;    // 是否获取更多中private boolean mIsPullUpDone;      // 是否回推完成private boolean mEnableAutoFetchMore;   // 是否允许自动获取更多// 头部文件的状态private static final int HEADER_VIEW_STATE_IDLE = 0;            // 空闲private static final int HEADER_VIEW_STATE_NOT_OVER_HEIGHT = 1; // 没有超过默认高度private static final int HEADER_VIEW_STATE_OVER_HEIGHT = 2;     // 超过默认高度private int mHeaderViewState = HEADER_VIEW_STATE_IDLE;public PullDownView(Context context, AttributeSet attrs) {super(context, attrs);initHeaderViewAndFooterViewAndListView(context);}public PullDownView(Context context) {super(context);initHeaderViewAndFooterViewAndListView(context);}/** ==================================* Public method* 外部使用,具体就是用这几个就可以了** ==================================*//*** 刷新事件接口*/public interface OnPullDownListener {void onRefresh();void onMore();}/*** 通知加载完了数据,要放在Adapter.notifyDataSetChanged后面* 当你加载完数据的时候,调用这个notifyDidLoad()* 才会隐藏头部,并初始化数据等*/public void notifyDidLoad() {mUIHandler.sendEmptyMessage(WHAT_DID_LOAD_DATA);}/*** 通知已经刷新完了,要放在Adapter.notifyDataSetChanged后面* 当你执行完刷新任务之后,调用这个notifyDidRefresh()* 才会隐藏掉头部文件等操作*/public void notifyDidRefresh() {mUIHandler.sendEmptyMessage(WHAT_DID_REFRESH);}/*** 通知已经获取完更多了,要放在Adapter.notifyDataSetChanged后面* 当你执行完更多任务之后,调用这个notyfyDidMore()* 才会隐藏加载圈等操作*/public void notifyDidMore() {mUIHandler.sendEmptyMessage(WHAT_DID_MORE);}/*** 设置监听器* @param listener*/public void setOnPullDownListener(OnPullDownListener listener){mOnPullDownListener = listener;}/*** 获取内嵌的listview* @return ScrollOverListView*/public ListView getListView(){return mListView;}/*** 是否开启自动获取更多* 自动获取更多,将会隐藏footer,并在到达底部的时候自动刷新* @param index 倒数第几个触发*/public void enableAutoFetchMore(boolean enable, int index){if(enable){mListView.setBottomPosition(index);mFooterLoadingView.setVisibility(View.VISIBLE);}else{mFooterTextView.setText("更多");mFooterLoadingView.setVisibility(View.GONE);}mEnableAutoFetchMore = enable;}/** ==================================* Private method* 具体实现下拉刷新等操作** ==================================*//*** 初始化界面*/private void initHeaderViewAndFooterViewAndListView(Context context){setOrientation(LinearLayout.VERTICAL);//setDrawingCacheEnabled(false);/** 自定义头部文件* 放在这里是因为考虑到很多界面都需要使用* 如果要修改,和它相关的设置都要更改*/mHeaderView = LayoutInflater.from(context).inflate(R.layout.pulldown_header, null);mHeaderViewParams = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);addView(mHeaderView, 0, mHeaderViewParams);mHeaderTextView = (TextView) mHeaderView.findViewById(R.id.pulldown_header_text);mHeaderArrowView = (ImageView) mHeaderView.findViewById(R.id.pulldown_header_arrow);mHeaderLoadingView = mHeaderView.findViewById(R.id.pulldown_header_loading);// 注意,图片旋转之后,再执行旋转,坐标会重新开始计算mRotateOTo180Animation = new RotateAnimation(0, 180,Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);mRotateOTo180Animation.setDuration(250);mRotateOTo180Animation.setFillAfter(true);mRotate180To0Animation = new RotateAnimation(180, 0,Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);mRotate180To0Animation.setDuration(250);mRotate180To0Animation.setFillAfter(true);/*** 自定义脚部文件*/mFooterView = LayoutInflater.from(context).inflate(R.layout.pulldown_footer, null);mFooterTextView = (TextView) mFooterView.findViewById(R.id.pulldown_footer_text);mFooterLoadingView = mFooterView.findViewById(R.id.pulldown_footer_loading);mFooterView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {if(!mIsFetchMoreing){mIsFetchMoreing = true;mFooterLoadingView.setVisibility(View.VISIBLE);mOnPullDownListener.onMore();}}});/** ScrollOverListView 同样是考虑到都是使用,所以放在这里* 同时因为,需要它的监听事件*/mListView = new ScrollOverListView(context);mListView.setOnScrollOverListener(this);mListView.setCacheColorHint(0);addView(mListView, LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);// 空的listenermOnPullDownListener = new OnPullDownListener() {@Overridepublic void onRefresh() {}@Overridepublic void onMore() {}};}/*** 在下拉和回推的时候检查头部文件的状态</br>* 如果超过了默认高度,就显示松开可以刷新,* 否则显示下拉可以刷新*/private void checkHeaderViewState(){if(mHeaderViewParams.height >= DEFAULT_HEADER_VIEW_HEIGHT){if(mHeaderViewState == HEADER_VIEW_STATE_OVER_HEIGHT) return;mHeaderViewState = HEADER_VIEW_STATE_OVER_HEIGHT;mHeaderTextView.setText("松开可以刷新");mHeaderArrowView.startAnimation(mRotateOTo180Animation);}else{if(mHeaderViewState == HEADER_VIEW_STATE_NOT_OVER_HEIGHT|| mHeaderViewState == HEADER_VIEW_STATE_IDLE) return;mHeaderViewState = HEADER_VIEW_STATE_NOT_OVER_HEIGHT;mHeaderTextView.setText("下拉可以刷新");mHeaderArrowView.startAnimation(mRotate180To0Animation);}}private void setHeaderHeight(final int height){mHeaderIncremental = height;mHeaderViewParams.height = height;mHeaderView.setLayoutParams(mHeaderViewParams);}/*** 自动隐藏动画*/class HideHeaderViewTask extends TimerTask{@Overridepublic void run() {if(mIsDown) {cancel();return;}mHeaderIncremental -= AUTO_INCREMENTAL;if(mHeaderIncremental > 0){mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);}else{mHeaderIncremental = 0;mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);cancel();}}}/*** 自动显示动画*/class ShowHeaderViewTask extends TimerTask{@Overridepublic void run() {if(mIsDown) {cancel();return;}mHeaderIncremental -= AUTO_INCREMENTAL;if(mHeaderIncremental > DEFAULT_HEADER_VIEW_HEIGHT){mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);}else{mHeaderIncremental = DEFAULT_HEADER_VIEW_HEIGHT;mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);if(!mIsRefreshing){mIsRefreshing = true;mUIHandler.sendEmptyMessage(WHAT_ON_REFRESH);}cancel();}}}private Handler mUIHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case WHAT_DID_LOAD_DATA:{mHeaderViewParams.height = 0;mHeaderLoadingView.setVisibility(View.GONE);mHeaderTextView.setText("下拉可以刷新");mHeaderViewDateView = (TextView) mHeaderView.findViewById(R.id.pulldown_header_date);mHeaderViewDateView.setVisibility(View.VISIBLE);mHeaderViewDateView.setText("更新于:" + dateFormat.format(new Date(System.currentTimeMillis())));mHeaderArrowView.setVisibility(View.VISIBLE);showFooterView();return;}case WHAT_ON_REFRESH:{// 要清除掉动画,否则无法隐藏
                    mHeaderArrowView.clearAnimation();mHeaderArrowView.setVisibility(View.INVISIBLE);mHeaderLoadingView.setVisibility(View.VISIBLE);mOnPullDownListener.onRefresh();return;}case WHAT_DID_REFRESH :{mIsRefreshing = false;mHeaderViewState = HEADER_VIEW_STATE_IDLE;mHeaderArrowView.setVisibility(View.VISIBLE);mHeaderLoadingView.setVisibility(View.GONE);mHeaderViewDateView.setText("更新于:" + dateFormat.format(new Date(System.currentTimeMillis())));setHeaderHeight(0);showFooterView();return;}case WHAT_SET_HEADER_HEIGHT :{setHeaderHeight(mHeaderIncremental);return;}case WHAT_DID_MORE :{mIsFetchMoreing = false;mFooterTextView.setText("更多");mFooterLoadingView.setVisibility(View.GONE);}}}};/*** 显示脚步脚部文件*/private void showFooterView(){if(mListView.getFooterViewsCount() == 0 && isFillScreenItem()){mListView.addFooterView(mFooterView);mListView.setAdapter(mListView.getAdapter());}}/*** 条目是否填满整个屏幕*/private boolean isFillScreenItem(){final int firstVisiblePosition = mListView.getFirstVisiblePosition();final int lastVisiblePostion = mListView.getLastVisiblePosition() - mListView.getFooterViewsCount();final int visibleItemCount = lastVisiblePostion - firstVisiblePosition + 1;final int totalItemCount = mListView.getCount() - mListView.getFooterViewsCount();if(visibleItemCount < totalItemCount) return true;return false;}/** ==================================* 实现 OnScrollOverListener接口*** ==================================*/@Overridepublic boolean onListViewTopAndPullDown(int delta) {if(mIsRefreshing || mListView.getCount() - mListView.getFooterViewsCount() == 0) return false;int absDelta = Math.abs(delta);final int i = (int) Math.ceil((double)absDelta / 2);mHeaderIncremental += i;if(mHeaderIncremental >= 0){ // && mIncremental <= mMaxHeight
            setHeaderHeight(mHeaderIncremental);checkHeaderViewState();}return true;}@Overridepublic boolean onListViewBottomAndPullUp(int delta) {if(!mEnableAutoFetchMore || mIsFetchMoreing) return false;// 数量充满屏幕才触发if(isFillScreenItem()){mIsFetchMoreing = true;mFooterTextView.setText("加载更多中...");mFooterLoadingView.setVisibility(View.VISIBLE);mOnPullDownListener.onMore();return true;}return false;}@Overridepublic boolean onMotionDown(MotionEvent ev) {mIsDown = true;mIsPullUpDone = false;mMotionDownLastY = ev.getRawY();return false;}@Overridepublic boolean onMotionMove(MotionEvent ev, int delta) {//当头部文件回推消失的时候,不允许滚动if(mIsPullUpDone) return true;// 如果开始按下到滑动距离不超过误差值,则不滑动final int absMotionY = (int) Math.abs(ev.getRawY() - mMotionDownLastY);if(absMotionY < START_PULL_DEVIATION) return true;final int absDelta = Math.abs(delta);final int i = (int) Math.ceil((double)absDelta / 2);// onTopDown在顶部,并上回推和onTopUp相对if(mHeaderViewParams.height > 0 && delta < 0){mHeaderIncremental -= i;if(mHeaderIncremental > 0){setHeaderHeight(mHeaderIncremental);checkHeaderViewState();}else{mHeaderViewState = HEADER_VIEW_STATE_IDLE;mHeaderIncremental = 0;setHeaderHeight(mHeaderIncremental);mIsPullUpDone = true;}return true;}return false;}@Overridepublic boolean onMotionUp(MotionEvent ev) {mIsDown = false;// 避免和点击事件冲突if(mHeaderViewParams.height > 0){// 判断头文件拉动的距离与设定的高度,小了就隐藏,多了就固定高度int x = mHeaderIncremental - DEFAULT_HEADER_VIEW_HEIGHT;Timer timer = new Timer(true);if(x < 0){timer.scheduleAtFixedRate(new HideHeaderViewTask(), 0, 10);}else{timer.scheduleAtFixedRate(new ShowHeaderViewTask(), 0, 10);}return true;}return false;}}

第七步:这个java文件就是封装数据,然后调用上两个文件就好了。废话不多说,上代码:::::

public class PullDownActivity extends Activity implements OnPullDownListener, OnItemClickListener{private static final int WHAT_DID_LOAD_DATA = 0;private static final int WHAT_DID_REFRESH = 1;private static final int WHAT_DID_MORE = 2;private ListView mListView;private ArrayAdapter<String> mAdapter;private PullDownView mPullDownView;private List<String> mStrings = new ArrayList<String>();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.pulldown);/** 1.使用PullDownView* 2.设置OnPullDownListener* 3.从mPullDownView里面获取ListView*/mPullDownView = (PullDownView) findViewById(R.id.pull_down_view);mPullDownView.setOnPullDownListener(this);mListView = mPullDownView.getListView();mListView.setOnItemClickListener(this);mAdapter = new ArrayAdapter<String>(this, R.layout.pulldown_item, mStrings);mListView.setAdapter(mAdapter);mPullDownView.enableAutoFetchMore(true, 1);loadData();}private void loadData(){new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}List<String> strings = new ArrayList<String>();for (String body : mStringArray) {strings.add(body);}Message msg = mUIHandler.obtainMessage(WHAT_DID_LOAD_DATA);msg.obj = strings;msg.sendToTarget();}}).start();}@Overridepublic void onRefresh() {new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}Message msg = mUIHandler.obtainMessage(WHAT_DID_REFRESH);msg.obj = "After refresh " + System.currentTimeMillis();msg.sendToTarget();}}).start();}@Overridepublic void onMore() {new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}Message msg = mUIHandler.obtainMessage(WHAT_DID_MORE);msg.obj = "After more " + System.currentTimeMillis();msg.sendToTarget();}}).start();}private Handler mUIHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case WHAT_DID_LOAD_DATA:{if(msg.obj != null){List<String> strings = (List<String>) msg.obj;if(!strings.isEmpty()){mStrings.addAll(strings);mAdapter.notifyDataSetChanged();}}// 诉它数据加载完毕;
                    mPullDownView.notifyDidLoad();break;}case WHAT_DID_REFRESH :{String body = (String) msg.obj;mStrings.add(0, body);mAdapter.notifyDataSetChanged();// 告诉它更新完毕
                    mPullDownView.notifyDidRefresh();break;}case WHAT_DID_MORE:{String body = (String) msg.obj;mStrings.add(body);mAdapter.notifyDataSetChanged();// 告诉它获取更多完毕
                    mPullDownView.notifyDidMore();break;}}}};@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {Toast.makeText(this, "啊,你点中我了 " + position, Toast.LENGTH_SHORT).show();}// 模拟数据private String[] mStringArray = {"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", "Alverca", "Ambert", "American Cheese"};}

整个程序就这么完成了,总之,可以使用前两个封装好的文件,然后,调用就好了~~~~没什么太难的地方

下拉列表刷新,分页加载相关推荐

  1. php下拉上滑分页,Flutter实现下拉刷新 上拉分页加载更多

    一.Flutter实现下拉刷新和上拉分页加载更多 在Flutter官方sdk中给我们提供了下拉刷新的组件RefreshIndicator.但是没有提供上拉分页加载更多的组件.但是在Flutter Li ...

  2. android 刷新某条数据_Android 支持刷新、加载更多、带反弹效果的RecyclerView

    点击上方"Android技术杂货铺",选择"标星" 干货文章,第一时间送达! 开篇 当前市面上很多支持刷新.加载更多RecyclerView开源库,为何我这里还 ...

  3. 深入理解Android Paging分页加载库

    来新公司半年多,最近一直在参与 Andorid 团队的架构升级工作.最近在图片选择库中使用了 paging 作为分页加载框架.顺便阅读了一下paging的源码.在这里记录一下. 初次接除 paging ...

  4. iOS 高效的分页加载

    今天在review代码的时候发现之前的tableview 和 collectview 的分页加载逻辑还有优化的余地,于是进行了优化. 一.tableview的分页加载的代码对比 没有优化之前的代码如下 ...

  5. 基于Android官方AsyncListUtil优化经典ListView分页加载机制(二)

    基于Android官方AsyncListUtil优化经典ListView分页加载机制(二) 我写的附录文章1,介绍了如何使用Android官方的分页加载框架AsyncListUtil优化改进常见的Re ...

  6. List 分页加载数据控制机制

    分页加载是一种应用很广泛的数据展示控制机制,相信绝大多数开发者对于这一套机制都非常熟悉.这篇文章的主要目的结合实际的使用场景,对以往在开发中遇到一些概念进行梳理,归纳的同时加深理解,也希望能帮助更多刚 ...

  7. 分页加载PullToRefreshLayout+PullableListView

    PullToRefreshLayout+PullableListView实现分页加载功能 1.修改布局文件(添加下拉刷新和上拉加载的布局) <com.abcaaaaa.zyypay.ui.wid ...

  8. Android Room联合AsyncListUtil实现RecyclerView分页加载ORM数据

    Android Room联合AsyncListUtil实现RecyclerView分页加载ORM数据 我之前写了一系列关于AsyncListUtil实现RecyclerView和ListView的分页 ...

  9. 微信小程序分页加载列表

    微信小程序分页加载列表: 项目开发时很多页面都需要用到分页加载列表,为了方便,自己封装了分页加载列表组件,下拉刷新列表,滑动至底部,加载更多 组件界面: 可以扫码查看是否符合自己的需求 wxml: & ...

  10. 微信小程序数据拼接_最佳方式实现微信小程序分页加载数据

    一般小程序做分页加载数据,会做一些下拉加载更多.然后上拉刷新的操作.数据放在一个for循环里去加载,数据源是一个数组对象.在加载下一页数据时,将下一页的数据拼到当前数组后面.这样的确可以实现分页加载数 ...

最新文章

  1. 心跳实现_真强啊!建议每一位Java程序员都读读Dubbo心跳设计的源码...
  2. .NET 并行(多核)编程系列之七 共享数据问题和解决概述
  3. 利用Python自动生成暴力破解的字典
  4. 关于C++,Java和Python中的随机数生成法
  5. 形容计算机老师风采的句子,关于老师的句子
  6. 使用json-lib进行Java和JSON之间的转换
  7. Java11.0.2怎么生成JRE_java环境变量配置,jdk13.0.1中没有jre解决办法
  8. java调用远程主机shell_Java 执行远程主机shell命令代码
  9. Flutter 即将占领整个 Web 开发
  10. arcgis的numpy模块_数据分析之numpy模块
  11. 一套完整的网络视频监控系统设计方案
  12. 微机 —— 可编程并行接口芯片8255A 应用
  13. 18软工实践-团队现场编程实战(抽奖系统)
  14. 如何通过JavaScript获取搜索引擎搜索关键词
  15. [原创]续一:WMI进程占用CPU过高,由Alibaba的pcUnitTest.exe文件引起
  16. ZXing 生成二维码和条形码
  17. 利用HTML5新特性实现拖拽交换表格单元格元素
  18. blur事件与click事件冲突的解决办法
  19. ANE-IOS与AS的互通
  20. SSM巢湖学院校园报修系统毕业设计源码021813

热门文章

  1. VLC-Android编译
  2. Android编译C/C++代码,编译出的so文件给别的项目用,CMakeLists.txt编译,请放弃Android.mk!
  3. 路过秋天版博客 V2.0 测试版发布 公测一周[支持多语言、多用户、多数据库、目录级URL]...
  4. 手机游戏广告条破解原理和APK加固保护方法
  5. magento 赠品_免费赠品发布:社交媒体垃圾壁纸
  6. 我那不怎么正经的2020
  7. 如何看待知识付费这现象?
  8. C#监控-通过PerformanceCounter实现Process的Cpu占用率以及GC占用监控
  9. 使用2.4寸OLED显示器遇到的小问题
  10. 八大现象论证人工智能威胁论真的存在!