自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

ViewGroup 默認順序繪制子 View,如何修改?什么場景需要修改繪制順序?

開發(fā) 架構(gòu)
View 的三大流程:測量、布局、繪制,我想大家應(yīng)該都爛熟于心。而在繪制階段,ViewGroup 不光要繪制自身,還需循環(huán)繪制其一眾子 View,這個繪制策略默認為順序繪制,即 [0 ~ childCount)。

 [[333892]]

一、序

大家好,我是承香墨影,許久不見,甚是想念!

今天我們來聊聊 View 繪制流程的一個小細節(jié),自定義繪制順序。

View 的三大流程:測量、布局、繪制,我想大家應(yīng)該都爛熟于心。而在繪制階段,ViewGroup 不光要繪制自身,還需循環(huán)繪制其一眾子 View,這個繪制策略默認為順序繪制,即 [0 ~ childCount)。

這個默認的策略,有辦法調(diào)整嗎?例如修改成 (childCount ~ 0],或是修成某個 View 最后繪制。同時又有什么場景需要我們做這樣的修改?

需要注意的是,繪制順序會影響覆蓋順序,同時也會影響 View 的事件分發(fā),這些都是關(guān)聯(lián)影響的,可謂是牽一發(fā)而動全身。

今天就來聊聊這個問題。

二、TV App 的 Item 處理

修改 View 的繪制順序,在日常開發(fā)中,基本用不到。眾多手機端 App 的 UI 設(shè)計,大部分采用扁平化的設(shè)計思想,除非是一些很特別的自定義 View,多數(shù)情況下,我們無需考慮 View 的默認繪制順序。

這也很好理解,正常情況下,ViewGroup 中后添加的 View,視覺上就是應(yīng)該覆蓋在之前的 View 之上。

但是有一個場景的設(shè)計,很特別,那就是 Android TV App。

在 TV 的設(shè)計上,因為需要遙控器按鍵控制,為了更豐富的視覺體驗,是需要額外處理 View 對焦點狀態(tài)的變化的。

例如:獲取焦點的 ItemView 整個高亮,放大再加個陰影,都是很常見的設(shè)計。

那么這就帶來一個問題,正常我們使用 RecyclerView 實現(xiàn)的列表效果,當 Item 之間的間距過小時,單個 Item 被放大就會出現(xiàn)遮蓋的效果。

例如上圖所示,一個很常見的焦點放大高亮的設(shè)計,但卻被后面的 View 遮蓋了。

這樣的情況,如何解決呢?

拍腦袋想,既然是間距太小了,那我們就拉大間距就好了。修改一個屬性解決一個需求,設(shè)計師哭暈在工位上。

不過確實有一些設(shè)計效果,間距足夠,也就不存在遮蓋的現(xiàn)象,例如 Bilibili TV 端的部分頁面。

但是我們不能只靠改間距解決問題,多數(shù)情況下,設(shè)計師留給我們的間距并不多。大部分 TV App 是這樣的。

既然逃不掉,那就研究一下如何解決。

三、修改繪制順序原理

修改繪制順序,其實很簡單,Android 已經(jīng)為我們留出了擴展點。

我們知道,ViewGroup 通過其成員 mChildren 數(shù)組,存儲子 View。而在 ViewGroup 繪制子 View 的 dispatchDraw() 方法循環(huán)中,并不是直接利用索引從 mChildren 數(shù)組中取值的。

  1. @Override 
  2. protected void dispatchDraw(Canvas canvas) { 
  3.   // ... 
  4.   final ArrayList<View> preorderedList = usingRenderNodeProperties 
  5.         ? null : buildOrderedChildList(); 
  6.   final boolean customOrder = preorderedList == null 
  7.         && isChildrenDrawingOrderEnabled(); 
  8.   for (int i = 0; i < childrenCount; i++) { 
  9.     // ... 
  10.     final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); 
  11.     // 并非直接從 mChildren 中獲取 
  12.     final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); 
  13.     if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { 
  14.         more |= drawChild(canvas, child, drawingTime); 
  15.     } 
  16.   } 
  17.   // ... 

可以看到,child 并非是從 mChildren 中直取,而是通過 getAndVerifyPreorderedView() 獲得,它的參數(shù)除了 children 外,還有一個 preorderedList 的 ArrayList,及子 View 的索引。

  1. private static View getAndVerifyPreorderedView(ArrayList<View> preorderedList, 
  2.         View[] children, 
  3.         int childIndex) { 
  4.   final View child; 
  5.   if (preorderedList != null) { 
  6.     child = preorderedList.get(childIndex); 
  7.     if (child == null) { 
  8.         throw new RuntimeException("Invalid preorderedList contained null child at index " 
  9.                 + childIndex); 
  10.     } 
  11.   } else { 
  12.     child = children[childIndex]; 
  13.   } 
  14.   return child; 

在其中,若 preorderedList 不為空,則從其中獲取子 View,反之則還是從 children 中獲取。

回到前面 dispatchDraw() 中,這里使用的 preorderedList 關(guān)鍵列表,來自 buildOrderedChildList(),在方法中通過 getAndVerifyPreorderedIndex() 獲取對應(yīng)子 View 的索引,此方法需要一個 Boolean 類型的 customOrder,即表示是否需要自定義順序。

  1. ArrayList<View> buildOrderedChildList() { 
  2.   // ... 
  3.   final boolean customOrder = isChildrenDrawingOrderEnabled(); 
  4.   for (int i = 0; i < childrenCount; i++) { 
  5.     // add next child (in child orderto end of list 
  6.     final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); 
  7.     final View nextChild = mChildren[childIndex]; 
  8.     final float currentZ = nextChild.getZ(); 
  9.     // insert ahead of any Views with greater Z 
  10.     int insertIndex = i; 
  11.     while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) { 
  12.         insertIndex--; 
  13.     } 
  14.     mPreSortedChildren.add(insertIndex, nextChild); 
  15.   } 
  16.   return mPreSortedChildren; 

buildOrderedChildList() 的邏輯就是按照 Z 軸調(diào)整 children 順序,Z 軸值相同則參考 customOrder 的配置。

通常 ViewGroup 中的子 View,Z 值一致,所以關(guān)鍵參數(shù)是 customOrder 開關(guān)。

從代碼上了解到 customOrder 是通過 isChildrenDrawingOrderEnabled() 方法獲取,與之對應(yīng)的是 setChildrenDrawingOrderEnabled() 可以設(shè)置 customOrder 的取值。

也就是說,如果我們要調(diào)整順序,只需 2 步調(diào)整:

調(diào)用 setChildrenDrawingOrderEnable(true) 開啟自定義繪制順序

重寫 getChildDrawingOrder() 修改 View 的取值索引

四、實例

最后,我們寫個 Demo,重寫 RecycleView 的 getChildDrawingOrder() 方法,來實現(xiàn)獲得焦點的 View 最后繪制。

  1. @Override 
  2. protected int getChildDrawingOrder(int childCount, int i) { 
  3.   View view = getLayoutManager().getFocusedChild(); 
  4.   if (null == view) { 
  5.     return super.getChildDrawingOrder(childCount, i); 
  6.   } 
  7.   int position = indexOfChild(view); 
  8.   if (position < 0) { 
  9.     return super.getChildDrawingOrder(childCount, i); 
  10.   } 
  11.   if (i == childCount - 1) { 
  12.     return position; 
  13.   } 
  14.   if (i == position) { 
  15.     return childCount - 1; 
  16.   } 
  17.   return super.getChildDrawingOrder(childCount, i); 

別忘了還需要調(diào)用 setChildrenDrawingOrderEnabled(true) 開啟自定義繪制順序。

此時,焦點放大時,就不會被其他 View 遮擋。

 

責(zé)任編輯:武曉燕 來源: 51CTO專欄
相關(guān)推薦

2010-10-08 12:03:03

修改mysql字段

2011-08-23 09:33:19

Ubuntu系統(tǒng)托盤

2017-08-21 21:36:23

AndroidViewJava

2016-07-29 11:21:16

Ubuntulinux程序

2010-08-11 14:47:54

Flex樣式

2010-06-09 08:59:30

UML活動圖

2010-09-07 16:05:23

SQL語句刪除

2010-06-08 10:35:38

UML圖

2010-06-09 18:56:44

UML用例圖

2021-04-26 07:53:04

繪制流程任務(wù)

2023-05-19 08:01:57

Go 語言map

2013-12-10 10:27:04

密碼

2011-07-25 14:54:53

iPhone iPhone開發(fā) View

2010-11-09 15:38:24

SQL Server默

2011-04-07 11:24:17

Distance 命令路由OSPF

2010-09-28 10:23:36

SQL修改字段

2009-09-03 10:26:07

C#修改DataRea

2017-04-27 20:30:33

Android動畫技巧

2010-07-12 11:36:32

UML活動圖

2010-06-11 09:46:55

UML順序圖
點贊
收藏

51CTO技術(shù)棧公眾號