Android RecyclerView初体验

很早之前就听说过RecyclerView这个组件了,但一直很忙没时间学习。趁着刚开学还没出各种课程大作业,就花了一天时间来学习RecyclerView。

最终效果

这里上传GIF图片的时候竟然说图片太大上传不了,我也是醉了。。。在GitHub上的项目目录中有一个demonstration.gif图片,你可以下载项目后查看,项目链接在文章最后。

准备工作

在Android Studio里新建一个Android项目,添加以下工具:

    compile 'com.android.support:support-v4:25.2.0'
    compile 'com.android.support:appcompat-v7:25.2.0'
    compile 'com.android.support:recyclerview-v7:25.2.0'
    compile 'com.github.bumptech.glide:glide:3.7.0'

前两个工具就不说了,基本每个Android项目都会用到,第三个就是使用RecyclerView需要添加的工具了,而最后一个工具是Google推荐使用的一个开源图像加载工具,它使得加载图像变得更为流畅,并且使用起来容易上手。

在drawable目录下放入一些图像,并将这些图像名命名为”image[a-zA-Z0-9]*”的形式(因为在程序里会用反射来查找所有以”image”开头的图像资源)。

在drawable目录下放入”add.png”和”remove.png”等用于顶部工具栏的图像资源。

基本用法

使用RecyclerView,可以依照如下步骤:
1. 在资源文件中定义一个RecyclerView组件;
2. 在Activity中获取定义的RecyclerView组件,变量名就简单叫作view;
3. 为view设置LayoutManager(布局);
4. 为view设置Adapter(适配器);
5. 为view添加ItemDecoration(分隔符,可选);
6. 为view设置ItemAnimator(动画,可选)。

下面在资源文件中定义了一个RecyclerView组件:

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:id="@+id/vert_view" />

然后就可以在Activity中取得这个RecyclerView组件:

RecyclerView view=(RecyclerView) findViewById(R.id.vert_view);

至于其他的细节将在下文介绍。

Adapter

用于RecyclerView的适配器需要继承自RecyclerView.Adapter类,并重写其中的T onCreateViewHolder(final ViewGroup parent, int viewType)void onBindViewHolder(T holder, final int position)int getItemCount()方法。下面是一个自定义的Adapter:

public class ViewAdapter extends RecyclerView.Adapter<ViewAdapter.ViewHolder> {
    private Context context;
    private List<Animal> datas;
    private int layoutResId;
    private int imageViewId;
    private int textViewId;
    private boolean isCenterCrop;

    public ViewAdapter(Context c, List<Animal> list, int lri, int ivi, int tvi, boolean centerCrop) {
        context=c;
        datas=list;
        layoutResId=lri;
        imageViewId=ivi;
        textViewId=tvi;
        isCenterCrop=centerCrop;
    }

    @Override
    public ViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
        View view= LayoutInflater.from(context).inflate(layoutResId, parent, false);
        return new ViewHolder(view, imageViewId, textViewId);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {
        final Animal animal=datas.get(position);
        if(isCenterCrop)
            Glide.with(context).load(animal.getImgResId()).centerCrop().into(holder.imageView);
        else
            Glide.with(context).load(animal.getImgResId()).into(holder.imageView);
        holder.textView.setText(animal.getDescription());
        holder.view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent=new Intent(context, DetailActivity.class);
                intent.putExtra("IMG_RES_ID", animal.getImgResId());
                intent.putExtra("DESCRIPTION", animal.getDescription());
                context.startActivity(intent);
            }
        });
        holder.view.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                Toast.makeText(context, "第"+position+"个元素", Toast.LENGTH_SHORT).show();
                return true;
            }
        });
    }

    @Override
    public int getItemCount() {
        return datas.size();
    }

    public void addItem(int position) {
        datas.add(position, ResourceUtils.getRandomAnimal());
        notifyItemInserted(position);
    }

    public void removeItem(int position) {
        if(position>=0 && position<datas.size()) {
            datas.remove(position);
            notifyItemRemoved(position);
        }
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        View view;
        ImageView imageView;
        TextView textView;

        public ViewHolder(View itemView, int ivi, int tvi) {
            super(itemView);
            view=itemView;
            imageView=(ImageView) itemView.findViewById(ivi);
            textView=(TextView) itemView.findViewById(tvi);
        }
    }
}

注意:
- 其中的Animal类封装了一张(动物)图像的资源ID和一段描述(字符串),Animal对象将作为我们的RecyclerView中的元素;
- onCreateViewHolder方法负责创建一个ViewHolder,因此我们需要提供一个元素使用的资源文件ID,我们需要使用到的view(这里包括一个ImageView和一个TextView)都可以通过这个ViewHolder得到;
- onBindViewHolder方法负责对某些view进行设置,这里我们为ImageView设置了图像,为TextView设置了文本,并为它们的父view注册了点击和长按监听器;
- 这里的addItem和removeItem方法是自定义方法,用于向/从RecyclerView中添加/移除一个元素(将在后面介绍),这里可以先忽略;
- 这里的ViewHolder是ViewAdapter.ViewHolder,它继承自RecyclerView.ViewHolder,我们将需要使用到的view交给它管理。

定义好Adapter后,就可以为RecyclerView设置适配器了:

    List<Animal> datas= ResourceUtils.getAnimals();
    adapter=new ViewAdapter(this, datas, R.layout.item_vertical, R.id.vert_image, R.id.vert_text, true);
    view.setAdapter(adapter);

垂直布局

要想让RecyclerView使用和ListView一样的布局,只需要为RecyclerView设置一个线性布局管理器就行了:

    view.setLayoutManager(new LinearLayoutManager(this));

水平布局

使用水平布局只需设置LinearLayoutManager的方向即可:

    LinearLayoutManager layoutManager=new LinearLayoutManager(this);
    layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
    view.setLayoutManager(layoutManager);

网格布局

使用网格布局需要借助GridLayoutManager类:

    GridLayoutManager manager=new GridLayoutManager(this, 3);
    // manager.setOrientation(GridLayoutManager.HORIZONTAL);
    view.setLayoutManager(manager);

创建GridLayoutManager时,第二个参数使用列数/行数。GridLayoutManager默认是上下滑动的,如果想要左右滑动,可以取消第二行的注释。

瀑布流布局

使用瀑布流布局只需使用StaggeredGridLayoutManager即可:

    view.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));

StaggeredGridLayoutManager的第一个参数指定列数/行数,第二个参数指定滑动方向,这里设置成上下滑动。如果使用StaggeredGridLayoutManager时所有元素的大小一致,则效果和网格布局一样。

添加分割线

RecyclerView默认是不显示分割线的,这点和ListView不同,但这也为更灵活的定制自己的分割线提供了机会。

要想定制自己的分割线,需要定义类继承自RecyclerView.ItemDecoration,并实现如下方法:
- void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state):此方法负责绘制分割线;
- void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state):此方法和onDraw方法一样,也是绘制分割线,不过绘制的时机是在绘制元素View完成后,通常只需要重写onDraw和此方法中的一个即可;
- void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state):此方法计算分割线的尺寸,以方便计算元素View的偏移。

这里以绘制垂直布局的分割线为例,介绍如何定义自己的分割线,至于其他几种布局的分割线可以参考着来写,文章最后也会提供所有源码:

public class VerticalItemDecoration extends RecyclerView.ItemDecoration {
    private Drawable divider;
    private static final int[] ATTRS=new int[] {
            android.R.attr.listDivider,
    };

    public VerticalItemDecoration(Context c) {
        TypedArray array=c.obtainStyledAttributes(ATTRS);
        divider=array.getDrawable(0);
        array.recycle();
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        DecorationUtils.onVerticalDraw(c, parent, divider);
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        int position=parent.getChildLayoutPosition(view);
        int childCount=parent.getAdapter().getItemCount();
        if((position+1)<childCount)
            outRect.set(0, 0, 0, divider.getIntrinsicHeight());
    }
}
public class DecorationUtils {
    public static void onVerticalDraw(Canvas c, RecyclerView parent, Drawable divider) {
        int left=parent.getPaddingLeft();
        int right=parent.getWidth()-parent.getPaddingRight();

        for(int i=0; i<parent.getChildCount()-1; i++) {
            View child=parent.getChildAt(i);
            RecyclerView.LayoutParams params=(RecyclerView.LayoutParams) child.getLayoutParams();
            int top=child.getBottom()+params.bottomMargin;
            int bottom=top+divider.getIntrinsicHeight();
            divider.setBounds(left, top, right, bottom);
            divider.draw(c);
        }
    }
    // ...
}

这里的分隔符样式使用了Android提供的listDivider,你可以重定义listDivider属性来修改分割线样式:

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="android:listDivider">@drawable/divider</item>
    </style>

添加动画

你可能会好奇,RecyclerView为什么需要添加动画,不是就一个滑动吗?难道是添加滑动的动画?

考虑这种情况,当需要向/从RecyclerView中添加/移除元素时,如果一个元素突然插进来或者突然消失,用户会是什么感受(肯定觉得这APP是不是犯二了)。如果在添加/移除元素时,能有一个动画显示元素的出现/消失,那用户体验就更好了。所以,我们这里是为元素添加动画。

RecyclerView提供了一个默认动画,只需要简单的一行代码:

    view.setItemAnimator(new DefaultItemAnimator());

好了,现在我们已经为元素的出现/消失添加了动画了。那么问题又来了,我们怎么添加/移除元素呢?

还记得我们上面介绍适配器时提供的ViewAdapter吗?当时让你暂时忽略了两个方法——addItem和removeItem,这两个方法就是用于添加/移除元素的。

我们现在来看下细节:

public void addItem(int position) {
    datas.add(position, ResourceUtils.getRandomAnimal());
    notifyItemInserted(position);
}

public void removeItem(int position) {
    if(position>=0 && position<datas.size()) {
        datas.remove(position);
        notifyItemRemoved(position);
    }
}

注意到调用了notifyItemInserted和notifyItemRemoved方法,它们就是用于通知RecyclerView元素发生改变的。

现在已经有了添加/移除元素的接口,下面还需要在Activity中添加交互的动作。我们提供了一个Toolbar(用于代替ActionBar的一个组件),并提供了两个item。用户可以点击这两个item来添加/移除元素:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ...
        Toolbar toolbar=(Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        // ...
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.toolbar, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.add:
                adapter.addItem(0);
                view.smoothScrollToPosition(0);
                break;
            case R.id.remove:
                adapter.removeItem(0);
                break;
            default:break;
        }
        return true;
    }
}

好了,现在你就可以在添加/移除元素时看到动画效果了。

源代码

整个项目的源代码已经上传到GitHub:
https://github.com/jzyhywxz/RecyclerView

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值