在清明節(jié)時(shí)各大APP都會(huì)進(jìn)行黑白化處理,當(dāng)時(shí)在接到這個(gè)需求的時(shí)候感覺(jué)好麻煩,是不是又要搞一套皮膚?
創(chuàng)新互聯(lián)建站從2013年成立,先為施秉等服務(wù)建站,施秉等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為施秉企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問(wèn)題。然而在一系列搜索之后,找到了兩位大神(鴻洋、U2tzJTNE)的實(shí)現(xiàn)方案,其實(shí)相當(dāng)?shù)暮?jiǎn)單!
讓我們一起站在巨人的肩膀上來(lái)分析一下原理,并思考會(huì)不會(huì)有更簡(jiǎn)便的實(shí)現(xiàn)?
一、原理兩位大神的置灰方案是相同的,都能看到一段同樣的代碼:
Paint mPaint = new Paint();
ColorMatrix mColorMatrix = new ColorMatrix();
// 設(shè)置飽和度為0
mColorMatrix.setSaturation(0);
mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));
他們都用了Android提供的ColorMatrix(顏色矩陣),將其飽和度設(shè)置為0,這樣使用Paint繪制出來(lái)的都是沒(méi)有飽和度的灰白樣式!
然而兩位在何時(shí)使用Paint繪制時(shí)選擇了不同方案。
1.1 鴻洋:重寫draw方法鴻洋分析,如果我們把每個(gè)Activity的根布局飽和度設(shè)置為0是不是就可以了?
那根布局是誰(shuí)?
鴻洋分析我們的布局最后setContentView最后都會(huì)設(shè)置到一個(gè)R.id.content的FrameLayout當(dāng)中。
我們?nèi)プ远x一個(gè)GrayFrameLayout,在draw的時(shí)候使用這個(gè)飽和度為0的畫筆,被這個(gè)FrameLayout包裹的布局都會(huì)變成黑白。
// 轉(zhuǎn)載自鴻洋
// https://blog.csdn.net/lmj623565791/article/details/105319752
public class GrayFrameLayout extends FrameLayout {
private Paint mPaint = new Paint();
public GrayFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
ColorMatrix cm = new ColorMatrix();
cm.setSaturation(0);
mPaint.setColorFilter(new ColorMatrixColorFilter(cm));
}
@Override
protected void dispatchDraw(Canvas canvas) {
canvas.saveLayer(null, mPaint, Canvas.ALL_SAVE_FLAG);
super.dispatchDraw(canvas);
canvas.restore();
}
@Override
public void draw(Canvas canvas) {
canvas.saveLayer(null, mPaint, Canvas.ALL_SAVE_FLAG);
super.draw(canvas);
canvas.restore();
}
}
然后我們用GrayFrameLayout去替換這個(gè)R.id.content的FrameLayout,是不是就可以做到將頁(yè)面黑白化了?
替換FrameLayout的方法可以去【鴻洋】這篇文章下查看。
1.2 U2tzJTNE:監(jiān)聽DecorView的添加U2tzJTNE大佬 使用了另一種巧妙的方案。
他先創(chuàng)建了一個(gè)具有數(shù)據(jù)變化感知能力的ObservableArrayList(當(dāng)內(nèi)容發(fā)生變化有回調(diào))。
之后使用反射將WindowManagerGlobal內(nèi)的mViews容器(ArrayList,該容器會(huì)存放所有的DecorView),替換為ObservableArrayList,這樣就可以監(jiān)聽到每個(gè)DecorView的創(chuàng)建,并且拿到View本身。
拿到DecorView,那就可以為所欲為了!
大佬使用了setLayerType(View.LAYER_TYPE_HARDWARE, mPaint),對(duì)布局進(jìn)行了重繪。至于為什么要用LAYER_TYPE_HARDWARE?因?yàn)槟J(rèn)的View.LAYER_TYPE_NONE會(huì)把Paint強(qiáng)制設(shè)置為null。
// 轉(zhuǎn)載自U2tzJTNE
// https://juejin.cn/post/6892277675012915207
public static void enable(boolean enable) {
try {
//灰色調(diào)Paint
final Paint mPaint = new Paint();
ColorMatrix mColorMatrix = new ColorMatrix();
mColorMatrix.setSaturation(enable ? 0 : 1);
mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));
//反射獲取windowManagerGlobal
@SuppressLint("PrivateApi")
Class>windowManagerGlobal = Class.forName("android.view.WindowManagerGlobal");
@SuppressLint("DiscouragedPrivateApi")
java.lang.reflect.Method getInstanceMethod = windowManagerGlobal.getDeclaredMethod("getInstance");
getInstanceMethod.setAccessible(true);
Object windowManagerGlobalInstance = getInstanceMethod.invoke(windowManagerGlobal);
//反射獲取mViews
Field mViewsField = windowManagerGlobal.getDeclaredField("mViews");
mViewsField.setAccessible(true);
Object mViewsObject = mViewsField.get(windowManagerGlobalInstance);
//創(chuàng)建具有數(shù)據(jù)感知能力的ObservableArrayList
ObservableArrayListobserverArrayList = new ObservableArrayList<>();
observerArrayList.addOnListChangedListener(new ObservableArrayList.OnListChangeListener() {
@Override
public void onChange(ArrayList list, int index, int count) {
}
@Override
public void onAdd(ArrayList list, int start, int count) {
// 拿到DecorView觸發(fā)重繪
View view = (View) list.get(start);
if (view != null) {
view.setLayerType(View.LAYER_TYPE_HARDWARE, mPaint);
}
}
@Override
public void onRemove(ArrayList list, int start, int count) {
}
});
//將原有的數(shù)據(jù)添加到新創(chuàng)建的list
observerArrayList.addAll((ArrayList) mViewsObject);
//替換掉原有的mViews
mViewsField.set(windowManagerGlobalInstance, observerArrayList);
} catch (Exception e) {
e.printStackTrace();
}
}
只需要在Application里面調(diào)用該方法即可。
1.3 方案分析兩位大佬的方案都非常的棒,咱們理性的來(lái)對(duì)比一下。
鴻洋: 使用自定義FrameLayout的方案需要一個(gè)BaseActivity統(tǒng)一設(shè)置,稍顯麻煩,代碼侵入性較強(qiáng)。
U2tzJTNE: 方案更加簡(jiǎn)單、動(dòng)態(tài),一行代碼設(shè)置甚至可以做到在當(dāng)前頁(yè)從彩色變黑白,但是使用了反射,有一點(diǎn)點(diǎn)性能消耗。
既然研究明白了大佬的方案,那有沒(méi)有又不需要反射,設(shè)置又簡(jiǎn)單的方法呢?
能不能使用原生方式獲取DecorView的實(shí)例呢?
突然靈光一閃,Application里面不是有registerActivityLifecycleCallbacks這個(gè)注冊(cè)監(jiān)聽方法嗎?監(jiān)聽里面的onActivityCreated不是可以獲取到當(dāng)前的Activity嗎?那DecorView不就拿到了!
搞起!上代碼!
public class StudyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
Paint mPaint = new Paint();
ColorMatrix mColorMatrix = new ColorMatrix();
mColorMatrix.setSaturation(0);
mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
// 當(dāng)Activity創(chuàng)建,我們拿到DecorView,使用Paint進(jìn)行重繪
View decorView = activity.getWindow().getDecorView();
decorView.setLayerType(View.LAYER_TYPE_HARDWARE, mPaint);
}
....
});
}
}
這樣看起來(lái)是不是更簡(jiǎn)單了!使用了APP原生的方法實(shí)現(xiàn)了黑白化!當(dāng)然也有缺點(diǎn),因?yàn)樵贏ctivity級(jí)別設(shè)置,無(wú)法做到在當(dāng)前頁(yè)面即時(shí)變?yōu)楹诎住?/p>三、注意事項(xiàng)
這三種方案因?yàn)槎际褂昧祟伾仃?,所以坑都是一樣的,?qǐng)注意。
3.1 啟動(dòng)圖windowBackground無(wú)法變色在我們可以設(shè)置渲染的時(shí)候windowBackground已經(jīng)展示完畢了。
解決方案:只能在當(dāng)前的包里修改,或者不去理會(huì)。
3.2 SurfaceView無(wú)法變色因?yàn)槲覀兪褂昧藄etLayerType進(jìn)行重繪,而SurfaceView是有獨(dú)立的Window,脫離布局內(nèi)的Window,運(yùn)行在其他線程,不影響主線程的繪制,所以當(dāng)前方案無(wú)法使SurfaceView變色。
解決方案:
1、使用TextureView。
2、看下這個(gè)SurfaceView是否可以設(shè)置濾鏡,正常都是一些三方或者自制的播放器。
我們可能會(huì)在APP內(nèi)置小程序,小程序基本是運(yùn)行在單獨(dú)的進(jìn)程中,但是如果我們的黑白配置在運(yùn)行過(guò)程中發(fā)生變化,其他進(jìn)程是無(wú)法感知的。
解決方案:使用 MMKV 存儲(chǔ)黑白配置,并設(shè)置多進(jìn)程共享,在開啟小程序之前都判斷一下黑白展示。
總結(jié)最后咱們?cè)倏偨Y(jié)一下黑白化方案。
使用了ColorMatrix設(shè)置飽和度為0,設(shè)置到Paint中,讓根布局拿著這個(gè)Paint去進(jìn)行重繪。
這樣APP全局黑白化的介紹就結(jié)束了,希望大家讀完這篇文章,會(huì)對(duì)APP黑白化有一個(gè)更深入的了解。如果我的文章能給大家?guī)?lái)一點(diǎn)點(diǎn)的福利,那在下就足夠開心了。
更多Android 知識(shí)點(diǎn)歸整Android 性能調(diào)優(yōu)系列:https://0a.fit/dNHYY
Android 車載學(xué)習(xí)指南:https://0a.fit/jdVoy
Android Framework核心知識(shí)點(diǎn)筆記:https://0a.fit/acnLL
Android 音視頻學(xué)習(xí)筆記:https://0a.fit/BzPVh
Jetpack全家桶(含Compose):https://0a.fit/GQJSl
Kotlin 入門到精進(jìn):https://0a.fit/kdfWR
Flutter 基礎(chǔ)到進(jìn)階實(shí)戰(zhàn):https://0a.fit/xvcHV
Android 八大知識(shí)體系:https://0a.fit/mieWJ
Android 中高級(jí)面試題錦:https://0a.fit/YXwVq
后續(xù)如有新知識(shí)點(diǎn),將會(huì)持續(xù)更新,盡請(qǐng)期待……
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購(gòu),新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧