这个页面主要功能是RecyclerView展示各个灯光卡片,选择色盘,亮度调节,整体算法联动控制的效果。
1. 色盘
首先我们来讲解下这个色盘吧。这个页面除了亮度算法,就是颜色的控制啦。
想搞出色盘,能够准确的计算出色值,需要搞清楚这几个属性:
色调H:0-360
从红色开始按逆时针方向计算,红色为0°,绿色为120°,蓝色为240°。它们的补色是:黄色为60°,青色为180°,紫色为300°饱和度S:0%-100%
饱和度高,颜色则深而艳。光谱色的白光成分为0,饱和度达到最高。通常取值范围为0%~100%,值越大,颜色越饱和。明度V:0%-100%
明度表示颜色明亮的程度,通常取值范围为0%(黑)到100%(白)。亮度B:0%-100%
亮度表示颜色明亮的程度,通常取值范围为0%(黑)到100%(白)。色温T:0%-100%
色温表示光谱能量分布的指标,色温高,光谱能量分布于短波的成分略多,颜色偏蓝。色温低,光谱能量分布于长波的成分略多,颜色偏黄。
即便理解了这几个属性,是不是也还是一脸懵逼呀?
日常生活中用到的,像有的灯具只有亮度可以调节,有的灯具可以调节亮度和冷暖色温,有的灯具可以调节色调,饱和度和亮度,功能再多点的灯,可以调节色调+饱和度+亮度+色温。
对应到UI色盘上的关系,或许可以让你更多的理解这几个属性,更清楚的知道怎么去控制这几个值。
从图上我们可以看出,色盘由圆形和长方形2种,各自对应的关系我已经标注的比较清楚了。
圆形相对与长方形来说,它需要根据弧度来确定色调H,根据点的位置和利用勾股定理来确定饱和度S。
画这个UI图对应到各个HSVBT值还比较简单,可以通过Color来计算和转换,比如HSVToColor,colorToHSV,RGBToHSV等,比较繁琐的是灯具不同,硬件上的值和各自转换算法不同,下发到灯具上的颜色亮度也不同,这个保密就不做介绍了。
2. 动画
从最上面的操作录屏中,你可以看到,SeekBar亮度调节的时候还有一些动画,为了让用户有圆润的动画体验,这里使用了弹性动画。
弹性动画这里选择了interpolator插值器的实现方式。
public class SpringScaleInterpolator implements Interpolator {
//弹性因数
private float factor;
public SpringScaleInterpolator(float factor) {
this.factor = factor;
}
@Override
public float getInterpolation(float input) {
return (float) (Math.pow(2, -10 * input) * Math.sin((input - factor / 4) * (2 * Math.PI) / factor) + 1);
}
}
从代码上看,可能你不清楚弹性因子和代码里的数字各自影响的动画效果是什么样的,那么就从这个网站查看效果吧。
关于差值器网站
在这个网站上可以在线看每种interpolator的效果,从而调节数字来选择所需要的interpolator。
factor的值越小,值来回变化的次数越多,对应到具体的动画就是:factor值越小,view来回缩放的次数越多,平移到指定位置后在指定位置上下或左右摆动的次数也越多
具体的弹性动画方面,还有其他的实现方式,可自行查看,这里不做详细介绍。
3. seekbar
从录屏中可以看一下,SeekBar的特点,除了动画效果外,还有圆角处理+颜色动态变换+透明度+色值渐变。每一个HSVBT值的变化,都需要计算color,来改变SeekBar和卡片的颜色,同时计算整体亮度。
-
SeekBar固定进度条颜色和圆角
SeekBar的进度条和滑块可以通过设置progressDrawable与thumb,这样设置会是固定的,从录屏上看我们需要的是动态的更改渐变颜色,100%的时候更改圆角。
我们先从progressDrawable和thumb开始吧。
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!--定义seekbar滑动条的底色-->
<item android:id="@android:id/background">
<shape>
<solid android:color="#39000000" />
</shape>
</item>
<!--定义seekbar滑动条第二背景颜色-->
<!-- <item android:id="@android:id/secondaryProgress">-->
<!-- <clip>-->
<!-- <shape>-->
<!-- <corners android:radius="5dp" />-->
<!-- <gradient-->
<!-- android:startColor="#80ffd300"-->
<!-- android:centerColor="#80ffb600"-->
<!-- android:centerY="0.75"-->
<!-- android:endColor="#a0ffcb00"-->
<!-- android:angle="270"/>-->
<!-- </shape>-->
<!-- </clip>-->
<!-- </item>-->
<!--定义seekbar滑动条进度颜色-->
<item android:id="@android:id/progress">
<scale android:scaleWidth="100%" >
<shape android:shape="rectangle">
<gradient
android:angle="180"
android:endColor="@color/transparent"
android:startColor="@color/white" />
<corners android:bottomRightRadius="7dp" android:topRightRadius="7dp"/>
</shape>
</scale>
</item>
</layer-list>
UI图上没有第二背景,这里就把它添加上并写了注释,有个细节的点需要了解下,就是scale与clip的区别。
先使用clip对比看一下UI:
<clip>
<shape android:shape="rectangle">
<gradient
android:angle="180"
android:endColor="@color/transparent"
android:startColor="@color/white" />
<corners android:bottomRightRadius="@dimen/dp_7" android:topRightRadius="@dimen/dp_7"/>
</shape>
</clip>
展示如下:
使用clip从截图来看,没有了圆角。
- scale能够将drawable进行缩放,通过其scaleWidth和scaleHeight属性,可设置drawable缩放的比例。
- 使用clip标签可实现对drawable的裁剪,用法与scale类似。通过在代码中调用getBackground()获取Drawable后,通过setLevel(int level)方法,可控制裁剪的程度。
滑块部分可自定查看文末的参考文档,在xml里设置android:thumb="@drawable/seekbar_thumb"
-
SeekBar动态设置颜色和圆角
我们可以使用getProgressDrawable得到进度背景LayerDrawable,它包含多个层次,对应上面layer-list设置的3个层次,可以通过Id获取各自的Drawable。
//获取seerbar层次drawable对象
LayerDrawable layerDrawable = (LayerDrawable) seekBar.getProgressDrawable();
// 有多少个层次(最多三个)
int layers = layerDrawable.getNumberOfLayers();
Drawable[] drawables = new Drawable[layers];
for (int i = 0; i < layers; i++) {
switch (layerDrawable.getId(i)) {
// seekbar背景
case android.R.id.background:
drawables[i] = layerDrawable.getDrawable(0);
break;
// 拖动条第一进度
case android.R.id.progress:
//动态设颜色值
drawables[i] = new PaintDrawable(progressColor);
drawables[i].setBounds(layerDrawable.getDrawable(0).getBounds());
break;
...
}
}
seekBar.setProgressDrawable(new LayerDrawable(drawables));
seekBar.invalidate();
由于我的UI只progress颜色在变化,可以直接取LayerDrawable的Drawable第1个Drawable,转变为GradientDrawable,进行设置渐变颜色和圆角
LayerDrawable layerDrawable = (LayerDrawable) seekBar.getProgressDrawable();
GradientDrawable gradientDrawable = (GradientDrawable)((ScaleDrawable)layerDrawable.getDrawable(1)).getDrawable();
//设置渐变颜色
int[] gradientColors = {mContext.getResources().getColor(R.color.white), mContext.getResources().getColor(R.color.transparent)};
gradientColors[1] = newColor;
gradientDrawable.setColors(gradientColors);
//设置圆角
float gradientRadius = mContext.getResources().getDimension((seekBar.getProgress() == 100) ? 0dp : 7dp);
float[] radius = {0f, 0f, gradientRadius, gradientRadius, gradientRadius, gradientRadius, 0f, 0f};
gradientDrawable.setCornerRadii(radius);
seekBar.setProgressDrawable(layerDrawable);
seekBar.invalidate();
这里需要了解下,用shape标签定义的xml,最终都是转化为GradientDrawable对象,而不是ShapeDrawable, 也不是类型对应的 OvalShape,RoundRectShape等。
GradientDrawable可以动态设置跟xml文件中android:shape的值一一对应的类型。
从UI上看,seekBar设置过颜色的同时,卡片的背景也需要设置,这里通过setColorFilter设置
Drawable itemBackground = itemBgView.getBackground();
itemBackground.setColorFilter(newColor, PorterDuff.Mode.SRC);
这里也有需要了解的点,PorterDuff.Mode.SRC,不做多介绍,详细的可以查看文末参考文档
从图上可以形象地说明了运用PorterDuff.Mode进行图像合成的作用,两个图形一圆一方通过一定的计算产生了不同的合成效果,我们在实际工作中需要做图片处理时可以参考这张图的示意快速地选择合适的Mode。
4. 缓存问题
单个使用SeekBar是没问题的,放在了RecyclerView里,动态的设置颜色发现是有问题的,他们相互影响显示同一个颜色,最终翻看源码,发现是Drawable资源缓存的问题。详细的源码可以查看ResourcesImpl里loadDrawable部分。
避免这个问题,可以通过setProgressDrawable设置SeekBar的LayerDrawable。
int progressDrawableRes = R.drawable.seekbar_progress_drawable;
LayerDrawable layerDrawable = (LayerDrawable)ContextCompat.getDrawable(mContext, progressDrawableRes).getConstantState().newDrawable().mutate();
seekBar.setProgressDrawable(layerDrawable);
5. notifyDataSetChanged问题
这里有个问题需要注意,从最上面的录屏看,滑动每一个SeekBar,动画执行的同时都需要动态的更新数据,实时的更新整体亮度,notifyDataSetChanged的时机需要跟动画结合,因为RecyclerView的缓存机制,可能动画执行一半,SeekBar对象就变了。
就这样吧,算是总结记录下最近搞的UI部分吧。
参考文档:
Android drawable资源分析
动态改变SeekBar进度条颜色与滑块颜色
GradientDrawable(shape标签定义) 静态使用和动态使用(圆角,渐变实现)
各个击破搞明白PorterDuff.Mode