Android開源庫V - Layout:淘寶、天貓都在用的UI框架

detachment_w發表於2020-12-12

轉載 Carson_Ho

前言

目錄

在這裡插入圖片描述

為什麼要使用 V - Layout

在講解 V - Layout 前,我們先來搞懂一個問題:為什麼要使用 V - Layout

背景

  • Android中 UI 效能消耗主要來自於兩個方面
    1. 佈局層次巢狀導致多重 measure/layout
    2. View控制元件的建立和銷燬
  • 為了解決上述問題,現有的解決方式是
    1. 少用巢狀佈局
    2. 使用 ListView/GirdView/RecyclerView等基礎控制元件來處理View的回收與複用

但是,很多時候我們需要在一個長列表下做多種型別的佈局來分配各種元素,,特別是電商平臺首頁等頁面,佈局元素結構更加複雜多樣。如下圖:
在這裡插入圖片描述
此時的解決方案有所變化:不採用子View的複用,只採用一個主要的複用容器(如ListView 或 RecyclerView+LinearLayoutManager),然後在其中使用巢狀方式直接用各個元件進行拼接,減少了複用的能力

問題

這種做法還是會損失Android應用的效能。

解決方案

  • 通過自定義 LayoutManager 管理所有的佈局型別
  • 即阿里出品的基礎 UI 框架專案 VirtualLayout就是採用該方式來解決上述問題

簡介

  • 定義:VirtualLayout是阿里出品的基礎 UI 框架專案
  • 作用:快速實現複雜的佈局格式的混排 & 通過元件回收提高效能

在這裡插入圖片描述

應用場景

  • 複雜的佈局格式混排,如:浮動佈局、欄格佈局、通欄佈局、一拖N佈局、瀑布流佈局,還可以組合使用這些佈局
  • 具體場景是:如電商平臺首頁、活動頁等等

V - Layout 目前已在手機天貓 & 淘寶 Android 版內廣泛使用
在這裡插入圖片描述

原理解析

V - Layout的本質原理是:通過自定義一個VirtualLayoutManager(繼承自 LayoutManager),用於管理一系列LayoutHelper,將具體的佈局能力交給LayoutHelper來完成,從而 快速實現組合佈局 的需求

  1. 每個 LayoutHelper負責 頁面某一個範圍內的佈局
  2. V - Layout預設實現了10種預設佈局:(對應同名的LayoutHelper)

在這裡插入圖片描述

原始碼類說明

V - Layout的原始碼類圖如下:
在這裡插入圖片描述
具體類說明
在這裡插入圖片描述
V - Layout預設實現了10種預設佈局:(對應同名的LayoutHelper)
在這裡插入圖片描述

下面會進行詳細介紹。

特別注意:

  1. 每一種LayoutHelper負責佈局一批元件範圍內的元件,不同元件範圍內的元件之間,如果型別相同,可以在滑動過程中回收複用。因此回收粒度比較細,且可以跨佈局型別複用
  2. 支援擴充套件外部:即註冊新的LayoutHelper,實現特殊的佈局方式。下面會詳細說明

介紹完類之後,我將詳細分析 V - Layout的工作流程。

工作流程

V - Layout的工作流程分為 初始化 & 佈局流程。如下圖:
在這裡插入圖片描述
下面我將對初始化 & 佈局流程作詳細分析。

初始化

  • 在使用 V - layout 快速實現複雜佈局前,需要先做一系列的初始化工作
    初始化流程與使用普通的 RecyclerView + LayoutManager 初始化流程基本一致:Vlayout的使用者
    在這裡插入圖片描述
  • 此處的初始化 實際上 就是 使用者在使用 V - layout 時需要做的初始化工作。
  • 此處主要先講解下資料列表的獲取本質上,是對頁面實體 進行 卡片 & 元件的拆解,形成一個位置列表
    在這裡插入圖片描述
  • 其他初始化步驟將在下面例項講解進行詳細說明

具體佈局流程

  • 當完成初始化工作後,每當使用者剛開啟頁面第一次渲染布局 或 使用者滑動頁面時,都會進行一次佈局流程
  • 佈局流程的本質是:自定義 VirtualLayoutManager持續獲取頁面狀態,並通過LayoutHelperFinder找到對應的LayoutHelper從而實現對應的佈局邏輯,從而快速實現組合佈局 的需求
  • 具體流程如下
    在這裡插入圖片描述

總結

下面用一張圖總結 V - Layout 的原理 & 工作流程
在這裡插入圖片描述
在講完原理後,接下來我將如何使用 V - Layout。

使用步驟

  • V - Layout的使用其實就是上面說的初始化工作
  • 使用步驟如下:
    在這裡插入圖片描述
    下面我將對每個步驟進行詳細說明。

步驟1:建立RecyclerView & VirtualLayoutManager 物件並進行繫結

recyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
// 建立RecyclerView物件

VirtualLayoutManager layoutManager = new VirtualLayoutManager(this);
// 建立VirtualLayoutManager物件
// 同時內部會建立一個LayoutHelperFinder物件,用來後續的LayoutHelper查詢

recyclerView.setLayoutManager(layoutManager);
// 將VirtualLayoutManager繫結到recyclerView

步驟2:設定回收複用池大小

如果一屏內相同型別的 View 個數比較多,需要設定一個合適的大小,防止來回滾動時重新建立 View)

// 設定元件複用回收池
RecyclerView.RecycledViewPool viewPool = new RecyclerView.RecycledViewPool();
recyclerView.setRecycledViewPool(viewPool);
viewPool.setMaxRecycledViews(0, 10);

步驟3:設定Adapter

設定 V - Layout的Adapter有兩種方式:

  • 方式1:繼承 自 DelegateAdapter
  • 方式2:繼承 自 VirtualLayoutAdapter

下面會進行詳細說明:

方式1:繼承 自 DelegateAdapter

  • 定義DelegateAdapter是V - Layout專門為管理 LayoutHelper定製的 Adapter

  • 作用通過管理不同佈局的Adapter,繼而管理不同的 LayoutHelper,從而實現使用不同組合佈局

    • 特別注意:雖不可直接繫結LayoutHelper,但是它內部有一個繼承自RecyclerView.Adapter的內部類Adapter可以繫結LayoutHelper
    • 通過一個List把繫結好的Adapter打包起來,再放去DelegateAdapter,這樣就可以實現組合使用不同的佈局
  • 具體做法

    public class MyAdapter extends DelegateAdapter.Adapter<MyAdapter.MainViewHolder> {
    
    	// 比系統自帶的RecyclerAdapter需要多過載onCreateLayoutHelper()
    
        @Override
        public LayoutHelper onCreateLayoutHelper() {
            return layoutHelper;
        }
    
    	...
    	// 其餘寫法與複寫系統自帶的Adapter相同
    }
    

方式2:繼承 自 VirtualLayoutAdapter

  • 定義當需要實現複雜需求時, 可以通過繼承VirtualLayoutAdapter從而實現自定義Adapter

  • 具體使用

    public class MyAdapter extends VirtualLayoutAdapter {
       ...// 自定義Adapter邏輯
    }
    

步驟4:根據資料列表,建立對應的LayoutHelper

  • 系統已封裝好以下佈局型別(對應同名的LayoutHelper)
    在這裡插入圖片描述
  • 具體使用如下:

1. 線性佈局(LinearLayoutHelper)

  • 佈局說明:佈局子元素(Item)以線性排布的佈局
    在這裡插入圖片描述
    具體使用
    	// 設定線性佈局
        LinearLayoutHelper linearLayoutHelper = new LinearLayoutHelper();
        // 建立對應的LayoutHelper物件
    
    	 // 所有佈局的公共屬性(屬性會在下面詳細說明)
        linearLayoutHelper.setItemCount(4);// 設定佈局裡Item個數
        linearLayoutHelper.setPadding(10,10,10,10);// 設定LayoutHelper的子元素相對LayoutHelper邊緣的距離
        linearLayoutHelper.setMargin(10,10,10,10);// 設定LayoutHelper邊緣相對父控制元件(即RecyclerView)的距離
        linearLayoutHelper.setBgColor(Color.GRAY);// 設定背景顏色
        linearLayoutHelper.setAspectRatio(6);// 設定設定佈局內每行佈局的寬與高的比
    
    	 // linearLayoutHelper特有屬性
        linearLayoutHelper.setDividerHeight(1); // 設定每行Item的距離
    
1. 所有佈局的共有屬性說明:

a. setItemCount屬性

  • 作用:設定整個佈局裡的Item數量
    如設定的Item總數如與Adapter的getItemCount()方法返回的數量不同,會取決於後者
    在這裡插入圖片描述
  • 具體使用
    // 介面示意
    public void setItemCount(int Count)
    // 具體使用
    linearLayoutHelper.setItemCount(4);
    

b. Adding & Margin屬性

  • 定義:都是邊距的含義,但二者邊距的定義不同:
    • Padding:是 LayoutHelper 的子元素相對 LayoutHelper 邊緣的距離;
    • Margin:是 LayoutHelper 邊緣相對父控制元件(即RecyclerView)的距離。具體如下圖:
      在這裡插入圖片描述
  • 具體使用
// 介面示意
public void setPadding(int leftPadding, int topPadding, int rightPadding, int bottomPadding)
public void setMargin(int leftMargin, int topMargin, int rightMargin, int bottomMargin)

// 具體使用
linearLayoutHelper.setPadding(10,10,10,10);
// 設定LayoutHelper的子元素相對LayoutHelper邊緣的距離
linearLayoutHelper.setMargin(10,10,10,10);
// 設定LayoutHelper邊緣相對父控制元件(即RecyclerView)的距離

c. bgColor屬性

  • 作用:設定佈局背景顏色
  • 具體使用:
    // 介面示意
    public void setBgColor(int bgColor)
    // 具體使用
    linearLayoutHelper.setBgColor(Color.YELLOW);
    

d. aspectRatio屬性

  • 作用:設定佈局內每行佈局的寬與高的比。如下圖
    在這裡插入圖片描述
  • 具體使用
    // 介面示意
    public void setAspectRatio(float aspectRatio);
    // LayoutHelper定義的aspectRatio
    
    ((VirutalLayoutManager.LayoutParams) layoutParams).mAspectRatio
    // 檢視的LayoutParams定義的aspectRatio
    // 在LayoutHelper計算出檢視寬度之後,用來確定檢視高度時使用的,
    // 它會覆蓋通過LayoutHelper的aspectRatio計算出來的檢視高度,因此具備更高優先順序。
    
    // 具體使用
    linearLayoutHelper.setAspectRatio(6);
    
2. LinearLayoutHelper佈局的特有屬性說明

a. dividerHeight屬性

  • 作用:設定每行Item之間的距離
    設定的間隔會與RecyclerView的addItemDecoration()新增的間隔疊加
    在這裡插入圖片描述
  • 具體使用
    // 介面示意
    public void setDividerHeight(int dividerHeight)
    // 具體使用
     linearLayoutHelper.setDividerHeight(1);
    

2. 網格佈局(GridLayout)

  • 佈局說明:佈局裡的Item以網格的形式進行排列
    在這裡插入圖片描述
  • 具體使用
    // 設定Grid佈局
    GridLayoutHelper gridLayoutHelper = new GridLayoutHelper(3);
    // 在建構函式設定每行的網格個數
    
    // 公共屬性
    gridLayoutHelper.setItemCount(6);// 設定佈局裡Item個數
    gridLayoutHelper.setPadding(20, 20, 20, 20);// 設定LayoutHelper的子元素相對LayoutHelper邊緣的距離
    gridLayoutHelper.setMargin(20, 20, 20, 20);// 設定LayoutHelper邊緣相對父控制元件(即RecyclerView)的距離
    gridLayoutHelper.setBgColor(Color.GRAY);// 設定背景顏色
    gridLayoutHelper.setAspectRatio(6);// 設定設定佈局內每行佈局的寬與高的比
    
    // gridLayoutHelper特有屬性(下面會詳細說明)
    gridLayoutHelper.setWeights(new float[]{40, 30, 30});//設定每行中 每個網格寬度 佔 每行總寬度 的比例
    gridLayoutHelper.setVGap(20);// 控制子元素之間的垂直間距
    gridLayoutHelper.setHGap(20);// 控制子元素之間的水平間距
    gridLayoutHelper.setAutoExpand(false);//是否自動填充空白區域
    gridLayoutHelper.setSpanCount(3);// 設定每行多少個網格
    // 通過自定義SpanSizeLookup來控制某個Item的佔網格個數
    gridLayoutHelper.setSpanSizeLookup(new GridLayoutHelper.SpanSizeLookup() {
        @Override
        public int getSpanSize(int position) {
            if (position > 7 ) {
                return 3;
                // 第7個位置後,每個Item佔3個網格
            }else {
                return 2;
                // 第7個位置前,每個Item佔2個網格
            }
        }
    });
    
GridLayoutHelper佈局的特有屬性說明

a. weights屬性

  • 作用:設定每行中每個網格寬度佔每行總寬度的比例
    • 預設情況下,每行中每個網格中的寬度相等
    • weights屬性是一個float陣列,每一項代表當個網格佔每行總寬度的百分比;總和是100,否則佈局會超出容器寬度
    • 如果佈局中有4列,那麼weights的長度也應該是4;長度大於4,多出的部分不參與寬度計算;如果小於4,不足的部分預設平分剩餘的空間
      在這裡插入圖片描述
  • 具體使用
    // 介面示意
    public void setWeights(float[] weights)
    // 具體使用
    gridLayoutHelper.setWeights(new float[]{40, 30, 30});
    

b. vGap、hGap屬性

  • 作用分別控制子元素之間的垂直間距 和 水平間距。
    在這裡插入圖片描述
  • 具體使用
    // 介面示意
    public void setHGap(int hGap)
    public void setVGap(int vGap)
    // 具體使用
    gridLayoutHelper.setVGap(20);// 控制子元素之間的垂直間距
    gridLayoutHelper.setHGap(20);// 控制子元素之間的水平間距
    

c. spanCount、spanSizeLookup屬性

  • 作用:

    • spanCount:設定每行多少個網格
    • spanSizeLookup:設定每個 Item佔用多少個網格(預設= 1)

    在這裡插入圖片描述

  • 具體使用

    // 介面示意
    public void setSpanCount(int spanCount)
    public void setSpanSizeLookup(SpanSizeLookup spanSizeLookup)
    
    // 具體使用
    gridLayoutHelper.setSpanCount(5);// 設定每行多少個網格
    
    // 通過自定義SpanSizeLookup來控制某個Item的佔網格個數
    gridLayoutHelper.setSpanSizeLookup(new GridLayoutHelper.SpanSizeLookup() {
        @Override
        public int getSpanSize(int position) {
            if (position > 7 ) {
                return 3;
                // 第7個位置後,每個Item佔3個網格
            }else {
                return 2;
                // 第7個位置前,每個Item佔2個網格
            }
        }
    });
    

d. autoExpand屬性

  • 作用當一行裡item的個數 < (每行網格列數 - spanCount值/ 每個Item佔有2個網格-setSpanSizeLookup時,是否自動填滿空白區域

    • 若autoExpand=true,那麼檢視的總寬度會填滿可用區域
    • 否則會在螢幕上留空白區域

    在這裡插入圖片描述

  • 具體使用

    // 介面示意
    public void setAutoExpand(boolean isAutoExpand)
    
    // 具體使用
    gridLayoutHelper.setAutoExpand(false);
    

3. 固定佈局(FixLayoutHelper)

  • 佈局說明:佈局裡的Item 固定位置
    固定在螢幕某個位置,且不可拖拽 & 不隨頁面滾動而滾動。如下圖:(左上角)
    在這裡插入圖片描述

  • 具體使用

    		/**
             設定固定佈局
             */
            FixLayoutHelper fixLayoutHelper = new FixLayoutHelper(FixLayoutHelper.TOP_LEFT,40,100);
            // 引數說明:
            // 引數1:設定吸邊時的基準位置(alignType) - 有四個取值:TOP_LEFT(預設), TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT
            // 引數2:基準位置的偏移量x
            // 引數3:基準位置的偏移量y
    
    
            // 公共屬性
            fixLayoutHelper.setItemCount(1);// 設定佈局裡Item個數
            // 從設定Item數目的原始碼可以看出,一個FixLayoutHelper只能設定一個
    //        @Override
    //        public void setItemCount(int itemCount) {
    //            if (itemCount > 0) {
    //                super.setItemCount(1);
    //            } else {
    //                super.setItemCount(0);
    //            }
    //        }
            fixLayoutHelper.setPadding(20, 20, 20, 20);// 設定LayoutHelper的子元素相對LayoutHelper邊緣的距離
            fixLayoutHelper.setMargin(20, 20, 20, 20);// 設定LayoutHelper邊緣相對父控制元件(即RecyclerView)的距離
            fixLayoutHelper.setBgColor(Color.GRAY);// 設定背景顏色
            fixLayoutHelper.setAspectRatio(6);// 設定設定佈局內每行佈局的寬與高的比
    
            // fixLayoutHelper特有屬性
            fixLayoutHelper.setAlignType(FixLayoutHelper.TOP_LEFT);// 設定吸邊時的基準位置(alignType)
            fixLayoutHelper.setX(30);// 設定基準位置的橫向偏移量X
            fixLayoutHelper.setY(50);// 設定基準位置的縱向偏移量Y
    
FixLayoutHelper特有屬性說明

a. AlignType、x、y屬性

  • 作用
    • alignType:吸邊基準型別
      共有4個取值:TOP_LEFT(預設), TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT,具體請看下面示意圖
    • x:基準位置的橫向偏移量
    • y:基準位置的縱向偏移量

在這裡插入圖片描述

  • 作用物件FixLayoutHelper, ScrollFixLayoutHelper, FloatLayoutHelper的屬性

    // 介面示意
    public void setAlignType(int alignType)
    public void setX(int x)
    public void setY(int y)
    
    // 具體使用
    fixLayoutHelper.setAlignType(FixLayoutHelper.TOP_LEFT);
    fixLayoutHelper.setX(30);
    fixLayoutHelper.setY(50);
    

4. 可選顯示的固定佈局(ScrollFixLayoutHelper)

  • 佈局說明:佈局裡的Item 固定位置

    • 固定在螢幕某個位置,且不可拖拽 & 不隨頁面滾動而滾動(繼承自固定佈局(FixLayoutHelper))
    • 唯一不同的是,可以自由設定該Item什麼時候顯示(到頂部顯示 / 到底部顯示),可如下圖:(左上角)
    • 需求場景到頁面底部顯示”一鍵到頂部“的按鈕功能

    以下示意圖為:滑動到底部,佈局才在左上角顯示
    在這裡插入圖片描述

  • 具體使用

    		/**
             設定可選固定佈局
             */
            ScrollFixLayoutHelper scrollFixLayoutHelper = new ScrollFixLayoutHelper(ScrollFixLayoutHelper.TOP_RIGHT,0,0);
            // 引數說明:
            // 引數1:設定吸邊時的基準位置(alignType) - 有四個取值:TOP_LEFT(預設), TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT
            // 引數2:基準位置的偏移量x
            // 引數3:基準位置的偏移量y
    
            // 公共屬性
            scrollFixLayoutHelper.setItemCount(1);// 設定佈局裡Item個數
            // 從設定Item數目的原始碼可以看出,一個FixLayoutHelper只能設定一個
    //        @Override
    //        public void setItemCount(int itemCount) {
    //            if (itemCount > 0) {
    //                super.setItemCount(1);
    //            } else {
    //                super.setItemCount(0);
    //            }
    //        }
            scrollFixLayoutHelper.setPadding(20, 20, 20, 20);// 設定LayoutHelper的子元素相對LayoutHelper邊緣的距離
            scrollFixLayoutHelper.setMargin(20, 20, 20, 20);// 設定LayoutHelper邊緣相對父控制元件(即RecyclerView)的距離
            scrollFixLayoutHelper.setBgColor(Color.GRAY);// 設定背景顏色
            scrollFixLayoutHelper.setAspectRatio(6);// 設定設定佈局內每行佈局的寬與高的比
    
            // fixLayoutHelper特有屬性
         	scrollFixLayoutHelper.setAlignType(FixLayoutHelper.TOP_LEFT);// 設定吸邊時的基準位置(alignType)
            scrollFixLayoutHelper.setX(30);// 設定基準位置的橫向偏移量X
            scrollFixLayoutHelper.setY(50);// 設定基準位置的縱向偏移量Y
            scrollFixLayoutHelper.setShowType(ScrollFixLayoutHelper.SHOW_ON_ENTER);// 設定Item的顯示模式
    
ScrollFixLayoutHelper特有屬性說明

a. AlignType、x、y屬性

  • 作用
    • alignType:吸邊基準型別
      共有4個取值:TOP_LEFT(預設), TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT,具體請看下面示意圖
    • x:基準位置的橫向偏移量
    • y:基準位置的縱向偏移量
      在這裡插入圖片描述
  • 具體使用
    // 介面示意
    public void setAlignType(int alignType)
    public void setX(int x)
    public void setY(int y)
    
    // 具體使用
    ScrollFixLayoutHelper.setAlignType(FixLayoutHelper.TOP_LEFT);
    ScrollFixLayoutHelper.setX(30);
    ScrollFixLayoutHelper.setY(50);
    

b. ShowType屬性

  • 作用設定Item的顯示模式,共有三種顯示模式
    • SHOW_ALWAYS:永遠顯示(即效果同固定佈局)
    • SHOW_ON_ENTER:預設不顯示檢視,當頁面滾動到該檢視位置時才顯示;
    • SHOW_ON_LEAVE:預設不顯示檢視,當頁面滾出該檢視位置時才顯示
  • 具體使用
    // 介面示意
    public void setShowType(int showType)
    // 具體使用
    scrollFixLayoutHelper.setShowType(ScrollFixLayoutHelper.SHOW_ON_ENTER);
    

5. 浮動佈局(FloatLayoutHelper)

  • 佈局說明:佈局裡的Item只有一個
    • 可隨意拖動,但最終會被吸邊到兩側
    • 不隨頁面滾動而移動
      在這裡插入圖片描述
  • 具體使用
    		/**
             設定浮動佈局
             */
            FloatLayoutHelper floatLayoutHelper = new FloatLayoutHelper();
            // 建立FloatLayoutHelper物件
    
            // 公共屬性
            floatLayoutHelper.setItemCount(1);// 設定佈局裡Item個數
            // 從設定Item數目的原始碼可以看出,一個FixLayoutHelper只能設定一個
    //        @Override
    //        public void setItemCount(int itemCount) {
    //            if (itemCount > 0) {
    //                super.setItemCount(1);
    //            } else {
    //                super.setItemCount(0);
    //            }
    //        }
            floatLayoutHelper.setPadding(20, 20, 20, 20);// 設定LayoutHelper的子元素相對LayoutHelper邊緣的距離
            floatLayoutHelper.setMargin(20, 20, 20, 20);// 設定LayoutHelper邊緣相對父控制元件(即RecyclerView)的距離
            floatLayoutHelper.setBgColor(Color.GRAY);// 設定背景顏色
            floatLayoutHelper.setAspectRatio(6);// 設定設定佈局內每行佈局的寬與高的比
    
            // floatLayoutHelper特有屬性
            floatLayoutHelper.setDefaultLocation(300,300);// 設定佈局裡Item的初始位置
    

6. 欄格佈局(ColumnLayoutHelper)

  • 佈局說明該佈局只設有一欄(該欄設定多個Item),可理解為只有一行的線性佈局
    在這裡插入圖片描述
    /**
     設定欄格佈局
     */
    ColumnLayoutHelper columnLayoutHelper = new ColumnLayoutHelper();
    // 建立物件
    
    // 公共屬性
    columnLayoutHelper.setItemCount(3);// 設定佈局裡Item個數
    columnLayoutHelper.setPadding(20, 20, 20, 20);// 設定LayoutHelper的子元素相對LayoutHelper邊緣的距離
    columnLayoutHelper.setMargin(20, 20, 20, 20);// 設定LayoutHelper邊緣相對父控制元件(即RecyclerView)的距離
    columnLayoutHelper.setBgColor(Color.GRAY);// 設定背景顏色
    columnLayoutHelper.setAspectRatio(6);// 設定設定佈局內每行佈局的寬與高的比
    
    // columnLayoutHelper特有屬性
    columnLayoutHelper.setWeights(new float[]{30, 40, 30});// 設定該行每個Item佔該行總寬度的比例
    // 同上面Weigths屬性講解
    

7. 通欄佈局(SingleLayoutHelper)

  • 佈局說明佈局只有一欄,該欄只有一個Item
    在這裡插入圖片描述
  • 具體使用
    /**
     設定通欄佈局
     */
    
    SingleLayoutHelper singleLayoutHelper = new SingleLayoutHelper();
    
    // 公共屬性
    singleLayoutHelper.setItemCount(3);// 設定佈局裡Item個數
    singleLayoutHelper.setPadding(20, 20, 20, 20);// 設定LayoutHelper的子元素相對LayoutHelper邊緣的距離
    singleLayoutHelper.setMargin(20, 20, 20, 20);// 設定LayoutHelper邊緣相對父控制元件(即RecyclerView)的距離
    singleLayoutHelper.setBgColor(Color.GRAY);// 設定背景顏色
    singleLayoutHelper.setAspectRatio(6);// 設定設定佈局內每行佈局的寬與高的比
    

8. 一拖N佈局 (OnePlusNLayoutHelper)

佈局說明將佈局分為不同比例,最多是1拖4。具體如下圖
在這裡插入圖片描述
在這裡插入圖片描述

  • 具體使用
    /**
     設定1拖N佈局
     */
    OnePlusNLayoutHelper onePlusNLayoutHelper = new OnePlusNLayoutHelper(5);
    // 在建構函式裡傳入顯示的Item數
    // 最多是1拖4,即5個
    
    // 公共屬性
    onePlusNLayoutHelper.setItemCount(3);// 設定佈局裡Item個數
    onePlusNLayoutHelper.setPadding(20, 20, 20, 20);// 設定LayoutHelper的子元素相對LayoutHelper邊緣的距離
    onePlusNLayoutHelper.setMargin(20, 20, 20, 20);// 設定LayoutHelper邊緣相對父控制元件(即RecyclerView)的距離
    onePlusNLayoutHelper.setBgColor(Color.GRAY);// 設定背景顏色
    onePlusNLayoutHelper.setAspectRatio(3);// 設定設定佈局內每行佈局的寬與高的比
    

9. 吸邊佈局(StickyLayoutHelper)

  • 佈局說明佈局只有一個Item,顯示邏輯如下:
    • 當它包含的元件處於螢幕可見範圍內時,像正常的元件一樣隨頁面滾動而滾動
    • 當元件將要被滑出螢幕返回的時候,可以吸到螢幕的頂部或者底部,實現一種吸住的效果
  • 示意圖(吸在頂部)
    在這裡插入圖片描述
  • 具體使用
    /**
     設定吸邊佈局
     */
    StickyLayoutHelper stickyLayoutHelper = new StickyLayoutHelper();
    
    // 公共屬性
    stickyLayoutHelper.setItemCount(3);// 設定佈局裡Item個數
    stickyLayoutHelper.setPadding(20, 20, 20, 20);// 設定LayoutHelper的子元素相對LayoutHelper邊緣的距離
    stickyLayoutHelper.setMargin(20, 20, 20, 20);// 設定LayoutHelper邊緣相對父控制元件(即RecyclerView)的距離
    stickyLayoutHelper.setBgColor(Color.GRAY);// 設定背景顏色
    stickyLayoutHelper.setAspectRatio(3);// 設定設定佈局內每行佈局的寬與高的比
    
    // 特有屬性
    stickyLayoutHelper.setStickyStart(true);
    // true = 元件吸在頂部
    // false = 元件吸在底部
    
    stickyLayoutHelper.setOffset(100);// 設定吸邊位置的偏移量
    
    Adapter_StickyLayout = new MyAdapter(this, stickyLayoutHelper,1, listItem) {
        // 設定需要展示的資料總數,此處設定是1
        // 為了展示效果,通過重寫onBindViewHolder()將佈局的第一個資料設定為Stick
        @Override
        public void onBindViewHolder(MainViewHolder holder, int position) {
            super.onBindViewHolder(holder, position);
            if (position == 0) {
                holder.Text.setText("Stick");
            }
        }
    };
    
    adapters.add(Adapter_StickyLayout) ;
    // 將當前的Adapter加入到Adapter列表裡
    
stickyStart、 offset屬性說明
  • 作用
    • stickyStart設定吸邊位置
      當檢視的位置在螢幕範圍內時,檢視會隨頁面滾動而滾動;當檢視的位置滑出螢幕時,StickyLayoutHelper會將檢視固定在頂部(stickyStart = true)或 底部(stickyStart = false)

    • offset:設定吸邊的偏移量

  • 具體使用
    // 介面示意
    public void setStickyStart(boolean stickyStart)
    public void setOffset(int offset)
    
    // 具體使用
    stickyLayoutHelper.setStickyStart(true);
    // true = 元件吸在頂部
    // false = 元件吸在底部
    stickyLayoutHelper.setOffset(100);// 設定吸邊位置的偏移量
    

10. 瀑布流佈局(StaggeredGridLayoutHelper)

  • 佈局說明:以網格的形式進行佈局。與網格佈局類似,區別在於:
    • 網格佈局每欄的Item高度是相等的
    • 瀑布流佈局每欄的Item高度是可以不相等的
      在這裡插入圖片描述
  • 具體使用
    /**
     設定瀑布流佈局
     */
    
    StaggeredGridLayoutHelper staggeredGridLayoutHelper = new StaggeredGridLayoutHelper();
    // 建立物件
    
    // 公有屬性
    staggeredGridLayoutHelper.setItemCount(20);// 設定佈局裡Item個數
    staggeredGridLayoutHelper.setPadding(20, 20, 20, 20);// 設定LayoutHelper的子元素相對LayoutHelper邊緣的距離
    staggeredGridLayoutHelper.setMargin(20, 20, 20, 20);// 設定LayoutHelper邊緣相對父控制元件(即RecyclerView)的距離
    staggeredGridLayoutHelper.setBgColor(Color.GRAY);// 設定背景顏色
    staggeredGridLayoutHelper.setAspectRatio(3);// 設定設定佈局內每行佈局的寬與高的比
    
    // 特有屬性
    staggeredGridLayoutHelper.setLane(3);// 設定控制瀑布流每行的Item數
    staggeredGridLayoutHelper.setHGap(20);// 設定子元素之間的水平間距
    staggeredGridLayoutHelper.setVGap(15);// 設定子元素之間的垂直間距
    

自定義佈局(即自定義LayoutHelper)

除了使用系統提供的預設佈局 LayoutHelper,開發者還可以通過自定義LayoutHelper從而實現自定義佈局樣式。有三種方式:

  • 繼承BaseLayoutHelper從上而下排列的順序 & 內部 View可以按行回收的佈局;主要實現layoutViews()、computeAlignOffset()等方法
    LinearLayoutHelper、GridLayoutHelper都是採用該方法實現

  • 繼承AbstractFullFillLayoutHelper:有些佈局內部的 View 並不是從上至下排列的順序(即 Adatper 裡的資料順序和物理檢視順序不一致,那麼可能就不能按資料順序佈局和回收),需要一次性佈局 & 回收主要實現layoutViews()等方法
    OnePlusNLayoutHelper採用該方法實現

  • 繼承FixAreaLayoutHelperfix 型別佈局,子節點不隨頁面滾動而滾動主要實現layoutViews()、beforeLayout()、afterLayout()等方法
    FixLayoutHelper採用該方法實現

步驟5:將生成的LayoutHelper 交給Adapter,並繫結到RecyclerView 物件

此處的做法會因步驟3中Adapter的設定而有所不同

<-- Adapter繼承 自 DelegateAdapter -->
// 步驟1:設定Adapter列表(同時也是設定LayoutHelper列表)
List<DelegateAdapter.Adapter> adapters = new LinkedList<>();
// 步驟2:建立自定義的Adapter物件 & 繫結資料 & 繫結上述對應的LayoutHelper
// 繫結你需要展示的佈局LayoutHelper即可,此處僅展示兩個。
MyAdapter Adapter_linearLayout = new MyAdapter(this, linearLayoutHelper,ListItem)// ListItem是需要繫結的資料(其實取決於你的Adapter如何定義)
MyAdapter Adapter_gridLayoutHelper = new MyAdapter(this, gridLayoutHelper,ListItem)// 步驟3:將建立的Adapter物件放入到DelegateAdapter.Adapter列表裡
adapters.add(Adapter_linearLayout ) ;
adapters.add(Adapter_gridLayoutHelper ) ;
// 步驟4:建立DelegateAdapter物件 & 將layoutManager繫結到DelegateAdapter
DelegateAdapter delegateAdapter = new DelegateAdapter(layoutManager);
// 步驟5:將DelegateAdapter.Adapter列表繫結到DelegateAdapter
delegateAdapter.setAdapters(adapters);
// 步驟6:將delegateAdapter繫結到recyclerView
recyclerView.setAdapter(delegateAdapter);

<-- Adapter繼承 自 VirtualLayoutAdapter -->
// 步驟1:設定LayoutHelper列表
List<LayoutHelper> helpers = new LinkedList<>();
// 步驟2:繫結上述對應的LayoutHelper
helpers.add(Adapter_linearLayout );
helpers.add(Adapter_gridLayoutHelper ) ;
// 步驟3:建立自定義的MyAdapter物件 & 繫結layoutManager
MyAdapter myAdapter = new MyAdapter(layoutManager);
// 步驟4:將 layoutHelper 列表傳遞給 adapter
myAdapter.setLayoutHelpers(helpers);
// 步驟5:將adapter繫結到recyclerView
recycler.setAdapter(myAdapter);

至此,使用過程講解完畢。

例項說明

  • V-Layout的優點在於快速的組合不同佈局
  • 下面,我將根據上面的步驟說明,用一個例項來使用 V - Layout快速組合佈局

步驟1:在Android - Gradle加入依賴

compile ('com.alibaba.android:vlayout:1.0.3@aar') {
    transitive = true
}

步驟2:定義主xml佈局

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="scut.carson_ho.v_layoutusage.MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/my_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="horizontal" />
</RelativeLayout>

步驟3:定義 RecyclerView每個子元素(Item)的xml佈局

item.xml

此處定義的 Item 佈局是常用的 上字下圖
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="New Text"
        android:id="@+id/Item" />

    <ImageView
        android:layout_alignParentRight="true"
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/Image"/>

</LinearLayout>

步驟4:設定Adapter

  • 設定 V - Layout的Adapter有兩種方式:
    • 繼承 自 DelegateAdapter,此處主要以該方式進行演示
    • 繼承 自 VirtualLayoutAdapter
  • 具體使用
    MyAdapter.java
    public class MyAdapter extends DelegateAdapter.Adapter<MyAdapter.MainViewHolder> {
        // 使用DelegateAdapter首先就是要自定義一個它的內部類Adapter,讓LayoutHelper和需要繫結的資料傳進去
        // 此處的Adapter和普通RecyclerView定義的Adapter只相差了一個onCreateLayoutHelper()方法,其他的都是一樣的做法.
    
        private ArrayList<HashMap<String, Object>> listItem;
        // 用於存放資料列表
    
        private Context context;
        private LayoutHelper layoutHelper;
        private RecyclerView.LayoutParams layoutParams;
        private int count = 0;
    
        private MyItemClickListener myItemClickListener;
        // 用於設定Item點選事件
    
        //建構函式(傳入每個的資料列表 & 展示的Item數量)
        public MyAdapter(Context context, LayoutHelper layoutHelper, int count, ArrayList<HashMap<String, Object>> listItem) {
            this(context, layoutHelper, count, new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300), listItem);
        }
    
        public MyAdapter(Context context, LayoutHelper layoutHelper, int count, @NonNull RecyclerView.LayoutParams layoutParams, ArrayList<HashMap<String, Object>> listItem) {
            this.context = context;
            this.layoutHelper = layoutHelper;
            this.count = count;
            this.layoutParams = layoutParams;
            this.listItem = listItem;
        }
    
        // 把ViewHolder繫結Item的佈局
        @Override
        public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new MainViewHolder(LayoutInflater.from(context).inflate(R.layout.item, parent, false));
        }
    
        // 此處的Adapter和普通RecyclerView定義的Adapter只相差了一個onCreateLayoutHelper()方法
        @Override
        public LayoutHelper onCreateLayoutHelper() {
            return layoutHelper;
        }
    
        // 繫結Item的資料
        @Override
        public void onBindViewHolder(MainViewHolder holder, int position) {
            holder.Text.setText((String) listItem.get(position).get("ItemTitle"));
            holder.image.setImageResource((Integer) listItem.get(position).get("ItemImage"));
    
        }
    
        // 返回Item數目
        @Override
        public int getItemCount() {
            return count;
        }
    
        // 設定Item的點選事件
        // 繫結MainActivity傳進來的點選監聽器
        public void setOnItemClickListener(MyItemClickListener listener) {
            myItemClickListener = listener;
        }
    
        
        //定義Viewholder
        class MainViewHolder extends RecyclerView.ViewHolder {
            public TextView Text;
            public ImageView image;
    
            public MainViewHolder(View root) {
                super(root);
                
                // 繫結檢視
                Text = (TextView) root.findViewById(R.id.Item);
                image = (ImageView) root.findViewById(R.id.Image);
    
                root.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (myItemClickListener != null)
                            myItemClickListener.onItemClick(v, getPosition());
                    }
    
                }
                //監聽到點選就回撥MainActivity的onItemClick函式
                );
    
            }
    
            public TextView getText() {
                return Text;
            }
        }
    }
    

以下步驟都將寫在同一個.Java檔案裡

  • 步驟1:建立RecyclerView & VirtualLayoutManager 物件並進行繫結
  • 步驟2:設定回收複用池大小
  • 步驟3:設定需要存放的資料
  • 步驟4:根據資料列表,建立對應的LayoutHelper
  • 步驟5:將生成的LayoutHelper 交給Adapter,並繫結到RecyclerView 物件

詳細請看註釋

*MainActivity.java*
public class MainActivity extends AppCompatActivity implements MyItemClickListener {
    RecyclerView recyclerView;
    MyAdapter Adapter_linearLayout,Adapter_GridLayout,Adapter_FixLayout,Adapter_ScrollFixLayout
            ,Adapter_FloatLayout,Adapter_ColumnLayout,Adapter_SingleLayout,Adapter_onePlusNLayout,
            Adapter_StickyLayout,Adapter_StaggeredGridLayout;
    private ArrayList<HashMap<String, Object>> listItem;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        /**
         * 步驟1:建立RecyclerView & VirtualLayoutManager 物件並進行繫結
         * */
        recyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
        // 建立RecyclerView物件

        VirtualLayoutManager layoutManager = new VirtualLayoutManager(this);
        // 建立VirtualLayoutManager物件
        // 同時內部會建立一個LayoutHelperFinder物件,用來後續的LayoutHelper查詢

        recyclerView.setLayoutManager(layoutManager);
        // 將VirtualLayoutManager繫結到recyclerView

        /**
         * 步驟2:設定元件複用回收池
         * */
        RecyclerView.RecycledViewPool viewPool = new RecyclerView.RecycledViewPool();
        recyclerView.setRecycledViewPool(viewPool);
        viewPool.setMaxRecycledViews(0, 10);

        /**
         * 步驟3:設定需要存放的資料
         * */
        listItem = new ArrayList<HashMap<String, Object>>();
        for (int i = 0; i < 100; i++) {
            HashMap<String, Object> map = new HashMap<String, Object>();
            map.put("ItemTitle", "第" + i + "行");
            map.put("ItemImage", R.mipmap.ic_launcher);
            listItem.add(map);

        }

        /**
         * 步驟4:根據資料列表,建立對應的LayoutHelper
         * */

        // 為了展示效果,此處將上面介紹的所有佈局都顯示出來

        /**
         設定線性佈局
         */
        LinearLayoutHelper linearLayoutHelper = new LinearLayoutHelper();
        // 建立對應的LayoutHelper物件

        // 公共屬性
        linearLayoutHelper.setItemCount(4);// 設定佈局裡Item個數
        linearLayoutHelper.setPadding(20, 20, 20, 20);// 設定LayoutHelper的子元素相對LayoutHelper邊緣的距離
        linearLayoutHelper.setMargin(20, 20, 20, 20);// 設定LayoutHelper邊緣相對父控制元件(即RecyclerView)的距離
        // linearLayoutHelper.setBgColor(Color.GRAY);// 設定背景顏色
        linearLayoutHelper.setAspectRatio(6);// 設定設定佈局內每行佈局的寬與高的比

        // linearLayoutHelper特有屬性
        linearLayoutHelper.setDividerHeight(10);
        // 設定間隔高度
        // 設定的間隔會與RecyclerView的addItemDecoration()新增的間隔疊加.

        linearLayoutHelper.setMarginBottom(100);
        // 設定佈局底部與下個佈局的間隔


        // 建立自定義的Adapter物件 & 繫結資料 & 繫結對應的LayoutHelper進行佈局繪製
         Adapter_linearLayout  = new MyAdapter(this, linearLayoutHelper, 4, listItem) {
             // 引數2:繫結繫結對應的LayoutHelper
             // 引數3:傳入該佈局需要顯示的資料個數
             // 引數4:傳入需要繫結的資料

             // 通過重寫onBindViewHolder()設定更豐富的佈局效果
             @Override
             public void onBindViewHolder(MainViewHolder holder, int position) {
                 super.onBindViewHolder(holder, position);
                 // 為了展示效果,將佈局的第一個資料設定為linearLayout
                 if (position == 0) {
                     holder.Text.setText("Linear");
                 }

                  //為了展示效果,將佈局裡不同位置的Item進行背景顏色設定
                 if (position < 2) {
                     holder.itemView.setBackgroundColor(0x66cc0000 + (position - 6) * 128);
                 } else if (position % 2 == 0) {
                     holder.itemView.setBackgroundColor(0xaa22ff22);
                 } else {
                     holder.itemView.setBackgroundColor(0xccff22ff);
                 }

             }
         };

        Adapter_linearLayout.setOnItemClickListener(this);
        // 設定每個Item的點選事件

        ....// 還有其他佈局,由於程式碼量就較多就不貼出來了。

        /**
         *步驟5:將生成的LayoutHelper 交給Adapter,並繫結到RecyclerView 物件
         **/

        // 1. 設定Adapter列表(同時也是設定LayoutHelper列表)
        List<DelegateAdapter.Adapter> adapters = new LinkedList<>();

        // 2. 將上述建立的Adapter物件放入到DelegateAdapter.Adapter列表裡
        adapters.add(Adapter_linearLayout) ;
        adapters.add(Adapter_StickyLayout) ;
        adapters.add(Adapter_ScrollFixLayout) ;
        adapters.add(Adapter_GridLayout) ;
        adapters.add(Adapter_FixLayout) ;
        adapters.add(Adapter_FloatLayout) ;
        adapters.add(Adapter_ColumnLayout) ;
        adapters.add(Adapter_SingleLayout) ;
        adapters.add(Adapter_onePlusNLayout) ;
        adapters.add(Adapter_StaggeredGridLayout) ;

        // 3. 建立DelegateAdapter物件 & 將layoutManager繫結到DelegateAdapter
        DelegateAdapter delegateAdapter = new DelegateAdapter(layoutManager);

        // 4. 將DelegateAdapter.Adapter列表繫結到DelegateAdapter
        delegateAdapter.setAdapters(adapters);

        // 5. 將delegateAdapter繫結到recyclerView
        recyclerView.setAdapter(delegateAdapter);


        /**
         *步驟6:Item之間的間隔
         **/

        recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
            public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
                outRect.set(5, 5, 5, 5);
            }
        });


    }

    /**
     *步驟7:實現Item點選事件
     **/
    // 點選事件的回撥函式
    @Override
    public void onItemClick(View view, int postion) {
        System.out.println("點選了第"+postion+"行");
        Toast.makeText(this, (String) listItem.get(postion).get("ItemTitle"), Toast.LENGTH_SHORT).show();
    }
}

效果圖
在這裡插入圖片描述

原始碼地址
Carson_Ho的Github地址:V - Layout

參考文件
https://github.com/alibaba/vlayout
http://pingguohe.net/2017/02/28/vlayout-design.html

總結

  • 看完本文,你應該非常瞭解阿里出品的V - Layout 的使用 & 原理
  • 但該開源庫還是存在許多小Bug,我在Github上也提交了一些,希望大家能一起在Github - alibaba - vlayout 上進行完善,共同為開源事業做貢獻吧!

相關文章