首页 > 代码库 > Android_PullListView

Android_PullListView

ListView 下拉刷新,上拉加载更多的原理:

(1)主要是onScroll()方法和onTouchEvent()方法,先是onTouchEvent()的ACTION_DOWN,然后是 ACTION_MOVE和onScroll()方法同时进行,最后是onTouchEvent()的ACTION_UP。
(2)刷新的4种状态:CLICK_TO_REFRESH(点击刷新状态,初始状态)、DROP_DOWN_TO_REFRESH(当刷新layout高 度低于一定范围时,为此状态)、RELEASE_TO_REFRESH(当刷新layout高度高于一定范围时,为此状态)、REFRESHING(刷新 中)
(3)onTouchEvent函数:
             ACTION_DOWN时:记录此时touch的点的y坐标,在下面调整高度时使用
             ACTION_MOVE时:此时不断调整header的高度
             ACTION_UP时:根据当前状态决定是进行刷新还是放弃刷新
(4)onScrollStateChanged函数:
              SCROLL_STATE_TOUCH_SCROLL:表示listview正在滚动中,并且手指没有离开屏幕
              SCROLL_STATE_FLING:listview正在滚动中,且手指已经离开屏幕
              SCROLL_STATE_IDLE:停止滚动
(5)onScroll函数:根据listView的当前滚动状态即currentScrollState和当前刷新的状态不断修改header内容显示和刷新状态,如下:
               ListView为SCROLL_STATE_TOUCH_SCROLL且刷新状态不为REFRESHING:
               (a)刷新对应的item可见时,若刷新layout高度超出范围,则置刷新状态为RELEASE_TO_REFRESH;若刷新layout高度低于高度范围,则置刷新状态为DROP_DOWN_TO_REFRESH。

                 (b) 刷新对应的item不可见,重置header

 (6) ListView为SCROLL_STATE_FLING状态(松手滚动中)

        (a) 若刷新对应的item可见并且刷新状态不为REFRESHING,设置position为1的(即第二个)item可见
        (b) 若反弹回来,设置position为1的(即第二个)item可见
 

import
java.text.SimpleDateFormat;import java.util.Date;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.LayoutInflater;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import android.view.animation.LinearInterpolator;import android.view.animation.RotateAnimation;import android.widget.AbsListView;import android.widget.AbsListView.OnScrollListener;import android.widget.ImageView;import android.widget.ListView;import android.widget.ProgressBar;import android.widget.TextView;/** * @author kchang * @E-mail:kchang244@163.com */public class PullListView extends ListView implements OnScrollListener { private static final String TAG = PullListView.class.getSimpleName(); //hear view 的四种状态 /**静止的状态*/ private final static int NONE = 0; /**下拉刷新的状态,文字信息提示下拉刷新,箭头朝下*/ private final static int PULL = 1; /**下拉刷新的状态,文字提示松开后刷新,箭头朝上*/ private final static int RELEASE = 2; /**正在刷新数据,提示正在刷新数据,没有箭头,显示圆形进度条*/ private final static int REFRESHING = 3; //区分当前操作是刷新还是加载 /**刷新*/ private final static int REFERSH = 0; /**加载数据*/ private final static int LOAD = 1; //区分pull和release的距离的大小 private final static int SPACE = 20; //状态 private int state ; private LayoutInflater layoutInflater; private View headView;//listview头部view private View footView;//listView底部view private TextView tipTv;//显示提示信息 private TextView lastUpdateTv;//显示最后加载的时间 private ImageView arrow; private ProgressBar progressBar;//圆形进度条提示状态 private TextView loadFullTv;//数据加载失败 private TextView noDataTv;///没有数据 private TextView moreTv;//加载更多数据 private ProgressBar loadPb;//显示加载数据的状态 private RotateAnimation animation; private RotateAnimation reverseAnimation; private int startY;//按下的Y轴位置 private int firstVisibleItem; private int scrollState;//滚动的状态 private int headerContentInitialHeight;// 头部view初始化高度 private int headerContentHeight;//头部内容的高度 /***只有在listview第一个item显示的时候才进行下来刷新,否则此时的下拉只是滚动listview*/ private boolean isRecodered; //判断是否正在加载 private boolean isLoading; //开启或者关闭加载更多功能 private boolean isLoadEnable = true; private boolean isLoadFull; /**分页加载一次加载的数据大小*/ private int pageSize = 10; private onl oadListener onl oadListener; private OnRefershListener onRefershListener; public PullListView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public PullListView(Context context, AttributeSet attrs,int defStyle) { super(context, attrs,defStyle); initView(context); } public PullListView (Context context) { super(context); initView(context); } // 设置下拉刷新监听 public void setOnRefreshListener(OnRefershListener onRefreshListener) { this.onRefershListener = onRefreshListener; } // 设置加载更多监听 public void setOnLoadListener(OnLoadListener onl oadListener) { this.isLoadEnable = true; this.onLoadListener = onl oadListener; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { //SCROLL_STATE_TOUCH_SCROLL 1表示listview正在滚动中,并且手指没有离开屏幕 //SCROLL_STATE_FLING 2:listview正在滚动中,且手指已经离开屏幕 //SCROLL_STATE_IDLE 0:停止滚动 this.scrollState = scrollState; ifNeedLoad(view, scrollState); Log.d(TAG, "onScrollStateChanged"); } // 根据listview滑动的状态判断是否需要加载更多 private void ifNeedLoad(AbsListView view, int scrollState) { if (!isLoadEnable) { return; } try { if (scrollState == OnScrollListener.SCROLL_STATE_IDLE && !isLoading && view.getLastVisiblePosition() == view .getPositionForView(footView) && !isLoadFull) { onl oadData(); isLoading = true; } } catch (Exception e) { e.printStackTrace(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { this.firstVisibleItem = firstVisibleItem; Log.d(TAG, "onScroll"); } /*** * 是否开启加载更多 * @return * true表示开启,false表示不加载 */ public boolean isLoadEnable() { return isLoadEnable; } // 这里的开启或者关闭加载更多,并不支持动态调整 public void setLoadEnable(boolean loadEnable) { this.isLoadEnable = loadEnable; this.removeFooterView(footView); } public int getPageSize() { return pageSize; } public void setPageSize(int pageSize) { this.pageSize = pageSize; } // 初始化组件 private void initView(Context context) { // 设置箭头特效 animation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); animation.setInterpolator(new LinearInterpolator()); animation.setDuration(100); animation.setFillAfter(true); reverseAnimation = new RotateAnimation(-180, 0, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); reverseAnimation.setInterpolator(new LinearInterpolator()); reverseAnimation.setDuration(100); reverseAnimation.setFillAfter(true); layoutInflater = LayoutInflater.from(context); footView = layoutInflater.inflate(R.layout.listview_footer, null); loadFullTv = (TextView) footView.findViewById(R.id.loadFull); noDataTv = (TextView) footView.findViewById(R.id.noData); moreTv = (TextView) footView.findViewById(R.id.more); loadPb = (ProgressBar) footView.findViewById(R.id.loading); headView = layoutInflater.inflate(R.layout.pull_to_refresh_header, null); arrow = (ImageView) headView.findViewById(R.id.arrow); tipTv = (TextView) headView.findViewById(R.id.tip); lastUpdateTv = (TextView) headView.findViewById(R.id.lastUpdate); progressBar = (ProgressBar) headView.findViewById(R.id.refreshing); // 为listview添加头部和尾部,并进行初始化 headerContentInitialHeight = headView.getPaddingTop(); measureView(headView); headerContentHeight = headView.getMeasuredHeight(); topPadding(-headerContentHeight); this.addHeaderView(headView); this.addFooterView(footView); this.setOnScrollListener(this); } // 调整header的大小。其实调整的只是距离顶部的高度。 private void topPadding(int topPadding) { headView.setPadding(headView.getPaddingLeft(), topPadding, headView.getPaddingRight(), headView.getPaddingBottom()); headView.invalidate(); } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: if (firstVisibleItem == 0) { isRecodered = true; startY = (int)ev.getY(); } break; case MotionEvent.ACTION_MOVE: //下拉刷新重点 whenMove(ev); break; case MotionEvent.ACTION_UP: if (state == PULL) { Log.d(TAG, "ACTION_UP PULL"); state = NONE; refreshHeaderViewByState(); } else if (state == RELEASE) {//提示用户松开手后刷新数据 Log.d(TAG, "ACTION_UP RELEASE"); state = REFRESHING; refreshHeaderViewByState(); onRefersh(); } break; case MotionEvent.ACTION_CANCEL: break; default: break; } return super.onTouchEvent(ev); } // 解读手势,刷新header状态 private void whenMove(MotionEvent ev) { if (!isRecodered) { return; } int tmpY = (int) ev.getY(); int space = tmpY - startY; int topPadding = space - headerContentHeight; switch (state) { case NONE: if (space > 0) { state = PULL; refreshHeaderViewByState(); } Log.d(TAG, "whenMove NONE"); break; case PULL: Log.d(TAG, "whenMove PULL"); topPadding(topPadding); if (scrollState == SCROLL_STATE_TOUCH_SCROLL && space > headerContentHeight + SPACE) { state = RELEASE; refreshHeaderViewByState(); } break; case RELEASE: Log.d(TAG, "whenMove RELEASE"); topPadding(topPadding); if (space > 0 && space < headerContentHeight + SPACE) { state = PULL; refreshHeaderViewByState(); } else if (space <= 0) { state = NONE; refreshHeaderViewByState(); } break; } } /** * 这个方法是根据结果的大小来决定footer显示的。 * <p> * 这里假定每次请求的条数为10。如果请求到了10条。则认为还有数据。如过结果不足10条,则认为数据已经全部加载,这时footer显示已经全部加载 * </p> * * @param resultSize */ public void setResultSize(int resultSize) { if (resultSize == 0) { isLoadFull = true; loadFullTv.setVisibility(View.GONE); loadPb.setVisibility(View.GONE); moreTv.setVisibility(View.GONE); noDataTv.setVisibility(View.VISIBLE); } else if (resultSize > 0 && resultSize < pageSize) { isLoadFull = true; loadFullTv.setVisibility(View.VISIBLE); loadPb.setVisibility(View.GONE); moreTv.setVisibility(View.GONE); noDataTv.setVisibility(View.GONE); } else if (resultSize == pageSize) { isLoadFull = false; loadFullTv.setVisibility(View.GONE); loadPb.setVisibility(View.VISIBLE); moreTv.setVisibility(View.VISIBLE); noDataTv.setVisibility(View.GONE); } } // 用来计算header大小的。 private void measureView(View child) { ViewGroup.LayoutParams p = child.getLayoutParams(); if (p == null) { p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } /*** * 执行刷新数据的方法 */ public void onRefersh() { if (null != onRefershListener) { onRefershListener.onRefersh(); } } /** * 执行加载数据的方法 */ public void onl oadData() { if (null != onl oadListener) { onl oadListener.onLoad(); } } // 用于加载更多结束后的回调 public void onl oadComplete() { isLoading = false; } /** * 刷新完以后的方法 * @param updateTime * 当前是时间 */ public void onRefershComplete(String updateTime) { lastUpdateTv.setText(getResources().getString(R.string.lastUpdateTime, updateTime)); state = NONE; refreshHeaderViewByState(); } /** * 刷新数据完毕 */ public void onRefershComlete() { String currentTiem = getCurrentTime(); onRefershComplete(currentTiem); } /** * 获取当前的时间 * @return */ private String getCurrentTime() { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return dateFormat.format(new Date()); } /*** * 根据当前状态,调整header * */ private void refreshHeaderViewByState() { switch (state) { case NONE: //原始状态 Log.d(TAG, "refreshHeaderViewByState NONE"); topPadding(-headerContentHeight); tipTv.setText(R.string.pull_to_refresh); progressBar.setVisibility(View.GONE); arrow.clearAnimation(); arrow.setImageResource(R.drawable.pull_to_refresh_arrow); break; case PULL: //箭头朝下 Log.d(TAG, "refreshHeaderViewByState PULL"); arrow.setVisibility(View.VISIBLE); tipTv.setVisibility(View.VISIBLE); lastUpdateTv.setVisibility(View.VISIBLE); progressBar.setVisibility(View.GONE); tipTv.setText(R.string.pull_to_refresh); arrow.clearAnimation(); arrow.setAnimation(reverseAnimation); break; case RELEASE: //箭头朝上 Log.d(TAG, "refreshHeaderViewByState RELEASE"); arrow.setVisibility(View.VISIBLE); tipTv.setVisibility(View.VISIBLE); lastUpdateTv.setVisibility(View.VISIBLE); progressBar.setVisibility(View.GONE); tipTv.setText(R.string.pull_to_refresh); tipTv.setText(R.string.release_to_refresh); arrow.clearAnimation(); arrow.setAnimation(animation); break; case REFRESHING: //显示数据加载的圆形进度条 Log.d(TAG, "refreshHeaderViewByState REFRESHING"); topPadding(headerContentInitialHeight); progressBar.setVisibility(View.VISIBLE); arrow.clearAnimation(); arrow.setVisibility(View.GONE); tipTv.setVisibility(View.GONE); lastUpdateTv.setVisibility(View.GONE); break; } } /** * 下拉刷新的接口 * */ public interface OnRefershListener { void onRefersh(); } /** * 定义加载更多的接口 */ public interface onl oadListener { void onl oad(); }}

 

Android_PullListView