V- Layout
是阿里出品的基礎 UI 框架,用於快速實現頁面的複雜佈局,在手機天貓 Android
版 內普遍使用V- Layout
終於在Github上開源!V- Layout
進行了詳細分析,我將獻上一份 V- Layout
的使用攻略 & 源碼分析,但願大家會喜歡。閱讀本文前請先看: Android Tangram模型:連淘寶、天貓都在用的UI框架模型你必定要懂java
在講解 V - Layout
前,咱們先來搞懂一個問題:爲何要使用 V - Layout
android
Android
中 UI
性能消耗主要來自於兩個方面:measure/layout
View
控件的建立和銷燬ListView/GirdView/RecyclerView
等基礎空間來處理View
的回收與複用。可是,不少時候咱們須要在一個長列表下作多種類型的佈局來分配各類元素,,特別是電商平臺首頁等頁面,佈局元素結構更加複雜多樣。以下圖: git
此時的解決方案有所變化:不採用子View
的複用,只採用一個主要的複用容器(如ListView
或 RecyclerView
+LinearLayoutManager
),而後在其中使用嵌套方式直接用各個組件進行拼接,減小了複用的能力github
這種作法仍是會損失Android應用的性能。數組
LayoutManager
管理全部的佈局類型VirtualLayout
就是採用該方式來解決上述問題VirtualLayout
是阿里出品的基礎 UI 框架項目
- 基於
RecyclerView
&LayoutManager
擴展- 目前已在Github開源:Github - alibaba - vlayout
V - Layout 目前已在手機天貓 & 淘寶 Android 版內普遍使用bash
V - Layout
的本質原理是:經過自定義一個VirtualLayoutManager
(繼承自 LayoutManager),用於管理一系列LayoutHelper
,將具體的佈局能力交給LayoutHelper
來完成,從而 快速實現組合佈局 的需求。微信
- 每一個
LayoutHelper
負責 頁面某一個範圍內的佈局V - Layout
默認實現了10種默認佈局:(對應同名的LayoutHelper)
V - Layout
的源碼類圖以下: 框架
V - layout
框架裏綁定 VirtualLayoutAdapter
(繼承Adapter
) & VirtualLayoutManager
(繼承LayoutManager
)繼承自系統的
Adaper
ide
getLayoutHelper()
:用於返回某個位置組件對應的一個LayoutHelper
setLayoutHelpers()
:調用此方法設置整個頁面所須要的一系列LayoutHelper
這兩方法的具體實現委託給
VirtualLayoutManager
完成
繼承自系統的
LinearLayoutManager
RecyclerView
加載組件或者滑動時調用VirtualLayoutManager
的layoutChunk()
,返回當前還有哪些空白區域可擺放組件LayoutHelper
列表VirtualLayoutAdapter
的 getLayoutHelper()
& setLayoutHelpers()
LayoutHelper
尋找器LayoutHelper
並返回給 VirtualLayoutManager
VirtualLayoutManager
會持有一個LayoutHelperFinder
- 當
layoutChunck()
被調用時會傳入一個位置參數,告訴VirtualLayoutManager
當前要佈局第幾個組件VirtualLayoutManager
通知持有的LayoutHelperFinder
找到傳入參數位置對應的LayoutHelper
(每一個LayoutHelper
都會綁定它負責的佈局區域的起始位置和結束位置)
VirtualLayoutManager
通訊:接口 | 做用 |
---|---|
isOutOfRange() | 告訴VirtualLayoutManager它所傳遞過來位置是否在當前LayoutHelper的佈局區域內; |
setRange() | 設置當前LayoutHelper負責的佈局區域 |
doLayout() | 真正的佈局邏輯接口 |
beforeLayout() | 在佈局前作一些前置工做 |
afterLayout() | 在佈局完成後作一些後置工做 |
LayoutHelper
LayoutHelper
,提供了佈局經常使用的內邊距padding
、外邊距margin
的計算功能MarginLayoutHelper
的第一層具體實現LayoutHelper
在屏幕範圍內的具體區域 背景色、背景圖等邏輯MarginLayoutHelper
的第二層具體實現
- 每種子
LayoutHelper
負責一種佈局邏輯- 重點實現了
beforeLayout()
、doLayout()
、afterLayout()
- 特別是
doLayout()
:會獲取一組件,並對組件進行尺寸計算、界面佈局。
V - Layout
默認實現了10種默認佈局:(對應同名的LayoutHelper)下面會進行詳細介紹。
LayoutHelper
負責佈局一批組件範圍內的組件,不一樣組件範圍內的組件之間,若是類型相同,能夠在滑動過程當中回收複用。所以回收粒度比較細,且能夠跨佈局類型複用。LayoutHelper
,實現特殊的佈局方式。下面會詳細說明介紹完類以後,我將詳細分析 V - Layout
的工做流程。
V - Layout
的工做流程分爲 初始化 & 佈局流程。以下圖:V - layout
快速實現複雜佈局前,須要先作一系列的初始化工做。初始化流程與使用普通的 RecyclerView + LayoutManager 初始化流程基本一致:Vlayout的使用者
V - layout
時須要作的初始化工做。VirtualLayoutManager
持續獲取頁面狀態,並經過LayoutHelperFinder
找到對應的LayoutHelper
從而實現對應的佈局邏輯,從而快速實現組合佈局 的需求下面用一張圖總結 V - Layout
的原理 & 工做流程
在講完原理後,接下來我將如何使用 V - Layout
。
V - Layout
的使用其實就是上面說的初始化工做下面我將對每一個步驟進行詳細說明。
recyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
// 建立RecyclerView對象
VirtualLayoutManager layoutManager = new VirtualLayoutManager(this);
// 建立VirtualLayoutManager對象
// 同時內部會建立一個LayoutHelperFinder對象,用來後續的LayoutHelper查找
recyclerView.setLayoutManager(layoutManager);
// 將VirtualLayoutManager綁定到recyclerView
複製代碼
若是一屏內相同類型的 View 個數比較多,須要設置一個合適的大小,防止來回滾動時從新建立 View)
// 設置組件複用回收池
RecyclerView.RecycledViewPool viewPool = new RecyclerView.RecycledViewPool();
recyclerView.setRecycledViewPool(viewPool);
viewPool.setMaxRecycledViews(0, 10);
複製代碼
設置 V - Layout的Adapter有兩種方式:
DelegateAdapter
VirtualLayoutAdapter
下面會進行詳細說明:方式1:繼承 自 DelegateAdapter
DelegateAdapter
是V - Layout專門爲管理 LayoutHelper
定製的 Adapter
繼承自VirtualLayoutAdapter
LayoutHelper
,從而實現使用不一樣組合佈局
- 特別注意:雖不可直接綁定LayoutHelper,可是它內部有一個繼承自RecyclerView.Adapter的內部類Adapter能夠綁定LayoutHelper;
- 即經過一個List把綁定好的Adapter打包起來,再放去DelegateAdapter,這樣就能夠實現組合使用不一樣的佈局
- 寫法與複寫系統自帶的Adapter很是相似:只比系統自帶的RecyclerAdapter須要多重載onCreateLayoutHelper方法,其他相似
- 關於Android系統自帶的RecyclerAdapter的使用具體請看我寫的文章Android開發:ListView、AdapterView、RecyclerView全面解析
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邏輯
}
複製代碼
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的距離
複製代碼
a. setItemCount屬性
如設置的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);
複製代碼
a. dividerHeight屬性
設置的間隔會與RecyclerView的addItemDecoration()添加的間隔疊加
// 接口示意
public void setDividerHeight(int dividerHeight)
// 具體使用
linearLayoutHelper.setDividerHeight(1);
複製代碼
/**
設置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個網格
}
}
});
複製代碼
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屬性
- 若autoExpand=true,那麼視圖的總寬度會填滿可用區域;
- 不然會在屏幕上留空白區域。
// 接口示意
public void setAutoExpand(boolean isAutoExpand)
// 具體使用
gridLayoutHelper.setAutoExpand(false);
複製代碼
固定在屏幕某個位置,且不可拖拽 & 不隨頁面滾動而滾動。以下圖:(左上角)
/**
設置固定佈局
*/
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
複製代碼
a. AlignType、x、y屬性
共有4個取值:TOP_LEFT(默認), TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT,具體請看下面示意圖
// 接口示意
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);
複製代碼
- 固定在屏幕某個位置,且不可拖拽 & 不隨頁面滾動而滾動(繼承自固定佈局(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的顯示模式
複製代碼
a. AlignType、x、y屬性
共有4個取值:TOP_LEFT(默認), TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT,具體請看下面示意圖
// 接口示意
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屬性
共有三種顯示模式
- SHOW_ALWAYS:永遠顯示(即效果同固定佈局)
- SHOW_ON_ENTER:默認不顯示視圖,當頁面滾動到該視圖位置時才顯示;
- SHOW_ON_LEAVE:默認不顯示視圖,當頁面滾出該視圖位置時才顯示
// 接口示意
public void setShowType(int showType)
// 具體使用
scrollFixLayoutHelper.setShowType(ScrollFixLayoutHelper.SHOW_ON_ENTER);
複製代碼
- 可隨意拖動,但最終會被吸邊到兩側
- 不隨頁面滾動而移動
/**
設置浮動佈局
*/
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的初始位置
複製代碼
可理解爲只有一行的線性佈局
/**
設置欄格佈局
*/
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屬性講解
複製代碼
/**
設置通欄佈局
*/
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拖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);// 設置設置佈局內每行佈局的寬與高的比
複製代碼
佈局說明:佈局只有一個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
:設置吸邊位置當視圖的位置在屏幕範圍內時,視圖會隨頁面滾動而滾動;當視圖的位置滑出屏幕時,StickyLayoutHelper會將視圖固定在頂部(stickyStart = true)或 底部(stickyStart = false)
offset
:設置吸邊的偏移量具體使用
// 接口示意
public void setStickyStart(boolean stickyStart)
public void setOffset(int offset)
// 具體使用
stickyLayoutHelper.setStickyStart(true);
// true = 組件吸在頂部
// false = 組件吸在底部
stickyLayoutHelper.setOffset(100);// 設置吸邊位置的偏移量
複製代碼
/**
設置瀑布流佈局
*/
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從而實現自定義佈局樣式。有三種方式:
BaseLayoutHelper
:從上而下排列的順序 & 內部 View
能夠按行回收的佈局;主要實現layoutViews()
、computeAlignOffset()
等方法
LinearLayoutHelper
、GridLayoutHelper
都是採用該方法實現
AbstractFullFillLayoutHelper
:有些佈局內部的 View
並非從上至下排列的順序(即 Adatper
裏的數據順序和物理視圖順序不一致,那麼可能就不能按數據順序佈局和回收),須要一次性佈局 & 回收。主要實現layoutViews()
等方法
OnePlusNLayoutHelper
採用該方法實現
FixAreaLayoutHelper
:fix
類型佈局,子節點不隨頁面滾動而滾動。主要實現layoutViews()
、beforeLayout()
、afterLayout()
等方法
FixLayoutHelper
採用該方法實現
此處的作法會因步驟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
快速組合佈局Android - Gradle
加入依賴compile ('com.alibaba.android:vlayout:1.0.3@aar') {
transitive = true
}
複製代碼
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>
複製代碼
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>
複製代碼
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
文件裏步驟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();
}
}
複製代碼
###效果圖
Carson_Ho的Github地址:V - Layout
V - Layout
的使用 & 原理Bug
,我在Github上也提交了一些,但願你們能一塊兒在Github - alibaba - vlayout 上進行完善,共同爲開源事業作貢獻吧!Android
其餘優秀的開源庫 進行詳細分析,有興趣能夠繼續關注Carson_Ho的安卓開發筆記