100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > ListView上拉加载和下拉刷新多种实现方式

ListView上拉加载和下拉刷新多种实现方式

时间:2019-02-21 05:36:57

相关推荐

ListView上拉加载和下拉刷新多种实现方式

ListView上拉加载和下拉刷新多种实现方式

该篇为ListView下拉刷新和上拉加载实现的各种方法大合集。可能在具体的细节逻辑上处理不太到位,但基本上完成逻辑的实现。细节方面,个人可以根据自己的需求进行完善。

该博客将以四种思路来完成下拉刷新和上拉加载

自定义View实现上拉加载和下拉刷新使用PullToRefresh 实现上拉加载和下拉刷新使用Ultra-Pull-To-Refresh实现上拉加载和下拉刷新使用SwipeToRefreshLayout实现上拉加载和下拉刷新

如果你对ListView不够熟悉,欢迎看我之前的博客 ListView使用总结

自定义View实现上拉加载和下拉刷新

该方法,我们通过完全自定义的方式实现,不添加任何依赖库和jar包。纯原生。

首先看一下我们实现的效果

实现的关键点:

ListView添加头布局和底布局。通过改变头布局的paddingTop值,来控制控件的显示和隐藏根据我们滑动的状态,动态修改头部布局和底部布局。

看一下代码:

public class CustomRefreshListView extends ListView implements OnScrollListener{/*** 头布局*/private View headerView;/*** 头部布局的高度*/private int headerViewHeight;/*** 头部旋转的图片*/private ImageView iv_arrow;/*** 头部下拉刷新时状态的描述*/private TextView tv_state;/*** 下拉刷新时间的显示控件*/private TextView tv_time;/*** 底部布局*/private View footerView;/*** 底部旋转progressbar*/private ProgressBar pb_rotate;/*** 底部布局的高度*/private int footerViewHeight;/*** 按下时的Y坐标*/private int downY;private final int PULL_REFRESH = 0;//下拉刷新的状态private final int RELEASE_REFRESH = 1;//松开刷新的状态private final int REFRESHING = 2;//正在刷新的状态/*** 当前下拉刷新处于的状态*/private int currentState = PULL_REFRESH;/*** 头部布局在下拉刷新改变时,图标的动画*/private RotateAnimation upAnimation,downAnimation;/*** 当前是否在加载数据*/private boolean isLoadingMore = false;public CustomRefreshListView(Context context) {this(context,null);}public CustomRefreshListView(Context context, AttributeSet attrs) {super(context, attrs);init();}private void init(){//设置滑动监听setOnScrollListener(this);//初始化头布局initHeaderView();//初始化头布局中图标的旋转动画initRotateAnimation();//初始化为尾布局initFooterView();}/*** 初始化headerView*/private void initHeaderView() {headerView = View.inflate(getContext(), R.layout.head_custom_listview, null);iv_arrow = (ImageView) headerView.findViewById(R.id.iv_arrow);pb_rotate = (ProgressBar) headerView.findViewById(R.id.pb_rotate);tv_state = (TextView) headerView.findViewById(R.id.tv_state);tv_time = (TextView) headerView.findViewById(R.id.tv_time);//测量headView的高度headerView.measure(0, 0);//获取高度,并保存headerViewHeight = headerView.getMeasuredHeight();//设置paddingTop = -headerViewHeight;这样,该控件被隐藏headerView.setPadding(0, -headerViewHeight, 0, 0);//添加头布局addHeaderView(headerView);}/*** 初始化旋转动画*/private void initRotateAnimation() {upAnimation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f,RotateAnimation.RELATIVE_TO_SELF, 0.5f);upAnimation.setDuration(300);upAnimation.setFillAfter(true);downAnimation = new RotateAnimation(-180, -360, RotateAnimation.RELATIVE_TO_SELF, 0.5f,RotateAnimation.RELATIVE_TO_SELF, 0.5f);downAnimation.setDuration(300);downAnimation.setFillAfter(true);}//初始化底布局,与头布局同理private void initFooterView() {footerView = View.inflate(getContext(), R.layout.foot_custom_listview, null);footerView.measure(0, 0);footerViewHeight = footerView.getMeasuredHeight();footerView.setPadding(0, -footerViewHeight, 0, 0);addFooterView(footerView);}@Overridepublic boolean onTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN://获取按下时y坐标downY = (int) ev.getY();break;case MotionEvent.ACTION_MOVE:if(currentState==REFRESHING){//如果当前处在滑动状态,则不做处理break;}//手指滑动偏移量int deltaY = (int) (ev.getY() - downY);//获取新的padding值int paddingTop = -headerViewHeight + deltaY;if(paddingTop>-headerViewHeight && getFirstVisiblePosition()==0){//向下滑,且处于顶部,设置padding值,该方法实现了顶布局慢慢滑动显现headerView.setPadding(0, paddingTop, 0, 0);if(paddingTop>=0 && currentState==PULL_REFRESH){//从下拉刷新进入松开刷新状态currentState = RELEASE_REFRESH;//刷新头布局refreshHeaderView();}else if (paddingTop<0 && currentState==RELEASE_REFRESH) {//进入下拉刷新状态currentState = PULL_REFRESH;refreshHeaderView();}return true;//拦截TouchMove,不让listview处理该次move事件,会造成listview无法滑动}break;case MotionEvent.ACTION_UP:if(currentState==PULL_REFRESH){//仍处于下拉刷新状态,未滑动一定距离,不加载数据,隐藏headViewheaderView.setPadding(0, -headerViewHeight, 0, 0);}else if (currentState==RELEASE_REFRESH) {//滑倒一定距离,显示无padding值得headcViewheaderView.setPadding(0, 0, 0, 0);//设置状态为刷新currentState = REFRESHING;//刷新头部布局refreshHeaderView();if(listener!=null){//接口回调加载数据listener.onPullRefresh();}}break;}return super.onTouchEvent(ev);}/*** 根据currentState来更新headerView*/private void refreshHeaderView(){switch (currentState) {case PULL_REFRESH:tv_state.setText("下拉刷新");iv_arrow.startAnimation(downAnimation);break;case RELEASE_REFRESH:tv_state.setText("松开刷新");iv_arrow.startAnimation(upAnimation);break;case REFRESHING:iv_arrow.clearAnimation();//因为向上的旋转动画有可能没有执行完iv_arrow.setVisibility(View.INVISIBLE);pb_rotate.setVisibility(View.VISIBLE);tv_state.setText("正在刷新...");break;}}/*** 完成刷新操作,重置状态,在你获取完数据并更新完adater之后,去在UI线程中调用该方法*/public void completeRefresh(){if(isLoadingMore){//重置footerView状态footerView.setPadding(0, -footerViewHeight, 0, 0);isLoadingMore = false;}else {//重置headerView状态headerView.setPadding(0, -headerViewHeight, 0, 0);currentState = PULL_REFRESH;pb_rotate.setVisibility(View.INVISIBLE);iv_arrow.setVisibility(View.VISIBLE);tv_state.setText("下拉刷新");tv_time.setText("最后刷新:"+getCurrentTime());}}/*** 获取当前系统时间,并格式化* @return*/private String getCurrentTime(){SimpleDateFormat format = new SimpleDateFormat("yy-MM-dd HH:mm:ss");return format.format(new Date());}private OnRefreshListener listener;public void setOnRefreshListener(OnRefreshListener listener){this.listener = listener;}public interface OnRefreshListener{void onPullRefresh();void onLoadingMore();}/*** SCROLL_STATE_IDLE:闲置状态,就是手指松开* SCROLL_STATE_TOUCH_SCROLL:手指触摸滑动,就是按着来滑动* SCROLL_STATE_FLING:快速滑动后松开*/@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {if(scrollState==OnScrollListener.SCROLL_STATE_IDLE && getLastVisiblePosition()==(getCount()-1) &&!isLoadingMore){isLoadingMore = true;footerView.setPadding(0, 0, 0, 0);//显示出footerViewsetSelection(getCount());//让listview最后一条显示出来,在页面完全显示出底布局if(listener!=null){listener.onLoadingMore();}}}@Overridepublic void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) {}}

代码的注释写的比较详细,在这里只说明主要的逻辑。

下拉刷新

下拉刷新是通过设置setOnTouchListener()方法,监听触摸事件,通过手指滑动的不同处理实现相应逻辑。

实现比较复杂,分为了三个情况,初始状态(显示下拉刷新),释放刷新状态,刷新状态。

其中下拉刷新状态和释放状态的改变,是由于手指滑动的不同距离,是在MotionEvent.ACTION_MOVE中进行判断,该判断不处理任何数据逻辑,只是根据手指滑动的偏移量该表UI的显示。

刷新状态的判断是在MotionEvent.ACTION_UP手指抬起时判断的。这很好理解,因为最终下拉刷新是否加载数据的确定,是由我们手指离开屏幕时与初始值的偏移量确定的。如果我们的偏移量小于了头布局的高度,代表不刷新,继续隐藏头布局。如果偏移量大于了头布局的高度,代表刷新,修改UI,同时通过接口回调,让其持有者进行加载数据。

上拉加载

上拉加载和下拉刷新不同,他的视线较为简单,我们通过ListView的滚动监听进行处理相应逻辑。即setOnScrollListener(this);

该方法需要实现两个回调方法

public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount):滚动监听的调用。public void onScrollStateChanged(AbsListView view, int scrollState):滑动状态改变的回调。其中scrollState为回调的状态,可能值为

SCROLL_STATE_IDLE:闲置状态,手指松开后的状态回调SCROLL_STATE_TOUCH_SCROLL:手指触摸滑动的状态回调SCROLL_STATE_FLING:手指松开后惯性滑动的回调

我们在onScrollStateChanged中进行判断,主要判断一下条件;

是否是停止状态是否滑倒最后是否正在加载数据

如果符合条件,则开始加载数据,通过接口回调。

使用PullToRefresh 实现上拉加载和下拉刷新

该类库已经很久没有更新过了,该类库因为作者的不更新,导致局限性非常大。在这里十分的不推荐,但还是说一下简单的用法

下载地址

添加控件

<!-- 引用下拉刷新(上拉加载)的控件 --><com.handmark.pulltorefresh.library.PullToRefreshListView android:id="@+id/ptrListViewId"android:layout_width="match_parent"android:layout_height="match_parent"/>

因为其继承ListView,所以其加载数据用法和ListView一样

代码设置相应属性

private void initPTR() {// TODO 初始化下拉刷新控件的相关设置//1. 设置刷新模式ptrListView.setMode(Mode.BOTH);//2. 设置下拉或上拉事件监听ptrListView.setOnRefreshListener(new OnRefreshListener2<ListView>() {@Overridepublic void onPullDownToRefresh(PullToRefreshBase<ListView> refreshView) {// TODO 下拉刷新数据refreshData(false);}@Overridepublic void onPullUpToRefresh(PullToRefreshBase<ListView> refreshView) {// TODO 上拉加载下页数据loadData(true);}});//3. 定制下拉或上拉布局中相应的提示信息与加载图标//获取下拉布局的代理对象ILoadingLayout proxyPullDown = ptrListView.getLoadingLayoutProxy(true,false);proxyPullDown.setPullLabel("下拉刷新");proxyPullDown.setReleaseLabel("放开以刷新");proxyPullDown.setRefreshingLabel("正在玩命地加载....");proxyPullDown.setLoadingDrawable(getResources().getDrawable(R.drawable.ic_launcher));ILoadingLayout proxyPullUp = ptrListView.getLoadingLayoutProxy(false,true);proxyPullUp.setPullLabel("上拉加载更多");proxyPullUp.setReleaseLabel("放开以加载");proxyPullUp.setRefreshingLabel("正在玩命地加载....");proxyPullUp.setLoadingDrawable(getResources().getDrawable(android.R.drawable.ic_menu_camera));}

在其中分为了三部:

ptrListView.setMode(Mode.BOTH);Mode.BOTH表示上拉加载和下拉刷新都有setOnRefreshListener():注意其传入参数为OnRefreshListener2,如果传入OnRefreshListener,表示只有刷新getLoadingLayoutProxy()方法,传入参数的不同分别获得上拉刷新和下拉加载的布局代理对象完成数据的加载,结束下拉刷新或上拉加载,调用ptrListView.onRefreshComplete();

这个刷新没有例子,上面还是我以前总结的笔记。该三方库已经有3年没有更新了。真的不推荐使用啊。

使用Ultra-Pull-To-Refresh实现上拉加载和下拉刷新

Ultra-Pull-To-Refresh是一个功能非常强大的类库,通过他,我们可以实现非常丰富的下拉刷新视图,并且他支持几乎所有的控件的下拉刷新(不仅仅是ListView),但该视图不支持上拉加载,作者可能在考虑此库设计时的想法与Google官方的SwipeRefreshLayout的理念符合。即刷新可能是许多控件都需要,而上拉加载只有列表视图需要。

那么我们就开始尝试使用他吧。

Android Studio 导入Ultra

在工程的build.gradle中,加上如下代码

allprojects {repositories {jcenter()mavenCentral();maven {url '/content/repositories/snapshots'}}}

添加依赖包

compile 'in.srain.cube:ultra-ptr:1.0.11'

Clean一下工程即可

Eclipse 导入 Ultra

因为作者提供的只有AndroidStudio版本,所以,我把它代码导出到了一个Eclipse工程中,直接添加依赖即可。

Ultra-Pull-To-Refresh Eclipse 版本下载地址

简单使用

添加xml文件:activity_listview_ultra_refresh

<LinearLayout xmlns:android="/apk/res/android"android:orientation="vertical" android:layout_width="match_parent"android:layout_height="match_parent"><in.srain.cube.views.ptr.PtrClassicFrameLayout android:id="@+id/ultra_ptr_frame"xmlns:cube_ptr="/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"><FrameLayout android:id="@+id/ultra_refresh_frame"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#f00"android:paddingTop="100dp"></FrameLayout></in.srain.cube.views.ptr.PtrClassicFrameLayout></LinearLayout>

添加了一个PtrClassicFrameLayout包含了一个FrameLayout.

看一下java代码

public class UltraRefreshActivity extends Activity {private PtrClassicFrameLayout ptrFrame;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_listview_ultra_refresh);ptrFrame = ((PtrClassicFrameLayout) findViewById(R.id.ultra_ptr_frame));ptrFrame.setLastUpdateTimeRelateObject(this);//下拉刷新的阻力,下拉时,下拉距离和显示头部的距离比例,值越大,则越不容易滑动ptrFrame.setRatioOfHeaderHeightToRefresh(1.2f);ptrFrame.setDurationToClose(200);//返回到刷新的位置(暂未找到)ptrFrame.setDurationToCloseHeader(1000);//关闭头部的时间 // default is falseptrFrame.setPullToRefresh(false);//当下拉到一定距离时,自动刷新(true),显示释放以刷新(false)ptrFrame.setKeepHeaderWhenRefresh(true);//见名只意ptrFrame.setPtrHandler(new PtrHandler() {@Overridepublic boolean checkCanDoRefresh(PtrFrameLayout frame,View content, View header) {return PtrDefaultHandler.checkContentCanBePulledDown(frame,content, header);}@Overridepublic void onRefreshBegin(PtrFrameLayout frame) {//数据刷新的回调ptrFrame.postDelayed(new Runnable() {@Overridepublic void run() {ptrFrame.refreshComplete();}}, 1500);}});}}

看一下效果

通过实现过程,我们可以发现,其下拉刷新和其包含的控件没有之间联系:也就是说我们可以在PtrClassicFrameLayout包含任何子控件。

他可以设置的一些常量,代码注释中都有体现。而他有两个关键性的方法:

ptrFrame.setPtrHandler(new PtrHandler()):数据刷新的接口回调。包含两个回调方法

checkCanDoRefresh:是否能够刷新。onRefreshBegin:开始刷新的回调。ptrFrame.addPtrUIHandler(new PtrUIHandler()):UI更新接口的回调。其中PtrUIHandler为一个接口,其定义如下

public interface PtrUIHandler {/*** When the content view has reached top and refresh has been completed, view will be reset.** @param frame*/public void onUIReset(PtrFrameLayout frame);/*** prepare for loading** @param frame*/public void onUIRefreshPrepare(PtrFrameLayout frame);/*** perform refreshing UI*/public void onUIRefreshBegin(PtrFrameLayout frame);/*** perform UI after refresh*/public void onUIRefreshComplete(PtrFrameLayout frame);public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator);}

含义如下:

onUIRefreshPrepare:开始下拉之前的接口回调。onUIRefreshBegin:开始刷新的接口回调。onUIRefreshComplete:刷新完成的接口回调。onUIReset:刷新完成之后,UI消失之后的接口回调。onUIPositionChange:下拉滑动的接口回调,多次调用。

isUnderTouch:手指是否触摸status:状态值ptrIndicator:滑动偏移量等值的封装对象。

那么对于onUIPositionChange中,都是返回了那些值呢,我写了一个自定义的HeadView,并设置高位100,同时打印相应的Log。在这只贴onUIPositionChange中的Log代码。

@Overridepublic void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) {int headerHeight = ptrIndicator.getHeaderHeight();//头部的高度int lastPosY = ptrIndicator.getLastPosY();//上一次滑动的Y偏移值int offsetToRefresh = ptrIndicator.getOffsetToRefresh();//刷新需要滑动的偏移量float offsetY = ptrIndicator.getOffsetY();//当前与上一次滑动处理的偏移值int currentPosY = ptrIndicator.getCurrentPosY();//当前系统偏移值Log.i("info","isUnderTouch"+isUnderTouch+"headHeight: "+headerHeight+" lastPosY "+lastPosY+" offsetToRefresh "+offsetToRefresh+" offsetY "+offsetY+" currentPosY "+currentPosY);}

看一下打印信息

onUIRefreshPrepareisUnderTouchtrueheadHeight: 100 lastPosY 0 offsetToRefresh 120 offsetY 23.025852 currentPosY 23............................................................isUnderTouchtrueheadHeight: 100 lastPosY 299 offsetToRefresh 120 offsetY 2.1575928 currentPosY 301onUIRefreshBeginisUnderTouchfalseheadHeight: 100 lastPosY 301 offsetToRefresh 120 offsetY 2.1575928 currentPosY 278............................................................isUnderTouchfalseheadHeight: 100 lastPosY 101 offsetToRefresh 120 offsetY 2.1575928 currentPosY 100onUIRefreshCompleteisUnderTouchfalseheadHeight: 100 lastPosY 100 offsetToRefresh 120 offsetY 2.1575928 currentPosY 96............................................................isUnderTouchtrueheadHeight: 100 lastPosY 239 offsetToRefresh 120 offsetY 1.324391 currentPosY 240onUIRefreshCompleteisUnderTouchfalseheadHeight: 100 lastPosY 240 offsetToRefresh 120 offsetY 1.324391 currentPosY 223............................................................isUnderTouchfalseheadHeight: 100 lastPosY 2 offsetToRefresh 120 offsetY 1.324391 currentPosY 1onUIResetisUnderTouchfalseheadHeight: 100 lastPosY 1 offsetToRefresh 120 offsetY 1.324391 currentPosY 0

关于每个字段的含义,代码注释已经说的很清楚了,不在多述。

Ultra自定义刷新

看一下我们将要实现的效果:

自定义头部的代码实现

public class CustomUltraRefreshHeader extends RelativeLayout implements PtrUIHandler {CircleView mCircleView;TextView mDescText;private ObjectAnimator anim;public CustomUltraRefreshHeader(Context context) {this(context,null);}public CustomUltraRefreshHeader(Context context, AttributeSet attrs) {this(context, attrs,0);}public CustomUltraRefreshHeader(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initView();}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);setMeasuredDimension(widthMeasureSpec,100);}/*** 初始化布局*/private void initView() {int circlewidth = (int) TypedValue.applyDimension(PLEX_UNIT_DIP, 30, getResources().getDisplayMetrics());mCircleView = new CircleView(getContext());LinearLayout.LayoutParams circleParams = new LinearLayout.LayoutParams(circlewidth,circlewidth);mCircleView.setLayoutParams(circleParams);mDescText = new TextView(getContext());LinearLayout.LayoutParams descParams = new LinearLayout.LayoutParams(circlewidth*3, ViewGroup.LayoutParams.WRAP_CONTENT);descParams.gravity = Gravity.CENTER;descParams.setMargins(circlewidth/2,0,0,0);mDescText.setLayoutParams(descParams);mDescText.setTextSize(12);mDescText.setTextColor(Color.GRAY);mDescText.setText("下拉刷新");//添加线性的父布局LinearLayout ll = new LinearLayout(getContext());RelativeLayout.LayoutParams llParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);llParams.addRule(CENTER_IN_PARENT);ll.setLayoutParams(llParams);ll.setPadding(10,10,10,10);ll.addView(mCircleView);ll.addView(mDescText);addView(ll);}@Overridepublic void onUIReset(PtrFrameLayout frame) {//重置时,将动画置为初始状态mCircleView.setRotation(0f);Log.i("info","onUIReset");}@Overridepublic void onUIRefreshPrepare(PtrFrameLayout frame) {mDescText.setText("下拉加载数据");Log.i("info","onUIRefreshPrepare");}@Overridepublic void onUIRefreshBegin(PtrFrameLayout frame) {//开始刷新,启动动画anim = ObjectAnimator.ofFloat(mCircleView, "rotation", mCircleView.getRotation(), mCircleView.getRotation()+360f).setDuration(500);anim.setRepeatCount(ValueAnimator.INFINITE);anim.setRepeatMode(ValueAnimator.RESTART);anim.start();mDescText.setText("正在加载数据");Log.i("info","onUIRefreshBegin");}@Overridepublic void onUIRefreshComplete(PtrFrameLayout frame) {anim.cancel();mDescText.setText("加载完成");Log.i("info","onUIRefreshComplete");}@Overridepublic void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) {int headerHeight = ptrIndicator.getHeaderHeight();//头部的高度int lastPosY = ptrIndicator.getLastPosY();//上一次滑动的Y偏移值int offsetToRefresh = ptrIndicator.getOffsetToRefresh();//舒心需要滑动的偏移量float offsetY = ptrIndicator.getOffsetY();//当前与上一次滑动处理的偏移值int currentPosY = ptrIndicator.getCurrentPosY();//当前系统偏移值if (isUnderTouch&&status== PtrFrameLayout.PTR_STATUS_PREPARE) {mCircleView.setRotation(currentPosY);if(currentPosY<offsetToRefresh&&lastPosY >= offsetToRefresh){//表示不刷新了mDescText.setText("下拉加载数据");}else if(currentPosY>offsetToRefresh&&lastPosY<=offsetToRefresh){mDescText.setText("松开加载更多");}}/* if (currentPos < mOffsetToRefresh && lastPos >= mOffsetToRefresh) {if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) {*//*Log.i("info","isUnderTouch"+isUnderTouch+"headHeight: "+headerHeight+" lastPosY "+lastPosY+" offsetToRefresh "+offsetToRefresh+" offsetY "+offsetY+" currentPosY "+currentPosY);*/}}

在这里我们实现了PtrUIHandler接口,用于下拉刷新时的接口会调,有两点说明:

使用的CircleView为自定义控件,就是图中的转盘,在我以前的博客中有相应的实现。RecyclerView 下拉刷新和上拉加载

在动态改变现实视图时,需要注意的便是onUIPositionChange,我们只有在手指触摸且statusPtrFrameLayout.PTR_STATUS_PREPARE才修改我们的属性,其他时候不能修改。因为该方法会一直回调。

封装自定义的UltraRefreshListView继承ListView,并实现了PtrHandler接口,用以数据更新回调。

public class UltraRefreshListView extends ListView implements PtrHandler,AbsListView.OnScrollListener {private UltraRefreshListener mUltraRefreshListener;/*** 根布局*/private View footView;/*** 是否正在加载数据*/private boolean isLoadData = false;/*** 是否是下拉刷新,主要在处理结果时使用*/private boolean isRefresh = false;public UltraRefreshListView(Context context) {this(context,null);}public UltraRefreshListView(Context context, AttributeSet attrs) {this(context, attrs,0);}public UltraRefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);//初始化布局,及添加一个跟布局initView();}private void initView() {footView = LayoutInflater.from(getContext()).inflate(R.layout.foot_ultra_refresh_listview, null);setOnScrollListener(this);}@Overridepublic void onRefreshBegin(PtrFrameLayout frame) {isLoadData =true;isRefresh =true;//下拉刷新的回调if(mUltraRefreshListener!=null){mUltraRefreshListener.onRefresh();}}@Overridepublic boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {// PtrHandler 的接口回调,是否能够加载数据的判断return !isLoadData&&checkContentCanBePulledDown(frame, content, header);}// 从PtrHandler的默认实现类 PtrDefaultHandler中找到的,用以判断是否可以下拉刷新public static boolean checkContentCanBePulledDown(PtrFrameLayout frame, View content, View header) {return !canChildScrollUp(content);}// 从PtrHandler的默认实现类 PtrDefaultHandler中找到的,用以判断是否可以下拉刷新public static boolean canChildScrollUp(View view) {if (android.os.Build.VERSION.SDK_INT < 14) {if (view instanceof AbsListView) {final AbsListView absListView = (AbsListView) view;return absListView.getChildCount() > 0&& (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0).getTop() < absListView.getPaddingTop());} else {return view.getScrollY() > 0;}} else {return view.canScrollVertically(-1);}}/*** 设置ListView的监听回调*/public void setUltraRefreshListener(UltraRefreshListener mUltraRefreshListener) {this.mUltraRefreshListener = mUltraRefreshListener;}@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {}@Overridepublic void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {/*Log.i("info","isLoadData:"+isLoadData+" totalItemCount "+totalItemCount+" firstVisibleItem "+firstVisibleItem+" visibleItemCount "+ visibleItemCount);*///加载更多的判断if(totalItemCount>1&&!isLoadData&&totalItemCount==firstVisibleItem+visibleItemCount){isRefresh =false;isLoadData = true;addFooterView(footView);mUltraRefreshListener.addMore();}}//刷新完成的后调用此方法还原布局public void refreshComplete(){isLoadData = false;if(isRefresh){//获取其父控件,刷新ViewParent parent = getParent();if(parent instanceof PtrClassicFrameLayout){((PtrClassicFrameLayout) parent).refreshComplete();}}else{removeFooterView(footView);}}}

其中有以下几个关键点需要注意:

isRefresh:标清当前是正在下拉刷新还是在正在加载更多,这样就不用其调用者在使用refreshComplete()时,传入相应参数。checkCanDoRefresh()方法中,在判断是否可以刷新时,加入了!isLoadData,该目的是,当正在加载更多时,不允许下拉刷新。checkContentCanBePulledDown()canChildScrollUp()方法的实现,是从其源码PtrDefaultHandler中找到的,作者已经给出了比较靠谱的实现方式。

该方法,将下拉刷新和上拉加载整合到了一起,定义一个共同的接口以便进行相应操作的回调

/*** 数据接口的回调* Created by Alex_MaHao on /5/18.*/public interface UltraRefreshListener {//下拉刷新void onRefresh();//上拉加载void addMore();}

最后我们看一下如何使用

<LinearLayout xmlns:android="/apk/res/android"android:orientation="vertical" android:layout_width="match_parent"android:layout_height="match_parent"><in.srain.cube.views.ptr.PtrClassicFrameLayout android:id="@+id/ultra_ptr"android:layout_width="match_parent"android:layout_height="match_parent"><com.mahao.alex.systemwidgetdemo.listView.ultra_refresh.UltraRefreshListView android:id="@+id/ultra_lv"android:layout_width="match_parent"android:layout_height="match_parent"/></in.srain.cube.views.ptr.PtrClassicFrameLayout></LinearLayout>

/*** Created by Alex_MaHao on /5/18.*/public class UltraRefreshListActivity extends AppCompatActivity implements UltraRefreshListener {private PtrClassicFrameLayout mPtrFrame;private List<String> datas = new ArrayList<>();private SimpleBaseAdapter mAdapter;private UltraRefreshListView mLv;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_listview_ultra);//查找控件mPtrFrame = ((PtrClassicFrameLayout) findViewById(R.id.ultra_ptr));mLv = ((UltraRefreshListView) findViewById(R.id.ultra_lv));//创建我们的自定义头部视图CustomUltraRefreshHeader header = new CustomUltraRefreshHeader(this);//设置头部视图mPtrFrame.setHeaderView(header);//设置视图修改的回调,因为我们的CustomUltraRefreshHeader实现了PtrUIHandlermPtrFrame.addPtrUIHandler(header);//设置数据刷新的会回调,因为UltraRefreshListView实现了PtrHandlermPtrFrame.setPtrHandler(mLv);mAdapter = new SimpleBaseAdapter(datas);mLv.setAdapter(mAdapter);//设置数据刷新回调接口mLv.setUltraRefreshListener(this);}@Overridepublic void onRefresh() {mPtrFrame.postDelayed(new Runnable() {@Overridepublic void run() {datas.clear();for(int i = 0;i<20;i++){datas.add("添加了数据~~"+i);}//刷新完成mLv.refreshComplete();mAdapter.notifyDataSetChanged();}},1000);}@Overridepublic void addMore() {mPtrFrame.postDelayed(new Runnable() {@Overridepublic void run() {int count = mAdapter.getCount();for(int i = count; i< count +10; i++){datas.add("添加了数据~~"+i);}mAdapter.notifyDataSetChanged();//刷新完成mLv.refreshComplete();}},1000);}}

OK,注释你要看不懂,那我也很纠结该怎么办。

该控件非常好用,推荐。

使用SwipeToRefreshLayout实现上拉加载和下拉刷新

之前的博客中讲解了RecyclerView 下拉刷新和上拉加载,其中就使用了SwipeToRefreshLayout框架,他的实现原理是基于Ultra的,但其实现了上拉加载和下拉刷新。

虽然之前的博客中说的是RecyclerView的使用,但其使用方法完全相同,这里不再重复了。

该项目源码已经共享到我的github,在模块systemwidgetdemo中。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。