Android好奇宝宝_10_RecyclerView+CardView+Palette

这几天被ListView搞得有点烦,听说官方出了个新控件来替换它,于是顺带另外两个5.0版本的新东西写了个Demo。

先上效果图:

Android好奇宝宝_10_RecyclerView+CardView+Palette_第1张图片Android好奇宝宝_10_RecyclerView+CardView+Palette_第2张图片


(1)RecyclerView


RecyclerView就是官方用来替代ListView的,其实同时也可以替代GridView,上面第二幅图的编码实现只跟第一幅差了一行代码而已。


RecyclerView一般需要两个东西搭配使用,LayoutManager和Adapter,比ListView和GridView(后面简称LG组合)多了一个LayoutManager。其实只是把布局功能给抽取了出来,通过不同的LayoutManager可以产生不同的布局。即把布局的任务交给了LayoutManager,这样更加灵活。目前官方提供了LinearLayoutManager和GridLayoutManager分别实现LG组合的布局样式,但是支持横向的布局,光是这点就比LG组合强了,而且以后如果有新的布局创意的话,只要实现新的LayoutManager就行了。

小结:RecyclerView像它的名字一样,只注重View的回收复用,至于View的布局位置它会去找LayoutManager帮它搞定。View的具体内容,它会去找Adapter帮它搞定。


下面来看下RecyclerView的一般用法(我拿官方例子改了一下):

[java] view plaincopy在CODE上查看代码片

  1. layoutManager = new LinearLayoutManager(this);  

  2. // layoutManager = new GridLayoutManager(this, 2);  

  3. initData();//初始化数据  

  4. recyclerView.setLayoutManager(layoutManager);//设置布局管理器  

  5. mAdapter = new MyRecycleAdapter(datas);  

  6. recyclerView.setAdapter(mAdapter);//设置适配器  


设置布局管理器没什么好讲的,现在也只有这两个,自己看下构造方法都有那些可以设置,然后赋给RecyclerView就行了。

下面看下Adapter的一般实现:

[java] view plaincopy在CODE上查看代码片

  1. public class MyRecycleAdapter extends RecyclerView.Adapter<MyRecycleAdapter.ViewHolder> {  

  2.   

  3.     private List<Item> list;  

  4.   

  5.     public MyRecycleAdapter(List<Item> list) {  

  6.         this.list = list;  

  7.     }  

  8.   

  9.     @Override  

  10.     public int getItemCount() {  

  11.         return list.size();  

  12.     }  

  13.   

  14.     @Override  

  15.     public void onBindViewHolder(ViewHolder holder, int position) {  

  16.         holder.mImageView.setImageResource(list.get(position).drawableId);  

  17.         holder.mTextView.setText(list.get(position).text);  

  18.     }  

  19.   

  20.     @Override  

  21.     public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int position) {  

  22.         View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item, viewGroup, false);  

  23.         ViewHolder holder = new ViewHolder(view);  

  24.         return holder;  

  25.     }  

  26.   

  27.     public class ViewHolder extends RecyclerView.ViewHolder {  

  28.         public TextView mTextView;  

  29.         public ImageView mImageView;  

  30.   

  31.         public ViewHolder(View itemView) {  

  32.             super(itemView);  

  33.             mTextView = (TextView) itemView.findViewById(R.id.txt);  

  34.             mImageView = (ImageView) itemView.findViewById(R.id.img);  

  35.         }  

  36.     }  

  37. }  


先来看下ViewHolder,这里我们自定义了一个ViewHolder,RecyclerView.ViewHolder被定义成抽象类,但其实它并没有任何方法需要重写才能实现功能,之所以定义成抽象类就是为了强调自定义ViewHolder能提高性能,你应该这么做,并且强制你这么做。

那么自定义ViewHolder是怎么提高性能的呢,其实跟我们以前在BaseAdapter里写的ViewHolder一样,是通过减少findViewById的调用次数。

首先看下下图,以前的AbsListView缓存的是itemView,而现在RecyclerView缓存的是ViewHolder,并且itemView成了ViewHolder的一个成员变量:

Android好奇宝宝_10_RecyclerView+CardView+Palette_第3张图片


下面看下上面3种方式是怎么获得控件引用的:

(1)AbsListView

[java] view plaincopy在CODE上查看代码片

  1. //在getView中  

  2. ViewHolder holder=(ViewHolder)itemView.getTag();  

  3. holder.textView.setText("Hello World!");  


(2)默认RecyclerView.ViewHolder

[java] view plaincopy在CODE上查看代码片

  1. //在onBindViewHolder中  

  2. View itemView = holder.itemView;  

  3. TextView textView = (TextView) itemView.findViewById(R.id.txt);  

  4. textView.setText("Hello World!");  


(3)自定义ViewHolder

[java] view plaincopy在CODE上查看代码片

  1. //也是在onBindViewHolder中  

  2. holder.textView.setText("Hello World!");  


可以看到,1和3都额外用一个TextView的引用把第一次findViewById找到的结果保存了下来,下次再用的时候直接使用这个引用就行了,而不用再去findViewById,这样就能减少findViewById的调用次数了。


下面讲下RecyclerView要显示位置为position的item时发生的一些关键事件:

Android好奇宝宝_10_RecyclerView+CardView+Palette_第4张图片


(1)RecyclerView从缓存的ViewHolder中寻找是否有适合该position的ViewHolder


(2)若没找到适合的,则开始通过Adapter去产生一个新的(找到适合的情况后面再说)


(3)调用Adapter的onCreateViewHolder方法去产生一个新的:

[java] view plaincopy在CODE上查看代码片

  1. public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int position) {  

  2.     View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item, viewGroup, false);  

  3.     ViewHolder holder = new ViewHolder(view);  

  4.     return holder;  

  5. }  


首先产生一个itemView,一个新的ViewHolder必须要有一个itemView,对于ViewHolder来说itemView是必不可少的。

然后会new一个ViewHolder,看下ViewHolder的构造方法:

[java] view plaincopy在CODE上查看代码片

  1. public ViewHolder(View itemView) {  

  2.     super(itemView);  

  3.     mTextView = (TextView) itemView.findViewById(R.id.txt);  

  4.     mImageView = (ImageView) itemView.findViewById(R.id.img);  

  5. }  

首先调用了父类的构造方法,父类的构造方法很简单,只是对itemView进行空判断然后赋值给成员变量而已:

[java] view plaincopy在CODE上查看代码片

  1. public ViewHolder(View itemView) {  

  2.     if (itemView == null) {  

  3.         throw new IllegalArgumentException("itemView may not be null");  

  4.     }  

  5.     this.itemView = itemView;  

  6. }  


然后我们把控件的引用保存下来(减少findViewById的调用,上面说过了)。

这样,一个新鲜出炉的ViewHolder就诞生了,这个ViewHolder有着itemView,还有着itemView中那些控件的引用。


(4)调用onBindViewHolder对itemView中某些控件的属性进行修改:

[java] view plaincopy在CODE上查看代码片

  1. public void onBindViewHolder(ViewHolder holder, int position) {  

  2.     holder.mImageView.setImageResource(list.get(position).drawableId);  

  3.     holder.mTextView.setText(list.get(position).text);  

  4. }  


至此,对ViewHolder及itemView的构建过程完结,开始进入通常的绘制流程。


(5)父View RecyclerView对holder.itemView进行测量,得到大小


(6)RecyclerView将布局任务交给LayoutManager,LayoutManager对holder.itemView进行布局,得到位置


(7)系统调用holder.itemView的draw方法将itemView绘制在屏幕上显示出来


一点补充:

如果从缓存中取到合适的ViewHolder,会直接跳到第4步,onCreateViewHolder方法不会被调用。要注意此时的holder已经经过至少一次onBindViewHolder了,holder.itemView中的控件属性值可能已经有值了,如果你没对其进行修改,就会显示之前设置的属性值,这就是以前也存在的View复用导致显示错乱的问题。遵守下面一个原则可以避免这个问题:


任何一个控件如果要在不同position的item显示不同的状态,那么应该有一个类似List的容器保存这个状态,并且在onBindViewHolder方法里从容器取出状态信息对控件进行重新设置。


(2)CardView


CardView的功能比较简单,就是为布局添加一个圆角的,有阴影效果的背景而已。


可以在xml中直接使用,下面是上面效果图中item的布局文件:

[html] view plaincopy在CODE上查看代码片

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

  2. <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"  

  3.     xmlns:card_view="http://schemas.android.com/apk/res-auto"  

  4.     android:layout_width="match_parent"  

  5.     android:layout_height="match_parent"  

  6.     android:layout_margin="10dp"  

  7.     android:orientation="vertical"  

  8.     card_view:cardCornerRadius="5dp"  

  9.     card_view:cardElevation="10dp" >  

  10.   

  11.     <RelativeLayout  

  12.         android:layout_width="match_parent"  

  13.         android:layout_height="160dp"  

  14.         android:padding="8dp" >  

  15.   

  16.         <ImageView  

  17.             android:id="@+id/img"  

  18.             android:layout_width="match_parent"  

  19.             android:layout_height="match_parent"  

  20.             android:layout_centerInParent="true"  

  21.             android:scaleType="centerCrop" />  

  22.   

  23.         <TextView  

  24.             android:id="@+id/txt"  

  25.             android:layout_width="match_parent"  

  26.             android:layout_height="match_parent"  

  27.             android:layout_margin="8dp"  

  28.             android:clickable="true"  

  29.             android:gravity="right|bottom"  

  30.             android:textColor="#ffffff"  

  31.             android:textSize="20sp" />  

  32.     </RelativeLayout>  

  33.   

  34. </android.support.v7.widget.CardView>  


CardView有下面这些自定义属性可以设置:

[html] view plaincopy在CODE上查看代码片

  1. <declare-styleable name="CardView">  

  2.         <!-- Background color for CardView. -->  

  3.         <attr name="cardBackgroundColor" format="color" />  

  4.         <!-- Corner radius for CardView. -->  

  5.         <attr name="cardCornerRadius" format="dimension" />  

  6.         <!-- Elevation for CardView. -->  

  7.         <attr name="cardElevation" format="dimension" />  

  8.         <!-- Maximum Elevation for CardView. -->  

  9.         <attr name="cardMaxElevation" format="dimension" />  

  10.         <!-- Add padding in API v21+ as well to have the same measurements with previous versions. -->  

  11.         <attr name="cardUseCompatPadding" format="boolean" />  

  12.         <!-- Add padding to CardView on v20 and before to prevent intersections between the Card content and rounded corners. -->  

  13.         <attr name="cardPreventCornerOverlap" format="boolean" />  

  14.         <!-- Inner padding between the edges of the Card and children of the CardView. -->  

  15.         <attr name="contentPadding" format="dimension" />  

  16.         <!-- Inner padding between the left edge of the Card and children of the CardView. -->  

  17.         <attr name="contentPaddingLeft" format="dimension" />  

  18.         <!-- Inner padding between the right edge of the Card and children of the CardView. -->  

  19.         <attr name="contentPaddingRight" format="dimension" />  

  20.         <!-- Inner padding between the top edge of the Card and children of the CardView. -->  

  21.         <attr name="contentPaddingTop" format="dimension" />  

  22.         <!-- Inner padding between the bottom edge of the Card and children of the CardView. -->  

  23.         <attr name="contentPaddingBottom" format="dimension" />  

  24.     </declare-styleable>  


其中比较重要常用的属性是:

cardBackgroundColor:设置卡片的背景颜色

cardCornerRadius:设置圆角的程度

cardElevation:设置阴影的高度,数值越大,阴影越明显


(3)Palette

Palette翻译成中文是“调色板”。官方解释它的作用是它可以从一个位图(Bitmap)中取得突出的颜色,可以把这个颜色设置给ActionBar等控件,使整个界面的色调统一。


Palette只能从Bitmap中提取颜色,而一般我们操作的都是View,所以要先将View转为Bitmap,下面讲下方法:

(1)

[java] view plaincopy在CODE上查看代码片

  1. view.buildDrawingCache(autoScale);  

  2. Bitmap bitmap = view.getDrawingCache(autoScale);  

  3. // doSomething by bitmap  

  4. view.destroyDrawingCache();  

(2)

[java] view plaincopy在CODE上查看代码片

  1. Bitmap bitmap=Bitmap.createBitmap(view.getWidth(), view.getHeight(), Config.ARGB_8888);  

  2. Canvas canvas=new Canvas(bitmap);  

  3. view.draw(canvas);  

  4. //doSomething by bitmap  


其实第二种方法是第一种方法的内部实现,大家可以自己看着用那种。


获得bitmap后就可以开始用了:

[java] view plaincopy在CODE上查看代码片

  1. Palette palette = Palette.generate(bitmap);  

  2. titleLayout.getChildAt(0).setBackgroundColor(palette.getDarkMutedColor(0XFFFFFFFF));  

  3. titleLayout.getChildAt(1).setBackgroundColor(palette.getDarkVibrantColor(0XFFFFFFFF));  

  4. titleLayout.getChildAt(2).setBackgroundColor(palette.getLightMutedColor(0XFFFFFFFF));  

  5. titleLayout.getChildAt(3).setBackgroundColor(palette.getLightVibrantColor(0XFFFFFFFF));  

  6. titleLayout.getChildAt(4).setBackgroundColor(palette.getMutedColor(0XFFFFFFFF));  

  7. titleLayout.getChildAt(5).setBackgroundColor(palette.getVibrantColor(0XFFFFFFFF));  


调用generate方法从bitmap中提取颜色,结果保存在palette中,通过上面6个get方法可以获得不同类型的颜色,如果该bitmap上不存在符合该类型的颜色,则会返回默认颜色。


含义:

Dark:暗淡的

Light:亮丽的

Muted:柔合的

Vibrant:有活力的、鲜艳的


效果图中顶部的六个色块就是从RecyclerView的当前显示中提取出来的6种类型颜色,拖动后停止时颜色会改变。

注:generate是同步方法,Palette还提供了异步方法generateAsync,一般情况下建议使用异步方法,我这里是偷懒了。


Demo下载



你可能感兴趣的:(Android好奇宝宝_10_RecyclerView+CardView+Palette)