当前位置:首页 > 嵌入式培训 > 嵌入式学习 > 讲师博文 > RecyclerView 学习笔记(一)---实现ListView

RecyclerView 学习笔记(一)---实现ListView 时间:2018-09-26      来源:未知

RecyclerView和ListView一样,都是做列表显示View子项的控件,它比ListView更高效和自由。

解析RecycleView,Recycle View,意思就是该控件只管回收和显示View子项,而对于如何显示,显示什么,它是不关心的,这给开发过程带来了极大的便利,比如ListView只能作为单列的列表显示,GridView将一个界面表格化,通常情况下GridView通过强制View子项的宽度来显示,在横竖屏切换时的效果很差。

而RecycleView可以实现:

ListView的功能

GridView的功能

横向ListView的功能

横向ScrollView的功能

瀑布流的功能

添加和删除View子项

这些功能,非常强大,可以看出,它几乎可以替代所有的动态布局控件。

这么强大的动态布局控件,得益于它的高度解耦,同样,众所周知,高度解耦,就意味着复杂度提升,相较于ListView、GridView等控件,RecycleView才实现过程是相对较复杂的。

RecyclerView的适配器需要继承自RecyclerView.Adapter,在该适配器将要面向ViewHolder,也就是说,它内部已经实现了缓存复用。

实现LIstView功能

注意,RecyclerView是谷歌在support-v7包中添加的新组件,在开发的时候,请添加support.v7包以及recyclerview-v7包,在module的Project Structure中的Dependencies部分添加如下图所示:

首先创建RecyclerViewDemo1,活动:RecyclerViewDemo.java,layout:activiy_main.xml。

简单通过RecyclerView实现ListView的功能,那么首先就要理解ListView的实现过程。ListView是通过列表的的形式依次显示一行行的数据,从编程的角度出发,就是一个LinnerLayout布局,方向为纵向,依次添加进数据,后通过Scrolling实现滚动屏幕。

再来对比RecyclerView,屏幕滚动部分,在RecyclerView中已经被实现好了,不需要关心,需要关心的就是布局,以及方向。

Java Code

recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

//设置RecyclerView的布局管理器,为了模仿ListView,

//这里设置为Vertical的LinnerLayout

LinearLayoutManager manager = new LinearLayoutManager(this);

manager.setOrientation(OrientationHelper.VERTICAL);

recyclerView.setLayoutManager(manager);

上述代码第5行,定义了一个LinearLayoutManager,传入上下文为活动的this,第6行将布局管理器的方向设置为VERTICAL,第7行,将这个布局管理器传递给RecyclerView。

对比ListView,这个时候我们只是拥有了一个ListView实例,数据源,适配器,以及View子项都还没有说明。

接下来,创建一个View子项,这个View子项可以显示一张图片,以及一段文字,可以使用一个TextView,当然为了自由度,将声明两个控件,一个TextView、一个ImageView,分别用来显示文字和图片。

子View还是通过xml文件来定义。

XML Code

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

<LinearLayout xmlns:android="//schemas.android.com/apk/res/android"

android:orientation="horizontal" android:layout_width="match_parent"

android:layout_height="wrap_content"

android:background="@drawable/bg">

<ImageView

android:id="@+id/img_item"

android:layout_width="50dp"

android:layout_height="50dp" />

<TextView

android:id="@+id/txv_item"

android:gravity="center_vertical|center_horizontal"

android:textSize="21sp"

android:layout_width="match_parent"

android:layout_height="wrap_content" />

</LinearLayout>

上述代码第7行和第11行,分别定义了一个ImageView和一个TextView,第4行,将打下限定在了wrap_content,View子项的大小可控。

四大组成部分中,已经实现了两部分,接下来是数据源,因为是一个Demo,这里数据源我们使用一个包含一个String和一个图片Id的HashMap来实现。

Java Code

public HashMap getImgHash()

{

HashMapimgHash = new HashMap<>();

imgHash.put("0000", R.drawable.zero);

imgHash.put("1111", R.drawable.one);

imgHash.put("2222", R.drawable.two);

imgHash.put("3333", R.drawable.three);

imgHash.put("4444", R.drawable.four);

imgHash.put("5555", R.drawable.five);

imgHash.put("6666", R.drawable.six);

imgHash.put("7777", R.drawable.seven);

imgHash.put("8888", R.drawable.eight);

imgHash.put("9999", R.drawable.nine);

imgHash.put("aaaa", R.drawable.a);

imgHash.put("bbbb", R.drawable.b);

imgHash.put("cccc", R.drawable.c);

imgHash.put("dddd", R.drawable.d);

imgHash.put("eeee", R.drawable.e);

imgHash.put("ffff", R.drawable.f);

return imgHash;

}

RecyclerView实例有了,数据源有了,View子项有了,只剩下适配器将这些东西串联起来了。

RecyclerView的Adapter,通常使用的是自定义的,这是由RecyclerView高度解耦,高度自由决定的,高度解耦意味着编码复杂度上升。

在创建Adapter的时候,继承自RecyclerView.Adapter,不过在这之前,请先建立一个自定义的ViewHolder类,这是因为,在RecyclerView的编码过程中,缓存部分的工作已经交由RecyclerView做好了,那么缓存什么,View子项是什么,需要程序员告知它。

Java Code

//自定义的ViewHolder

class MyViewHolder extends RecyclerView.ViewHolder{

public ImageView img;

public TextView textView;

public MyViewHolder(View itemView) {

super(itemView);

img = (ImageView) itemView.findViewById(R.id.img_item);

textView = (TextView) itemView.findViewById(R.id.txv_item);

}

}

编写自定义适配器。

Java Code

public class MyAdapter extends RecyclerView.Adapter{

private Context context;

private List nameList;

private List imgList;

private LayoutInflater inflater;

private OnItemClickListener onItemClickListener;

//自定义构造器,传入上下文和数据源

public MyAdapter(Context context, HashMap imgHash) {

this.context = context;

imgList = new ArrayList<>();

nameList = new ArrayList<>();

for (Object o : imgHash.entrySet()){

Map.Entry entry = (Map.Entry) o;

String name = (String) entry.getKey();

int img = (int) entry.getValue();

nameList.add(name);

imgList.add(nameList.indexOf(name), img);

}

inflater = LayoutInflater.from(context);

}

//面向ViewHolder编程

@Override

public MyViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {

//View子项

View view = inflater.inflate(R.layout.item_recycler, viewGroup, false);

return new MyViewHolder(view);

  }

//面向ViewHolder编程

@Override

public void onBindViewHolder(MyViewHolder myViewHolder,

@SuppressLint("RecyclerView") final int i) {

//对View子项赋值

myViewHolder.img.setImageResource(imgList.get(i));

myViewHolder.textView.setText(nameList.get(i));

}

@Override

public int getItemCount() {

return nameList.size();

}

//自定义的ViewHolder

class MyViewHolder extends RecyclerView.ViewHolder{

public ImageView img;

public TextView textView;

//和View子项真正建立连接

public MyViewHolder(View itemView) {

super(itemView);

img = (ImageView) itemView.findViewById(R.id.img_item);

textView = (TextView) itemView.findViewById(R.id.txv_item);

}

}

}

四大组成部分已经编写完成,接下来,

myAdapter = new MyAdapter(this, imgHash);

recyclerView.setAdapter(myAdapter);

完成适配器配置。

来看一下现象如下图所示:

和ListView的效果很像,已经将数据完整的显示在活动中,但是仔细看,会发现,View子项和View子项之间没有明显的分隔。并没有ListView中的分隔的效果。

对于RecyclerView来说,添加分隔线也是耦合事件,所以,当想要分隔线的时候,需要自己添加。需要添加recyclerView.addItemDecoration()方法添加分隔线。该方法的参数需要一个RecycleView.ItemDecoration实例,该类是一个abstract的虚类,要实现该类需要做自定义操作,谷歌并没有提供默认的实现。

创建MyItemDecoration继承自RecyclerView.ItemDecoration,观察该类,需要重写其onDraw方法,getItemOffsets方法。

Java Code

package com.hqyj.dev.recyclerviewdemo1;

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.view.View;

/**

* Created by jiyangkang on 2016/7/15 0015.

*/

public class MyItemDecoration extends RecyclerView.ItemDecoration {

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

android.R.attr.listDivider

};

private static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;

private static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;

private Drawable mDivider;

private int mOrientation;

//构造器,从参数离别中拿出要绘制的Drawable

public MyItemDecoration(Context context, int orientation) {

//参数列表

final TypedArray a = context.obtainStyledAttributes(ATTRS);

//参数列表0项

mDivider = a.getDrawable(0);

//使用完参数列表之后一定要做recycle()

a.recycle();

try {

setmOrientation(orientation);

} catch (IllegalAccessException e) {

e.printStackTrace();

}

}

//选择绘制什么样的分隔线

@Override

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

super.onDraw(c, parent, state);

if (mOrientation == VERTICAL_LIST) {

drawVertiacl(c, parent, state);

} else {

drawHorizontal(c, parent, state);

}

}

public void setmOrientation(int orientation) throws IllegalAccessException {

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

throw new IllegalAccessException("invalid orientation");

}

mOrientation = orientation;

}

//纵向排列的View子项

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

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);

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(c);

}

}

//横向排列的View子项

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

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(c);

}

}

@Override

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

super.getItemOffsets(outRect, view, parent, state);

if (mOrientation == VERTICAL_LIST) {

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

} else {

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

}

}

}

自定义ItemDecoration写好厚,就可以将其添加到RecyclerView中了。

添加recyclerView.addItemDecoration(new MyItemDecoration(this, LinearLayoutManager.VERTICAL));带活动的onCreate方法中。

再看展示的效果图,如下图:

可以看到,已经有分隔线了,这个分隔线是我们在ATTRS中放入的第0个Drawable的id,我们知道它是来自android.R的,即系统集成的,使用该分隔线的一个好处就是,我们可以对其进行自定义。

在res下的drawable目录下创建一个shap,来设置一个渐变drawable.

XML Code

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

<shape xmlns:android="//schemas.android.com/apk/res/android"

android:shape="rectangle" >

<gradient

android:centerColor="#ff00ff00"

android:startColor="#ff0000ff"

android:endColor="#ffff0000"/>

<size android:height="2dp"/>

</shape>

然后在styles.xml中为工程的AppTheme中添加一个android:listDivider子项,为listDivider赋值为定义好的drawable。

效果图如下:

这样的效果已经和ListView很像了,但是,在点击对应的View子项的时候,会发现,是没有效果的,这是因为RecyclerView中,并没有和ListView一样的OnItemClickLisener接口。

还是因为RecyclerView高度解耦的缘故,而自定义监听将更加自由。在Adapter中添加一个接口,用以监听按钮事件。

Java Code

public interface OnItemClickListener

{

void onClick(int position, String name, int imgSource);

void onLongClick(int position, String name, int imgSource);

}

public void setOnItemClickListener(OnItemClickListener onItemClickListener)

{

this.onItemClickListener = onItemClickListener;

}

@Override

public void onBindViewHolder(MyViewHolder myViewHolder, @SuppressLint("RecyclerView") final int i)

{

myViewHolder.img.setImageResource(imgList.get(i));

myViewHolder.textView.setText(nameList.get(i));

if (onItemClickListener != null)

{

myViewHolder.itemView.setOnClickListener(new View.OnClickListener()

{

@Override

public void onClick(View view)

{

onItemClickListener.onClick(i, nameList.get(i), imgList.get(i));

}

});

myViewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener()

{

@Override

public boolean onLongClick(View view)

{

//onItemClickListener.onLongClick(i, nameList.get(i), imgList.get(i));

//removeData(i);

return false;

}

});

}

}

在调用Adapter的部分添加

Java Code

myAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener()

{

@SuppressLint("DefaultLocale")

@Override

public void onClick(int position, String name, int imgSource)

{

textView.setText(String.format("%d is clicked, %s is contained", position, name));

}

@SuppressLint("DefaultLocale")

@Override

public void onLongClick(int position, String name, int imgSource)

{

textView.setText(String.format("%d is Long clicked, %s is contained", position, name));

}

});

到此,点击事件也已经注册到活动中,点击每个View子项都已经有相应。

但是,还有一个问题,点击事件发生的时候,View子项没有任何反应,位View子项添加一个background,在Drawable里边顶一个selector。就可以实现点击事件发生时改变背景颜色的功能。

XML Code

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

<selector xmlns:android="//schemas.android.com/apk/res/android" >

<item android:state_pressed="true"

android:drawable="@color/colorBg_press"/>

<item android:drawable="@color/colorBg_default"/>

</selector>

至此,一个仿照的ListView就被实现出来了,而且更具灵活性,也更生动。

在灵活性上,一个很重要的体现就是,可以任意的增加和删除View子项。在上述的代码中其实已经可以看见一部分了,在长按的监听事件中,长按发生时,会将对应的View子项从列表中删除。

Java Code

public void removeData(int position){

nameList.remove(position);

imgList.remove(position);

notifyItemRemoved(position);

notifyDataSetChanged();//必须添加

}

notifyItemRemoved方法即为删除方法。同样的,还有一个为notifyItemInser(int position)的方法,用以插入一个View子项。View子项也是可以移动的,方法为notifyItemMove(int fromPosition, int toPosition)。

上一篇:谈谈socket 套接字

下一篇:异步通信和同步通信简单对比

热点文章推荐
华清学员就业榜单
高薪学员经验分享
热点新闻推荐
前台专线:010-82525158 企业培训洽谈专线:010-82525379 院校合作洽谈专线:010-82525379 Copyright © 2004-2022 北京华清远见科技集团有限公司 版权所有 ,京ICP备16055225号-5京公海网安备11010802025203号

回到顶部