RecyclerView
1. 介绍
RecyclerView是一个滑行组件,一种灵活的视图,用于向大数据集提供有限的窗口。
1.1 优点
相对于list view更易于实现布局切换
1.2 基本使用
RecyclerView的使用要先在gradle里面导入RecyclerView类包
compile 'com.android.support:recyclerview-v7:26.1.0'
在activity布局中引用该控件:
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff7f7f7">
</android.support.v7.widget.RecyclerView>
再定义每个item的布局文件(比如添加一个图片,并在下方添加一段文字):
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="120dp"
android:layout_height="130dp">
<ImageView
android:id="@+id/ig_item_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="30dp"
android:duplicateParentState="true"
android:src="@mipmap/item" />
<TextView
android:id="@+id/tv_item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="86dp"
android:duplicateParentState="true"
android:gravity="center"
android:text="name"
android:textColor="#ff000000"
android:textSize="14sp" />
</FrameLayout>
创建适配器
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder>{
private final LayoutInflater mInflater;
private String[] mItemTitles = null;
private int[] image = null;
public RecyclerAdapter(Context context) {
this.mInflater = LayoutInflater.from(context);
mItemTitles = context.getResources().getStringArray(R.array.tv_name_array);
image = MainActivity.imageResource;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.item_layout, parent, false);
ViewHolder viewHolder = new ViewHolder(view);
view.setOnClickListener(this);
return viewHolder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.item_tv_title.setText(mItemTitles[position]);
holder.item_image_view.setImageResource(image[position]);
holder.itemView.setTag(position);
}
@Override
public int getItemCount() {
return mItemTitles.length;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView item_tv_title;
public ImageView item_image_view;
public TextView item_tv_content;
public ViewHolder(View view) {
super(view);
item_image_view = (ImageView) view.findViewById(R.id.ig_item_icon);
item_tv_title = (TextView) view.findViewById(R.id.tv_item_name);
}
}
}
在activity设置适配器,并设置相应的管理器(我使用的是grid,网格布局管理器,3是每行列数)
GridLayoutManager girdLayoutManager = new GridLayoutManager(this, 3);
recyclerView.setLayoutManager(girdLayoutManager);
recyclerView.setAdapter(mAdapter);
还有其他的管理器,如线性布局(new LinearLayoutManager(this))
传入context,瀑布流布局(new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.HORIZONTAL))
,传入列数和方向
1.3 其他使用
1.3.1 RecyclerView item点击监听
1)adapter中新建interface
public interface OnItemClickListener {
/**
* click event monitor
*
* @param position click position
*/
void onItemClick(int position);
}
2)给item设置tag
holder.itemView.setTag(position);
3)adapter实现implements View.OnClickListener ,重写onclick方法
@Override
public void onClick(View v) {
if (mItemClickListener != null) {
mItemClickListener.onItemClick((Integer) v.getTag());
}
}
并添加如下方法:
public void setItemClickListener(OnItemClickListener itemClickListener) {
mItemClickListener = itemClickListener;
}
4)在activity中实现OnItemClickListener ,并重写onItemClick
@Override
public void onItemClick(int position) {
String tag;
switch (position) {
}
}
1.3.2 RecyclerView设置分割线
1)定义分割线样式
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#f00" />
<size
android:width="2dp"
android:height="2dp" />
<corners
android:bottomLeftRadius="2dp"
android:bottomRightRadius="2dp"
android:topLeftRadius="2dp"
android:topRightRadius="2dp" />
</shape>
2)定义一个class 继承自RecyclerView.ItemDecoration
class GridDivider extends RecyclerView.ItemDecoration
实现父类的onDraw方法,在该方法中画出分割线
3)在activity中设置分割线
recyclerView.addItemDecoration(new GridDivider(this, R.drawable.grid_line));
2 源码分析
2.1 本质
class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2
RecyclerView 继承自ViewGroup,其本质是一个viewGroup,并不是一个单纯的view,它还实现了 ScrollingView和NestedScrollingChild2接口,为其提供了滚动机制。
2.2 初始化
三个构造函数,最终都会走到如下这个构造函数
public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
}
三个参数,第一个不多说,上下文;第二个标准解释:The attributes of the XML tag that is inflating the view.(当前正在绘制的视图的属性值,即style);第三个值,An attribute in the current theme that contains a reference to a style resource that supplies default values for the view. Can be 0 to not look for defaults.(当前主题中的属性,包含对为视图提供默认值的样式资源的引用。 默认值为0。)
首先,调用父类的构造函数
super(context, attrs, defStyle);
判断attrs属性值!=null,则获取android.R.attr.clipToPadding属性值,默认为true;等于null则直接设置为true。
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0);
mClipToPadding = a.getBoolean(0, true);
a.recycle();
} else {
mClipToPadding = true;
}
那么clipToPadding属性值的作用是啥呢?
官方解释:
the ViewGroup will clip its children and resize (but not clip) any EdgeEffect to its padding, if padding is not zero.
当布局设置padding时, 若clipToPadding为true,将不能绘制padding里面的子view;为false时,能够绘制padding里面的子view
举个例子:当我们给RecyclerView或者listview设置padding时,若不设置clipToPadding,则滑动RecyclerView时,会出现padding区域滑动不到;看起来缺了一片区域,十分古怪;设置为false,看起来就会很自然。
设置完clipToPadding后,继续设置其他属性值,如是否可滚动,是否可以与获得焦点等。
然后,设置动画监听,初始化适配器 和ChildrenHelper(管理子view)
mItemAnimator.setListener(mItemAnimatorListener);
initAdapterManager();
initChildrenHelper();
void initAdapterManager() {
mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() {
//省略代码
});
}
private void initChildrenHelper() {
mChildHelper = new ChildHelper(new ChildHelper.Callback() {
//省略代码...
});
}
后面继续初始化一些属性值,然后,创建布局管理器,通过ClassLoader 和layoutManagerName获取manager,然后设置manager,后续讲解setLayoutManager
createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes);
代码
private void createLayoutManager(Context context, String className, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
if (className != null) {
className = className.trim();
if (!className.isEmpty()) {
className = getFullClassName(context, className);
try {
ClassLoader classLoader;
if (isInEditMode()) {
// Stupid layoutlib cannot handle simple class loaders.
classLoader = this.getClass().getClassLoader();
} else {
classLoader = context.getClassLoader();
}
Class<? extends LayoutManager> layoutManagerClass =
classLoader.loadClass(className).asSubclass(LayoutManager.class);
Constructor<? extends LayoutManager> constructor;
Object[] constructorArgs = null;
try {
constructor = layoutManagerClass
.getConstructor(LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE);
constructorArgs = new Object[]{context, attrs, defStyleAttr, defStyleRes};
} catch (NoSuchMethodException e) {
try {
constructor = layoutManagerClass.getConstructor();
} catch (NoSuchMethodException e1) {
e1.initCause(e);
throw new IllegalStateException(attrs.getPositionDescription()
+ ": Error creating LayoutManager " + className, e1);
}
}
constructor.setAccessible(true);
setLayoutManager(constructor.newInstance(constructorArgs));
}
...
}
最后,setNestedScrollingEnabled结束构造函数。
// Re-set whether nested scrolling is enabled so that it is set on all API levels
//重新设置是否启用嵌套滚动,以便在所有API级别上设置它
setNestedScrollingEnabled(nestedScrollingEnabled);
2.3 布局管理
通过setLayoutManager函数来管理布局该函数代码如下
public void setLayoutManager(LayoutManager layout) {
if (layout == mLayout) {
return;
}
stopScroll();
// TODO We should do this switch a dispatchLayout pass and animate children. There is a good
// chance that LayoutManagers will re-use views.
if (mLayout != null) {
// end all running animations
if (mItemAnimator != null) {
mItemAnimator.endAnimations();
}
mLayout.removeAndRecycleAllViews(mRecycler);
mLayout.removeAndRecycleScrapInt(mRecycler);
mRecycler.clear();
if (mIsAttached) {
mLayout.dispatchDetachedFromWindow(this, mRecycler);
}
mLayout.setRecyclerView(null);
mLayout = null;
} else {
mRecycler.clear();
}
// this is just a defensive measure for faulty item animators.
mChildHelper.removeAllViewsUnfiltered();
mLayout = layout;
if (layout != null) {
if (layout.mRecyclerView != null) {
throw new IllegalArgumentException("LayoutManager " + layout
+ " is already attached to a RecyclerView:"
+ layout.mRecyclerView.exceptionLabel());
}
mLayout.setRecyclerView(this);
if (mIsAttached) {
mLayout.dispatchAttachedToWindow(this);
}
}
mRecycler.updateViewCacheSize();
requestLayout();
}
可看到,会先判断传入的layout与先前的是否一致,一致则不进行任何操作;不一致先停止滑动,再进行下面的操作。
首先,会停止view的滑动,再进行一系列的操作将layout赋值给mLayout,使用updateViewCacheSize更新cache大小,最后,使用requestLayout()更新布局。
2.4 适配器设置
通过setadapeter方法设置
public void setAdapter(Adapter adapter) {
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, false, true);
requestLayout();
}
setLayoutFrozen(false);此方法作用为:Enable or disable layout and scroll.(启用或禁用布局和滚动。)参数介绍:(true to freeze layout and scroll, false to re-enable.)true表示冻结布局并滚动,false表示重新启用。
设置布局适配器,先启用滚动,在调用setAdapterInternal设置适配器,最后,请求更新布局
我们重点讲解setAdapterInternal方法。
private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);
mAdapter.onDetachedFromRecyclerView(this);
}
if (!compatibleWithPrevious || removeAndRecycleViews) {
removeAndRecycleViews();
}
mAdapterHelper.reset();
final Adapter oldAdapter = mAdapter;
mAdapter = adapter;
if (adapter != null) {
adapter.registerAdapterDataObserver(mObserver);
adapter.onAttachedToRecyclerView(this);
}
if (mLayout != null) {
mLayout.onAdapterChanged(oldAdapter, mAdapter);
}
mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
mState.mStructureChanged = true;
setDataSetChangedAfterLayout();
}
首先之前设置过adapter,则先清除之前adapter的数据监听;接下来,删除并回收所有视图 - 包括当前连接的视图和回收器中的视图;然后,重设AdapterHelper,对mAdapter 赋值和重新监听;最后,调用setDataSetChangedAfterLayout,通知所有适配器内容已更改。
总结
RecyclerView就讲到这里,本篇讲了RecyclerView的使用,和初始化,布局管理以及适配器的设置。