Android自定义PullListView下拉刷新,上拉加载更多分页数据

一、测试效果截图

Android自定义PullListView下拉刷新,上拉加载更多分页数据_第1张图片    Android自定义PullListView下拉刷新,上拉加载更多分页数据_第2张图片   Android自定义PullListView下拉刷新,上拉加载更多分页数据_第3张图片

 

Android自定义PullListView下拉刷新,上拉加载更多分页数据_第4张图片   Android自定义PullListView下拉刷新,上拉加载更多分页数据_第5张图片   Android自定义PullListView下拉刷新,上拉加载更多分页数据_第6张图片

 

二、原理和思路见源码(注释很详细)

 

 

package com.widget.pull_tofresh_listview;

import com.example.nxtravelclient.R;

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.animation.Animation;
import android.view.animation.AnimationUtils;
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.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;

/**
 * @name 下拉刷新、上拉加载分页ListView控件
 * @Descripation 
* 1、需要捕获的用户手势:down按下、up抬起、move移动。主要捕获move状态下手指(纵坐标)在屏幕上滑动的距离, * 通过计算下滑或上滑的距离decline/increase来改变头部(底部)的状态。 * 2、列表的5种互斥状态:pull、release、refreshing、down、back。下拉(上拉)、临界、松手、进入刷新()状态、刷新完成。
* 3、头部(底部)布局高度随手势变化:初始状态下,头部上边距和底部下边距均为零。下拉头部布局时,头部上边距逐渐增大, * 返回时逐渐减小;上拉底部布局时,底部下边距逐渐增大, 返回时逐渐减小。
* 4、头部(底部)动画效果随手势变化:下拉临界时,头部箭逆置,原路返回时不变;上拉临界时,底部箭头逆置,原路返回时不变。
* 5、用户事件接口:下拉刷新OnRefreshListener、上拉加载更多分页数据OnLoadMoreListener, * 同步更新最近时间。
* @author Freedoman * @date 2014-9-24 * @version 1.0 */ public class PullListView extends ListView implements OnScrollListener { private final static String TAG_HEADER = "HeaderView"; private final static String TAG_FOOTER = "FooterView"; // 头部布局及其组件(指示、进度、指示文本、最近更新时间) private LinearLayout headerViewLayout; private ImageView headerArrowImage; private ImageView headerProgressImage; private TextView headerTipsText; private TextView headerLastUpdateText; // 底部布局及其组件 private LinearLayout footerViewLayout; private ImageView footerArrowImage; private ImageView footerProgressImage; private TextView footerTipsText; private TextView footerLastUpdateText; // 箭头旋转和逆置、进度条旋转动画 private RotateAnimation rotateAnimation; private RotateAnimation reverseAnimation; private Animation progressAnimation; // 下拉(上拉)、松手、正在刷新和完成4种状态 private final static int PULL = 0; private final static int RELEASE = 1; private final static int REFRESHING = 2; private final static int DONE = 3; // 标记是否从最头部下拉,最底部上拉 private boolean isFromFirstItem; private boolean isFromLastItem; private int firstItemIndex; private int lastItemIndex; private int curTotalItemCount; // 标记是否原路返回 private boolean isHeaderBack; private boolean isFooterBack; // 当前状态 private int headerState; private int footerState; // 头部视图的上边距、底部视图的下边距 private int headerTopPadding; private int footerBottomPadding; // 当前手势的滑动的纵向坐标 private float headerGestureStartY; private float footerGestureStartY; // 布局实际高度和指定拉动临界值 private int viewLayoutHeight; private int pullHeighSpec = 100; // 头部刷新、底部加载更多用户接口 public OnRefreshListener onRefreshListener; public OnLoadMoreListener onLoadMoreListener; public PullListView(Context context, AttributeSet attrs) { super(context, attrs); initAnimation(context); initHeaderView(context); initFooterViews(context); setOnScrollListener(this); } public PullListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initAnimation(context); initHeaderView(context); initFooterViews(context); setOnScrollListener(this); } /** * 初始化头部和底部使用的动画效果 */ private void initAnimation(Context context) { // 中心旋转180(0,-180) rotateAnimation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); rotateAnimation.setInterpolator(new LinearInterpolator()); rotateAnimation.setDuration(100); rotateAnimation.setFillAfter(true); // 逆置动画(-180,0) 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); // 进度条旋转动画 progressAnimation = AnimationUtils.loadAnimation(context, R.anim.loading_rotate_anim); } /** * 加载头部所有组件(箭头图标、圆圈进度条、提示文本、最后一次更新时间),设置初始大小和样式 */ private void initHeaderView(Context context) { headerViewLayout = (LinearLayout) LayoutInflater.from(context).inflate( R.layout.header_footer_list_view, null); headerArrowImage = (ImageView) headerViewLayout .findViewById(R.id.arrow_image); headerArrowImage.setMinimumWidth(50); headerArrowImage.setMinimumHeight(50); headerProgressImage = (ImageView) headerViewLayout .findViewById(R.id.progress_image); headerTipsText = (TextView) headerViewLayout .findViewById(R.id.tips_text); headerLastUpdateText = (TextView) headerViewLayout .findViewById(R.id.last_updated_text); headerViewLayout.measure(0, 0); headerViewLayout.measure(0, 0); viewLayoutHeight = headerViewLayout.getMeasuredHeight(); // 初始边距为负值,即为隐藏状态 headerTopPadding = headerViewLayout.getPaddingTop(); headerViewLayout.setPadding(headerViewLayout.getPaddingLeft(), -1 * viewLayoutHeight, headerViewLayout.getPaddingRight(), headerViewLayout.getPaddingBottom()); headerViewLayout.invalidate(); addHeaderView(headerViewLayout); } /** * 加载底部所有组件(箭头图标、圆圈进度条、提示文本、最后一次更新时间),设置初始大小和样式 */ private void initFooterViews(Context context) { footerViewLayout = (LinearLayout) LayoutInflater.from(context).inflate( R.layout.header_footer_list_view, null); footerArrowImage = (ImageView) footerViewLayout .findViewById(R.id.arrow_image); footerArrowImage.setMinimumWidth(50); footerArrowImage.setMinimumHeight(50); footerProgressImage = (ImageView) footerViewLayout .findViewById(R.id.progress_image); footerTipsText = (TextView) footerViewLayout .findViewById(R.id.tips_text); footerLastUpdateText = (TextView) footerViewLayout .findViewById(R.id.last_updated_text); footerViewLayout.measure(0, 0); // 初始边距为负值,即为隐藏状态 footerBottomPadding = footerViewLayout.getPaddingBottom(); footerViewLayout.setPadding(footerViewLayout.getPaddingLeft(), footerViewLayout.getPaddingTop(), footerViewLayout.getPaddingRight(), -1 * viewLayoutHeight); footerViewLayout.invalidate(); addFooterView(footerViewLayout); } /** * 监听列表滚动,记录列表的第一项和最后一项(为头部刷新和底部刷新提供依据) */ public void onScroll(AbsListView view, int firstVisiableItem, int visibleItemCount, int totalItemCount) { firstItemIndex = firstVisiableItem; lastItemIndex = firstVisiableItem + visibleItemCount; curTotalItemCount = totalItemCount; } public void onScrollStateChanged(AbsListView view, int scrollState) { } /** * 捕获手势,计算滑动距离来改变列表状态 */ public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: // 从头开始向下滑动并记录下滑起始位置 if (!isFromFirstItem && firstItemIndex == 0) { isFromFirstItem = true; headerGestureStartY = event.getY(); } // 从底开始上滑并记录上滑起始位置 if (!isFromLastItem && (lastItemIndex == curTotalItemCount)) { isFromLastItem = true; footerGestureStartY = event.getY(); } // 从头部开始滑动且当前状态处于‘非’正在刷新REFRESHING状态(pull,release,done三种状态互斥)时 if (headerState != REFRESHING && isFromFirstItem) { // 手势下滑程度 float decline = event.getY() - headerGestureStartY; listenHeaderStateChanged(decline); } // 从底部开始上滑且当前状态处于‘非’正在刷新REFRESHING状态(pull,release,done三种状态互斥)时 if (footerState != REFRESHING && isFromLastItem) { // 手势上滑程度 float increase = footerGestureStartY - event.getY(); listenFooterStateChanged(increase); } break; // 手势抬起时会出现三种不同的状态(release,done,pull) case MotionEvent.ACTION_UP: // 头部 if (headerState != REFRESHING) { if (headerState == DONE) { } else if (headerState == PULL) { headerState = DONE; changeHeaderViewByState(); } else if (headerState == RELEASE) { headerState = REFRESHING; changeHeaderViewByState(); onRefresh(); } } isFromFirstItem = false; isHeaderBack = false; // 底部 if (footerState != REFRESHING) { if (footerState == DONE) { } else if (footerState == PULL) { footerState = DONE; changeFooterViewByState(); } else if (footerState == RELEASE) { footerState = REFRESHING; changeFooterViewByState(); onLoadMore(); } } isFromLastItem = false; isFooterBack = false; break; } return super.onTouchEvent(event); } /** * 监听头部状态随手势下滑程度的变化 * * @param decline */ private void listenHeaderStateChanged(float decline) { // 当前状态:下拉刷新(0 < decline < 指定高度) if (headerState == PULL) { Log.i(TAG_HEADER, ">>>state=" + headerState + ",decline=" + decline); if (decline >= viewLayoutHeight + pullHeighSpec) { headerState = RELEASE; isHeaderBack = true; changeHeaderViewByState(); } else if (decline <= 0) { headerState = DONE; changeHeaderViewByState(); } } // 当前状态:松手刷新(decline >= 指定高度时) else if (headerState == RELEASE) { Log.i(TAG_HEADER, ">>>state=" + headerState + ",decline=" + decline); if (decline > 0 && decline < (viewLayoutHeight + pullHeighSpec)) { headerState = PULL; changeHeaderViewByState(); } else if (decline <= 0) { headerState = DONE; changeHeaderViewByState(); } } // 当前状态:完成(decline = 0) else if (headerState == DONE) { Log.i(TAG_HEADER, ">>>state=" + headerState + ",decline=" + decline); if (decline > 0) { headerState = PULL; changeHeaderViewByState(); } } // 根据不同的状态来改变头部布局的上边距,达到头部跟随手势一起下滑的效果 // 下拉刷新状态下:上边距逐渐增大 if (headerState == PULL) { int topPadding = (int) ((-1 * viewLayoutHeight + (decline))); headerViewLayout.setPadding(headerViewLayout.getPaddingLeft(), topPadding, headerViewLayout.getPaddingRight(), headerViewLayout.getPaddingBottom()); headerViewLayout.invalidate(); } // 松手刷新状态下:上边距逐渐减小 if (headerState == RELEASE) { int topPadding = (int) ((decline - viewLayoutHeight)); headerViewLayout.setPadding(headerViewLayout.getPaddingLeft(), topPadding, headerViewLayout.getPaddingRight(), headerViewLayout.getPaddingBottom()); headerViewLayout.invalidate(); } } /** * 监听底部状态随手势下滑程度的变化 */ private void listenFooterStateChanged(float increase) { // 当前状态:上拉刷新(0 < increase < 指定高度) if (footerState == PULL) { Log.i(TAG_FOOTER, ">>>state=" + footerState + ",increase=" + increase); if (increase >= viewLayoutHeight + pullHeighSpec) { footerState = RELEASE; isFooterBack = true; changeFooterViewByState(); } else if (increase <= 0) { footerState = DONE; changeFooterViewByState(); } } // 当前状态:松手刷新(increase >= 指定高度时) else if (footerState == RELEASE) { Log.i(TAG_FOOTER, ">>>state=" + footerState + ",increase=" + increase); if (increase > 0 && increase < (viewLayoutHeight + pullHeighSpec)) { footerState = PULL; changeFooterViewByState(); } else if (increase <= 0) { footerState = DONE; changeFooterViewByState(); } } // 当前状态:完成(increase <= 0) else if (footerState == DONE) { Log.i(TAG_FOOTER, ">>>state=" + footerState + ",increase=" + increase); if (increase > 0) { footerState = PULL; changeFooterViewByState(); } } // 根据不同的状态来改变底部布局的下边距,达到头部跟随手势一起上滑的效果 // 下拉刷新状态下:上边距逐渐增大 if (footerState == PULL) { int bottomPaddding = (int) ((-1 * viewLayoutHeight + (increase))); footerViewLayout.setPadding(footerViewLayout.getPaddingLeft(), footerViewLayout.getPaddingTop(), footerViewLayout.getPaddingRight(), bottomPaddding); footerViewLayout.invalidate(); } // 松手刷新状态下:上边距逐渐减小 if (footerState == RELEASE) { int bottomPadding = (int) ((increase - viewLayoutHeight)); footerViewLayout.setPadding(footerViewLayout.getPaddingLeft(), footerViewLayout.getPaddingTop(), footerViewLayout.getPaddingRight(), bottomPadding); footerViewLayout.invalidate(); } } /** * 根据当前状态同步刷新头部界面 */ private void changeHeaderViewByState() { switch (headerState) { // 下拉刷新:除进度条外全部显示 case PULL: headerArrowImage.setVisibility(View.VISIBLE); headerArrowImage.clearAnimation(); if (isHeaderBack) { isHeaderBack = false; headerArrowImage.clearAnimation(); headerArrowImage.startAnimation(reverseAnimation); } headerProgressImage.clearAnimation(); headerProgressImage.setVisibility(View.GONE); headerTipsText.setVisibility(View.VISIBLE); headerTipsText.setText("下拉刷新"); headerLastUpdateText.setVisibility(View.VISIBLE); break; // 松手刷新:除进度条外全部显示 case RELEASE: headerArrowImage.setVisibility(View.VISIBLE); headerArrowImage.clearAnimation(); headerArrowImage.startAnimation(rotateAnimation); headerProgressImage.clearAnimation(); headerProgressImage.setVisibility(View.GONE); headerTipsText.setVisibility(View.VISIBLE); headerTipsText.setText("松手刷新"); headerLastUpdateText.setVisibility(View.VISIBLE); break; // 正在刷新:隐藏指示图标、显示加载进度条 case REFRESHING: headerViewLayout.setPadding(headerViewLayout.getPaddingLeft(), headerTopPadding, headerViewLayout.getPaddingRight(), headerViewLayout.getPaddingBottom()); headerViewLayout.invalidate(); headerArrowImage.clearAnimation(); headerArrowImage.setVisibility(View.GONE); headerProgressImage.setVisibility(View.VISIBLE); headerProgressImage.startAnimation(progressAnimation); headerTipsText.setText("正在拼命加载..."); headerLastUpdateText.setVisibility(View.GONE); break; // 完成:恢复原样 case DONE: headerViewLayout.setPadding(headerViewLayout.getPaddingLeft(), -1 * viewLayoutHeight, headerViewLayout.getPaddingRight(), headerViewLayout.getPaddingBottom()); headerViewLayout.invalidate(); headerArrowImage.clearAnimation(); headerArrowImage.setImageResource(R.drawable.ic_pull_arrow_down); headerProgressImage.clearAnimation(); headerProgressImage.setVisibility(View.GONE); headerTipsText.setText("下拉刷新"); headerLastUpdateText.setVisibility(View.VISIBLE); break; } } /** * 根据当前状态同步刷新底部界面 */ private void changeFooterViewByState() { switch (footerState) { // 上拉刷新、除进度条隐藏、箭头向下 case PULL: footerArrowImage.setVisibility(View.VISIBLE); footerArrowImage.startAnimation(rotateAnimation); if (isFooterBack) { isFooterBack = false; footerArrowImage.clearAnimation(); } footerProgressImage.clearAnimation(); footerProgressImage.setVisibility(View.GONE); footerTipsText.setVisibility(View.VISIBLE); footerTipsText.setText("上拉加载更多"); footerLastUpdateText.setVisibility(View.VISIBLE); break; // 松手刷新:除进度条外全部显示 case RELEASE: footerArrowImage.setVisibility(View.VISIBLE); footerArrowImage.startAnimation(reverseAnimation); footerProgressImage.clearAnimation(); footerProgressImage.setVisibility(View.GONE); footerTipsText.setVisibility(View.VISIBLE); footerTipsText.setText("松手加载"); footerLastUpdateText.setVisibility(View.VISIBLE); break; // 正在刷新:隐藏指示图标、显示加载进度条 case REFRESHING: footerViewLayout.setPadding(footerViewLayout.getPaddingLeft(), footerViewLayout.getPaddingTop(), footerViewLayout.getPaddingRight(), footerBottomPadding); footerViewLayout.invalidate(); footerArrowImage.clearAnimation(); footerArrowImage.setVisibility(View.GONE); footerProgressImage.setVisibility(View.VISIBLE); footerProgressImage.startAnimation(progressAnimation); footerTipsText.setText("正在拼命加载..."); footerLastUpdateText.setVisibility(View.GONE); break; // 完成:恢复原样 case DONE: footerViewLayout.setPadding(footerViewLayout.getPaddingLeft(), footerViewLayout.getPaddingTop(), footerViewLayout.getPaddingRight(), -1 * viewLayoutHeight); footerViewLayout.invalidate(); footerArrowImage.clearAnimation(); footerArrowImage.setImageResource(R.drawable.ic_pull_arrow_down); footerProgressImage.clearAnimation(); footerProgressImage.setVisibility(View.GONE); footerTipsText.setText("上拉加载更多"); footerLastUpdateText.setVisibility(View.VISIBLE); break; } } /** * 进入头部刷新状态时,执行用户指定操作 */ private void onRefresh() { if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } /** * 进入底部加载更多状态时,执行用户指定的操作 */ private void onLoadMore() { if (onLoadMoreListener != null) { onLoadMoreListener.onLoadMore(); } } /** * 用户调用此方法,注入头部刷新接口的子类对象 * * @param refreshListener */ public void setOnRefreshListener(OnRefreshListener refreshListener) { this.onRefreshListener = refreshListener; } /** * 用户调用此方法,注入底部加载更多接口的子类对象 * * @param loadMoreListener */ public void setOnLoadMoreListener(OnLoadMoreListener loadMoreListener) { this.onLoadMoreListener = loadMoreListener; } /** * 用户调用此方法,注入头部刷新操作完成时的时间 */ public void onRefreshComplete(String update) { headerLastUpdateText.setText(update); onRefreshComplete(); } public void onRefreshComplete() { headerState = DONE; changeHeaderViewByState(); } /** * 用户调用此方法,注入头部刷新操作完成时的时间 */ public void onLoadMoreComplete(String update) { footerLastUpdateText.setText(update); footerState = DONE; changeFooterViewByState(); } public void onLoadMoreComplete() { footerState = DONE; changeFooterViewByState(); } }

 

 

三、总结

1、需要捕获的用户手势:down按下、up抬起、move移动。主要捕获move状态下手指(纵坐标)在屏幕上滑动的距离,通过计算下滑或上滑的距离decline/increase来改变头部(底部)的状态。

2、列表的5种互斥状态:pull、release、refreshing、down、back。下拉(上拉)、临界、松手、进入刷新()状态、刷新完成。

3、头部(底部)布局高度随手势变化:初始状态下,头部上边距和底部下边距均为零。下拉头部布局时,头部上边距逐渐增大,返回时逐渐减小;上拉底部布局时,底部下边距逐渐增大, 返回时逐渐减小。

 4、头部(底部)动画效果随手势变化:下拉临界时,头部箭逆置,原路返回时不变;上拉临界时,底部箭头逆置,原路返回时不变。

5、用户事件接口:下拉刷新OnRefreshListener、上拉加载更多分页数据OnLoadMoreListener,同步更新最近时间。

你可能感兴趣的:(Android)