Android好奇宝宝_10_RecyclerView+CardView+Palette

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

先上效果图:

(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的一般用法(我拿官方例子改了一下):

		layoutManager = new LinearLayoutManager(this);
		// layoutManager = new GridLayoutManager(this, 2);
		initData();//初始化数据
		recyclerView.setLayoutManager(layoutManager);//设置布局管理器
		mAdapter = new MyRecycleAdapter(datas);
		recyclerView.setAdapter(mAdapter);//设置适配器

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

下面看下Adapter的一般实现:

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

		private List<Item> list;

		public MyRecycleAdapter(List<Item> list) {
			this.list = list;
		}

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

		@Override
		public void onBindViewHolder(ViewHolder holder, int position) {
			holder.mImageView.setImageResource(list.get(position).drawableId);
			holder.mTextView.setText(list.get(position).text);
		}

		@Override
		public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int position) {
			View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item, viewGroup, false);
			ViewHolder holder = new ViewHolder(view);
			return holder;
		}

		public class ViewHolder extends RecyclerView.ViewHolder {
			public TextView mTextView;
			public ImageView mImageView;

			public ViewHolder(View itemView) {
				super(itemView);
				mTextView = (TextView) itemView.findViewById(R.id.txt);
				mImageView = (ImageView) itemView.findViewById(R.id.img);
			}
		}
	}

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

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

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

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

(1)AbsListView

		//在getView中
		ViewHolder holder=(ViewHolder)itemView.getTag();
		holder.textView.setText("Hello World!");

(2)默认RecyclerView.ViewHolder

		//在onBindViewHolder中
		View itemView = holder.itemView;
		TextView textView = (TextView) itemView.findViewById(R.id.txt);
		textView.setText("Hello World!");

(3)自定义ViewHolder

		//也是在onBindViewHolder中
		holder.textView.setText("Hello World!");

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

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

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

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

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

		public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int position) {
			View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item, viewGroup, false);
			ViewHolder holder = new ViewHolder(view);
			return holder;
		}

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

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

			public ViewHolder(View itemView) {
				super(itemView);
				mTextView = (TextView) itemView.findViewById(R.id.txt);
				mImageView = (ImageView) itemView.findViewById(R.id.img);
			}

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

        public ViewHolder(View itemView) {
            if (itemView == null) {
                throw new IllegalArgumentException("itemView may not be null");
            }
            this.itemView = itemView;
        }

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

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

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

		public void onBindViewHolder(ViewHolder holder, int position) {
			holder.mImageView.setImageResource(list.get(position).drawableId);
			holder.mTextView.setText(list.get(position).text);
		}

至此,对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的布局文件:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="10dp"
    android:orientation="vertical"
    card_view:cardCornerRadius="5dp"
    card_view:cardElevation="10dp" >

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="160dp"
        android:padding="8dp" >

        <ImageView
            android:id="@+id/img"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_centerInParent="true"
            android:scaleType="centerCrop" />

        <TextView
            android:id="@+id/txt"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="8dp"
            android:clickable="true"
            android:gravity="right|bottom"
            android:textColor="#ffffff"
            android:textSize="20sp" />
    </RelativeLayout>

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

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

<declare-styleable name="CardView">
        <!-- Background color for CardView. -->
        <attr name="cardBackgroundColor" format="color" />
        <!-- Corner radius for CardView. -->
        <attr name="cardCornerRadius" format="dimension" />
        <!-- Elevation for CardView. -->
        <attr name="cardElevation" format="dimension" />
        <!-- Maximum Elevation for CardView. -->
        <attr name="cardMaxElevation" format="dimension" />
        <!-- Add padding in API v21+ as well to have the same measurements with previous versions. -->
        <attr name="cardUseCompatPadding" format="boolean" />
        <!-- Add padding to CardView on v20 and before to prevent intersections between the Card content and rounded corners. -->
        <attr name="cardPreventCornerOverlap" format="boolean" />
        <!-- Inner padding between the edges of the Card and children of the CardView. -->
        <attr name="contentPadding" format="dimension" />
        <!-- Inner padding between the left edge of the Card and children of the CardView. -->
        <attr name="contentPaddingLeft" format="dimension" />
        <!-- Inner padding between the right edge of the Card and children of the CardView. -->
        <attr name="contentPaddingRight" format="dimension" />
        <!-- Inner padding between the top edge of the Card and children of the CardView. -->
        <attr name="contentPaddingTop" format="dimension" />
        <!-- Inner padding between the bottom edge of the Card and children of the CardView. -->
        <attr name="contentPaddingBottom" format="dimension" />
    </declare-styleable>

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

cardBackgroundColor:设置卡片的背景颜色

cardCornerRadius:设置圆角的程度

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

(3)Palette

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

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

(1)

		view.buildDrawingCache(autoScale);
		Bitmap bitmap = view.getDrawingCache(autoScale);
		// doSomething by bitmap
		view.destroyDrawingCache();

(2)

	Bitmap bitmap=Bitmap.createBitmap(view.getWidth(), view.getHeight(), Config.ARGB_8888);
	Canvas canvas=new Canvas(bitmap);
	view.draw(canvas);
	//doSomething by bitmap

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

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

	Palette palette = Palette.generate(bitmap);
	titleLayout.getChildAt(0).setBackgroundColor(palette.getDarkMutedColor(0XFFFFFFFF));
	titleLayout.getChildAt(1).setBackgroundColor(palette.getDarkVibrantColor(0XFFFFFFFF));
	titleLayout.getChildAt(2).setBackgroundColor(palette.getLightMutedColor(0XFFFFFFFF));
	titleLayout.getChildAt(3).setBackgroundColor(palette.getLightVibrantColor(0XFFFFFFFF));
	titleLayout.getChildAt(4).setBackgroundColor(palette.getMutedColor(0XFFFFFFFF));
	titleLayout.getChildAt(5).setBackgroundColor(palette.getVibrantColor(0XFFFFFFFF));

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

含义:

Dark:暗淡的

Light:亮丽的

Muted:柔合的

Vibrant:有活力的、鲜艳的

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

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

Demo下载

本篇完结

时间: 2024-10-13 06:02:48

Android好奇宝宝_10_RecyclerView+CardView+Palette的相关文章

Android好奇宝宝_04_一个有3个功能的Adapter

感觉Android好奇宝宝这个系列是脱离不了ListView和GridView了... 这一篇呢来分享点好东西 一个自定义Adapter,可以快速实现三个功能: (1)自动缓存处理 好吧,这个功能不是我实现的.我只是照搬鸿洋大大的,我会简单说下,不过还是请先看下他的原文,再来看我添加的两个功能,传送门 (2)支持item的不同布局 提供一个接口来通过position和该position的数据来设置不同的布局 (3)局部刷新 只刷新指定item的某个子View,避免一直调用notifyDataSe

Android好奇宝宝_番外篇_看脸的世界_08

废话少说,先上效果图: (左侧的图片是我用window画图软件1分钟画的,所以就不要嫌丑了,You can you up no bb.) 这是我发过最挫的效果图了,不过这是由于没有图片素材导致的,就不要在意这些细节了,知道实现原理后完全可以发挥你的想象去实现更美观的效果. 这个效果也是有开源库的,不过我又把名字给忘了,不过我记得原理,于是就试着自己写了一下. 其实原理很简单,我在另一篇博客(一个有吃豆人删除动画的ListView)也说过了,这一篇当做兑换那些年少轻狂不更事时许下的诺言(是不是瞬间

Android好奇宝宝_11_SwipeRefreshLayout原理浅析

上一篇文章写了一个RecyclerView的Demo,然后就想加个下拉刷新功能进去试试,由于RecyclerView算比较新的东西,所以暂时找不到什么开源库使用.于是想到了官方提供的SwipeRefreshLayout,号称能为任何View添加下拉刷新功能. SwipeRefreshLayout的使用很简单: (1)将要下拉刷新的View嵌套到SwipeRefreshLayout中: <jjj.demo.newstuffdemo.JJJSwipeRefreshLayout android:id=

Android好奇宝宝_番外篇_看脸的世界_05

上一篇番外篇讲了一个炒鸡炒鸡简单的自定义ProgressBar,这一篇基于上一篇的基础扩展为SeekBar,没看过上一篇的,请先看一遍:传送门 先上效果图(2G内存的机子运行模拟器,所以有点卡): 这个效果之前不知道在哪里看到过,我也忘了. 下面进入正题: 测量大小和绘制部分沿用上一篇ProgressBar的,不清楚的请走上面的传送门. 对比上一篇的扩展: (1)SeekBar能通过触摸改变刻度 (2)SeekBar上方添加一个显示当前刻度的浮动View(后面用FloatView表示) (1)通

Android好奇宝宝_番外篇_看脸的世界_03

无聊刷帖看到一个求助,试着写了一下. 一个自定义Switch控件,附带动画效果. 说是控件,其实是一个布局容器,先上效果图: 先讲原理,再看高清源码. 原理: 好像没啥原理,汗... 跟其它自定义容器控件一样,一般要注意: (1)计算好大小,宽度和高度 (2)计算好子View的布局位置 不是一般要注意的: (3)动画是用的nineoldandroids (4)遮挡效果是通过控制子View的绘制顺序 高清源码: (1)计算大小: protected void onMeasure(int width

Android好奇宝宝_06_聊一聊Android里的动画

这一篇我们来聊一聊高大上的动画效果. 首先说一个常识,一个对理解动画最重要的概念,亦是动画的本质: 动画的原理是利人眼的视觉暂留的特性,即如果一帧帧图像切换的足够快的话,人眼就察觉不到停顿,看起来就像连续的动画了. 动画的原理很简单,就是让图像进行快速的切换.动画的难点是计算出每两帧之间的差异,比如一个位移动画,对于每一帧你都必须计算出它的位置,如果是直线匀速的.很容易计算,但如果是曲线的而且还是有加速度(即移动的速度是会变化的)的,那么计算就会变的复杂了. 总结一下,动画有两个要素,一个是若干

Android好奇宝宝_07_ViewPager切换动画(兼容低版本)

闲着无聊,写写Demo 想着写一个图片轮播,百度了一下基本都是用ViewPager实现的,那就用ViewPager来练手. 写完了再自定义切换效果,发现3.0以下不兼容,只好想办法. 先上效果图: 下面一步一步来: (1)写布局: <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <jjj.demo.viewpager

Android好奇宝宝_05_PopupWindow与悬浮窗

这一篇讲讲PopupWindow与悬浮窗之间那些不得不说的故事. 之所以把PopupWindow与悬浮窗这两个放到一起讲,是因为这两个的实现原理基本是一致的,只是有点不同而已. 原理: 使用系统服务(WindowManagerService)将要显示的View添加进Window中. WindowManagerService和ActivityManagerService是Android系统中两个最重要的服务,其中一个管理窗口显示,一个管理四大组件. ActivityManagerService这里

Android好奇宝宝_番外篇_看脸的世界_02

一个有吃豆人删除动画的ListView 这是一个无聊的效果,由一个无聊的程序猿,在无聊的情况下写的. 虽然这效果不中看中用,不过就当学习了. 先上图 效果一目了然,主要是: (1)移除item时执行吃豆人动画 (2)滚动时吃豆人也相应移动 (3)应对可见与不可见状态间的切换 简单原理分析: (1)吃豆人.豆.和左边的白色矩形(当然所有颜色都是可以改的,你想换成图片也行)都是用canvas画出来的. (2)问:canvas那里来的?答:ListView的canvas.具体是重写ListView的这