Android 下拉刷新控件XListView原理解析

在Android app开发过程中,使用下拉刷新控件的机会是非常多的,比如列表页或是首页,一般都是要下拉刷新的。在Github中下拉刷新控件有很多,但是我现在介绍的是已经停更很久的XListView,因为我觉得这个库写的简介明了,功能稳定,bug少。非常适合自己学习下拉刷新的原理。面试的时候也通常会问到某些控件的原理,所以,了解一下还是很有必要的。

XListView在github中的仓库:https://github.com/Maxwin-z/XListView-Android

本人根据@Maxwin-z大神的库,添加了一下功能,主要是类似支付宝首页的在刷新Header上添加一个header,往上滑会跟着滑的功能,希望大家也多多支持,仓库地址是:https://github.com/mengchaoshen/SListView-Android/

首先介绍一下XListView的简单使用:

因为XListView它是继承自ListView的,所以使用方法和普通ListView没什么两样,

1.使用<XListView>标签,放入你的布局文件中

2.使用Adapter,设置数据源

3.不同的地方是,它可以设置下拉刷新的callback,也就是说,出发下拉刷新时,需要你去刷新数据,并且让你去更新数据源,然后告诉它,我已经刷新完毕

mXListView.setXListViewListener(new XListView.IXListViewListener() {
            @Override
            public void onRefresh() {
                new Thread() {
                    @Override
                    public void run() {
                        super.run();//这里可以去调用接口获取数据,我这里简单的延迟2000ms,看看效果。
                        mHandler.sendEmptyMessageDelayed(0, 2000);
                    }
                }.run();
            }

            @Override
            public void onLoadMore() {

            }
        });
private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);//这里是数据获取成功后,要调用stopRefresh()来改变状态
            mSListView.stopRefresh();
        }
    }

简单的使用,就大概这样,在这里不具体展开了。

接下来详细讲解下XListView的实现原理:

首先大家应该知道ListView是包含一个HeaderView的,下拉刷新就是围绕这个展开的。XListView只有三个类,XListView.java,XListViewHeader.java,XListViewFooter.java。

先介绍一下主要流程

主要流程,主要是在XListView.java中,大致原理就是,当ListView滚动到顶部时,这个时候手指再往下拉,就开始触发下拉刷新的第一个步骤,(首先HeaderView默认设置height=0,也就是完全不显示的)通过onTouch事件逐步传递滑动间距,来设置HeaderView的高度值,这样看起来HeaderView就是一点点显示出来了。当headerView已经完全显示出来时,继续往下拉,就触发第二个步骤,开始变为准备状态,文案也变为“松开刷新数据”,就是文案的意思,这个时候松开手指,就会去回调你设置的刷新接口。当你松开手指,就触发了第三个步骤,文案变为“数据加载中...”。知道数据加载完成,你调用了stopRefresh()方法,就又回到第一个状态,HeaderView隐藏回去。

主要过程就是这样,是不是很简单,但是细细看源码,发现还是有很多值的回味的地方。比如如何使显示的HeaderView逐渐隐藏回去,手指下拉和HeaderView显示有一个阻尼效果是如何实现的,HeaderView有那么多中状态,是如何来控制的,,带着这些问题,来详细看看源码

首先看看HeaderView源码

它是一个基础自LinearLayout的自定义布局,里面包含状态文案,刷新事件,箭头提示,progressBar等包含状态文案,刷新事件,箭头提示,progressBar等。

它的主要功能就是根据XListView的命令,来显示不同的状态和高度

首先它定义了三种状态,

//normal状态,显示“下拉刷新”,箭头朝下,如果是ready状态转换而来,需要执行箭头从上到下的动画
public final static int STATE_NORMAL = 0;
//ready状态,显示“松开刷新数据”,需要执行箭头向上的动画
public final static int STATE_READY = 1;
//refreshing状态,显示“正在加载...”隐藏箭头,显示progressBar	
public final static int STATE_REFRESHING = 2;

实现代码如下:

public void setState(int state) {
		if (state == mState) return ;
		
		if (state == STATE_REFRESHING) {	// 显示进度
			mArrowImageView.clearAnimation();
			mArrowImageView.setVisibility(View.INVISIBLE);
			mProgressBar.setVisibility(View.VISIBLE);
		} else {	// 显示箭头图片
			mArrowImageView.setVisibility(View.VISIBLE);
			mProgressBar.setVisibility(View.INVISIBLE);
		}
		
		switch(state){
		case STATE_NORMAL:
			if (mState == STATE_READY) {//如果是ready状态变为normal状态,需要开始动画,把箭头变为向下
				mArrowImageView.startAnimation(mRotateDownAnim);
			}
			if (mState == STATE_REFRESHING) {
				mArrowImageView.clearAnimation();
			}
			mHintTextView.setText(R.string.xlistview_header_hint_normal);
			break;
		case STATE_READY:
			if (mState != STATE_READY) {//如果是其他状态变为ready状态,需要开始动画,把箭头变为向上
				mArrowImageView.clearAnimation();
				mArrowImageView.startAnimation(mRotateUpAnim);
				mHintTextView.setText(R.string.xlistview_header_hint_ready);
			}
			break;
		case STATE_REFRESHING:
			mHintTextView.setText(R.string.xlistview_header_hint_loading);
			break;
			default:
		}
		
		mState = state;
	}

还有一个就是设置HeaderView的高度,这也是很关键的

public void setVisibleHeight(int height) {//设置为传入的高度,如果传入的高度小于0,就设置为0
		if (height < 0)
			height = 0;
		LayoutParams lp = (LayoutParams) mContainer
				.getLayoutParams();
		lp.height = height;
		mContainer.setLayoutParams(lp);
	}
再看看最关键的XListView的源码

一些简单的初始化就直接跳过了,大家可以直接看源码,直接步入主题,查看onTouch部分源码

@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if (mLastY == -1) {
			mLastY = ev.getRawY();
		}

		switch (ev.getAction()) {
		case MotionEvent.ACTION_DOWN:
			mLastY = ev.getRawY();//ACTION_DOWN事件时,记录y坐标值
			break;
		case MotionEvent.ACTION_MOVE://手指移动
			final float deltaY = ev.getRawY() - mLastY;//ACTION_MOVE事件时,记录与上一事件y坐标的差值
			mLastY = ev.getRawY();
			if (getFirstVisiblePosition() == 0//如果ListView已经拉到顶部
					&& (mHeaderView.getVisiableHeight() > 0 || deltaY > 0)) {//HeaderView已经显示,或手指继续往下拉
				// the first item is showing, header has shown or pull down.
				updateHeaderHeight(deltaY / OFFSET_RADIO);//开始逐步把HeaderView显示出来,并且带有阻尼系数
				invokeOnScrolling();
			} else if (getLastVisiblePosition() == mTotalItemCount - 1//ListView已经滑动到底部
					&& (mFooterView.getBottomMargin() > 0 || deltaY < 0)) {//FooterView已经离开底部或手指继续往上拉
				// last item, already pulled up or want to pull up.
				updateFooterHeight(-deltaY / OFFSET_RADIO);//更新FooterView的高度
			}
			break;
		default://手指松开
			mLastY = -1; // reset
			if (getFirstVisiblePosition() == 0) {//ListView在顶部
				// invoke refresh
				if (mEnablePullRefresh//开启下拉刷新功能
						&& mHeaderView.getVisiableHeight() > mHeaderViewHeight) {//HeaderView可见高度已经比自身高度要高
					mPullRefreshing = true;
					mHeaderView.setState(XListViewHeader.STATE_REFRESHING);//触发刷新,HeaderView进入刷新中状态if (mListViewListener != null) {
						mListViewListener.onRefresh();//回调你设置的刷新方法
					}
				}
				resetHeaderHeight();//重新回到HeaderView的初始状态
			} else if (getLastVisiblePosition() == mTotalItemCount - 1) {//下拉刷新也是类似的
				// invoke load more.
				if (mEnablePullLoad
				    && mFooterView.getBottomMargin() > PULL_LOAD_MORE_DELTA
				    && !mPullLoading) {
					startLoadMore();
				}
				resetFooterHeight();
			}
			break;
		}
		return super.onTouchEvent(ev);
	}

上面的源码和注释,已经把整个流程讲得很清楚了,这里还有一个需要介绍的就是resetHeaderHeight()方法,这里使用了Scroller来把HeaderView滑动回初始位置或者全部显示位置(刷新中状态)

private void resetHeaderHeight() {
		int height = mHeaderView.getVisiableHeight();
		if (height == 0) // not visible. 如果HeaderView是不可见状态,则不需要滑动
			return;
		// refreshing and header isn't shown fully. do nothing.
		if (mPullRefreshing && height <= mHeaderViewHeight) {//如果正在刷新中,并且可见高度是小于固有高度的,不需要滑动
			return;
		}
		int finalHeight = 0; // default: scroll back to dismiss header.默认把HeaderView滑动回原位
		// is refreshing, just scroll back to show all the header.
		if (mPullRefreshing && height > mHeaderViewHeight) {//如果是刷新中,并且可见高度是大于固有高度,需要滚动到全部显示位置
			finalHeight = mHeaderViewHeight;//这个时候,需要把HeaderView滑动回全部显示的位置
		}
		mScrollBack = SCROLLBACK_HEADER;
		mScroller.startScroll(0, height, 0, finalHeight - height,
				SCROLL_DURATION);//根据之前设置的finalHeight来滑动到目标位置(初始位置或全部显示位置)
		// trigger computeScroll
		invalidate();//这里触发滑动
	}
这里还有一些关于设置,是否可以刷新等,就不一一介绍了,看懂上面的,一些细节都能迎刃而解。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值