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

前言

本文章來源於:Carson_Ho的博客博客地址








  • V- Layout 是阿里出品的基礎 UI 框架,用於快速實現頁面的複雜佈局,在手機天貓 Android版 內廣泛使用

電商圖


Github
  • 在五一假期我對V- Layout進行了詳細分析,我將獻上一份 V- Layout的使用攻略 & 源碼分析,希望你們會喜歡。 

目錄

目錄


1. 爲什麼要使用 V - Layout

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

1.1 背景

  • Android中 UI 性能消耗主要來自於兩個方面:

    1. 佈局層次嵌套導致多重 measure/layout
    2. View控件的創建和銷燬
  • 爲了解決上述問題,現有的解決方式是:

    1. 少用嵌套佈局
    2. 使用 ListView/GirdView/RecyclerView等基礎空間來處理View的回收與複用。

但是,很多時候我們需要在一個長列表下做多種類型的佈局來分配各種元素,,特別是電商平臺首頁等頁面,佈局元素結構更加複雜多樣。如下圖: 
電商圖

此時的解決方案有所變化:不採用子View的複用,只採用一個主要的複用容器(如ListView 或 RecyclerView+LinearLayoutManager),然後在其中使用嵌套方式直接用各個組件進行拼接,減少了複用的能力

1.2 問題

這種做法還是會損失android應用的性能。

1.3 解決方案

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

2. 簡介

  • 定義:VirtualLayout是阿里出品的基礎 UI 框架項目
  • 作用:快速實現複雜的佈局格式的混排 

    效果圖

3. 應用場景

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

    V - Layout 目前已在手機天貓 & 淘寶 Android 版內廣泛使用


實際應用效果圖

4. 原理解析

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

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

4.1 源碼類說明

V - Layout的源碼類圖如下:

UML類圖

1.RecyclerView

  • 定義:頁面佈局的主體
  • 特別注意:在V - layout框架裏綁定 VirtualLayoutAdapter(繼承Adapter) & VirtualLayoutManager(繼承LayoutManager

2. VirtualLayoutAdapter

  • 定義:數據適配器。 
    繼承自系統的Adaper
  • 作用:創建組件 & 綁定數據到組件
  • 額外:定義了兩個接口: 
    1. getLayoutHelper():用於返回某個位置組件對應的一個LayoutHelper
    2. setLayoutHelpers():調用此方法設置整個頁面所需要的一系列LayoutHelper 
      這兩方法的具體實現委託給 VirtualLayoutManager完成

3. VirtualLayoutManager

  • 定義:佈局管理器 
    繼承自系統的 LinearLayoutManager
  • 作用: 
    1. 在 RecyclerView加載組件或者滑動時調用VirtualLayoutManager 的layoutChunk(),返回當前還有哪些空白區域可擺放組件
    2. 管理 LayoutHelper列表
  • 額外:實現了VirtualLayoutAdapter的 getLayoutHelper() & setLayoutHelpers()

4. LayoutHelperFinder

  • 定義:LayoutHelper 尋找器
  • 作用:根據頁面狀態 尋找對應的 LayoutHelper 並返回給 VirtualLayoutManager 
    1. VirtualLayoutManager會持有一個LayoutHelperFinder 
    2. layoutChunck()被調用時會傳入一個位置參數,告訴VirtualLayoutManager當前要佈局第幾個組件 
    3. VirtualLayoutManager通知持有的 LayoutHelperFinder找到傳入參數位置對應的 LayoutHelper(每個 LayoutHelper 都會綁定它負責的佈局區域的起始位置和結束位置)

5. LayoutHelper

  • 定義:佈局協助器
  • 作用:負責具體的佈局邏輯
  • 其中定義了一系列接口用於和VirtualLayoutManager通信:
接口 作用
isOutOfRange() 告訴VirtualLayoutManager它所傳遞過來位置是否在當前LayoutHelper的佈局區域內;
setRange() 設置當前LayoutHelper負責的佈局區域
doLayout() 真正的佈局邏輯接口
beforeLayout() 在佈局前做一些前置工作
afterLayout() 在佈局完成後做一些後置工作

6. MarginLayoutHelper

  • 定義:繼承自 LayoutHelper
  • 作用:擴展LayoutHelper,提供了佈局常用的內邊距padding、外邊距margin的計算功能

7. BaseLayoutHelper

  • 定義:MarginLayoutHelper的第一層具體實現
  • 作用:填充 當前LayoutHelper在屏幕範圍內的具體區域 背景色、背景圖等邏輯

8. 子LayoutHelper

  • 定義:MarginLayoutHelper的第二層具體實現
  • 作用:負責具體的佈局邏輯 
    1. 每種子LayoutHelper負責一種佈局邏輯 
    2. 重點實現了beforeLayout()doLayout()afterLayout() 
    3. 特別是doLayout():會獲取一組件,並對組件進行尺寸計算、界面佈局。
    4. V - Layout默認實現了10種默認佈局:(對應同名的LayoutHelper)

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

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


4.2 工作流程

  • V - Layout的工作流程分爲 初始化 & 佈局流程。如下圖:

工作流程

  • 下面我將對初始化 & 佈局流程作詳細分析。

4.2.1 初始化


  • 在使用 V - layout 快速實現複雜佈局前,需要先做一系列的初始化工作。 

初始化流程與使用普通的 RecyclerView + LayoutManager 初始化流程基本一致:Vlayout的使用者 
初始化流程

此處的初始化 實際上 就是 使用者在使用 V - layout 時需要做的初始化工作,在下面的實例講解我會進行更加詳細的說明。


4.2.2 具體佈局流程

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

佈局流程


總結

下面用一張圖總結 V - Layout 的原理 & 工作流程 
原理 & 工作流程

在講完原理後,接下來我將如何使用 V - Layout


5. 使用步驟

  • 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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

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

// 設置組件複用回收池
        RecyclerView.RecycledViewPool viewPool = new RecyclerView.RecycledViewPool();
        recyclerView.setRecycledViewPool(viewPool);
        viewPool.setMaxRecycledViews(0, 10);
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

步驟3:設置Adapter

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

  • 方式1:繼承 自 DelegateAdapter
  • 方式2:繼承 自 VirtualLayoutAdapter 
    下面會進行詳細說明:

方式1:繼承 自 DelegateAdapter

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

    繼承自VirtualLayoutAdapter

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

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

    1. 寫法與複寫系統自帶的Adapter非常類似:只比系統自帶的RecyclerAdapter需要多重載onCreateLayoutHelper方法,其餘類似
    2. 關於Android系統自帶的RecyclerAdapter的使用具體請看我寫的文章Android開發:ListView、AdapterView、RecyclerView全面解析 
public class MyAdapter extends DelegateAdapter.Adapter<MyAdapter.MainViewHolder> {

// 比系統自帶的RecyclerAdapter需要多重載onCreateLayoutHelper()

    @Override
    public LayoutHelper onCreateLayoutHelper() {
        return layoutHelper;
    }

... // 其餘寫法與複寫系統自帶的Adapter相同
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

方式2:繼承 自 VirtualLayoutAdapter

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

  • 具體使用

public class MyAdapter extends VirtualLayoutAdapter {
   ...// 自定義Adapter邏輯
}
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

步驟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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

1. 所有佈局的共有屬性說明:

a. setItemCount屬性

  • 作用:設置整個佈局裏的Item數量 

    如設置的Item總數如與Adapter的getItemCount()方法返回的數量不同,會取決於後者


示意圖
  • 具體使用
// 接口示意
    public void setItemCount(int Count)

// 具體使用
        linearLayoutHelper.setItemCount(4);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

b. Adding & Margin屬性

  • 定義:都是邊距的含義,但二者邊距的定義不同:

    1. Padding:是 LayoutHelper 的子元素相對 LayoutHelper 邊緣的距離;
    2. 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)的距離
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

c. bgColor屬性

  • 作用:設置佈局背景顏色
  • 具體使用:
// 接口示意
public void setBgColor(int bgColor)

// 具體使用
linearLayoutHelper.setBgColor(Color.YELLOW);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

d. aspectRatio屬性

  • 作用:設置佈局內每行佈局的寬與高的比。如下圖

示意圖

  • 具體使用
// 接口示意
public void setAspectRatio(float aspectRatio);
// LayoutHelper定義的aspectRatio

((VirutalLayoutManager.LayoutParams) layoutParams).mAspectRatio
// 視圖的LayoutParams定義的aspectRatio
// 在LayoutHelper計算出視圖寬度之後,用來確定視圖高度時使用的,它會覆蓋通過LayoutHelper的aspectRatio計算出來的視圖高度,因此具備更高優先級。

// 具體使用
        linearLayoutHelper.setAspectRatio(6);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2. LinearLayoutHelper佈局的特有屬性說明

a. dividerHeight屬性

  • 作用:設置每行Item之間的距離 

    設置的間隔會與RecyclerView的addItemDecoration()添加的間隔疊加


示意圖
  • 具體使用
// 接口示意
public void setDividerHeight(int dividerHeight)

// 具體使用
 linearLayoutHelper.setDividerHeight(1);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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個網格
                }
            }
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
GridLayoutHelper佈局的特有屬性說明

a. weights屬性

  • 作用:設置每行中每個網格寬度佔每行總寬度的比例 
    1. 默認情況下,每行中每個網格中的寬度相等 
    2. weights屬性是一個float數組,每一項代表當個網格佔每行總寬度的百分比;總和是100,否則佈局會超出容器寬度; 
    3. 如果佈局中有4列,那麼weights的長度也應該是4;長度大於4,多出的部分不參與寬度計算;如果小於4,不足的部分默認平分剩餘的空間。

示意圖
  • 具體使用
// 接口示意
public void setWeights(float[] weights)

// 具體使用
gridLayoutHelper.setWeights(new float[]{40, 30, 30});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

b. vGap、hGap屬性

  • 作用:分別控制子元素之間的垂直間距 和 水平間距。

示意圖

  • 具體使用
// 接口示意
    public void setHGap(int hGap)
    public void setVGap(int vGap)

// 具體使用
    gridLayoutHelper.setVGap(20);// 控制子元素之間的垂直間距
    gridLayoutHelper.setHGap(20);// 控制子元素之間的水平間距
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

c. spanCount、spanSizeLookup屬性

  • 作用: 
    1. spanCount:設置每行多少個網格
    2. 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個網格
                }
            }
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

d. autoExpand屬性

  • 作用:當一行裏item的個數 < (每行網格列數 - spanCount值/ 每個Item佔有2個網格-setSpanSizeLookup )時,是否自動填滿空白區域 
    1. 若autoExpand=true,那麼視圖的總寬度會填滿可用區域; 
    2. 否則會在屏幕上留空白區域。

示意圖
  • 具體使用
// 接口示意
public void setAutoExpand(boolean isAutoExpand)

// 具體使用
gridLayoutHelper.setAutoExpand(false);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
FixLayoutHelper特有屬性說明

a. AlignType、x、y屬性

  • 作用: 
    1. alignType:吸邊基準類型 

      共有4個取值:TOP_LEFT(默認), TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT,具體請看下面示意圖

    2. x:基準位置的橫向偏移量
    3. 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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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

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

以下示意圖爲:滑動到底部,佈局纔在左上角顯示 
滑動到底部纔在左上角顯示
  • 具體使用
/**
         設置可選固定佈局
         */

        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的顯示模式
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
ScrollFixLayoutHelper特有屬性說明

a. AlignType、x、y屬性

  • 作用: 
    1. alignType:吸邊基準類型 

      共有4個取值:TOP_LEFT(默認), TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT,具體請看下面示意圖

    2. x:基準位置的橫向偏移量
    3. 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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

b. ShowType屬性

  • 作用:設置Item的顯示模式

    共有三種顯示模式

    1. SHOW_ALWAYS:永遠顯示(即效果同固定佈局)
    2. SHOW_ON_ENTER:默認不顯示視圖,當頁面滾動到該視圖位置時才顯示;
    3. SHOW_ON_LEAVE:默認不顯示視圖,當頁面滾出該視圖位置時才顯示
  • 具體使用

// 接口示意
public void setShowType(int showType)

// 具體使用
scrollFixLayoutHelper.setShowType(ScrollFixLayoutHelper.SHOW_ON_ENTER);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

5. 浮動佈局(FloatLayoutHelper)

  • 佈局說明:佈局裏的Item只有一個 
    1. 可隨意拖動,但最終會被吸邊到兩側 
    2. 不隨頁面滾動而移動

示意圖
  • 具體使用
/**
         設置浮動佈局
         */
        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的初始位置

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

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屬性講解
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

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);// 設置設置佈局內每行佈局的寬與高的比

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

8. 一拖N佈局 (OnePlusNLayoutHelper)

  • 佈局說明:將佈局分爲不同比例,最多是1拖4。具體如下圖

示意圖1

示意圖2

  • 具體使用
/**
         設置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);// 設置設置佈局內每行佈局的寬與高的比
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

9. 吸邊佈局(StickyLayoutHelper)

  • 佈局說明:佈局只有一個Item,顯示邏輯如下:

    1. 當它包含的組件處於屏幕可見範圍內時,像正常的組件一樣隨頁面滾動而滾動
    2. 當組件將要被滑出屏幕返回的時候,可以吸到屏幕的頂部或者底部,實現一種吸住的效果
  • 示意圖(吸在頂部)

示意圖

  • 具體使用
        /**
         設置吸邊佈局
         */
        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列表裏
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

stickyStart、 offset屬性說明

  • 作用:

    1. stickyStart:設置吸邊位置

      當視圖的位置在屏幕範圍內時,視圖會隨頁面滾動而滾動;當視圖的位置滑出屏幕時,StickyLayoutHelper會將視圖固定在頂部(stickyStart = true)或 底部(stickyStart = false)

    2. offset:設置吸邊的偏移量

  • 具體使用

// 接口示意
        public void setStickyStart(boolean stickyStart)
        public void setOffset(int offset)

// 具體使用
        stickyLayoutHelper.setStickyStart(true);
        // true = 組件吸在頂部
        // false = 組件吸在底部
        stickyLayoutHelper.setOffset(100);// 設置吸邊位置的偏移量
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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);// 設置子元素之間的垂直間距

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

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

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

  1. 繼承BaseLayoutHelper:從上而下排列的順序 & 內部 View可以按行回收的佈局;主要實現layoutViews()computeAlignOffset()等方法

    LinearLayoutHelperGridLayoutHelper都是採用該方法實現

  2. 繼承AbstractFullFillLayoutHelper:有些佈局內部的 View 並不是從上至下排列的順序(即 Adatper 裏的數據順序和物理視圖順序不一致,那麼可能就不能按數據順序佈局和回收),需要一次性佈局 
    & 回收。主要實現layoutViews()等方法

    OnePlusNLayoutHelper採用該方法實現

  3. 繼承FixAreaLayoutHelperfix 類型佈局,子節點不隨頁面滾動而滾動。主要實現layoutViews()beforeLayout()afterLayout()等方法

    FixLayoutHelper採用該方法實現


步驟5:將生成的LayoutHelper 交給Adapter,並綁定到RecyclerView 對象

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

<-- 方式1: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);



<-- 方式2: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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

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


6. 實例說明

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

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

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

步驟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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

步驟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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

步驟4:設置Adapter

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

    1. 繼承 自 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;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91

以下步驟都將寫在同一個.Java文件裏

步驟5:創建RecyclerView & VirtualLayoutManager 對象並進行綁定 
步驟6:設置回收複用池大小 
步驟7:設置需要存放的數據 
步驟8:根據數據列表,創建對應的LayoutHelper 
步驟9:將生成的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();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156

效果圖

總效果圖

源碼地址

Carson_Ho的Github地址:V - Layout

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


7. 總結

  • 看完本文,你應該非常瞭解阿里出品的V - Layout 的使用 & 原理
  • 但該開源庫還是存在許多小Bug,我在Github上也提交了一些,希望大家能一起在Github - alibaba - vlayout 上進行完善,共同爲開源事業做貢獻吧!
  • 下面我將繼續對 Android 其他優秀的開源庫 進行詳細分析,有興趣可以繼續關注Carson_Ho的安卓開發筆記
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章