您好,登錄后才能下訂單哦!
這篇文章主要講解了“如何修改ViewGroup默認的順序繪制子View”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“如何修改ViewGroup默認的順序繪制子View”吧!
TV App 的 Item 處理
修改 View 的繪制順序,在日常開發中,基本用不到。眾多手機端 App 的 UI 設計,大部分采用扁平化的設計思想,除非是一些很特別的自定義 View,多數情況下,我們無需考慮 View 的默認繪制順序。
這也很好理解,正常情況下,ViewGroup 中后添加的 View,視覺上就是應該覆蓋在之前的 View 之上。
但是有一個場景的設計,很特別,那就是 Android TV App。
在 TV 的設計上,因為需要遙控器按鍵控制,為了更豐富的視覺體驗,是需要額外處理 View 對焦點狀態的變化的。
例如:獲取焦點的 ItemView 整個高亮,放大再加個陰影,都是很常見的設計。
那么這就帶來一個問題,正常我們使用 RecyclerView 實現的列表效果,當 Item 之間的間距過小時,單個 Item 被放大就會出現遮蓋的效果。
例如上圖所示,一個很常見的焦點放大高亮的設計,但卻被后面的 View 遮蓋了。
這樣的情況,如何解決呢?
拍腦袋想,既然是間距太小了,那我們就拉大間距就好了。修改一個屬性解決一個需求,設計師哭暈在工位上。
不過確實有一些設計效果,間距足夠,也就不存在遮蓋的現象
但是我們不能只靠改間距解決問題,多數情況下,設計師留給我們的間距并不多
既然逃不掉,那就研究一下如何解決。
修改繪制順序原理
修改繪制順序,其實很簡單,Android 已經為我們留出了擴展點。
我們知道,ViewGroup 通過其成員 mChildren 數組,存儲子 View。而在 ViewGroup 繪制子 View 的 dispatchDraw() 方法循環中,并不是直接利用索引從 mChildren 數組中取值的。
@Override protected void dispatchDraw(Canvas canvas) { // ... final ArrayList<View> 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 中直取,而是通過 getAndVerifyPreorderedView() 獲得,它的參數除了 children 外,還有一個 preorderedList 的 ArrayList,及子 View 的索引。
private static View getAndVerifyPreorderedView(ArrayList<View> 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 關鍵列表,來自 buildOrderedChildList(),在方法中通過 getAndVerifyPreorderedIndex() 獲取對應子 View 的索引,此方法需要一個 Boolean 類型的 customOrder,即表示是否需要自定義順序。
ArrayList<View> 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 軸調整 children 順序,Z 軸值相同則參考 customOrder 的配置。
通常 ViewGroup 中的子 View,Z 值一致,所以關鍵參數是 customOrder 開關。
從代碼上了解到 customOrder 是通過 isChildrenDrawingOrderEnabled() 方法獲取,與之對應的是 setChildrenDrawingOrderEnabled() 可以設置 customOrder 的取值。
也就是說,如果我們要調整順序,只需 2 步調整:
調用 setChildrenDrawingOrderEnable(true) 開啟自定義繪制順序
重寫 getChildDrawingOrder() 修改 View 的取值索引
實例
最后,我們寫個 Demo,重寫 RecycleView 的 getChildDrawingOrder() 方法,來實現獲得焦點的 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); }
別忘了還需要調用 setChildrenDrawingOrderEnabled(true) 開啟自定義繪制順序。
此時,焦點放大時,就不會被其他 View 遮擋。
感謝各位的閱讀,以上就是“如何修改ViewGroup默認的順序繪制子View”的內容了,經過本文的學習后,相信大家對如何修改ViewGroup默認的順序繪制子View這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。