[转] Android PullToRefresh扩展RecyclerView

转载请标明出处: 
http://blog.csdn.net/xuehuayous/article/details/50387089
本文出自:【Kevin.zhou的博客】

前言:接着上一篇《AndroidPullToRefresh 分析之三、响应手势事件》,这一篇主要分析如何扩展PullToRefreshBase,来创建各式各样的刷新加载内容区域。

一、 回顾

我们在第二篇《PullToRefresh 分析之二、UI结构》中提出了四个问题,只是简单粗暴的说了怎么解决,没有去看源码,下面先把这四个问题再拿出来:

  1. 刷新加载的方向是怎样的,通常的是竖向,万一奇葩的需求提出横向呢?
  2. "内容区域"应该显示什么?怎么设置才好扩展?
  3. 怎么判断到顶部了,要触发刷新动作?
  4. 怎么判断到底部了,要触发加载操作?

二、 源码分析

还是以简单的ScrollView为例进行分析,为毛我们老是欺负PullToRefreshScrollView,不急分析完ScrollView我们再分析PullToRefreshListView。翠花,上代码!

(一)、PullToRefreshScrollView 分析

首先看下都有哪些方法:

除了构造方法之外,只有四个方法,即getPullToRefreshScrollDirection()、createRefreshableView()、isReadyForPullStart()、isReadyForPullEnd()。这四个方法通过名字就可以看出来是对应的我们提出的四个问题,那么一一来分析下:

1. 刷新方向是怎样的?

对于这个问题大家肯定知道那就是竖向的,代码里面也比较简单:

@Override
public final Orientation getPullToRefreshScrollDirection() {
	return Orientation.VERTICAL;
}

2. "内容区域"显示什么?

因为是PullToRefreshScrollView,这里肯定是生成一个ScrollView对象:

@Override
protected ScrollView createRefreshableView(Context context, AttributeSet attrs) {
	ScrollView scrollView;
	if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
		scrollView = new InternalScrollViewSDK9(context, attrs);
	} else {
		scrollView = new ScrollView(context, attrs);
	}

	scrollView.setId(R.id.scrollview);
	return scrollView;
}

可以看到如果当前系统版本大于9创建的是InternalScrollVIewSDK9,其他情况是创建的系统的ScrollView,那么这个InternalScrollView是什么呢,来看下:

@TargetApi(9)
final class InternalScrollViewSDK9 extends ScrollView {

	public InternalScrollViewSDK9(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	@Override
	protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
			 int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {

	final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
	scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);

	// Does all of the hard work...
	OverscrollHelper.overScrollBy(PullToRefreshScrollView.this, deltaX, scrollX, deltaY, scrollY,
	getScrollRange(), isTouchEvent);

		return returnValue;
	}

	/**
	* Taken from the AOSP ScrollView source
	*/
	private int getScrollRange() {
	int scrollRange = 0;
		if (getChildCount() > 0) {
			View child = getChildAt(0);
			scrollRange = Math.max(0, child.getHeight() - (getHeight() - getPaddingBottom() - getPaddingTop()));
		}
	return scrollRange;
	}
}

可以看到该类还是继承的系统的ScrollView,只不过是覆写了两个方法overScrollBy()和getScrillRange()。那么为什么要覆写这两个方法呢?overScrollBy()是android2.3提供的一个方法,用来实现View的过度滚动,即滚动到头部之后还可以继续滚动。默认是没有这个效果的,这里就不去关系它了。

3.怎么判断到顶部了,要触发刷新动作?

判断到达了顶部,这个时候就可以触发一系列的刷新加载操作了,由于ScrollView只能包含一个子View,所以只要判断子View的头部完全显示出来就可以了。

@Override
protected boolean isReadyForPullStart() {
	return mRefreshableView.getScrollY() == 0;
}

    只是简单的一行代码,那么这个getScrollY()是什么意思呢?google给我们的注释是 Return the scrolled top position of this view. 大致意思就是获取ScrollView内的子View已经向上滚动的距离,那么如果滚动的距离为0的话就是子View的头部就完全显示啦。

4.怎么判断到底部了,要触发加载操作?

判断到达了底部的作用其实和判断到达顶部的作用是一样的,在ScrollView中我们只需判断子View已经滚动到了底部就可以:

@Override
protected boolean isReadyForPullEnd() {
	View scrollViewChild = mRefreshableView.getChildAt(0);
	if (null != scrollViewChild) {
		return mRefreshableView.getScrollY() >= (scrollViewChild.getHeight() - getHeight());
	}
	return false;
}

通过代码可以看出,就是已经向上滚动的距离 + ScrollView的高度 = 子View的高度 就可以断定已经到了底部。

(二)、PullToRefreshListView 分析

首先看下类的继承关系:


    和PullToRefreshScrollView直接继承PullToRefreshBase不同的是多了一个中间的PullToRefreshAdapterViewBase,那么按照辈分PullToRefreshScrollView应该是PullToRefreshListView 的叔叔或者大伯。那么还是去找我们关系的那四个方法:

很不幸,我们只是找到了两个getPullToRefershScrillDirection()和createRefreshableView(),那么先看下这两个方法:

1. 刷新方向是怎样的?

@Override
public final Orientation getPullToRefreshScrollDirection() {
	return Orientation.VERTICAL;
}

这里也是返回了竖向。

2. "内容区域"显示什么?

@Override
protected ListView createRefreshableView(Context context, AttributeSet attrs) {
	ListView lv = createListView(context, attrs);

	// Set it to this so it can be used in ListActivity/ListFragment
	lv.setId(android.R.id.list);
	return lv;
}

这里也和我们预期的一样,返回了一个ListView对象。

3.怎么判断到顶部了,要触发刷新动作?

我们要找的是isReadyForPullStart(),但是在PullToRefreshListView中我们没有发现它的踪迹,那就到它的父类中去看看,果然在PullToRefreshAdapterViewBase找到了它的身影:

@Override
protected boolean isReadyForPullStart() {
	return isFirstItemVisible();
}

调用了一个isFirstItemVisible()方法,也比较好理解,判断是否可以触发刷新动作的依据就是判断ListView的第一个条目是否完全可见:

private boolean isFirstItemVisible() {
	final Adapter adapter = mRefreshableView.getAdapter();
	......
	if (mRefreshableView.getFirstVisiblePosition() <= 1) {
		final View firstVisibleChild = mRefreshableView.getChildAt(0);
		if (firstVisibleChild != null) {
		return firstVisibleChild.getTop() >= mRefreshableView.getTop();
	}

	return false;
}

可以看出,这里获取ListView的第一个条目,然后判断该条目是否完全可见。

4.怎么判断到底部了,要触发加载操作?

这里和isReadyForPullStart()类似,isReadyForPullEnd()也是在PullToRefreshAdapterViewBase中:

protected boolean isReadyForPullEnd() {
	return isLastItemVisible();
}

相同地,它也调用了一个isLastItemVisible()的方法:

private boolean isLastItemVisible() {
	final Adapter adapter = mRefreshableView.getAdapter();
	......
	final int lastItemPosition = mRefreshableView.getCount() - 1;
	   final int lastVisiblePosition = mRefreshableView.getLastVisiblePosition();

	if (lastVisiblePosition >= lastItemPosition - 1) {
		final int childIndex = lastVisiblePosition - mRefreshableView.getFirstVisiblePosition();
			final View lastVisibleChild = mRefreshableView.getChildAt(childIndex);
			if (lastVisibleChild != null) {
				return lastVisibleChild.getBottom() <= mRefreshableView.getBottom();
		}
	}

	return false;
}

这里通过判断,可见的最后一个条目是否是最后一个并且是否完全可见来判定到达了底部。

三、 扩展RecyclerView

 

通过以上分析,我们可以知道如果想自己扩展的话只需要继承PullToRefreshBase并且根据扩展View的属性来实现这四个方法就可以了,是不是还算比较简单。

1. 继承PullToRefreshBase:

2. 我们发现有错误,那么再实现抽象的方法:

3. 还有错误,原来是缺少构造函数:

public class PullToRefreshRecyclerView extends PullToRefreshBase<RecyclerView>{

	public PullToRefreshRecyclerView(Context context) {
		super(context);
	}

	public PullToRefreshRecyclerView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public PullToRefreshRecyclerView(Context context, Mode mode) {
		super(context, mode);
	}

	public PullToRefreshRecyclerView(Context context, Mode mode, AnimationStyle animStyle) {
		super(context, mode, animStyle);
	}

	@Override
	public Orientation getPullToRefreshScrollDirection() {
		return null;
	}

	@Override
	protected RecyclerView createRefreshableView(Context context, AttributeSet attrs) {
		return null;
	}

	@Override
	protected boolean isReadyForPullEnd() {
		return false;
	}

	@Override
	protected boolean isReadyForPullStart() {
		return false;
	}
}

OK,至此为止,继承一个类,修复下错误,5秒钟就搞定了架子,然后往里面填肉。

4. 设定刷新加载方向

@Override
public Orientation getPullToRefreshScrollDirection() {
	return Orientation.VERTICAL;
}

由于我们想要的效果是竖向的刷新加载,所以这里设置为 VERTICAL。

5. "内容区域"对象创建

@Override
protected RecyclerView createRefreshableView(Context context, AttributeSet attrs) {
	RecyclerView recyclerView = new RecyclerView(context, attrs);
    return recyclerView;
}

这里也比较简单,new 一个 RecyclerView就可以了。

6. 怎么判断到顶部了

@Override
protected boolean isReadyForPullStart() {
return isFirstItemVisible();
}

在这里我们模仿下PullToRefreshListView的设置,搞一个isFirstItemVisible(),那么重点就是这个方法的编写了:

private boolean isFirstItemVisible() {
	final Adapter<?> adapter = getRefreshableView().getAdapter();

	// 如果未设置Adapter或者Adapter没有数据可以下拉刷新
	if (null == adapter || adapter.getItemCount() == 0) {
		if (DEBUG) {
					Log.d(LOG_TAG, "isFirstItemVisible. Empty View.");
		}
		return true;

	} else {
		// 第一个条目完全展示,可以刷新
		if (getFirstVisiblePosition() == 0) {
			return mRefreshableView.getChildAt(0).getTop() >= mRefreshableView.getTop();
		}
	}

	return false;
}

这里又分为当前RecyclerView是否设置了Adapter,如果未设置数据适配器,我们是允许进行刷新加载动作的,所以返回 true;然后就是获取第一个条目View,getTop()就是获取它距离父控件(RecyclerView)顶端的距离,如果该距离等于RecyclerView距离顶端的距离那么就是第一条木是完全可见的,有点不好理解,来画个图说明下:

如上图所示,在《PullToRefresh 分析之二、UI结构》中我们知道UI的结构为LinearLayout中添加了头部、尾部以及中间的内容, 在"刷新头部"、"加载尾部"都没有显示的时候,在LinearLayout中只有一个FrameLayout的中间内容的父控件,在上图右侧图片,第一个条目View,getTop()获取的值为负数,小于 RecyclerView的getTop()值0。所以这个时候返回false;只有当第一个条目完全显示的时候getTop()获取值为0,等于 RecyclerView的getTop()值0。

不知道大家注意没有,在获取第一个可见Item位置下标的时候用到了getFirstVisiablePosition()方法,

/**
 * @Description: 获取第一个可见子View的位置下标
 *
 * @return int: 位置
 * @version 1.0
 * @date 2015-9-23
 * @Author zhou.wenkai
 */
private int getFirstVisiblePosition() {
    View firstVisibleChild = mRefreshableView.getChildAt(0);
    return firstVisibleChild != null ? mRefreshableView.getChildAdapterPosition(firstVisibleChild) : -1;
}

6.怎么判断到底部了

这个的思路和判断到顶部逻辑类似。

四、总结

下面把扩展控件的过程步骤总结下:

1. 继承PullToRefreshBase;

2. 实现父类中的四个抽象方法;

3. 实现父类中所有构造方法;

4. 在getPullToRefreshScrollDirection()设置方向;

5. 在createRefreshableView()初始化刷新加载View;

6. 在isReadyForPullStart()判断刷新加载View顶端是否可见;

7. 在isReadyForPullEnd()判断刷新加载View底端是否可见。

五、源码下载

给大家提供一个github的地址:Android-PullToRefresh

另外,欢迎 star or f**k me on github! 

六、结语

在该篇中,我们搞清楚了怎么去扩展PullToRefresh框架,并且自己扩展了一个PullToRefreshRecyclerView,相信大家能在自己开发中快速扩展出想要的控件,下篇中《Android PullToRefresh 分析之五、扩展刷新加载样式》我们将要修改PullToRefresh框架,使其能够优雅地定义自己的刷新加载样式!

时间: 2024-11-08 19:11:20

[转] Android PullToRefresh扩展RecyclerView的相关文章

Android最新组件RecyclerView,替代ListView

转载请注明出处:http://blog.csdn.net/allen315410/article/details/40379159 万众瞩目的android最新5.0版本号不久前已经正式公布了,对于我这样对新事物不感冒的人来说,自然也是会关注的,除了新的android5.0带来的新的UI设计和用户体验之外,最让android程序猿感兴趣的是5.0版本号的sdk和一大堆新的API.5.0据说是额外添加或者改动了5000个API,新增了一些新的组件,以下介绍的RecyclerView就是当中之中的一

Eclipse中使用recyclerview时出现Caused by: java.lang.NoClassDefFoundError: android.support.v7.recyclerview.R$styleable

转自: http://blog.csdn.net/chenleicpp/article/details/46848785 程序崩溃,错误提示: Caused by: java.lang.NoClassDefFoundError: android.support.v7.recyclerview.R$styleable 原因: 在eclipse中使用RecyclerView,编译没有问题,但是运行时候会出现如下错误,百思不得其解,又说v4包与v7包版本不一致,有说没有导入v7-compat包的,经反

Android输入法扩展之外接键盘中文输入

大家想不想要这样一台Android  Surface平板,看着就过瘾吧. 我们知道,android目前的输入都是通过软键盘实现的,用外接键盘的少,这个在手机上是可以理解的.当手机接上外接键盘后,整体会显得头重脚轻,并且用键盘输入时,人离手机的距离就远了,自然不太适合看清手机上的内容.那在平板上呢?如果平板只是平时用来浏览看视频,不进行大量输入,自然也用不上外接键盘.那究竟什么时候需要用到外接键盘呢?本人觉得首先要满足如下两个条件. 1)   平板和外接键盘完美融合,组合后很像笔记本使用模式.类似

Android PullToRefresh (ListView GridView 下拉刷新) 使用详解

Android PullToRefresh (ListView GridView 下拉刷新) 使用详解 标签: Android下拉刷新pullToRefreshListViewGridView 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38238749,本文出自:[张鸿洋的博客] 群里一哥们今天聊天偶然提到这个git hub上的控件:pull-to-r

Android自学历程—RecyclerView的使用(2)

Introduction to RecyclerView RecyclerView在Android 5中被介绍,在 Support-V7的包中.她允许展示items在随意任何之处(可联想ListView),正如包名所说的,在API7以上均可使用(Android 22).   她的名字来自于其工作的方式,当一个Item被隐藏时,不是去destroyed她并且随后为每一个新new出来的对象去创建一个新的item,隐藏的item被回收:她们被重用,并且会有新的数据绑定她们. 一个RccyclerVie

Android PullToRefresh 下拉刷新,上拉更多,支持ScrollView,ListView,可方便拓展GridView,WebView等

在写着东西之前,从网上找到很多这方面的源码,但是基本没有找到满意的,包括在GitHub上的比较有名的Android-PullToRefresh-master,思来想去还是自己写吧,当然其中借鉴了一些别的开源代码! 废话不多说,直接上代码,注释很全乎,应该不难理解,Demo下载地址在最后: package com.zs.pulltorefreshtest; import android.content.Context; import android.util.AttributeSet; impor

Android输入法扩展之远程输入法

近年来,互联网电视開始火热.乐视TV,小米TV,近期爱奇艺也在大肆的招人做爱奇艺电视.当然还有更被关注的苹果电视.事实上.这个趋势非常正常,也非常合理.传统单纯的接收电视节目的电视已经太传统了,是该被革命了.乐视为代表的新一代互联网电视採取互联网的营销方式,不须要实体店,不须要实体工厂,仅仅需方案.服务,网上预约,直接快递等方式大大减少了成本,同一时候也能够控制库存,预防风险.同一时候他们都坚持硬件不赚钱,服务收费,更看重电视用户对象这一潜在价值. 用户多了,干啥都方便,当然还有更大的智能家居大

Android自学历程—RecyclerView的使用

在网上看见有关RecyclerView的介绍,说是ListView的进阶版,官方推荐,便找来资料,耍耍. 首先挂上官方的教程,官方是最具权威和最让人信服的第一手资料. https://developer.android.com/training/material/lists-cards.html To create complex lists and cards with material design styles in your apps, you can use the RecyclerV

如何使用Android最新的RecyclerView取代ListView?

效果图如下: 使用RecyclerView之前需要先导入android.support.v7.widget.RecyclerView所在的jar包.就在support.v7下面,目录结构如下: ...\android-sdk-windows\extras\android\support\v7\recyclerview\libs\android-support-v7-recyclerview.jar 找到自己的SDK目录,按照上述路径找到android-support-v7-recyclervie