今天项目也快结束了,就写篇博客吧,记录一下我对ListView的下拉刷新与底部载入更多的一点理解
首先我们知道ListView是没有下拉刷新的接口的,我们要借助现有的功能去扩展更多的功能我们就需要对ListView做一点修改,使它满足我们的需求:
一、ListView的表面理解就是List的格式显示的View,也就是要一行一行的显示,对ListView添加适配器这是必不可少的。所以我们修改以后的ListView当然也可以添加适配器等等常见的对ListView的操作;
二、要捕捉我们对列表界面的操作,下拉或者滚动到底部必须要有对事件的监听器,这里就尤为重要了,我们知道对按钮添加监听器,我们可以捕获到我们对某个按钮施加了操作,然后就执行我们的操作,这里的下拉或者滚动到底部,我们当然也需要捕捉到这个动作,然后执行我们想要执行的动作;
三、原理我们知道啦,现在就剩下怎么去捕获这个动作,然后捕获动作后我们怎么添加入我们想要的操作。只要捕获到动作,你想干什么我不关心,因为需求各式各样;
四、在捕获到动作以后我们开始给用户点反应。。。。。
下边我们来看看这个Demo的代码
一、继承ListView
package com.example.pulldownview;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.widget.ListView;/** * <p>一个可以监听ListView是否滚动到最顶部或最底部的自定义控件</p> * 只能监听由触摸产生的,如果是ListView本身Flying导致的,则不能监听</br> * 如果加以改进,可以实现监听scroll滚动的具体位置等 * @author solo ho</br> Email:[email protected] */public class ScrollOverListView extends ListView { private static final String TAG = "ScrollOverListView"; private static final boolean DEBUG = false; 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; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { if (DEBUG) Log.d(TAG, "onInterceptTouchEvent Action down"); mLastY = (int) ev.getRawY(); } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { final int action = ev.getAction(); final int y = (int) ev.getRawY(); boolean isHandled = false; switch(action){ case MotionEvent.ACTION_DOWN:{ if (DEBUG) Log.d(TAG, "action down"); mLastY = y; isHandled = mOnScrollOverListener.onMotionDown(ev); if (isHandled) { break; } break; } case MotionEvent.ACTION_MOVE:{ if (DEBUG) Log.d(TAG, "action move"); final int childCount = getChildCount(); if(childCount == 0) { break; } final int itemCount = getAdapter().getCount() - mBottomPosition; final int deltaY = y - mLastY; if (DEBUG) Log.d(TAG, "lastY=" + mLastY +" y=" + 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(); isHandled = mOnScrollOverListener.onMotionMove(ev, deltaY); if(isHandled){ break; } //DLog.d("firstVisiblePosition=%d firstTop=%d listPaddingTop=%d deltaY=%d", firstVisiblePosition, firstTop, listPadding, deltaY); if (firstVisiblePosition <= mTopPosition && firstTop >= listPadding && deltaY > 0) { if (DEBUG) Log.d(TAG, "action move pull down"); isHandled = mOnScrollOverListener.onListViewTopAndPullDown(ev, deltaY); if(isHandled){ break; } } // DLog.d("lastBottom=%d end=%d deltaY=%d", lastBottom, end, deltaY); if (firstVisiblePosition + childCount >= itemCount && lastBottom <= end && deltaY < 0) { if (DEBUG) Log.d(TAG, "action move pull up"); isHandled = mOnScrollOverListener.onListViewBottomAndPullUp(ev, deltaY); if(isHandled){ break; } } break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP:{ if (DEBUG) Log.d(TAG, "action move pull up"); isHandled = mOnScrollOverListener.onMotionUp(ev); if (isHandled) { break; } break; } } mLastY = y; if (isHandled) { return true; } return super.onTouchEvent(ev); } /**空的*/ private OnScrollOverListener mOnScrollOverListener = new OnScrollOverListener(){ @Override public boolean onListViewTopAndPullDown(MotionEvent event, int delta) { return false; } @Override public boolean onListViewBottomAndPullUp(MotionEvent event, int delta) { return false; } @Override public boolean onMotionDown(MotionEvent ev) { return false; } @Override public boolean onMotionMove(MotionEvent ev, int delta) { return false; } @Override public boolean onMotionUp(MotionEvent ev) { return false; } }; // =============================== public method =============================== /** * 可以自定义其中一个条目为头部,头部触发的事件将以这个为准,默认为第一个 * * @param index 正数第几个,必须在条目数范围之内 */ public void setTopPosition(int index){ if(index < 0) throw new IllegalArgumentException("Top position must > 0"); mTopPosition = index; } /** * 可以自定义其中一个条目为尾部,尾部触发的事件将以这个为准,默认为最后一个 * * @param index 倒数第几个,必须在条目数范围之内 */ public void setBottomPosition(int index){ 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) * * @author solo ho</br> Email:[email protected] */ public interface OnScrollOverListener { /** * 到达最顶部触发 * * @param delta 手指点击移动产生的偏移量 * @return */ boolean onListViewTopAndPullDown(MotionEvent event, int delta); /** * 到达最底部触发 * * @param delta 手指点击移动产生的偏移量 * @return */ boolean onListViewBottomAndPullUp(MotionEvent event, int delta); /** * 手指触摸按下触发,[email protected] MotionEvent#ACTION_DOWN} * * @return 返回true表示自己处理 * @see View#onTouchEvent(MotionEvent) */ boolean onMotionDown(MotionEvent ev); /** * 手指触摸移动触发,[email protected] MotionEvent#ACTION_MOVE} * * @return 返回true表示自己处理 * @see View#onTouchEvent(MotionEvent) */ boolean onMotionMove(MotionEvent ev, int delta); /** * 手指触摸后提起触发,[email protected] MotionEvent#ACTION_UP} * * @return 返回true表示自己处理 * @see View#onTouchEvent(MotionEvent) */ boolean onMotionUp(MotionEvent ev); }}
二、下来刷新
package com.example.pulldownview;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.text.SimpleDateFormat;import java.util.Date;import java.util.Timer;import java.util.TimerTask;import android.content.Context;import android.os.Handler;import android.os.Message;import android.util.AttributeSet;import android.util.Log;import android.view.LayoutInflater;import android.view.MotionEvent;import android.view.View;import android.view.animation.Animation;import android.view.animation.RotateAnimation;import android.widget.AbsListView;import android.widget.AbsListView.OnScrollListener;import android.widget.ImageView;import android.widget.LinearLayout;import android.widget.TextView;import com.example.pulldownview.ScrollOverListView.OnScrollOverListener;/** * 下拉刷新控件</br> * 真正实现下拉刷新的是这个控件, * ScrollOverListView只是提供触摸的事件等 * @author Solo Email:[email protected] */public class PullDownView extends LinearLayout implements OnScrollOverListener, OnScrollListener { private static final String TAG = "PullDownView"; private static final boolean DEBUG = false; private static final int AUTO_INCREMENTAL = 10; // 自增量,用于回弹 private static final int WHAT_SET_HEADER_HEIGHT = 1;// Handler what 设置高度 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 Context mContext; private Field overScrollModeField; private int mMoveDeviation; // 移动误差 private int mHeaderIncremental; // 头部文件自增量增量 private int mDefaultHeaderViewHeight; // 头部文件原本的高度 private int mStartIndex; // 当前List可显示第一项 //private int mEndIndex; // 当前List可显示最后一项 private float mMotionDownLastY; // 按下时候的Y轴坐标 private boolean mEnablePullDown; // 是否可以下拉 private boolean mIsPullUpDone; // 是否回推完成 private boolean mEnableLoadMore; // 是否启用更多 private boolean mEnableAutoFetchMore; // 是否允许自动获取更多 private boolean mIsNoMoreData; // 没有更多的数据了 private boolean mIsDidLoad; // 是否加载了数据 // 头部文件的状态 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; private static final int STATE_NONE = 0; private static final int STATE_REFRESHING = 1; // 刷新中 private static final int STATE_LOADING_MORE = 2; // 加载更多中 private static final int STATE_DRAGING = 4; // 拖动中 private static final int STATE_MOTION_DOWN = 8; // 按下 private int state = STATE_NONE; public PullDownView(Context context, AttributeSet attrs) { super(context, attrs); initHeaderViewAndFooterViewAndListView(context); } public PullDownView(Context context) { super(context); initHeaderViewAndFooterViewAndListView(context); } /* * ================================== * Public method * 外部使用,具体就是用这几个就可以了 * * ================================== */ /** * 刷新事件接口 * @author Solo Email:[email protected] */ public interface OnPullDownListener { void onRefresh(); void onLoadMore(); } /** * 通知加载完了数据,要放在Adapter.notifyDataSetChanged后面 * 当你加载完数据的时候,调用这个notifyDidLoad() * 才会隐藏头部,并初始化数据等 */ public void notifyDidDataLoad(boolean isNoMoreData) { mIsDidLoad = true; mIsNoMoreData = isNoMoreData; mFooterView.setVisibility(View.VISIBLE); updateFooter(); mListView.setFooterDividersEnabled(true); mHeaderViewParams.height = 0; mHeaderView.setLayoutParams(mHeaderViewParams); updateHeader(); doListViewIdleActionOnDataDidLoad(); } /** * 通知已经刷新完了,要放在Adapter.notifyDataSetChanged后面 * 当你执行完刷新任务之后,调用这个notifyDidRefresh() * 才会隐藏掉头部文件等操作 */ public void notifyDidRefresh(boolean isNoMoreData) { mIsNoMoreData = isNoMoreData; updateFooter(); state &= ~STATE_REFRESHING; mHeaderViewState = HEADER_VIEW_STATE_IDLE; setHeaderHeight(0); updateHeader(); doListViewIdleActionOnDataDidLoad(); } /** * 通知已经获取完更多了,要放在Adapter.notifyDataSetChanged后面 * 当你执行完更多任务之后,调用这个notyfyDidMore() * 才会隐藏加载圈等操作 */ public void notifyDidLoadMore(boolean isNoMoreData) { mIsNoMoreData = isNoMoreData; state &= ~STATE_LOADING_MORE; updateFooter(); } /** * 设置监听器 * @param listener */ public void setOnPullDownListener(OnPullDownListener listener){ mOnPullDownListener = listener; } /** * 获取内嵌的listview * @return ScrollOverListView */ public ScrollOverListView getListView(){ return mListView; } /** * 是否开启自动获取更多</br> * 自动获取更多,并在触摸到达底部的时候自动刷新 * @param index 倒数第几个触发 */ public void enableAutoFetchMore(boolean enable, int index){ if(!mEnableLoadMore) return; mEnableAutoFetchMore = enable; if(enable){ mListView.setBottomPosition(index); }else{ updateFooter(); } } /** * 是否启用加载更多</br> * 只能在初始化的时候调用 */ public void enableLoadMore(boolean enable) { mEnableLoadMore = enable; if (!enable) { // TODO 暂时使用这种错开的方法 // 更好的办法是写到属性里面 mUIHandler.post(new Runnable() { @Override public void run() { removeFooter(); } }); } } /** * 是否启用下拉刷新 */ public void enablePullDown(boolean enable) { mEnablePullDown = enable; } /* * ================================== * Private method * 具体实现下拉刷新等操作 * * ================================== */ /** * 初始化界面 */ private void initHeaderViewAndFooterViewAndListView(Context context){ setOrientation(LinearLayout.VERTICAL); mContext = context; /* * 自定义头部文件 * 放在这里是因为考虑到很多界面都需要使用 * 如果要修改,和它相关的设置都要更改 */ mEnablePullDown = true; mHeaderView = LayoutInflater.from(context).inflate(R.layout.pulldown_header, null); mHeaderViewParams = new LayoutParams(android.view.ViewGroup.LayoutParams.FILL_PARENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT); mHeaderIncremental = mDefaultHeaderViewHeight; addView(mHeaderView, 0, mHeaderViewParams); mDefaultHeaderViewHeight = getResources().getDimensionPixelSize(R.dimen.pulldown_headerview_height); mMoveDeviation = getResources().getDimensionPixelSize(R.dimen.pulldown_move_deviation); mHeaderTextView = (TextView) mHeaderView.findViewById(R.id.pulldown_header_text); mHeaderArrowView = (ImageView) mHeaderView.findViewById(R.id.pulldown_header_arrow); mHeaderViewDateView = (TextView) mHeaderView.findViewById(R.id.pulldown_header_date); 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); /* * 自定义脚部文件 */ mEnableLoadMore = true; mFooterView = LayoutInflater.from(mContext).inflate(R.layout.pulldown_footer, null); mFooterView.setVisibility(View.GONE); mFooterTextView = (TextView) mFooterView.findViewById(R.id.pulldown_footer_text); mFooterLoadingView = mFooterView.findViewById(R.id.pulldown_footer_loading); mFooterView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mIsNoMoreData || !mIsDidLoad || (state & STATE_LOADING_MORE) == STATE_LOADING_MORE) return; ScrollOverListView listView = mListView; if (listView.getCount() - listView.getHeaderViewsCount() - listView.getFooterViewsCount() > 0) { state |= STATE_LOADING_MORE; updateFooter(); mOnPullDownListener.onLoadMore(); } } }); /* * ScrollOverListView 同样是考虑到基本都要使用,所以放在这里 * 同时因为,需要它的监听事件 */ mListView = new ScrollOverListView(context); mListView.setFooterDividersEnabled(false); mListView.setId(android.R.id.list); mListView.addFooterView(mFooterView); mListView.setOnScrollOverListener(this); mListView.setOnScrollListener(this); // 因为2.3之后的某些ListView控件自己实现了pull阴影动画效果 // 所以我们在这里屏蔽他们 try { Method method = AbsListView.class.getDeclaredMethod("setOverScrollMode", int.class); method.setAccessible(true); method.invoke(mListView, 2);//View.OVER_SCROLL_NEVER } catch (Exception e) { e.printStackTrace(); } addView(mListView, android.view.ViewGroup.LayoutParams.FILL_PARENT, android.view.ViewGroup.LayoutParams.FILL_PARENT); // set ListView animation /* AnimationSet set = new AnimationSet(true); Animation animation = new AlphaAnimation(0.0f, 1.0f); animation.setDuration(50); set.addAnimation(animation); animation = new TranslateAnimation( Animation.RELATIVE_TO_SELF, 0.0f,Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, -1.0f,Animation.RELATIVE_TO_SELF, 0.0f ); animation.setDuration(100); set.addAnimation(animation); LayoutAnimationController controller = new LayoutAnimationController(set, 0.5f); mListView.setLayoutAnimation(controller); */ // 空的listener mOnPullDownListener = new OnPullDownListener() { @Override public void onRefresh() {} @Override public void onLoadMore() {} }; } /** * 去掉底部文件 */ private void removeFooter(){ if(mListView.getFooterViewsCount() > 0 && mListView != null && mFooterView != null){ mListView.removeFooterView(mFooterView); } } /** * 更新头部文件</br> * 如果刷新中显示Loading, * 如果拖动中,显示拖动效果, * 否则显示更新时间。 */ private void updateHeader() { if ((state & STATE_REFRESHING) == STATE_REFRESHING) { mHeaderArrowView.clearAnimation(); mHeaderArrowView.setVisibility(View.GONE); mHeaderLoadingView.setVisibility(View.VISIBLE); mHeaderTextView.setText("正在刷新中..."); } else if ((state & STATE_DRAGING) == STATE_DRAGING) { if(mHeaderViewParams.height >= mDefaultHeaderViewHeight){ if(mHeaderViewState == HEADER_VIEW_STATE_OVER_HEIGHT) return; mHeaderArrowView.setVisibility(View.VISIBLE); mHeaderLoadingView.setVisibility(View.GONE); mHeaderViewDateView.setVisibility(View.VISIBLE); 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; mHeaderArrowView.setVisibility(View.VISIBLE); mHeaderLoadingView.setVisibility(View.GONE); mHeaderViewDateView.setVisibility(View.VISIBLE); mHeaderViewState = HEADER_VIEW_STATE_NOT_OVER_HEIGHT; mHeaderTextView.setText("下拉可以刷新"); mHeaderArrowView.startAnimation(mRotate180To0Animation); } } else { mHeaderLoadingView.setVisibility(View.GONE); mHeaderViewDateView.setVisibility(View.VISIBLE); mHeaderArrowView.setVisibility(View.VISIBLE); mHeaderTextView.setText("下拉可以刷新"); mHeaderViewDateView.setText("更新于:" + dateFormat.format(new Date(System.currentTimeMillis()))); } } /** * 更新脚部文件</br> * 正常显示"更多", * 如果没有更多的数据就显示"已加载完全部", * 如果加载中就显示"加载中..."。 */ private void updateFooter() { if (!mEnableLoadMore) return; if (mIsNoMoreData) { mFooterTextView.setText("已加载完全部"); mFooterLoadingView.setVisibility(View.GONE); } else if ((state & STATE_LOADING_MORE) == STATE_LOADING_MORE) { mFooterTextView.setText("加载更多中..."); mFooterLoadingView.setVisibility(View.VISIBLE); } else { mFooterTextView.setText("更多"); mFooterLoadingView.setVisibility(View.GONE); } } private void setHeaderHeight(final int height){ mHeaderIncremental = height; mHeaderViewParams.height = height; mHeaderView.setLayoutParams(mHeaderViewParams); } /** * 自动隐藏动画 */ class HideHeaderViewTask extends TimerTask{ @Override public void run() { if((state & STATE_MOTION_DOWN) == STATE_MOTION_DOWN) { 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{ @Override public void run() { if((state & STATE_MOTION_DOWN) == STATE_MOTION_DOWN) { cancel(); return; } mHeaderIncremental -= AUTO_INCREMENTAL; if(mHeaderIncremental > mDefaultHeaderViewHeight){ mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT); }else{ mHeaderIncremental = mDefaultHeaderViewHeight; mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT); if (mIsDidLoad && (state & STATE_REFRESHING) != STATE_REFRESHING) { state |= STATE_REFRESHING; mUIHandler.post(new Runnable() { @Override public void run() { // 要清除掉动画,否则无法隐藏 updateHeader(); mHeaderArrowView.clearAnimation(); mHeaderArrowView.setVisibility(View.INVISIBLE); mHeaderLoadingView.setVisibility(View.VISIBLE); mOnPullDownListener.onRefresh(); } }); } cancel(); } } } private Handler mUIHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what) { case WHAT_SET_HEADER_HEIGHT :{ setHeaderHeight(mHeaderIncremental); return; } } } }; /** * 当刷新完数据时,手动调用onIdle事件 */ private void doListViewIdleActionOnDataDidLoad(){ new Handler().postDelayed(new Runnable() { @Override public void run() { if(mOnListViewIdleListener != null){ // 不需要考虑FooterView final int firstVisiblePosition = mListView.getFirstVisiblePosition(); final int childCount = mListView.getChildCount(); //Log.d(TAG, "[doListViewIdleActionOnDataDidLoad] firstVisiblePosition:" + firstVisiblePosition + " childCount:" + childCount); mOnListViewIdleListener.onIdle(firstVisiblePosition, childCount); } } }, 0); } /** * 条目是否填满整个屏幕 */ private boolean isFillScreenItem(){ final int firstVisiblePosition = mListView.getFirstVisiblePosition(); final int lastVisiblePosition = mListView.getLastVisiblePosition() - mListView.getFooterViewsCount(); final int visibleItemCount = lastVisiblePosition - firstVisiblePosition + 1; final int totalItemCount = mListView.getCount() - mListView.getFooterViewsCount(); if(visibleItemCount < totalItemCount) return true; return false; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { state |= STATE_MOTION_DOWN; mIsPullUpDone = false; mMotionDownLastY = ev.getRawY(); Log.d(TAG, "pulldownview.onIntercept:" + mMotionDownLastY); } return super.onInterceptTouchEvent(ev); } /* * ================================== * 实现 OnScrollOverListener接口 * * * ================================== */ @Override public boolean onListViewTopAndPullDown(MotionEvent event, int delta) { // 因为某些ListView控件自己已经添加了下拉效果 // 所以我们必须返回true,控制ListView不会滚动,达到屏蔽的效果 if ((state & STATE_REFRESHING) == STATE_REFRESHING || !mEnablePullDown || !mIsDidLoad) { return true; } if (mListView.getCount() - mListView.getFooterViewsCount() == 0) { return true; } // 如果开始按下到滑动距离不超过误差值,则不滑动 if(mHeaderViewParams.height <= 0){ final int absMotionY = (int) Math.abs(event.getRawY() - mMotionDownLastY); if(absMotionY < mMoveDeviation) { return true; } } int absDelta = Math.abs(delta); final int i = (int) Math.ceil((double)absDelta / 2); mHeaderIncremental += i; if(mHeaderIncremental >= 0){ // && mIncremental <= mMaxHeight setHeaderHeight(mHeaderIncremental); updateHeader(); } return true; } @Override public boolean onListViewBottomAndPullUp(MotionEvent event, int delta) { if((state & STATE_LOADING_MORE) == STATE_LOADING_MORE || !mIsDidLoad || !mEnableAutoFetchMore || mIsNoMoreData) return false; ScrollOverListView listView = mListView; if (listView.getCount() - listView.getHeaderViewsCount() - listView.getFooterViewsCount() > 0) { state |= STATE_LOADING_MORE; updateFooter(); mOnPullDownListener.onLoadMore(); } return true; } @Override public boolean onMotionDown(MotionEvent ev) { return false; } @Override public boolean onMotionMove(MotionEvent ev, int delta) { state |= STATE_DRAGING; //当头部文件回推消失的时候,不允许滚动 if(mIsPullUpDone) return true; // onTopDown在顶部,并向上回推和onTopUp相对 if(mHeaderViewParams.height > 0 && delta < 0){ final int absDelta = Math.abs(delta); final int i = (int) Math.ceil((double)absDelta / 2); mHeaderIncremental -= i; if(mHeaderIncremental > 0){ setHeaderHeight(mHeaderIncremental); updateHeader(); }else{ mHeaderViewState = HEADER_VIEW_STATE_IDLE; mHeaderIncremental = 0; setHeaderHeight(mHeaderIncremental); mIsPullUpDone = true; } return true; } return false; } @Override public boolean onMotionUp(MotionEvent ev) { state &= ~STATE_DRAGING; state &= ~STATE_MOTION_DOWN; // 避免和点击事件冲突 if(mHeaderViewParams.height > 0 || mIsPullUpDone){ // 判断头文件拉动的距离与设定的高度,小了就隐藏,多了就固定高度 int x = mHeaderIncremental - mDefaultHeaderViewHeight; 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;//TODO } /* * ===================================== * 监听ListView停止事件 * ===================================== */ private OnListViewIdleListener mOnListViewIdleListener; public interface OnListViewIdleListener { void onIdle(int startIndex, int count); } public void setOnListViewIdleListener(OnListViewIdleListener listener){ if(mListView != null){ mOnListViewIdleListener = listener; } } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if(scrollState == SCROLL_STATE_IDLE){ if (DEBUG) Log.d(TAG, "IDLE"); if(mOnListViewIdleListener != null){ int count = mListView.getChildCount(); final int childEndIndex = mStartIndex + mListView.getChildCount() -1; final int listEndIndex = mListView.getCount() -1; if(childEndIndex == listEndIndex){ count -= mListView.getFooterViewsCount(); } mOnListViewIdleListener.onIdle(mStartIndex, count); } } switch (scrollState) { case SCROLL_STATE_FLING: if (DEBUG) Log.d(TAG, "FLING"); break; case SCROLL_STATE_TOUCH_SCROLL: if (DEBUG) Log.d(TAG, "SCROLL"); default: break; } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (DEBUG) Log.d(TAG, "onScroll"); mStartIndex = firstVisibleItem; // 因为三星I91002.3版本如果设置over_scroll_never会出现回弹问题,所以在这里处理一下 // 更新12-9-9 2:08:晕死,测试三星2.3.3版本如果反射使用这些属性会报错。 /* final ScrollOverListView localListView = this.mListView; final boolean hasItem = localListView.getCount() > 0; try { if (overScrollModeField == null) return; final Integer mode = (Integer) overScrollModeField.get(localListView); if (firstVisibleItem <= 0 && hasItem) { if (mode != View.OVER_SCROLL_NEVER) { if (DEBUG) Log.w(TAG, "set over scroll never"); overScrollModeField.set(localListView, View.OVER_SCROLL_NEVER); } } else if (firstVisibleItem + visibleItemCount >= totalItemCount && hasItem) { if (mode != View.OVER_SCROLL_ALWAYS) { if (DEBUG) Log.w(TAG, "set over scroll always"); overScrollModeField.set(localListView, View.OVER_SCROLL_ALWAYS); } } } catch (Exception e) { e.printStackTrace(); } */ }}
三、Demo界面
package com.example.pulldownview;import java.util.ArrayList;import java.util.List;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.TextView;import com.example.pulldownview.PullDownView.OnPullDownListener;public class MainActivity extends Activity { private PullDownView pullDownView; private ScrollOverListView listView; private MyAdapter adapter; private List<String> arrays; private LayoutInflater inflater; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); inflater = getLayoutInflater(); pullDownView = (PullDownView) findViewById(R.id.pulldownview); pullDownView.enableAutoFetchMore(true, 0); listView = pullDownView.getListView(); adapter = new MyAdapter(); listView.setAdapter(adapter); initArrays(new Handler(){ @Override public void handleMessage(Message msg) { arrays = (List<String>) msg.obj; adapter.notifyDataSetChanged(); pullDownView.notifyDidDataLoad(false); } }); pullDownView.setOnPullDownListener(new OnPullDownListener() { @Override public void onRefresh() { getNewString(new Handler(){ @Override public void handleMessage(Message msg) { arrays.add(0, (String) msg.obj); adapter.notifyDataSetChanged(); pullDownView.notifyDidRefresh(arrays.isEmpty()); } }); } @Override public void onLoadMore() { getNewString(new Handler(){ @Override public void handleMessage(Message msg) { arrays.add((String) msg.obj); adapter.notifyDataSetChanged(); pullDownView.notifyDidLoadMore(arrays.isEmpty()); } }); } }); } private void initArrays(final Handler handler) { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(3000); } catch (Exception e) { Thread.interrupted(); e.printStackTrace(); } List<String> as = new ArrayList<String>(); as.add("first"); as.add("second"); as.add("third"); as.add("four"); as.add("five"); as.add("first"); as.add("second"); as.add("third"); as.add("four"); as.add("five"); as.add("first"); as.add("second"); as.add("third"); as.add("four"); as.add("five"); as.add("first"); as.add("second"); as.add("third"); as.add("four"); as.add("five"); as.add("first"); as.add("second"); as.add("third"); as.add("four"); as.add("five"); handler.obtainMessage(0, as).sendToTarget(); } }).start(); } private void getNewString(final Handler handler) { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (Exception e) { Thread.interrupted(); e.printStackTrace(); } handler.obtainMessage(0, "New Text " + System.currentTimeMillis()).sendToTarget(); } }).start(); } private class MyAdapter extends BaseAdapter { @Override public int getCount() { return arrays == null ? 0 : arrays.size(); } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { holder = new ViewHolder(); convertView = inflater.inflate(R.layout.item_list, null); holder.textView = (TextView) convertView.findViewById(R.id.text); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.textView.setText(arrays.get(position)); return convertView; } } private static class ViewHolder { TextView textView; }}
1 楼 nbzxforever 2012-09-13
感谢分享!