你們好,今天又帶來了項目中具體遇到的需求。作一個首界面,該首界面有不少功能塊,同時這些功能塊是動態的,由於登陸的人的權限的不一樣,會顯示不一樣的功能塊,由於功能模塊的數量不必定,因此當功能塊多的時候,整個界面是能夠上下滑動的。其實相似有點像淘寶的首界面。以下圖所示。javascript
1.首先由於功能塊多的時候,須要界面可以滾動,因此我想到最外面用的ScrollView,而後ScrollView中包含了一個豎向排布的LienarLayout。
而後在放入一個ImageView顯示這個頂部圖片:
java
而後須要二個橫向的LinearLayout,用來顯示這個大的分類標題:android
而後再放入二個GridView顯示功能模塊:git
OK。我發現個人首界面寫好了以後:github
<ScrollView>
<LinearLayout>
<ImageView> <頂部圖片>
<LinearLayout><View/><TextView/><LinearLayout> <個人服務標題欄>
<GridView /> <個人服務功能塊>
<LinearLayout><View/><TextView/><LinearLayout> <個人功能標題欄>
<GridView /> <個人功能功能塊>
<LinearLayout>
<ScrollView/>複製代碼
1.佈局的內容很是之多。維護很不方便
2.定製化功能差了不少,若是我下次想在《個人服務》和《個人功能》大功能分類中,再多加一個《個人售後》,又的去佈局中查找相應的位置,而後去去添加新的佈局代碼,或者是我想刪除模塊功能了,我還得去佈局中找出來,而後去刪除它。反正就是很麻煩。
3.當前這個界面還算簡單的,畢竟功能塊都是以相似九宮格的形式呈現,若是哪天多了個《個人售後》,而後這個《個人售後》不是以這種九宮格的形式呈現,整個界面中有各類各樣的布句呈現,管理會變的十分麻煩。算法
上面說到過。咱們的界面有沒有像淘寶的首界面,各類布句雜糅在一塊兒,而後又能夠上下滾動,沒錯,那我就模仿淘寶的首頁同樣寫個不就好了麼。框架
而後裏面的不一樣佈局方式使用不一樣的LayoutManager不就能夠了麼。固然由於前面講了咱們能夠模仿淘寶的首頁來寫,那咱們固然是使用阿里巴巴開源的vlayout。ide
這時候介紹一下咱們的主角:vlayout函數
vlayout is a powerfull LayoutManager extension for RecyclerView, it provides a group of layouts for RecyclerView. Make it able to handle a complicate situation when grid, list and other layouts in the same recyclerview.佈局
咱們能夠看到,vlayout是一個強大的RecycleView的LayoutManager,它能夠幫我在RecycleView中呈現多種佈局方式。
首先,vlayout的基本使用方法,其餘大神寫的不少也很好。我也不會浪費時間再寫一遍:
請看這篇,基本就可以對Vlayout有所瞭解及使用了:
Android開源庫V - Layout:淘寶、天貓都在用的UI框架,趕忙用起來吧!
Android開源庫V - Layout:淘寶、天貓都在用的UI框架,趕忙用起來吧!
Android開源庫V - Layout:淘寶、天貓都在用的UI框架,趕忙用起來吧!
咱們回頭再來看咱們上面的具體的項目需求:
咱們首先整個activity的佈局變爲了:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/work_recycleview" android:background="@android:color/white" android:layout_width="match_parent" android:layout_height="match_parent" android:overScrollMode="never" > </android.support.v7.widget.RecyclerView>複製代碼
是否是變的乾淨簡潔了!!!
而後咱們要使用Vlayout來設置咱們RecycleView中的各類佈局。
RecycleView workRecycleview = (RecycleView)findViewById(R.id.work_recycleview);
//創建咱們的委託LayoutManger
VirtualLayoutManager layoutManager = new VirtualLayoutManager(this);
workRecycleview.setLayoutManager(layoutManager);
//經過這個layoutManager來管理一系列的LayoutHelper
//因此咱們先創建一個List來存放等會要用到的LayoutHelper.
List<LayoutHelper> helperList = new LinkedList<>();
//1.
//由於第一個底部圖片就這一項,因此咱們就直接使用SingleLayoutHelper
SingleLayoutHelper bannerLayoutHelper = new SingleLayoutHelper();
bannerLayoutHelper.setItemCount(1);
helperList.add(bannerLayoutHelper);
//2.
//由於大標題欄是一個橫向的LinearLayout,因此使用LinearLayoutHelper
LinearLayoutHelper personTitleHelper = new LinearLayoutHelper();
personTitleHelper.setItemCount(1);
helperList.add(personTitleHelper);
//3.
//由於功能塊目前是九宮格,因此使用的是GridLayoutHelper
GridLayoutHelper personGridHelper = new GridLayoutHelper(3);
personGridHelper.setAutoExpand(false);
personGridHelper.setWeights(new float[]{33, 33, 33});
//設置登陸時候獲取到的該用戶權限下顯示的功能數量。
personGridHelper.setItemCount(mPersonFunctions.size());
helperList.add(personGridHelper);
//4.
//同2界面
LinearLayoutHelper companyTitleHelper = new LinearLayoutHelper();
companyTitleHelper.setItemCount(1);
helperList.add(companyTitleHelper);
//5.
//同3界面
GridLayoutHelper companyGridHelper = new GridLayoutHelper(3);
companyGridHelper.setWeights(new float[]{33, 33, 33});
companyGridHelper.setAutoExpand(false);
//設置登陸時候獲取到的該用戶權限下顯示的功能數量。
companyGridHelper.setItemCount(mCompanyFunctions.size());
helperList.add(companyGridHelper);複製代碼
若是咱們須要增長新的佈局控制。咱們只須要添加新的LayoutHelper,按順序添加到咱們的helperList中便可。
目前的LayoutHelper有如下幾種:
- LinearLayoutHelper: 線性佈局
- GridLayoutHelper: Grid佈局, 支持橫向的colspan
- FixLayoutHelper: 固定佈局,始終在屏幕固定位置顯示
- ScrollFixLayoutHelper: 固定佈局,但以後當頁面滑動到該圖片區域才顯示, 能夠用來作返回頂部或其餘書籤等
- FloatLayoutHelper: 浮動佈局,能夠固定顯示在屏幕上,但用戶能夠拖拽其位置
- ColumnLayoutHelper: 欄格佈局,都佈局在一排,能夠配置不一樣列之間的寬度比值
- SingleLayoutHelper: 通欄佈局,只會顯示一個組件View
- OnePlusNLayoutHelper: 一拖N佈局,能夠配置1-5個子元素
- StickyLayoutHelper: stikcy佈局, 能夠配置吸頂或者吸底
- StaggeredGridLayoutHelper: 瀑布流佈局,可配置間隔高度/寬度
既然用到RecycleView ,那怎麼能夠沒有Adapter呢,上面的準備工做作了一部分後,咱們開始寫咱們的Adapter。
咱們這裏選擇繼承了VirtualLayoutAdapter:
咱們在構造函數中傳入咱們二個九宮格功能塊對應的List進來。
public class WorkAdapter extends VirtualLayoutAdapter {
int oneFuncs, twoFuncs;//
public List<FunctionBean> oneFunctions;
public List<FunctionBean> twoFunctions;
public static final int BANNER_VIEW_TYPE = 0;
public static final int DIVIDER_VIEW_TYPE = 1;
public static final int FUN_VIEW_TYPE = 2;
public WorkAdapter(@NonNull VirtualLayoutManager layoutManager, List<FunctionBean> oneFunctions, List<FunctionBean> twoFunctions,funcItemOnClickListener listener) {
super(layoutManager);
this.oneFunctions = oneFunctions;
this.twoFunctions = twoFunctions;
this.listener = listener;
oneFuncs = oneFunctions.size();
twoFuncs = twoFunctions.size();
}
}複製代碼
咱們來分別看Adapter中每一個方法具體的複寫:
1.
@Override
public int getItemCount() {
int totalCount = 0;
List<LayoutHelper> helpers = getLayoutHelpers();
if (helpers == null) {
return 0;
}
for (int i = 0; i < helpers.size(); i++) {
totalCount += helpers.get(i).getItemCount();
}
return totalCount;
}複製代碼
咱們能夠看到在getItemCount()
方法中,咱們經過遍歷了LayoutHelper,分別取每一個LayoutHelper中咱們剛設置的個數。而後加起來,做爲整個RecycleView 的個數。
2.
@Override
public int getItemViewType(int position) {
if (position == 0) {
return BANNER_VIEW_TYPE;
} else if (position == 1 || position == (2 + oneFuncs)) {
return DIVIDER_VIEW_TYPE;
} else {
return FUN_VIEW_TYPE;
}
}複製代碼
咱們能夠看到。咱們在getItemViewType
方法中確定position的值,返回不一樣的type,這樣等會在onCreateViewHolder
方法中就能夠返回不一樣的ViewHolder了。
3.
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case BANNER_VIEW_TYPE:
return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_work_banner, parent, false));
case DIVIDER_VIEW_TYPE:
return new DividerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_work_divider, parent, false));
case FUN_VIEW_TYPE:
return new FuncViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_work_func, parent, false));
default:
return null;
}
}複製代碼
4.
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof DividerViewHolder) {
if (position == 1) {
((DividerViewHolder) holder).dividerTitle.setText("個人服務");
((DividerViewHolder) holder).colorView.setBackgroundColor(Color.parseColor("#F9C025"));
} else {
((DividerViewHolder) holder).dividerTitle.setText("個人功能");
((DividerViewHolder) holder).colorView.setBackgroundColor(Color.parseColor("#35A7FF"));
}
} else if (holder instanceof FuncViewHolder) {
if(position > 1 && position < 2+ oneFuncs){
...
...
...
}else if(position > 2+ oneFuncs){
...
...
...
}
}
}複製代碼
就這樣咱們就具體的實現了多個佈局的設置,並且當你要再加一個新的也很方便。可是也許你這時候會發現,若是咱們的佈局很長,有不少九宮格,或者真的像淘寶同樣,這個界面有各類功能塊。那咱們剛寫的
@Override
public int getItemViewType(int position) {
if (position == 0) {
return BANNER_VIEW_TYPE;
} else if (position == 1 || position == (2 + oneFuncs)) {
return DIVIDER_VIEW_TYPE;
} else {
return FUN_VIEW_TYPE;
}
}複製代碼
這裏你判斷的position就會不少。你可能就要有不少的if-else 來控制返回不一樣的type.
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof DividerViewHolder) {
if (position == 1) {
((DividerViewHolder) holder).dividerTitle.setText("個人服務");
((DividerViewHolder) holder).colorView.setBackgroundColor(Color.parseColor("#F9C025"));
} else {
((DividerViewHolder) holder).dividerTitle.setText("個人功能");
((DividerViewHolder) holder).colorView.setBackgroundColor(Color.parseColor("#35A7FF"));
}
} else if (holder instanceof FuncViewHolder) {
if(position > 1 && position < 2+ oneFuncs){
...
...
...
}else if(position > 2+ oneFuncs){
...
...
...
}
}
}複製代碼
而後在onBindViewHolder
方法裏面也要有不少的if-else,到後面維護又會變的很麻煩,因此這樣寫相對簡單,適合模塊很少的狀況。
咱們前面是把不少LayoutHelper加入到了Adapter中,而後RecycleView直接設置該Adapter,
咱們此次就不這麼作了。並且中間多設置一步,就是先每一個LayoutHelper設置一個Adapter,成爲<子的Adapter>,而後把這些Adapter再統一放入到一個<總的Adapter>中,再把這個<總的Adapter>賦值給RecycleView便可。
由於這樣在<總的Adapter>中,對於每一個Helper就能夠知道個數,而且ViewHolder等賦值都被分配到了那些<子的Adapter>中處理了。咱們不用再if-else的寫不少狀況了。
(不過我不會徹底很仔細的講解,代碼我也不會貼所有,就貼一些主要的地方,講主要的部分。)
1.
public class DelegateAdapter extends VirtualLayoutAdapter<RecyclerView.ViewHolder> {
private int mTotal = 0;
private int mIndex = 0;
private SparseArray<Adapter> mItemTypeAry = new SparseArray<>();
@NonNull
private final List<Pair<AdapterDataObserver, Adapter>> mAdapters = new ArrayList<>();
private final SparseArray<Pair<AdapterDataObserver, Adapter>> mIndexAry = new SparseArray<>();
}複製代碼
咱們先定義了幾個參數,
mIndex
用來等於標記各個加入的<子的Adapter>的序號
一個用來存<子的Adapter>的Map(別問我爲何Map類型裏面只填了一個Adapter,不是Key-Value? 能夠補下SparseArray和ArrayMap的知識了,Android中用來替換HashMap的類。)
一個存放了Pair<AdapterDataObserver, Adapter>
的List集合(Pair若是也不知道,也能夠去補充下,就簡單理解爲一個有二個屬性的對象,第一個屬性是AdapterDataObserver,第二個是Adapter)
一個存放了Pair<AdapterDataObserver, Adapter>
的Map集合。
2.
那咱們知道確定要有個方法把這些子的Adapter給加進來:
public void setAdapters(@Nullable List<Adapter> adapters) {
clear();//把相關的參數都從新置空,這裏不寫出來了。
if (adapters == null) {
adapters = Collections.emptyList();
}
List<LayoutHelper> helpers = new LinkedList<>();
mTotal = 0;
Pair<AdapterDataObserver, Adapter> pair;
for (Adapter adapter : adapters) {
// every adapter has an unique index id
//自定義類AdapterDataObserver ,繼承於RecyclerView.AdapterDataObserver
AdapterDataObserver observer = new AdapterDataObserver(mTotal, mIndex++);
adapter.registerAdapterDataObserver(observer);
//子的Adapter中自定義的方法:onCreateLayoutHelper(),用來返回子的Adapter中的LayoutHelper
LayoutHelper helper = adapter.onCreateLayoutHelper();
//並且這些子的Adapter中的LayoutHelper的個數,就是這些子的Adapter的個數
helper.setItemCount(adapter.getItemCount());
//總數爲每一個LayoutHelper的個數之和,也就是每一個子的Adapter的個數之和
mTotal += helper.getItemCount();
helpers.add(helper);
pair = Pair.create(observer, adapter);
//這裏的mIndexAry存放了以加入的順序mIndex爲Key的Pair<AdapterObserver,Adapter>
mIndexAry.put(observer.mIndex, pair);
//同時mAdapters的List集合中也存放了Pair<AdapterObserver,Adapter>
mAdapters.add(pair);
}
super.setLayoutHelpers(helpers);
}複製代碼
咱們看下AdapterDataObser的部分代碼:
protected class AdapterDataObserver extends RecyclerView.AdapterDataObserver {
int mStartPosition;
int mIndex = -1;
public AdapterDataObserver(int startPosition, int index) {
this.mStartPosition = startPosition;
this.mIndex = index;
}
}複製代碼
咱們能夠看到咱們剛在new每一個AdapterDataObser的時候傳入的構造函數參數是
mTotal
,mIndex++
,這樣是否是正好每一個Adapter中的AdapterDataObserver中的mStartPosition
參數就是你的這個Adapter在全部整個RecycleView中的開始的position值。而mIndex
又說明了這個AdapterDataObserver是第幾個,也就是這個Adapter是全部的<子的Adapter>中的第幾個。
3.
@Override
public int getItemCount() {
return mTotal;
}複製代碼
總數就是返回上面咱們的mTotal參數。
4.
@Override
public int getItemViewType(int position) {
Pair<AdapterDataObserver, Adapter> p = findAdapterByPosition(position);
if (p == null) {
return RecyclerView.INVALID_TYPE;
}
int subItemType = p.second.getItemViewType(position - p.first.mStartPosition);
if (subItemType < 0) {
// negative integer, invalid, just return
return subItemType;
}
if (mHasConsistItemType) {
mItemTypeAry.put(subItemType, p.second);
return subItemType;
}
int index = p.first.mIndex;
return (int) getCantor(subItemType, index);
}複製代碼
咱們一步步來看這個比較關鍵的地方,咱們之因此不用咱們最剛開始第一次講的Vlayout使用的方法,就是由於咱們的LayoutHelper多了以後,在getItemViewType()
方法中返回不一樣的ViewType須要不少if-else來處理。因此這裏咱們看他是如何自動處理的。
第一步:Pair<AdapterDataObserver, Adapter> p = findAdapterByPosition(position);
咱們看findAdapterByPosition方法的具體實現:
@Nullable
public Pair<AdapterDataObserver, Adapter> findAdapterByPosition(int position) {
//獲取咱們上面的mAdapter集合,裏面存的是Pair<AdapterObserver,Adapter>
final int count = mAdapters.size();
if (count == 0) {
return null;
}
int s = 0, e = count - 1, m;
Pair<AdapterDataObserver, Adapter> rs = null;
// binary search range
while (s <= e) {
m = (s + e) / 2;
rs = mAdapters.get(m);
int endPosition = rs.first.mStartPosition + rs.second.getItemCount() - 1;
if (rs.first.mStartPosition > position) {
e = m - 1;
} else if (endPosition < position) {
s = m + 1;
} else if (rs.first.mStartPosition <= position && endPosition >= position) {
break;
}
rs = null;
}
return rs;
}複製代碼
經過這個方法的字面意思咱們不難理解:經過這個<總的Adapter>返回的item的position,來知道這個position是屬於咱們存了Adapter集合中的哪一個Adapter的。
先獲取咱們上面已經保存了各個Pair<AdapterObserver,Adapter>
的mAdapters集合,而後判斷個數,爲0就直接返回了。不爲0,咱們就經過二分法查找的方式來進行查找。咱們前面已經在每一個AdapterDataObserver中存了相對於的Adapter的起始的Position,咱們只須要不停的判斷如今傳給這個方法的position是在(子的Adapter 的起始position) 與 (子的Adapter 的起始position + 子的Adapter的個數)之間,若是是,就說明是屬於這個Adapter,咱們就在mAdapters集合中取出相應的Pair<AdapterObserver,Adapter>
。
第二步:
int subItemType = p.second.getItemViewType(position - p.first.mStartPosition);
if (subItemType < 0) {
// negative integer, invalid, just return
return subItemType;
}
if (mHasConsistItemType) {
mItemTypeAry.put(subItemType, p.second);
return subItemType;
}
int index = p.first.mIndex;
return (int) getCantor(subItemType, index);複製代碼
這裏的
(position - p.first.mStartPosition)
其實就是這個<總的Adapter>的處於position的這一項,在這個<子的Adapter>裏面的具體的position值。最後經過這個<子的Adapter>的getItemViewType
來獲得<子的Adapter>的ViewType。這樣就自動幫咱們判斷了在<總的Adapter>中的某個position值的Item的所屬的<子的Adapter>的ViewType了。而不用寫不少if-else來判斷了。
mHasConsistItemType
來控制:在這個<總的Adapter>構造函數中傳入,它的做用是whether sub adapters itemTypes are consistent
,就是咱們的全部的<子的Adapter>的itemType都是同樣的。由於若是你在<子的Adapter>中沒有覆寫getItemViewType
方法的話,默認都是返回0,即:
public int getItemViewType(int position) {
return 0;
}複製代碼
咱們也知道,RecycleView在運行的時候,執行順序是:
getItemViewType ->onCreateViewHolder ->
getItemViewType ->onCreateViewHolder ->
getItemViewType ->onCreateViewHolder ->...複製代碼
若是咱們的mHasConsistItemType設置爲true的話:
因此咱們若是全部的<子的Adapter>中的要用同一個viewType的話,好比這裏是0,咱們就在getItemViewType
方法中執行mItemTypeAry.put(subItemType, p.second);
,這樣當前的這個<子的Apdater>就存在了key爲0的集合中了,而後咱們在onCreateViewHolder
方法中經過Adapter adapter = mItemTypeAry.get(viewType);
取出來就好了,這時候由於viewType爲0,就正好取出來咱們剛存的Adapter,而後再進入下一次的getItemViewType
的時候,就用新的adapter覆蓋了key爲0的value值,而後再拿到onCreateViewHolder
方法裏面使用。
若是咱們的mHasConsistItemType設置爲false的話:
那這時候就用了另一種方法,首先,由於子的Adapter默認拿到的ViewType都是0,因此咱們用了要設置一個可逆算法,好比A方法和還原的B方法,A方法中咱們每次傳入viewType和另一個值(這裏選定了上面咱們拿到的Pair<AdapterDataObserver, Adapter>
中的AdapterDataObserver的index值),由於每一個<子的Adapter>的index值不一樣,因此生成的ViewType也不一樣,而後咱們在onCreateViewHolder
方法裏面,用還原的B方法,獲取到index值,而後經過這個index再找回<子的Adapter>,這時候咱們就能夠調用<子的Adapter>的onCreateViewHolder
方法了。
A方法:
private static long getCantor(long k1, long k2) {
return (k1 + k2) * (k1 + k2 + 1) / 2 + k2;
}複製代碼
B方法:(具體看onCreateViewHolder方法中)
// reverse Cantor Function
int w = (int) (Math.floor(Math.sqrt(8 * viewType + 1) - 1) / 2);
int t = (w * w + w) / 2;
int index = viewType - t;
int subItemType = w - index;複製代碼
5.
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// reverse Cantor Function
int w = (int) (Math.floor(Math.sqrt(8 * viewType + 1) - 1) / 2);
int t = (w * w + w) / 2;
int index = viewType - t;
int subItemType = w - index;
Adapter adapter = findAdapterByIndex(index);
if (adapter == null) {
return null;
}
return adapter.onCreateViewHolder(parent, subItemType);
}複製代碼
6.
@SuppressWarnings("unchecked")
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
Pair<AdapterDataObserver, Adapter> pair = findAdapterByPosition(position);
if (pair == null) {
return;
}
pair.second.onBindViewHolder(holder, position - pair.first.mStartPosition);
pair.second.onBindViewHolderWithOffset(holder, position - pair.first.mStartPosition, position);
}複製代碼
findAdapterByPosition:(也是二分法查找,和上面的findAdapterByIndex方法同樣,不介紹了。)
@Nullable
public Pair<AdapterDataObserver, Adapter> findAdapterByPosition(int position) {
final int count = mAdapters.size();
if (count == 0) {
return null;
}
int s = 0, e = count - 1, m;
Pair<AdapterDataObserver, Adapter> rs = null;
// binary search range
while (s <= e) {
m = (s + e) / 2;
rs = mAdapters.get(m);
int endPosition = rs.first.mStartPosition + rs.second.getItemCount() - 1;
if (rs.first.mStartPosition > position) {
e = m - 1;
} else if (endPosition < position) {
s = m + 1;
} else if (rs.first.mStartPosition <= position && endPosition >= position) {
break;
}
rs = null;
}
return rs;
}複製代碼
看了上面咱們發現了,最後咱們雖然給RecycleView賦值了一個<總的Adapter>,可是實際上的onCreateViewHolder
方法和onBindViewHolder
方法都是調用了每一個具體的<子的Adapter>的。
因此咱們最終在咱們的Activity中的使用
final DelegateAdapter delegateAdapter = new DelegateAdapter(layoutManager, true);
recyclerView.setAdapter(delegateAdapter);
List<DelegateAdapter.Adapter> adapters = new LinkedList<>();
adapters.add(XXXXX);//添加不一樣的子Adapter.
...
//好比這樣:
GridLayoutHelper layoutHelper;
layoutHelper = new GridLayoutHelper(4);
layoutHelper.setMargin(0, 10, 0, 10);
layoutHelper.setHGap(3);
layoutHelper.setAspectRatio(4f);
adapters.add(new ASubAdapter(this, layoutHelper, 8));
...
...
delegateAdapter.setAdapters(adapters);複製代碼
若是我想新加一個功能塊,只要新建一個針對這個功能塊的Adapter,而後添加到adapters集合中就能夠了。徹底不用修改原來的代碼。只須要在這個新加的功能塊的Adapter中處理便可。