RecyclerView分析

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的使用,和初始化,布局管理以及适配器的设置。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值