新聞中心
一、序

開(kāi)平ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場(chǎng)景,ssl證書未來(lái)市場(chǎng)廣闊!成為創(chuàng)新互聯(lián)公司的ssl證書銷售渠道,可以享受市場(chǎng)價(jià)格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:028-86922220(備注:SSL證書合作)期待與您的合作!
大家好,我是承香墨影,許久不見(jiàn),甚是想念!
今天我們來(lái)聊聊 View 繪制流程的一個(gè)小細(xì)節(jié),自定義繪制順序。
View 的三大流程:測(cè)量、布局、繪制,我想大家應(yīng)該都爛熟于心。而在繪制階段,ViewGroup 不光要繪制自身,還需循環(huán)繪制其一眾子 View,這個(gè)繪制策略默認(rèn)為順序繪制,即 [0 ~ childCount)。
這個(gè)默認(rèn)的策略,有辦法調(diào)整嗎?例如修改成 (childCount ~ 0],或是修成某個(gè) View 最后繪制。同時(shí)又有什么場(chǎng)景需要我們做這樣的修改?
需要注意的是,繪制順序會(huì)影響覆蓋順序,同時(shí)也會(huì)影響 View 的事件分發(fā),這些都是關(guān)聯(lián)影響的,可謂是牽一發(fā)而動(dòng)全身。
今天就來(lái)聊聊這個(gè)問(wèn)題。
二、TV App 的 Item 處理
修改 View 的繪制順序,在日常開(kāi)發(fā)中,基本用不到。眾多手機(jī)端 App 的 UI 設(shè)計(jì),大部分采用扁平化的設(shè)計(jì)思想,除非是一些很特別的自定義 View,多數(shù)情況下,我們無(wú)需考慮 View 的默認(rèn)繪制順序。
這也很好理解,正常情況下,ViewGroup 中后添加的 View,視覺(jué)上就是應(yīng)該覆蓋在之前的 View 之上。
但是有一個(gè)場(chǎng)景的設(shè)計(jì),很特別,那就是 Android TV App。
在 TV 的設(shè)計(jì)上,因?yàn)樾枰b控器按鍵控制,為了更豐富的視覺(jué)體驗(yàn),是需要額外處理 View 對(duì)焦點(diǎn)狀態(tài)的變化的。
例如:獲取焦點(diǎn)的 ItemView 整個(gè)高亮,放大再加個(gè)陰影,都是很常見(jiàn)的設(shè)計(jì)。
那么這就帶來(lái)一個(gè)問(wèn)題,正常我們使用 RecyclerView 實(shí)現(xiàn)的列表效果,當(dāng) Item 之間的間距過(guò)小時(shí),單個(gè) Item 被放大就會(huì)出現(xiàn)遮蓋的效果。
例如上圖所示,一個(gè)很常見(jiàn)的焦點(diǎn)放大高亮的設(shè)計(jì),但卻被后面的 View 遮蓋了。
這樣的情況,如何解決呢?
拍腦袋想,既然是間距太小了,那我們就拉大間距就好了。修改一個(gè)屬性解決一個(gè)需求,設(shè)計(jì)師哭暈在工位上。
不過(guò)確實(shí)有一些設(shè)計(jì)效果,間距足夠,也就不存在遮蓋的現(xiàn)象,例如 Bilibili TV 端的部分頁(yè)面。
但是我們不能只靠改間距解決問(wèn)題,多數(shù)情況下,設(shè)計(jì)師留給我們的間距并不多。大部分 TV App 是這樣的。
既然逃不掉,那就研究一下如何解決。
三、修改繪制順序原理
修改繪制順序,其實(shí)很簡(jiǎn)單,Android 已經(jīng)為我們留出了擴(kuò)展點(diǎn)。
我們知道,ViewGroup 通過(guò)其成員 mChildren 數(shù)組,存儲(chǔ)子 View。而在 ViewGroup 繪制子 View 的 dispatchDraw() 方法循環(huán)中,并不是直接利用索引從 mChildren 數(shù)組中取值的。
- @Override
- protected void dispatchDraw(Canvas canvas) {
- // ...
- final ArrayList
preorderedList = usingRenderNodeProperties - ? null : buildOrderedChildList();
- final boolean customOrder = preorderedList == null
- && isChildrenDrawingOrderEnabled();
- for (int i = 0; i < childrenCount; i++) {
- // ...
- final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
- // 并非直接從 mChildren 中獲取
- final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
- more |= drawChild(canvas, child, drawingTime);
- }
- }
- // ...
- }
可以看到,child 并非是從 mChildren 中直取,而是通過(guò) getAndVerifyPreorderedView() 獲得,它的參數(shù)除了 children 外,還有一個(gè) preorderedList 的 ArrayList,及子 View 的索引。
- private static View getAndVerifyPreorderedView(ArrayList
preorderedList, - View[] children,
- int childIndex) {
- final View child;
- if (preorderedList != null) {
- child = preorderedList.get(childIndex);
- if (child == null) {
- throw new RuntimeException("Invalid preorderedList contained null child at index "
- + childIndex);
- }
- } else {
- child = children[childIndex];
- }
- return child;
- }
在其中,若 preorderedList 不為空,則從其中獲取子 View,反之則還是從 children 中獲取。
回到前面 dispatchDraw() 中,這里使用的 preorderedList 關(guān)鍵列表,來(lái)自 buildOrderedChildList(),在方法中通過(guò) getAndVerifyPreorderedIndex() 獲取對(duì)應(yīng)子 View 的索引,此方法需要一個(gè) Boolean 類型的 customOrder,即表示是否需要自定義順序。
- ArrayList
buildOrderedChildList() { - // ...
- final boolean customOrder = isChildrenDrawingOrderEnabled();
- for (int i = 0; i < childrenCount; i++) {
- // add next child (in child order) to end of list
- final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
- final View nextChild = mChildren[childIndex];
- final float currentZ = nextChild.getZ();
- // insert ahead of any Views with greater Z
- int insertIndex = i;
- while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
- insertIndex--;
- }
- mPreSortedChildren.add(insertIndex, nextChild);
- }
- return mPreSortedChildren;
- }
buildOrderedChildList() 的邏輯就是按照 Z 軸調(diào)整 children 順序,Z 軸值相同則參考 customOrder 的配置。
通常 ViewGroup 中的子 View,Z 值一致,所以關(guān)鍵參數(shù)是 customOrder 開(kāi)關(guān)。
從代碼上了解到 customOrder 是通過(guò) isChildrenDrawingOrderEnabled() 方法獲取,與之對(duì)應(yīng)的是 setChildrenDrawingOrderEnabled() 可以設(shè)置 customOrder 的取值。
也就是說(shuō),如果我們要調(diào)整順序,只需 2 步調(diào)整:
調(diào)用 setChildrenDrawingOrderEnable(true) 開(kāi)啟自定義繪制順序
重寫 getChildDrawingOrder() 修改 View 的取值索引
四、實(shí)例
最后,我們寫個(gè) Demo,重寫 RecycleView 的 getChildDrawingOrder() 方法,來(lái)實(shí)現(xiàn)獲得焦點(diǎn)的 View 最后繪制。
- @Override
- protected int getChildDrawingOrder(int childCount, int i) {
- View view = getLayoutManager().getFocusedChild();
- if (null == view) {
- return super.getChildDrawingOrder(childCount, i);
- }
- int position = indexOfChild(view);
- if (position < 0) {
- return super.getChildDrawingOrder(childCount, i);
- }
- if (i == childCount - 1) {
- return position;
- }
- if (i == position) {
- return childCount - 1;
- }
- return super.getChildDrawingOrder(childCount, i);
- }
別忘了還需要調(diào)用 setChildrenDrawingOrderEnabled(true) 開(kāi)啟自定義繪制順序。
此時(shí),焦點(diǎn)放大時(shí),就不會(huì)被其他 View 遮擋。
本文名稱:ViewGroup默認(rèn)順序繪制子View,如何修改?什么場(chǎng)景需要修改繪制順序?
分享鏈接:http://fisionsoft.com.cn/article/cceepcg.html


咨詢
建站咨詢
