
這個(gè)頁(yè)面主要功能是RecyclerView展示各個(gè)燈光卡片,選擇色盤(pán),亮度調(diào)節(jié),整體算法聯(lián)動(dòng)控制的效果。
1. 色盤(pán)
首先我們來(lái)講解下這個(gè)色盤(pán)吧。這個(gè)頁(yè)面除了亮度算法,就是顏色的控制啦。
想搞出色盤(pán),能夠準(zhǔn)確的計(jì)算出色值,需要搞清楚這幾個(gè)屬性:
色調(diào)H:0-360
從紅色開(kāi)始按逆時(shí)針?lè)较蛴?jì)算,紅色為0°,綠色為120°,藍(lán)色為240°。它們的補(bǔ)色是:黃色為60°,青色為180°,紫色為300°飽和度S:0%-100%
飽和度高,顏色則深而艷。光譜色的白光成分為0,飽和度達(dá)到最高。通常取值范圍為0%~100%,值越大,顏色越飽和。明度V:0%-100%
明度表示顏色明亮的程度,通常取值范圍為0%(黑)到100%(白)。亮度B:0%-100%
亮度表示顏色明亮的程度,通常取值范圍為0%(黑)到100%(白)。色溫T:0%-100%
色溫表示光譜能量分布的指標(biāo),色溫高,光譜能量分布于短波的成分略多,顏色偏藍(lán)。色溫低,光譜能量分布于長(zhǎng)波的成分略多,顏色偏黃。
即便理解了這幾個(gè)屬性,是不是也還是一臉懵逼呀?
日常生活中用到的,像有的燈具只有亮度可以調(diào)節(jié),有的燈具可以調(diào)節(jié)亮度和冷暖色溫,有的燈具可以調(diào)節(jié)色調(diào),飽和度和亮度,功能再多點(diǎn)的燈,可以調(diào)節(jié)色調(diào)+飽和度+亮度+色溫。
對(duì)應(yīng)到UI色盤(pán)上的關(guān)系,或許可以讓你更多的理解這幾個(gè)屬性,更清楚的知道怎么去控制這幾個(gè)值。

從圖上我們可以看出,色盤(pán)由圓形和長(zhǎng)方形2種,各自對(duì)應(yīng)的關(guān)系我已經(jīng)標(biāo)注的比較清楚了。
圓形相對(duì)與長(zhǎng)方形來(lái)說(shuō),它需要根據(jù)弧度來(lái)確定色調(diào)H,根據(jù)點(diǎn)的位置和利用勾股定理來(lái)確定飽和度S。
畫(huà)這個(gè)UI圖對(duì)應(yīng)到各個(gè)HSVBT值還比較簡(jiǎn)單,可以通過(guò)Color來(lái)計(jì)算和轉(zhuǎn)換,比如HSVToColor,colorToHSV,RGBToHSV等,比較繁瑣的是燈具不同,硬件上的值和各自轉(zhuǎn)換算法不同,下發(fā)到燈具上的顏色亮度也不同,這個(gè)保密就不做介紹了。
2. 動(dòng)畫(huà)
從最上面的操作錄屏中,你可以看到,SeekBar亮度調(diào)節(jié)的時(shí)候還有一些動(dòng)畫(huà),為了讓用戶有圓潤(rùn)的動(dòng)畫(huà)體驗(yàn),這里使用了彈性動(dòng)畫(huà)。
彈性動(dòng)畫(huà)這里選擇了interpolator插值器的實(shí)現(xiàn)方式。
public class SpringScaleInterpolator implements Interpolator {
//彈性因數(shù)
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);
}
}
從代碼上看,可能你不清楚彈性因子和代碼里的數(shù)字各自影響的動(dòng)畫(huà)效果是什么樣的,那么就從這個(gè)網(wǎng)站查看效果吧。
關(guān)于差值器網(wǎng)站

在這個(gè)網(wǎng)站上可以在線看每種interpolator的效果,從而調(diào)節(jié)數(shù)字來(lái)選擇所需要的interpolator。
factor的值越小,值來(lái)回變化的次數(shù)越多,對(duì)應(yīng)到具體的動(dòng)畫(huà)就是:factor值越小,view來(lái)回縮放的次數(shù)越多,平移到指定位置后在指定位置上下或左右擺動(dòng)的次數(shù)也越多
具體的彈性動(dòng)畫(huà)方面,還有其他的實(shí)現(xiàn)方式,可自行查看,這里不做詳細(xì)介紹。
3. seekbar
從錄屏中可以看一下,SeekBar的特點(diǎn),除了動(dòng)畫(huà)效果外,還有圓角處理+顏色動(dòng)態(tài)變換+透明度+色值漸變。每一個(gè)HSVBT值的變化,都需要計(jì)算color,來(lái)改變SeekBar和卡片的顏色,同時(shí)計(jì)算整體亮度。

-
SeekBar固定進(jìn)度條顏色和圓角
SeekBar的進(jìn)度條和滑塊可以通過(guò)設(shè)置progressDrawable與thumb,這樣設(shè)置會(huì)是固定的,從錄屏上看我們需要的是動(dòng)態(tài)的更改漸變顏色,100%的時(shí)候更改圓角。
我們先從progressDrawable和thumb開(kāi)始吧。
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!--定義seekbar滑動(dòng)條的底色-->
<item android:id="@android:id/background">
<shape>
<solid android:color="#39000000" />
</shape>
</item>
<!--定義seekbar滑動(dòng)條第二背景顏色-->
<!-- <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滑動(dòng)條進(jìn)度顏色-->
<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圖上沒(méi)有第二背景,這里就把它添加上并寫(xiě)了注釋?zhuān)袀€(gè)細(xì)節(jié)的點(diǎn)需要了解下,就是scale與clip的區(qū)別。
先使用clip對(duì)比看一下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從截圖來(lái)看,沒(méi)有了圓角。
- scale能夠?qū)rawable進(jìn)行縮放,通過(guò)其scaleWidth和scaleHeight屬性,可設(shè)置drawable縮放的比例。
- 使用clip標(biāo)簽可實(shí)現(xiàn)對(duì)drawable的裁剪,用法與scale類(lèi)似。通過(guò)在代碼中調(diào)用getBackground()獲取Drawable后,通過(guò)setLevel(int level)方法,可控制裁剪的程度。
滑塊部分可自定查看文末的參考文檔,在xml里設(shè)置android:thumb="@drawable/seekbar_thumb"
-
SeekBar動(dòng)態(tài)設(shè)置顏色和圓角
我們可以使用getProgressDrawable得到進(jìn)度背景LayerDrawable,它包含多個(gè)層次,對(duì)應(yīng)上面layer-list設(shè)置的3個(gè)層次,可以通過(guò)Id獲取各自的Drawable。
//獲取seerbar層次drawable對(duì)象
LayerDrawable layerDrawable = (LayerDrawable) seekBar.getProgressDrawable();
// 有多少個(gè)層次(最多三個(gè))
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;
// 拖動(dòng)條第一進(jìn)度
case android.R.id.progress:
//動(dòng)態(tài)設(shè)顏色值
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個(gè)Drawable,轉(zhuǎn)變?yōu)镚radientDrawable,進(jìn)行設(shè)置漸變顏色和圓角
LayerDrawable layerDrawable = (LayerDrawable) seekBar.getProgressDrawable();
GradientDrawable gradientDrawable = (GradientDrawable)((ScaleDrawable)layerDrawable.getDrawable(1)).getDrawable();
//設(shè)置漸變顏色
int[] gradientColors = {mContext.getResources().getColor(R.color.white), mContext.getResources().getColor(R.color.transparent)};
gradientColors[1] = newColor;
gradientDrawable.setColors(gradientColors);
//設(shè)置圓角
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標(biāo)簽定義的xml,最終都是轉(zhuǎn)化為GradientDrawable對(duì)象,而不是ShapeDrawable, 也不是類(lèi)型對(duì)應(yīng)的 OvalShape,RoundRectShape等。
GradientDrawable可以動(dòng)態(tài)設(shè)置跟xml文件中android:shape的值一一對(duì)應(yīng)的類(lèi)型。
從UI上看,seekBar設(shè)置過(guò)顏色的同時(shí),卡片的背景也需要設(shè)置,這里通過(guò)setColorFilter設(shè)置
Drawable itemBackground = itemBgView.getBackground();
itemBackground.setColorFilter(newColor, PorterDuff.Mode.SRC);
這里也有需要了解的點(diǎn),PorterDuff.Mode.SRC,不做多介紹,詳細(xì)的可以查看文末參考文檔
從圖上可以形象地說(shuō)明了運(yùn)用PorterDuff.Mode進(jìn)行圖像合成的作用,兩個(gè)圖形一圓一方通過(guò)一定的計(jì)算產(chǎn)生了不同的合成效果,我們?cè)趯?shí)際工作中需要做圖片處理時(shí)可以參考這張圖的示意快速地選擇合適的Mode。
4. 緩存問(wèn)題
單個(gè)使用SeekBar是沒(méi)問(wèn)題的,放在了RecyclerView里,動(dòng)態(tài)的設(shè)置顏色發(fā)現(xiàn)是有問(wèn)題的,他們相互影響顯示同一個(gè)顏色,最終翻看源碼,發(fā)現(xiàn)是Drawable資源緩存的問(wèn)題。詳細(xì)的源碼可以查看ResourcesImpl里loadDrawable部分。
避免這個(gè)問(wèn)題,可以通過(guò)setProgressDrawable設(shè)置SeekBar的LayerDrawable。
int progressDrawableRes = R.drawable.seekbar_progress_drawable;
LayerDrawable layerDrawable = (LayerDrawable)ContextCompat.getDrawable(mContext, progressDrawableRes).getConstantState().newDrawable().mutate();
seekBar.setProgressDrawable(layerDrawable);
5. notifyDataSetChanged問(wèn)題
這里有個(gè)問(wèn)題需要注意,從最上面的錄屏看,滑動(dòng)每一個(gè)SeekBar,動(dòng)畫(huà)執(zhí)行的同時(shí)都需要?jiǎng)討B(tài)的更新數(shù)據(jù),實(shí)時(shí)的更新整體亮度,notifyDataSetChanged的時(shí)機(jī)需要跟動(dòng)畫(huà)結(jié)合,因?yàn)镽ecyclerView的緩存機(jī)制,可能動(dòng)畫(huà)執(zhí)行一半,SeekBar對(duì)象就變了。
就這樣吧,算是總結(jié)記錄下最近搞的UI部分吧。
參考文檔:
Android drawable資源分析
動(dòng)態(tài)改變SeekBar進(jìn)度條顏色與滑塊顏色
GradientDrawable(shape標(biāo)簽定義) 靜態(tài)使用和動(dòng)態(tài)使用(圓角,漸變實(shí)現(xiàn))
各個(gè)擊破搞明白PorterDuff.Mode
