Palette 是 Android L SDK 中的新特性,可以使用 Palette 从图像中提取出突出的颜色(主色调),获取到颜色之后我们再将这个颜色值赋给 ActionBar、状态栏等。从而达到界面色调的统一,使界面美观协调。
Palette 原理:通过得到一个 bitmap,通过方法进行分析,取出 LightVibrantSwatch,DarkVibrantSwatch,LightMutedSwatch,DarkMutedSwatch 这些样本,然后得到 rgb 值。
Palette.Builder 生成器类,生成 Palette 实例
Palette.Filter 过滤器接口,使 Palette 有更加细腻的颜色过滤
Palette.PaletteAsyncListener 异步加载监听
pattle.Swatch 提供获取结果的色彩样本
from(List<Palette.Switch> switches) 通过预设的 Palette.Swatch 颜色样本列表 来生成 Palette
from(Bitmap bitmap) 通过返回 Palette.Builder 实例来构建 Palette
palette.getDarkMutedColor(Color.BLUE) 获取到柔和的深色的颜色(可传默认值)
palette.getDarkVibrantColor(Color.BLUE) 获取到活跃的深色的颜色(可传默认值)
palette.getLightMutedColor(Color.BLUE) 获取到柔和的明亮的颜色(可传默认值)
palette.getLightVibrantColor(Color.BLUE) 获取到活跃的明亮的颜色(可传默认值)
palette.getVibrantColor(Color.BLUE) 获取图片中最活跃的颜色(也可以说整个图片出现最多的颜色)(可传默认值)
palette.getMutedColor(Color.BLUE) 获取图片中一个最柔和的颜色(可传默认值)
Palette 创建有同步和异步两种方式,开发中我们为了提高应用性能,比较耗时的操作都会采用异步方式。
Bitmap bm =BitmapFactory.decodeResource(getResources(),R.drawable.kale); // 同步 Palette.Builder builder = Palette.from(bm); Palette palette=builder.generate(); // 异步 builder.generate(bitmap, new Palette.PaletteAsyncListener() { @Override public void onGenerated(Palette palette) { } }Palette 可以分析提取出以下突出的颜色,在应用适用的环境下灵活使用,如下图所示,获取到屏幕中图片 BitMap 对象,然后通过 Palette 提取到相关属性,在 TextView 设置背景颜色。
Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap(); Palette.from(bitmap).generate(new Palette.PaletteAsyncListener() { @Override public void onGenerated(@Nullable Palette palette) { //获取到柔和的深色的颜色(可传默认值) int darkMutedColor = palette.getDarkMutedColor(Color.BLUE);//如果分析不出来,则返回默认颜色 //获取到柔和的明亮的颜色(可传默认值) int lightMutedColor = palette.getLightMutedColor(Color.BLUE); //获取到活跃的深色的颜色(可传默认值) int darkVibrantColor = palette.getDarkVibrantColor(Color.BLUE); //获取到活跃的明亮的颜色(可传默认值) int lightVibrantColor = palette.getLightVibrantColor(Color.BLUE); //获取图片中一个最柔和的颜色(可传默认值) int mutedColor = palette.getMutedColor(Color.BLUE); //获取图片中最活跃的颜色(也可以说整个图片出现最多的颜色)(可传默认值) int vibrantColor = palette.getVibrantColor(Color.BLUE); //获取某种特性颜色的样品 Palette.Swatch lightVibrantSwatch = palette.getVibrantSwatch(); //谷歌推荐的:图片的整体的颜色rgb的混合值---主色调 int rgb = lightVibrantSwatch.getRgb(); //谷歌推荐:图片中间的文字颜色 int bodyTextColor = lightVibrantSwatch.getBodyTextColor(); //谷歌推荐:作为标题的颜色(有一定的和图片的对比度的颜色值) int titleTextColor = lightVibrantSwatch.getTitleTextColor(); //颜色向量 float[] hsl = lightVibrantSwatch.getHsl(); //分析该颜色在图片中所占的像素多少值 int population = lightVibrantSwatch.getPopulation(); tv1.setText("darkMutedColor"); tv1.setBackgroundColor(darkMutedColor); tv2.setText("lightMutedColor"); tv2.setBackgroundColor(lightMutedColor); tv3.setText("darkVibrantColor"); tv3.setBackgroundColor(darkVibrantColor); tv4.setText("lightVibrantColor"); tv4.setBackgroundColor(lightVibrantColor); tv5.setText("mutedColor"); tv5.setBackgroundColor(mutedColor); tv6.setText("vibrantColor"); tv6.setBackgroundColor(vibrantColor); } });通过上述简单介绍,我们已经大致清除 Palette 是干什么的,接下来就根据开发中常见需求实现效果。
在列表页加载卡片的情况下,经常会在图片底部或者顶部添加一片半透明区域,用来显示文本信息,以前常规做法是设置一个半透明颜色,但是这种做法在暗灰色的图片上,展示效果极差。Google 提供 Palette 之后,就可以根据图片分析出最适合的底色作为背景色,让图片和整列表更加优雅的展示。
本文主要内容是 Palette,如果对 CardView 不熟悉的朋友可以查看前文:
创建完一个 Palette 实例之后,我们还需要得到一种采集的样本(swatch),有 6 中样本(swatch)
Palette.getVibrantSwatch() 返回一个鲜明(有活力)的样本类Palette.getDarkVibrantSwatch() 返回一个鲜明(有活力)的暗色调样本类Palette.getLightVibrantSwatch() 返回一个鲜明(有活力)的亮色调样本类Palette.getMutedSwatch() 返回一个柔和的样本类Palette.getDarkMutedSwatch() 返回一个柔和的暗色调样本类Palette.getLightMutedSwatch() 返回一个柔和的亮色调样本类以上 6 种样本色调使用方式一模一样,这里用getVibrantSwatch()举例说明,其他用法自行上手练习。
//获取某种特性颜色的样品 Palette.Swatch lightVibrantSwatch = palette.getVibrantSwatch(); if (lightVibrantSwatch == null) { for (Palette.Swatch swatch : palette.getSwatches()) { lightVibrantSwatch = swatch; break; } } //谷歌推荐的:图片的整体的颜色rgb的混合值---主色调 int rgb = lightVibrantSwatch.getRgb(); //谷歌推荐:图片中间的文字颜色 int bodyTextColor = lightVibrantSwatch.getBodyTextColor(); //谷歌推荐:作为标题的颜色(有一定的和图片的对比度的颜色值) int titleTextColor = lightVibrantSwatch.getTitleTextColor();注意:getVibrantSwatch()方法返回的 Palette.Swatch 对象有可能为 null,所以一定要判断是否为 null,否则可能会抛出NullPointerException异常。
当我们获取到图片的整体的颜色 rgb 的混合值,这个值就是跟图片最接近的颜色,也是 Google 官方推荐使用的主色调,要想达到前文中降的效果,应用时还需对 rgb 值进行透明后再使用。
/** * @param percent 透明度 * @param rgb RGB值 * @return 最终设置过透明度的颜色值 */ protected int getTranslucentColor(float percent, int rgb) { int blue = Color.blue(rgb); int green = Color.green(rgb); int red = Color.red(rgb); int alpha = Color.alpha(rgb); alpha = Math.round(alpha * percent); return Color.argb(alpha, red, green, blue); }因为发现很多文章都是采用本地资源库图片,获取 BitMap 方式比较简单,在实际项目中,如果加载网络图片的话,可以使用 Glide.asBitMap() 方法来实现,这里使用了一个 Adapter 的三方库,防止朋友们看懵逼,贴上 Adapter 完整代码,如果想学习的小伙伴,建议在文末下载源码学习。
<?xml version="1.0" encoding="utf-8"?> <androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/cardView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp" android:clickable="true" android:focusable="true" android:foreground="@drawable/item_touch_bg" app:cardCornerRadius="1dp" app:cardElevation="1dp" app:cardPreventCornerOverlap="false" app:cardUseCompatPadding="true"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/articleListImg" android:layout_width="match_parent" android:layout_height="200dp" android:contentDescription="@null" android:scaleType="centerCrop" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:src="@drawable/ic_launcher_background" /> <TextView android:id="@+id/articleListTitle" android:layout_width="match_parent" android:layout_height="70dp" android:gravity="center" android:textColor="@color/white" android:textSize="18dp" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="@+id/articleListImg" app:layout_constraintStart_toStartOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </androidx.cardview.widget.CardView> public class PaletteImageAdapter extends BaseCompatAdapter<ArticleBean, BaseViewHolder> { public PaletteImageAdapter(int layoutResId, List<ArticleBean> data) { super(layoutResId, data); } @Override protected void convert(BaseViewHolder helper, ArticleBean item) { // 使用Glide.asBitmap()方法转换 FutureTarget<Bitmap> bitmap = Glide.with(mContext) .asBitmap() .load(item.getImageUrl()) .submit(); // 在子线程中执行提取颜色任务,Palette提取颜色根据图片质量耗时不同,属于比较耗时的操作 new Thread(() -> { try { setPalette(bitmap.get(), helper, item); } catch (Exception e) { e.printStackTrace(); } }).start(); } private void setPalette(Bitmap bitmap, BaseViewHolder helper, ArticleBean item) { Palette.from(bitmap).generate(new Palette.PaletteAsyncListener() { @Override public void onGenerated(@Nullable Palette palette) { //获取某种特性颜色的样品 Palette.Swatch lightVibrantSwatch = palette.getVibrantSwatch(); if (lightVibrantSwatch == null) { for (Palette.Swatch swatch : palette.getSwatches()) { lightVibrantSwatch = swatch; break; } } //谷歌推荐的:图片的整体的颜色rgb的混合值---主色调 int rgb = lightVibrantSwatch.getRgb(); //谷歌推荐:图片中间的文字颜色 int bodyTextColor = lightVibrantSwatch.getBodyTextColor(); //谷歌推荐:作为标题的颜色(有一定的和图片的对比度的颜色值) int titleTextColor = lightVibrantSwatch.getTitleTextColor(); helper.setText(R.id.articleListTitle, item.getTitle()) .setTextColor(R.id.articleListTitle, titleTextColor) .setBackgroundColor(R.id.articleListTitle, getTranslucentColor(0.8f, rgb)); ((ImageView) helper.getView(R.id.articleListImg)).setImageBitmap(bitmap); } }); } }
其实前面代码注释里已经提到了,Palette 加载不能在主线程中进行,如果是列表展示图片时,会报错。因为 Palette 提取图片色彩的操作是比较耗时的,所以一定要在子线程中执行。
加载方式有同步加载和异步加载两种:
由于他们很可能会比较耗时(在分析大图片或者所需颜色较多时),所以它们不应该在主线程中执行。你应该先在别的线程中使用这两个函数进行解析,解析成功之后再使用。
有时候你不会在加载图片的线程(非主线程)中使用解析出的颜色,所以 Palette 提供了异步方法,他们与之前的函数的区别就是需要传入 PaletteAsyncListener,提供在图片解析完成后的回调函数。
源码下载 源码包含 Material Design 系列控件集合,定时更新,敬请期待!
现在很多流行的 APP 列表界面垂直滑动时,会根据内容色调动态更改 ToolBar 的颜色,这种效果就可以借助 Palette 来采取图片颜色实现。其实 Palette 并不只适用于我写的示例,Palette 应用的情况很多,感兴趣的朋友可以在官网详细学习!
我的微信:Jaynm888
欢迎点评,诚邀 Android 程序员加入微信交流群,公众号回复加群或者加我微信邀请入群。