recyclerview 多布局混合布局怎么弄

android RecycleView复杂多条目的布局
日期: 14:04:19
来源:ITeye
android RecycleView复杂多条目的布局
用RecycleView来实现布局形式,默认只能指定一种布局格式,但是实际中我们的布局经常会用到多种类型的布局方式。如何实现呢?
今天来说下常用的2钟方式。
通过自定义addHeadView方法来添加头布局
RecycleViewWithHead.java
import android.app.A
import android.os.B
import android.support.v7.widget.GridLayoutM
import android.support.v7.widget.GridLayoutManager.SpanSizeL
import android.support.v7.widget.RecyclerV
import android.view.V
public class RecycleViewWithHead extends Activity {
private RecyclerV
// 当前的条目是recyclerView的头布局
public static final int HEADER_RECYCLER_VIEW_ITEM = 0;
// 当前的条目是普通recyclerView的条目
public static final int NORMAL_RECYCLER_VIEW_ITEM = 1;
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recycle);
rcv = (RecyclerView) findViewById(R.id.rcv);
// 设置布局管理
GridLayoutManager manager = new GridLayoutManager(this, 2);
// 设置布局管理一条数据占用几行,如果是头布局则头布局自己占用一行
manager.setSpanSizeLookup(new SpanSizeLookup() {
public int getSpanSize(int postion) {
if (postion == 0) {
rcv.setLayoutManager(manager);
MyRecycleAdapter adapter = new MyRecycleAdapter(
RecycleViewWithHead.this, 20);
View view = View.inflate(this, R.layout.head, null);
adapter.addHeadView(view);
rcv.setAdapter(adapter);
布局文件activity_recycle.xml
&RelativeLayout xmlns:android="/apk/res/android"
xmlns:tools="/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"&
&android.support.v7.widget.RecyclerView
android:id="@+id/rcv"
android:layout_width="match_parent"
android:layout_height="wrap_content" /&
&/RelativeLayout&
头布局文件head.xml
&?xml version="1.0" encoding="utf-8"?&
&RelativeLayout xmlns:android="/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" &
&ImageView
android:id="@+id/iv_head"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_margin="10dp"
android:src="@drawable/head" /&
android:id="@+id/tv_head"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/iv_head"
android:layout_toRightOf="@+id/iv_head"
android:text="这是一张熊猫的图片" /&
&ImageView
android:id="@+id/iv_head_bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/iv_head"
android:layout_toRightOf="@+id/iv_head"
android:src="@drawable/type" /&
&/RelativeLayout&
适配器MyRecycleAdapter.java
import android.content.C
import android.support.v7.widget.RecyclerV
import android.support.v7.widget.RecyclerView.ViewH
import android.view.LayoutI
import android.view.V
import android.view.View.OnClickL
import android.view.ViewG
import android.widget.T
public class MyRecycleAdapter extends
RecyclerView.Adapter&MyRecycleAdapterHolder& {
private View headV
private Context mC
MyRecycleAdapter(Context mContext, int count) {
this.count =
this.mContext = mC
* 设置数据源总的条目
public int getItemCount() {
//返回条目数加头布局个数
return count + 1;
public void onBindViewHolder(MyRecycleAdapterHolder holder,
final int position) {
int itemViewType = getItemViewType(position);
if (itemViewType == RecycleViewWithHead.HEADER_RECYCLER_VIEW_ITEM) {
} else if (itemViewType == RecycleViewWithHead.NORMAL_RECYCLER_VIEW_ITEM) {//普通条目
holder.iv_item_icon.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Toast.makeText(mContext, (position - 1) + "", 0).show();
public MyRecycleAdapterHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
View root =
if (viewType == RecycleViewWithHead.HEADER_RECYCLER_VIEW_ITEM) {
root = headV
root = LayoutInflater.from(mContext).inflate(R.layout.item, parent,
return new MyRecycleAdapterHolder(root, viewType);
* 添加自定义头部
public void addHeadView(View view) {
this.headView =
public int getItemViewType(int position) {
if (position == 0) {
return RecycleViewWithHead.HEADER_RECYCLER_VIEW_ITEM;
return RecycleViewWithHead.NORMAL_RECYCLER_VIEW_ITEM;
普通条目的布局文件item.xml
&?xml version="1.0" encoding="utf-8"?&
&LinearLayout xmlns:android="/apk/res/android"
android:id="@+id/ll_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginTop="5dp"
android:orientation="vertical" &
&ImageView
android:id="@+id/iv_item_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/item" /&
android:id="@+id/tv_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="5dp"
android:text="这是一只熊猫" /&
&/LinearLayout&
普通条目的ViewHolder
import android.support.v7.widget.RecyclerV
import android.view.V
import android.widget.ImageV
import android.widget.TextV
public class MyRecycleAdapterHolder extends RecyclerView.ViewHolder{
public ImageView iv_item_
public TextView tv_
public MyRecycleAdapterHolder(View itemView) {
super(itemView);
public MyRecycleAdapterHolder(View itemView,int viewType) {
super(itemView);
initView(itemView,viewType);
private void initView(View itemView, int viewType) {
iv_item_icon = (ImageView) itemView.findViewById(R.id.iv_item_icon);
tv_item = (TextView) itemView.findViewById(R.id.tv_item);
还有一种情况类似于淘宝的商品展示我们可以切换每行显示的数量,其实也很简单
先看下要实现的效果:
我先说下大致的实现思路:
1.给adapter设置一个当前显示多行还是单行的标记。
2.每次切换视图时重置标记,并重置RecycleView的LayoutManager。
3.调用adapter.notifyItemRangeChanged(2, adapter.getItemCount());(第一个参数是动画开始的位置索引)
好了再来看下RecycleViewWithHead.java
import com.example.myrecycleviewdemo.adapter.MyRecycleA
import android.app.A
import android.os.B
import android.support.v7.widget.GridLayoutM
import android.support.v7.widget.GridLayoutManager.SpanSizeL
import android.support.v7.widget.RecyclerV
import android.view.V
import android.view.View.OnClickL
import android.widget.ImageV
public class RecycleViewWithHead extends Activity implements OnClickListener {
private RecyclerV
// 当前的条目是recyclerView的头布局
public static final int HEADER_RECYCLER_VIEW_ITEM = 0;
// 当前的条目是普通recyclerView的条目
public static final int NORMAL_RECYCLER_VIEW_ITEM = 1;
// 一行显示一个
public static final int RECYCLER_VIEW_ITEM_SINGLE = 3;
// 一行显示两个
public static final int RECYCLER_VIEW_ITEM_DOUBLE = 4;
private ImageView iv_// 视图转换
private MyRecycleA
private GridLayoutM
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recycle);
rcv = (RecyclerView) findViewById(R.id.rcv);
iv_switch = (ImageView) findViewById(R.id.iv_switch);
iv_switch.setOnClickListener(this);
manager = new GridLayoutManager(this, 2);
// 设置布局管理一条数据占用几行,如果是头布局则头布局自己占用一行
manager.setSpanSizeLookup(new SpanSizeLookup() {
public int getSpanSize(int postion) {
if (postion == 0) {
rcv.setLayoutManager(manager);
adapter = new MyRecycleAdapter(RecycleViewWithHead.this, 20);
View view = View.inflate(this, R.layout.head, null);
// 设置当前ViewType
adapter.setSpanSize(RecycleViewWithHead.RECYCLER_VIEW_ITEM_DOUBLE);
adapter.addHeadView(view);
rcv.setAdapter(adapter);
public void onClick(View v) {
switch (v.getId()) {
case R.id.iv_switch:
changeRecycleViewList();
* 改变RecycleView的显示列数
private void changeRecycleViewList() {
if (adapter != null) {
int spanSize = adapter.getSpanSize();
// 当前一行显示一列
if (spanSize == RecycleViewWithHead.RECYCLER_VIEW_ITEM_SINGLE) {
manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
public int getSpanSize(int position) {
if (adapter.getItemViewType(position) == RecycleViewWithHead.HEADER_RECYCLER_VIEW_ITEM) {
adapter.setSpanSize(RecycleViewWithHead.RECYCLER_VIEW_ITEM_DOUBLE);
// 当前一行显示两列
else if (spanSize == RecycleViewWithHead.RECYCLER_VIEW_ITEM_DOUBLE) {
manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
public int getSpanSize(int position) {
if (adapter.getItemViewType(position) == RecycleViewWithHead.HEADER_RECYCLER_VIEW_ITEM) {
adapter.setSpanSize(RecycleViewWithHead.RECYCLER_VIEW_ITEM_SINGLE);
// 第一个参数是动画开始的位置索引
adapter.notifyItemRangeChanged(2, adapter.getItemCount());
布局文件activity_recycle.xml
&RelativeLayout xmlns:android="/apk/res/android"
xmlns:tools="/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"&
&android.support.v7.widget.RecyclerView
android:id="@+id/rcv"
android:layout_width="match_parent"
android:layout_height="wrap_content" /&
&ImageView
android:id="@+id/iv_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_margin="20dp"
android:src="@drawable/more"
&/RelativeLayout&
适配器MyRecycleAdapter.java
import com.example.myrecycleviewdemo.R;
import com.example.myrecycleviewdemo.RecycleViewWithH
import com.example.myrecycleviewdemo.R.
import android.content.C
import android.support.v7.widget.RecyclerV
import android.support.v7.widget.RecyclerView.ViewH
import android.view.LayoutI
import android.view.V
import android.view.View.OnClickL
import android.view.ViewG
import android.widget.T
public class MyRecycleAdapter extends
RecyclerView.Adapter&MyRecycleAdapterHolder& {
public View headV
public Context mC
private int spanS// 当前每行显示几列
public MyRecycleAdapter(Context mContext, int count) {
this.count =
this.mContext = mC
* 设置数据源总的条目
public int getItemCount() {
// 返回条目数加头布局个数
return count + 1;
public void onBindViewHolder(MyRecycleAdapterHolder holder,
final int position) {
int itemViewType = getItemViewType(position);
if (itemViewType == RecycleViewWithHead.HEADER_RECYCLER_VIEW_ITEM) {
} else {// 普通条目
if (itemViewType == RecycleViewWithHead.RECYCLER_VIEW_ITEM_DOUBLE) {// 一行两列视图
holder.iv_item_icon.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Toast.makeText(mContext, "2列," + (position - 1) + "", 0)
} else if (itemViewType == RecycleViewWithHead.RECYCLER_VIEW_ITEM_SINGLE) {// 一行一列视图
holder.iv_item_icon_single.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Toast.makeText(mContext,"单列," + (position - 1) + "", 0).show();
public MyRecycleAdapterHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
View root =
if (viewType == RecycleViewWithHead.HEADER_RECYCLER_VIEW_ITEM) {
root = headV
} else {// 普通条目
/** 一行显示一条 */
if (viewType == RecycleViewWithHead.RECYCLER_VIEW_ITEM_SINGLE) {
root = LayoutInflater.from(mContext).inflate(R.layout.item_single, parent, false);
/** 一行显示两条 */
root = LayoutInflater.from(mContext).inflate(R.layout.item_double, parent, false);
return new MyRecycleAdapterHolder(root, viewType);
* 添加自定义头部
public void addHeadView(View view) {
this.headView =
public int getItemViewType(int position) {
if (position == 0) {
return RecycleViewWithHead.HEADER_RECYCLER_VIEW_ITEM;
/** 一行显示一条 */
if (spanSize == RecycleViewWithHead.RECYCLER_VIEW_ITEM_SINGLE) {
return RecycleViewWithHead.RECYCLER_VIEW_ITEM_SINGLE;
/** 一行显示两条 */
return RecycleViewWithHead.RECYCLER_VIEW_ITEM_DOUBLE;
public int getSpanSize() {
return spanS
public void setSpanSize(int spanSize) {
this.spanSize = spanS
普通条目的ViewHolder
import com.example.myrecycleviewdemo.R;
import com.example.myrecycleviewdemo.R.
import com.example.myrecycleviewdemo.RecycleViewWithH
import android.support.v7.widget.RecyclerV
import android.view.V
import android.widget.ImageV
import android.widget.TextV
public class MyRecycleAdapterHolder extends RecyclerView.ViewHolder{
//一行两列视图
public ImageView iv_item_
public TextView tv_
//一行一列视图
public ImageView iv_item_icon_
public TextView tv_item_
public MyRecycleAdapterHolder(View itemView) {
super(itemView);
public MyRecycleAdapterHolder(View itemView,int viewType) {
super(itemView);
initView(itemView,viewType);
private void initView(View itemView, int viewType) {
if(viewType == RecycleViewWithHead.RECYCLER_VIEW_ITEM_DOUBLE){
iv_item_icon = (ImageView) itemView.findViewById(R.id.iv_item_icon);
tv_item = (TextView) itemView.findViewById(R.id.tv_item);
}else if(viewType == RecycleViewWithHead.RECYCLER_VIEW_ITEM_SINGLE){
iv_item_icon_single = (ImageView) itemView.findViewById(R.id.iv_item_icon_single);
tv_item_single = (TextView) itemView.findViewById(R.id.tv_item_single);
头布局文件head.xm没有任何变化
条目的布局拆分为2个item_double.xml和item_single.xml
item_double.xml和之前的item.xml一样。
item_single.xml
&?xml version="1.0" encoding="utf-8"?&
&LinearLayout xmlns:android="/apk/res/android"
android:id="@+id/ll_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginTop="5dp"
android:orientation="horizontal" &
&ImageView
android:id="@+id/iv_item_icon_single"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/item" /&
android:id="@+id/tv_item_single"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginTop="5dp"
android:text="这是一只熊猫" /&
&/LinearLayout&
源码地址:http://download.csdn.net/detail/linder_qzy/9487799
本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。
下面异常是属于Runtime Exception 的是(abcd)(多选) A、ArithmeticException B、IllegalArgumentException C、NullPointerException D、BufferUnderflowException 解析: Java提供了两类主要的异常:runtime exception和checked exception。checked 异常也就是我们经常遇到的IO异常,以及SQL异常都是这种异常。对于这种异常,JAVA编译器强制要求我们必需对出
一 iOS集成指南 1.1 导入SDK 第一步:先下载IM对应的sdk,解压之后目录如图所示例: Yuntx_IMLib_V5.x.xr.a库是静态库,Manager文件夹是主调函数声明,Delegate文件夹是回调函数声明。 第二步:然后导入sdk。将解压后的文件夹(Yuntx_IMLib_SDK)拖入您的工程中,并勾选上Destination,如图所示: 1.2、设置工程属性 向Build Phases - Link Binary With Libraries 中添加系统依赖库,操作步骤如下所示: 按
0 框架效果图 一 讲解顺序 1 标题部分 2 内容显示部分 3 完善代码 4 知识补充 二 内容显示部分解析 1 搭建: 通过观察该部分运行情况,支持上下滑动,同时也支持左右滑动 —- 1.1 结论: 父控件采用UIScrollV子控件采用五个tableView 2 分析一: 考虑内容显示的数据量比较大 —- 2.1 做法: tableView采用循环利用 3 tableView的排列顺序: 设置scrollerView的偏移量为5个tableView的宽度(contentSize),将tabl
前言: iOS的内存管理机制ARC和MRC是程序员参加面试基本必问的问题,也是考察一个iOS基本功是 否扎实的关键,这样深入理解内存管理机制的重要性就不言而喻了。 iOS内存管理机制发展史 iOS 5以前 :MRC(手动引用计数) iOS 5及以后:ARC (自动引入计数) MRC机制时代 “谁开辟申请,谁及时合理释放” 面对自己申请的内存空间是要及时进行回收的: 不及时释放会造成什么结果? 对象存储在栈上,可能会大量的占用内存,内存不足造成程序闪退 (也就是所说的内存泄露) 不合理释放会造成什么后果?
一、 默认快捷键 提高开发工具使用效率,首先必谈的就是快捷键了, 首先, 在IDE自带的快捷键中,常用的也不过二十来个,对于这些常用的操作,我们当然是希望按键越少越好,能按1个键完成不用2个键,能2个键完成坚决不用3个键,然而IDE默认的按键并不完全符合我们的要求,这个时候当然就是改改改啦。 接下来按照各类操作来介绍: 补全+修正+提示(必备) 操作 按键 备注 自动修正 Alt + Enter 相当于eclipse的Ctrl + 1 格式化代码 Ctrl + Alt + L 相当于eclipse的Ctr
官方文档原文地址 应用程序原理 Android应用程序是通过Java编程语言来写。Android软件开发工具把你的代码和其他数据、资源文件一起编译、打包成一个APK文件,这个文档以.apk为后缀,保存了一个Android应用程序所有的内容,Android设备通过它来安装对应的应用。 一旦安装到设备上,每个Android应用程序就运行在各自独立的安全沙盒中: Android系统是一个多用户的Linux系统,每一个应用都是一个用户。 Android系统默认会给每个应用分配一个唯一的用户ID(这个ID只被系统使
本文首发: http://prototypez.github.io//rxjava-common-mistakes-1/ 转载请注明出处 准备写这篇文章的时候看了下 RxJava 在 Github 上已经 12000+ 个 star 了,可见火爆程度,自己使用 RxJava 也已经有一小段时间。最初是在社区对 RxJava 一片赞扬之声下,开始使用 RxJava 来代替项目中一些简单异步请求,到后来才开始接触一些高级玩法,这中间阅读别人的代码加上自己踩的坑,慢慢积累了一些经验,很多都是
目录 目录 概述 处理流程 裁剪方式 裁剪原理 裁剪流程 图片加载 图片绘制 获取屏幕的大小 计算图片绘制区域 绘制图片 计算裁剪框 绘制裁剪框 裁剪操作 计算实际裁剪区域 裁剪并保存图片 关于移动的限制 关于缩放的限制 使用方式 源码 裁剪类源码 裁剪View源码 接口源码 GitHub地址 概述 处理流程 裁剪方式 图片的裁剪方式有很多种,最常见的有两种吧. 但不管是哪什么类型的裁剪方式,至少需要处理的有以下两个点: 图片显示 裁剪框显示 裁剪方式的不同在于以上两部分的处理方式不同而不同. 系统默认方
上一篇我们简单地说了一下Android java层的基本框架。接下来我们就来聊一下在android中音量控制的相关内容。 1.音量定义 在Android中,音量的控制与流类型是密不可分的,每种流类型都独立地拥有自己的音量设置,各种流类型的音量是互不干扰的,例如音乐音量、通话音量就是相互独立的。Andorid当前在AudioSystem.java默认有10种流类型(见下表列二)。既然Android当中有10种流类型,每种流类型的音量都是相互独立的,所以在默认音量方面,Android也给每种流类型初始化了一个
1. 手机适配方式 1.1 适配方式之 dp 名词解释 : 分辨率:eg:480*800,。表示物理屏幕区域内像素点的总和。(切记:跟屏幕适配没有任何关系) 因为我们既可以把
的分辨率做到 4.0 的手机上面。我也可以把
的分辨率做到 5.0 英寸的手机 上面,如果分辨率相同,屏幕越小越清晰 px(pix) : 像素, 就是屏幕中最小的一个显示单元 dip(像素密度) : 即每英寸屏幕所拥有的像素数,像素密度越大,显示画面细节就越加丰富 计算公式 : 像
Copyright (C)
ITfish.net创建一个 RecyclerView LayoutManager – Part 1 - 推酷
创建一个 RecyclerView LayoutManager – Part 1
创建一个 RecyclerView LayoutManager – Part 1
原文链接 :
原文作者 :
状态 : 完成
Building a RecyclerView LayoutManager – Part 1
本文是这个系列中的 Part 1,这里是
现如今,如果你是 Android 开发者,你肯定听说过 RecyclerView 这个控件;
它是一个即将加入 support library 之中的新组件,
通过方便的视图复用轻松实现自定义高效的视图集。
已经有很多优秀的文章介绍了 RecyclerView 基础,讲解如何使用
RecyclerView 提供的内建部分,包括 item animations。所以,
我们就不再重复前人的工作了,下面是一些帮你入门的资料:
我们的这个系列文章会关注 RecyclerView 底层细节,涉及到创建你自己的LayoutManager,做些比单一的 垂直/水平 滚动列表稍稍复杂的东西。
在我们开始前,你需要知道 LayoutManager API 之所以能够让我们实现
强大复杂的布局是因为它只替你做了很少的工作;这就意味着
你不得不自己完成数量可观的代码。如果要在一个项目中使用自定义视图,
不要陷入过度优化或过度泛化代码之中。你只需要关心
在你的用例中需要实现的特性就可以了。
RecyclerView Playground
这个系列中所有的代码段都取自
这个项目,
示例应用里包含了各个方面使用 RecyclerView 的实例,从创建简单的 list
到自定义 LayoutManagers。
本文的代码来自
FixedGridLayoutManager
,一个可以垂直和水平
滚动的二维的网格布局。
support library 里也有一个自定义的 LayoutManager;本质上
是一个自定义 vertical linear list 的实现:
[SDK_PATH]/extras/android/compatibility/samples/Support7Demos/src/com/example/android/supportv7/widget/RecyclerViewActivity.java
同时,Android L 和 新的 support libraries 可能还没加入 AOSP
之中,不过 RecyclerView 提供了
资源,可以在这里找到:
[SDK_PATH]/extras/android/m2repository/com/android/support/recyclerview-v7/21.0.0-rc1/recyclerview-v7-21.0.0-rc1-sources.jar
The Recycler
首先,了解下 API 的结构。当你需要从一个可能再生的前子视图中
回收旧的 view 或者 获取新的 view 时,
你的 LayoutManager 可以访问一个
Recycler 也免掉了直接访问 view 当前适配器方法的麻烦。当你的
LayoutManager 需要一个新的子视图时,只要调用
getViewForPosition()
这个方法,Recycler 会决定到底是从头创建一个新的视图
还是重用一个已存在的废弃视图。
你的 LayoutManager 需要及时将不再显示的视图传递给 Recycler,
避免 Recycler 创建不必要的 view 对象。
Detach vs. Remove
布局更新时有两个方法处理已存在的子视图:detach 和
remove (分离和移除)。Detach 是一个轻量的记录 view 操作。
被 detach 的视图在你的代码返回前能够重新连接。可以通过 Recycler
在不 重新绑定/重新构建 子视图的情况下修改已连接子视图的索引。
Remove 意味着这个 view 已经不需要了。任何被永久移除的 view 都应该
放到 Recycler 中,方便以后重用,不过 API 并没有强制要求。
被 remove 的视图是否被回收取决于你。
Scrap vs. Recycle
Recycler 有两级视图缓存系统: scrap heap 和 recycle pool (垃圾堆和回收池),
Scrap heap 是一个轻量的集合,视图可以不经过适配器直接返回给
LayoutManager 。通常被 detach
但会在同一布局重新使用的视图会临时储存在这里。Recycle pool 存放的
是那些假定并没有得到正确数据(相应位置的数据)的视图,
因此它们都要经过适配器重新绑定后才能返回给 LayoutManager。
当要给 LayoutManager 提供一个新 view 时,Recycler 首先会
检查 scrap heap 有没有对应的 position/id;如果有对应的内容,
就直接返回数据不需要通过适配器重新绑定。如果没有的话,
Recycler 就会从 recycle pool 里弄一个合适的视图出来,
然后用 adapter 给它绑定必要的数据
RecyclerView.Adapter.bindViewHolder()
) 再返回。
如果 recycle pool 中也不存在有效 view ,就会在绑定数据前
创建新的 view (就是
RecyclerView.Adapter.createViewHolder()
最后返回数据。
只要你原意,LayoutManager 的 API 允许你独立完成所有这些任务,
所以可能的组合有点多。通常来说,
如果你想要临时整理并且希望稍后在同一布局中重新使用某个 view 的话,
可以对它调用
detachAndScrapView()
。如果基于当前布局
你不再需要某个 view 的话,对其调用
removeAndRecycleView()
Building The Core
LayoutManager 需要实时添加,测量和布局所有它需要的子视图。
当用户滚动屏幕时,布局管理器将来决定什么时候添加新的子视图,
什么时候可以 detache/scrap (分离/废弃)视图。
你需要实现下面这些方法创建一个可行的 LayoutManager 最小系统。
generateDefaultLayoutParams()
事实上你只要重写这个方法你的 LayoutManager 就能编译通过了。
实现也很简单,返回一个你想要默认应用给所有从
Recycler 中获得的子视图做参数的
RecyclerView.LayoutParams
这些参数会在对应的
getViewForPosition()
返回前赋值给相应的子视图。
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(
RecyclerView.LayoutParams.WRAP_CONTENT,
RecyclerView.LayoutParams.WRAP_CONTENT);
onLayoutChildren()
onLayoutChildren()
是 LayoutManager 的主入口。
它会在 view 需要初始化布局时调用,
当适配器的数据改变时(或者整个适配器被换掉时)会再次调用。
注意!这个方法不是在每次你对布局作出改变时调用的。
它是 初始化布局 或者 在数据改变时重置子视图布局的好位置。
在接下来的部分,我们会分析在适配器更新时
是怎样使用它基于当前可见元素刷新布局的。
现在,我们将简单地解决这个问题当做子视图布局第一关。
FixedGridLayoutManager
示例的精简版:
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
//Scrap measure one child
View scrap = recycler.getViewForPosition(0);
addView(scrap);
measureChildWithMargins(scrap, 0, 0);
* We make some assumptions in this code based on every child
* view being the same size (i.e. a uniform grid). This allows
* us to compute the following values up front because they
* won't change.
mDecoratedChildWidth = getDecoratedMeasuredWidth(scrap);
mDecoratedChildHeight = getDecoratedMeasuredHeight(scrap);
detachAndScrapView(scrap, recycler);
updateWindowSizing();
int childL
int childT
* Reset the visible and scroll positions
mFirstVisiblePosition = 0;
childLeft = childTop = 0;
//Clear all attached views into the recycle bin
detachAndScrapAttachedViews(recycler);
//Fill the grid for the initial layout of views
fillGrid(DIRECTION_NONE, childLeft, childTop, recycler);
我们会对子视图做一些记录和安排
(为了简便,假设来自适配器的所有子视图都是一样大的),
确保所有已存在的视图在 scrap heap 之中。我将
大部分工作抽象到
fillGrid()
这个辅助方法中以便重用。
我们很快就会看到这个方法在更新可见视图和滚动屏幕中被大量调用。
就像是自定义实现一个 ViewGroup,你负责触发测量和布局每一个从 Recycler 获取到的子视图。API 没有直接完成这项工作 。
通常来说,在这类方法之中你需要完成的主要步骤如下:
在滚动事件结束后检查所有附加视图当前的偏移位置。
判断是否需要添加新视图填充由滚动屏幕产生的空白部分。并从
Recycler 中获取视图。
判断当前视图是否不再显示。移除它们并放置到 Recycler 中。
判断剩余视图是否需要整理。发生上述变化后可能
需要你修改视图的子索引来更好地和它们的适配器位置校准。
注意我们放进
FixedGridLayoutManager.fillGrid()
里填充 RecyclerView 的主要步骤。
当到达最大行数时,这个 manager 将位置从右到左排序,封装。
清点目前我们所有的视图。将他们 Detach 以便稍后重新连接。
SparseArray&View& viewCache = new SparseArray&View&(getChildCount());
if (getChildCount() != 0) {
//Cache all views by their existing position, before updating counts
for (int i=0; i & getChildCount(); i++) {
int position = positionOfIndex(i);
final View child = getChildAt(i);
viewCache.put(position, child);
//Temporarily detach all views.
// Views we still need will be added back at the proper index.
for (int i=0; i & viewCache.size(); i++) {
detachView(viewCache.valueAt(i));
测量/布局每一个当前可见的子视图。重新连接已有的视图很简单;新的视图是从 Recycler 之中获取的。
for (int i = 0; i & getVisibleChildCount(); i++) {
//Layout this position
View view = viewCache.get(nextPosition);
if (view == null) {
* The Recycler will give us either a newly constructed view,
* or a recycled view it has on-hand. In either case, the
* view will already be fully bound to the data by the
* adapter for us.
view = recycler.getViewForPosition(nextPosition);
addView(view);
* It is prudent to measure/layout each new view we
* receive from the Recycler. We don't have to do
* this for views we are just re-arranging.
measureChildWithMargins(view, 0, 0);
layoutDecorated(view, leftOffset, topOffset,
leftOffset + mDecoratedChildWidth,
topOffset + mDecoratedChildHeight);
//Re-attach the cached view at its new index
attachView(view);
viewCache.remove(nextPosition);
最终,所有在第一步中 detach 并且没有被重新连接的视图都不可见。将它们移入 Recycler 中,以备后用。
for (int i=0; i & viewCache.size(); i++) {
recycler.recycleView(viewCache.valueAt(i));
说明一下,先将所有视图 detach 之后再将需要的视图重新连接是为了
保持每一个视图子索引的顺序 (就是
getChildAt()
的索引)。我们希望
可见视图从左上到右下的索引从
getChildCount()-1
当我们上下滑动视图,新的子视图被添加,它的索引顺序会变得不可靠。
我们需要保留正确的索引来在任意点上定位每一个视图。在一个简单地
LayoutManager (比如 LinearLayoutManager)中,子视图可以轻松地插入
list 的两端, 记录层就没有存在的必要了。
添加用户交互
目前,我们已经有一个非常好的初始布局,但是它并不能动起来。
RecyclerView 的关键就在于当用户浏览一组数据时动态提供视图。
覆盖一些方法就能实现我们的目的。
canScrollHorizontally() & canScrollVertically()
这些方法很简单,在你想要滚动方向对应的方法里返回 true ,不想要滚动方向对应的方法里返回 false。
public boolean canScrollVertically() {
//We do allow scrolling
scrollHorizontallyBy() & scrollVerticallyBy()
在这里你应该实现 content 移动的逻辑。RecyclerView 已经处理了 scrolling
(注:Fling: Gross gesture, no on-screen target)
触摸操作,不需要处理 MotionEvents 或者 GestureDetectors 这些麻烦事。
你只需要完成下面这三个任务:
将所有的子视图移动适当的位置 (对的,你得自己做这个)。
决定移动视图后 添加/移除 视图。
返回滚动的实际距离。框架会根据它判断你是否触碰到边界。
在 FixedGridLayoutManager 里,这两个方法很像。这里是精简后的垂直滚动实现:
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (getChildCount() == 0) {
//Take top measurements from the top-left child
final View topView = getChildAt(0);
//Take bottom measurements from the bottom-right child.
final View bottomView = getChildAt(getChildCount()-1);
//Optimize the case where the entire data set is too small to scroll
int viewSpan = getDecoratedBottom(bottomView) - getDecoratedTop(topView);
if (viewSpan &= getVerticalSpace()) {
//We cannot scroll in either direction
int maxRowCount = getTotalRowCount();
boolean topBoundReached = getFirstVisibleRow() == 0;
boolean bottomBoundReached = getLastVisibleRow() &= maxRowC
if (dy & 0) { // Contents are scrolling up
//Check against bottom bound
if (bottomBoundReached) {
//If we've reached the last row, enforce limits
int bottomO
if (rowOfIndex(getChildCount() - 1) &= (maxRowCount - 1)) {
//We are truly at the bottom, determine how far
bottomOffset = getVerticalSpace() - getDecoratedBottom(bottomView)
+ getPaddingBottom();
* Extra space added to account for allowing bottom space in the grid.
* This occurs when the overlap in the last row is not large enough to
* ensure that at least one element in that row isn't fully recycled.
bottomOffset = getVerticalSpace() - (getDecoratedBottom(bottomView)
+ mDecoratedChildHeight) + getPaddingBottom();
delta = Math.max(-dy, bottomOffset);
//No limits while the last row isn't visible
} else { // Contents are scrolling down
//Check against top bound
if (topBoundReached) {
int topOffset = -getDecoratedTop(topView) + getPaddingTop();
delta = Math.min(-dy, topOffset);
offsetChildrenVertical(delta);
if (dy & 0) {
if (getDecoratedBottom(topView) & 0 && !bottomBoundReached) {
fillGrid(DIRECTION_DOWN, recycler);
} else if (!bottomBoundReached) {
fillGrid(DIRECTION_NONE, recycler);
if (getDecoratedTop(topView) & 0 && !topBoundReached) {
fillGrid(DIRECTION_UP, recycler);
} else if (!topBoundReached) {
fillGrid(DIRECTION_NONE, recycler);
* Return value determines if a boundary has been reached
* (for edge effects and flings). If returned value does not
* match original delta (passed in), RecyclerView will draw
* an edge effect.
我们获得了滚动距离(dx/dy)的增量来验证。方法的第一部分判断
按照所给的距离(标志给了滚动方向)滚动会不会超过边界。如果会,
我们需要计算出视图实际滚动的距离。
在这个方法里,我们需要自己手工移动这些视图。
offsetChildrenVertical()
offsetChildrenHorizontal()
这两个方法 可以帮助我们处理匀速移动。
如果你不实现它,你的视图就不会滚动。
移动视图操作完成后,我们触发另一个填充操作,
根据滚动的距离替换视图。
最后,将实际位移距离应用给子视图。RecyclerView 根据这个值判断是否
绘制到达边界的效果。一般意义上,如果返回值不等于传入的值就意味着
需要绘制边缘的发光效果了。
如果你返回了一个带有错误方向的值,框架的函数会把这个当做一个大的变化
你将不能获得正确的边缘发光特效。
除了用来判断绘制边界特效外,返回值还被用来决定什么时候取消 flings。
返回错误的值会让你失去对 content fling 的控制。框架会认为你已经提前
触碰到边缘并取消了 fling。
目前,我们已经实现了基本的功能。它少了很多的细节部分,
不过滚动和适当的视图回收已经完成了。
关于自定义 LayoutManager 还有很多要说的东西。
,我们会细致的介绍 decorations, data set changes
还有实现滚动到特定位置。
已发表评论数()
已收藏到推刊!
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见
正文不准确
标题不准确
排版有问题
没有分页内容
图片无法显示
视频无法显示
与原文不一致}

我要回帖

更多关于 recyclerview不同布局 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信