2024年Android RecyclerView 使用完全解析 体验艺术般的控件,2024年最新附学习笔记+面试整理+进阶书籍

最后

我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了7、8年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

不断奔跑,你就知道学习的意义所在!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

import android.os.Bundle;

import android.support.v7.app.ActionBarActivity;

import android.support.v7.widget.LinearLayoutManager;

import android.support.v7.widget.RecyclerView;

import android.support.v7.widget.RecyclerView.ViewHolder;

import android.view.LayoutInflater;

import android.view.View;

import android.view.ViewGroup;

import android.widget.TextView;

public class HomeActivity extends ActionBarActivity

{

private RecyclerView mRecyclerView;

private List mDatas;

private HomeAdapter mAdapter;

@Override

protected void onCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_single_recyclerview);

initData();

mRecyclerView = (RecyclerView) findViewById(R.id.id_recyclerview);

mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

mRecyclerView.setAdapter(mAdapter = new HomeAdapter());

}

protected void initData()

{

mDatas = new ArrayList();

for (int i = ‘A’; i < ‘z’; i++)

{

mDatas.add(“” + (char) i);

}

}

class HomeAdapter extends RecyclerView.Adapter<HomeAdapter.MyViewHolder>

{

@Override

public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType)

{

MyViewHolder holder = new MyViewHolder(LayoutInflater.from(

HomeActivity.this).inflate(R.layout.item_home, parent,

false));

return holder;

}

@Override

public void onBindViewHolder(MyViewHolder holder, int position)

{

holder.tv.setText(mDatas.get(position));

}

@Override

public int getItemCount()

{

return mDatas.size();

}

class MyViewHolder extends ViewHolder

{

TextView tv;

public MyViewHolder(View view)

{

super(view);

tv = (TextView) view.findViewById(R.id.id_num);

}

}

}

}

  • Activity的布局文件

<RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android”

xmlns:tools=“http://schemas.android.com/tools”

android:layout_width=“match_parent”

android:layout_height=“match_parent” >

<android.support.v7.widget.RecyclerView

android:id=“@+id/id_recyclerview”

android:divider=“#ffff0000”

android:dividerHeight=“10dp”

android:layout_width=“match_parent”

android:layout_height=“match_parent” />

  • Item的布局文件
<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android=“http://schemas.android.com/apk/res/android”

android:layout_width=“match_parent”

android:background=“#44ff0000”

android:layout_height=“wrap_content” >

<TextView

android:id=“@+id/id_num”

android:layout_width=“match_parent”

android:layout_height=“50dp”

android:gravity=“center”

android:text=“1” />

这么看起来用法与ListView的代码基本一致哈~~

看下效果图:

看起来好丑,Item间应该有个分割线,当你去找时,你会发现RecyclerView并没有支持divider这样的属性。那么怎么办,你可以给Item的布局去设置margin,当然了这种方式不够优雅,我们文章开始说了,我们可以自由的去定制它,当然我们的分割线也是可以定制的。

ItemDecoration

我们可以通过该方法添加分割线:

mRecyclerView.addItemDecoration()

该方法的参数为RecyclerView.ItemDecoration,该类为抽象类,官方目前并没有提供默认的实现类(我觉得最好能提供几个)。

该类的源码:

public static abstract class ItemDecoration {

public void onDraw(Canvas c, RecyclerView parent, State state) {

onDraw(c, parent);

}

public void onDrawOver(Canvas c, RecyclerView parent, State state) {

onDrawOver(c, parent);

}

public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {

getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),

parent);

}

@Deprecated

public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {

outRect.set(0, 0, 0, 0);

}

当我们调用mRecyclerView.addItemDecoration()方法添加decoration的时候,RecyclerView在绘制的时候,去会绘制decorator,即调用该类的onDraw和onDrawOver方法,

  • onDraw方法先于drawChildren

  • onDrawOver在drawChildren之后,一般我们选择复写其中一个即可。

  • getItemOffsets 可以通过outRect.set()为每个Item设置一定的偏移量,主要用于绘制Decorator。

接下来我们看一个RecyclerView.ItemDecoration的实现类,该类很好的实现了RecyclerView添加分割线(当使用LayoutManager为LinearLayoutManager时)。

该类参考自:DividerItemDecoration

package com.zhy.sample.demo_recyclerview;

/*

  • Copyright © 2014 The Android Open Source Project

  • Licensed under the Apache License, Version 2.0 (the “License”);

  • limitations under the License.

*/

import android.content.Context;

import android.content.res.TypedArray;

import android.graphics.Canvas;

import android.graphics.Rect;

import android.graphics.drawable.Drawable;

import android.support.v7.widget.LinearLayoutManager;

import android.support.v7.widget.RecyclerView;

import android.support.v7.widget.RecyclerView.State;

import android.util.Log;

import android.view.View;

/**

  • This class is from the v7 samples of the Android SDK. It’s not by me!

  • See the license above for details.

*/

public class DividerItemDecoration extends RecyclerView.ItemDecoration {

private static final int[] ATTRS = new int[]{

android.R.attr.listDivider

};

public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;

public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;

private Drawable mDivider;

private int mOrientation;

public DividerItemDecoration(Context context, int orientation) {

final TypedArray a = context.obtainStyledAttributes(ATTRS);

mDivider = a.getDrawable(0);

a.recycle();

setOrientation(orientation);

}

public void setOrientation(int orientation) {

if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {

throw new IllegalArgumentException(“invalid orientation”);

}

mOrientation = orientation;

}

@Override

public void onDraw(Canvas c, RecyclerView parent) {

Log.v(“recyclerview - itemdecoration”, “onDraw()”);

if (mOrientation == VERTICAL_LIST) {

drawVertical(c, parent);

} else {

drawHorizontal(c, parent);

}

}

public void drawVertical(Canvas c, RecyclerView parent) {

final int left = parent.getPaddingLeft();

final int right = parent.getWidth() - parent.getPaddingRight();

final int childCount = parent.getChildCount();

for (int i = 0; i < childCount; i++) {

final View child = parent.getChildAt(i);

android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());

final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child

.getLayoutParams();

final int top = child.getBottom() + params.bottomMargin;

final int bottom = top + mDivider.getIntrinsicHeight();

mDivider.setBounds(left, top, right, bottom);

mDivider.draw©;

}

}

public void drawHorizontal(Canvas c, RecyclerView parent) {

final int top = parent.getPaddingTop();

final int bottom = parent.getHeight() - parent.getPaddingBottom();

final int childCount = parent.getChildCount();

for (int i = 0; i < childCount; i++) {

final View child = parent.getChildAt(i);

final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child

.getLayoutParams();

final int left = child.getRight() + params.rightMargin;

final int right = left + mDivider.getIntrinsicHeight();

mDivider.setBounds(left, top, right, bottom);

mDivider.draw©;

}

}

@Override

public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {

if (mOrientation == VERTICAL_LIST) {

outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());

} else {

outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);

}

}

}

该实现类可以看到通过读取系统主题中的 android.R.attr.listDivider作为Item间的分割线,并且支持横向和纵向。如果你不清楚它是怎么做到的读取系统的属性用于自身,请参考我的另一篇博文:Android 深入理解Android中的自定义属性

获取到listDivider以后,该属性的值是个Drawable,在getItemOffsets中,outRect去设置了绘制的范围。onDraw中实现了真正的绘制。

我们在原来的代码中添加一句:

mRecyclerView.addItemDecoration(new DividerItemDecoration(this,

DividerItemDecoration.VERTICAL_LIST));

ok,现在再运行,就可以看到分割线的效果了。

该分割线是系统默认的,你可以在theme.xml中找到该属性的使用情况。那么,使用系统的listDivider有什么好处呢?就是方便我们去随意的改变,该属性我们可以直接声明在:

然后自己写个drawable即可,下面我们换一种分隔符:

<?xml version="1.0" encoding="utf-8"?>

<shape xmlns:android=“http://schemas.android.com/apk/res/android”

android:shape=“rectangle” >

<gradient

android:centerColor=“#ff00ff00”

android:endColor=“#ff0000ff”

android:startColor=“#ffff0000”

android:type=“linear” />

现在的样子是:

当然了,你可以根据自己的需求,去随意的绘制,反正是画出来的,随便玩~~

ok,看到这,你可能觉得,这玩意真尼玛麻烦,完全不能比拟的心爱的ListView。那么继续看。

LayoutManager

好了,上面实现了类似ListView样子的Demo,通过使用其默认的LinearLayoutManager。

RecyclerView.LayoutManager吧,这是一个抽象类,好在系统提供了3个实现类:

  1. LinearLayoutManager 现行管理器,支持横向、纵向。

  2. GridLayoutManager 网格布局管理器

  3. StaggeredGridLayoutManager 瀑布就式布局管理器

上面我们已经初步体验了下LinearLayoutManager,接下来看GridLayoutManager。

  • GridLayoutManager

我们尝试去实现类似GridView,秒秒钟的事情:

//mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

mRecyclerView.setLayoutManager(new GridLayoutManager(this,4));

只需要修改LayoutManager即可,还是很nice的。

当然了,改为GridLayoutManager以后,对于分割线,前面的DividerItemDecoration就不适用了,主要是因为它在绘制的时候,比如水平线,针对每个child的取值为:

final int left = parent.getPaddingLeft();

final int right = parent.getWidth() - parent.getPaddingRight();

因为每个Item一行,这样是没问题的。而GridLayoutManager时,一行有多个childItem,这样就多次绘制了,并且GridLayoutManager时,Item如果为最后一列(则右边无间隔线)或者为最后一行(底部无分割线)。

针对上述,我们编写了DividerGridItemDecoration

package com.zhy.sample.demo_recyclerview;

import android.content.Context;

import android.content.res.TypedArray;

import android.graphics.Canvas;

import android.graphics.Rect;

import android.graphics.drawable.Drawable;

import android.support.v7.widget.GridLayoutManager;

import android.support.v7.widget.RecyclerView;

import android.support.v7.widget.RecyclerView.LayoutManager;

import android.support.v7.widget.RecyclerView.State;

import android.support.v7.widget.StaggeredGridLayoutManager;

import android.view.View;

/**

  • @author zhy

*/

public class DividerGridItemDecoration extends RecyclerView.ItemDecoration

{

private static final int[] ATTRS = new int[] { android.R.attr.listDivider };

private Drawable mDivider;

public DividerGridItemDecoration(Context context)

{

final TypedArray a = context.obtainStyledAttributes(ATTRS);

mDivider = a.getDrawable(0);

a.recycle();

}

@Override

public void onDraw(Canvas c, RecyclerView parent, State state)

{

drawHorizontal(c, parent);

drawVertical(c, parent);

}

private int getSpanCount(RecyclerView parent)

{

// 列数

int spanCount = -1;

LayoutManager layoutManager = parent.getLayoutManager();

if (layoutManager instanceof GridLayoutManager)

{

spanCount = ((GridLayoutManager) layoutManager).getSpanCount();

} else if (layoutManager instanceof StaggeredGridLayoutManager)

{

spanCount = ((StaggeredGridLayoutManager) layoutManager)

.getSpanCount();

}

return spanCount;

}

public void drawHorizontal(Canvas c, RecyclerView parent)

{

int childCount = parent.getChildCount();

for (int i = 0; i < childCount; i++)

{

final View child = parent.getChildAt(i);

final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child

.getLayoutParams();

final int left = child.getLeft() - params.leftMargin;

final int right = child.getRight() + params.rightMargin

  • mDivider.getIntrinsicWidth();

final int top = child.getBottom() + params.bottomMargin;

final int bottom = top + mDivider.getIntrinsicHeight();

mDivider.setBounds(left, top, right, bottom);

mDivider.draw©;

}

}

public void drawVertical(Canvas c, RecyclerView parent)

{

final int childCount = parent.getChildCount();

for (int i = 0; i < childCount; i++)

{

final View child = parent.getChildAt(i);

final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child

.getLayoutParams();

final int top = child.getTop() - params.topMargin;

final int bottom = child.getBottom() + params.bottomMargin;

final int left = child.getRight() + params.rightMargin;

final int right = left + mDivider.getIntrinsicWidth();

mDivider.setBounds(left, top, right, bottom);

mDivider.draw©;

}

}

private boolean isLastColum(RecyclerView parent, int pos, int spanCount,

int childCount)

{

LayoutManager layoutManager = parent.getLayoutManager();

if (layoutManager instanceof GridLayoutManager)

{

if ((pos + 1) % spanCount == 0)// 如果是最后一列,则不需要绘制右边

{

return true;

}

} else if (layoutManager instanceof StaggeredGridLayoutManager)

{

int orientation = ((StaggeredGridLayoutManager) layoutManager)

.getOrientation();

if (orientation == StaggeredGridLayoutManager.VERTICAL)

{

if ((pos + 1) % spanCount == 0)// 如果是最后一列,则不需要绘制右边

{

return true;

}

} else

{

childCount = childCount - childCount % spanCount;

if (pos >= childCount)// 如果是最后一列,则不需要绘制右边

return true;

}

}

return false;

}

private boolean isLastRaw(RecyclerView parent, int pos, int spanCount,

int childCount)

{

LayoutManager layoutManager = parent.getLayoutManager();

if (layoutManager instanceof GridLayoutManager)

{

childCount = childCount - childCount % spanCount;

if (pos >= childCount)// 如果是最后一行,则不需要绘制底部

return true;

} else if (layoutManager instanceof StaggeredGridLayoutManager)

{

int orientation = ((StaggeredGridLayoutManager) layoutManager)

.getOrientation();

// StaggeredGridLayoutManager 且纵向滚动

if (orientation == StaggeredGridLayoutManager.VERTICAL)

{

childCount = childCount - childCount % spanCount;

// 如果是最后一行,则不需要绘制底部

if (pos >= childCount)

return true;

推荐学习资料


  • 脑图
    360°全方位性能调优

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

childCount)

{

LayoutManager layoutManager = parent.getLayoutManager();

if (layoutManager instanceof GridLayoutManager)

{

childCount = childCount - childCount % spanCount;

if (pos >= childCount)// 如果是最后一行,则不需要绘制底部

return true;

} else if (layoutManager instanceof StaggeredGridLayoutManager)

{

int orientation = ((StaggeredGridLayoutManager) layoutManager)

.getOrientation();

// StaggeredGridLayoutManager 且纵向滚动

if (orientation == StaggeredGridLayoutManager.VERTICAL)

{

childCount = childCount - childCount % spanCount;

// 如果是最后一行,则不需要绘制底部

if (pos >= childCount)

return true;

推荐学习资料


  • 脑图
    [外链图片转存中…(img-hT6OfxGu-1714813136623)]
    [外链图片转存中…(img-N6umfZqk-1714813136624)]
    [外链图片转存中…(img-04GG2Ccc-1714813136624)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值