阿里VLayout學習筆記(Kotlin)
VLayout中LayoutHelper分類(輔助Adapter實現RecyclerView的Item各種類型的佈局方式)
LinearLayoutHelper: 線性佈局
GridLayoutHelper: Grid佈局, 支持橫向的colspan
FixLayoutHelper: 固定佈局,始終在屏幕固定位置顯示
ScrollFixLayoutHelper: 固定佈局,但之後當頁面滑動到該圖片區域才顯示, 可以用來做返回頂部或其他書籤等
FloatLayoutHelper: 浮動佈局,可以固定顯示在屏幕上,但用戶可以拖拽其位置
ColumnLayoutHelper: 欄格佈局,都佈局在一排,可以配置不同列之間的寬度比值
SingleLayoutHelper: 通欄佈局,只會顯示一個組件View
OnePlusNLayoutHelper: 一拖N佈局,可以配置1-5個子元素
StickyLayoutHelper: stikcy佈局, 可以配置吸頂或者吸底
StaggeredGridLayoutHelper: 瀑布流佈局,可配置間隔高度/寬度
新建Helper的方法詳見VLayout
第一步:設置依賴
在項目app->build.gradle->dependencies中添加如下依賴
注意:使用過程中可能存在AnimatorCompatHelper.java找不到的情況,原因可能是當前編譯使用的support-v4包中不存在該類(例如26.0.1)
解決方案一:在項目中新建android.support.v4.animation包,並將…\sdk\sources\android-25\android\support\v4\animation路徑(在SDK安裝路徑下搜索AnimatorCompatHelper.java得到的路徑)下的AnimatorCompatHelper.java複製到新建的包中
解決方案二:使用com.android.support:recyclerview-v7和com.android.support:support-v4最新的依賴包
compile ('com.alibaba.android:vlayout:1.2.8@aar') {
transitive = true
}
第二步: 爲RecyclerView設置回收複用池大小
如果一屏內相同類型的 ItemView 個數比較多,需要設置一個合適的大小,防止來回滾動時重新創建 ItemView
val viewPool = RecyclerView.RecycledViewPool()
rv.setRecycledViewPool(viewPool)
//參數一:itemView類型ID 參數二:最大回收復用ItemView數(設置1:全部重建,設置10:屏幕最多保留10個相同ItemView不重建)
viewPool.setMaxRecycledViews(0, 10)
第二步: 初始化RecyclerView
方法一:通過DelegateAdapter初始化RecyclerView
//參數一:上下文 參數二:佈局方向
val layoutManager = VirtualLayoutManager(mContext, VirtualLayoutManager.VERTICAL)
rv.setLayoutManager(layoutManager)
//參數一:佈局管理器 參數二:是否所有子adapter共享Item佈局(當hasConsistItemType=true的時候,不論是不是屬於同一個子adapter,相同類型的item都能複用。表示它們共享一個類型。 當hasConsistItemType=false的時候,不同子adapter之間的類型不共享)
val delegateAdapter = DelegateAdapter(layoutManager, true)
val adapters = ArrayList<DelegateAdapter.Adapter<RecyclerView.ViewHolder>>
//MyAdapter是繼承DelegateAdapter.Adapter的實例適配器,詳見BaseVLayoutDelegateAdapter
adapters.add(MyAdapter(floatLayoutHelper))
...
adapters.add(MyAdapter(stickyLayoutHelper))
//設置子adapter集合
delegateAdapter.setAdapters(adapters)
//爲RecyclerView設置adapter
rv.adapter = delegateAdapter
方法二:通過VirtualLayoutAdapter初始化RecyclerView
//參數一:上下文 參數二:佈局方向
val layoutManager = VirtualLayoutManager(mContext, VirtualLayoutManager.VERTICAL) rv.setLayoutManager(layoutManager)
val helpers = LinkedList<LayoutHelper>()
helpers.add(stickyLayoutHelper)
...
helpers.add(linearLayoutHelper)
//Adapter是繼承VirtualLayoutAdapter的實例適配器 詳見 BaseVLayoutVirtualLayoutAdapter
val adapter = Adapter(layoutManager, helpers)
rv.adapter = adapter
最後一步添加忽略
-keepattributes InnerClasses
-keep class com.alibaba.android.vlayout.ExposeLinearLayoutManagerEx { *; }
-keep class android.support.v7.widget.RecyclerView$LayoutParams { *; }
-keep class android.support.v7.widget.RecyclerView$ViewHolder { *; }
-keep class android.support.v7.widget.ChildHelper { *; }
-keep class android.support.v7.widget.ChildHelper$Bucket { *; }
-keep class android.support.v7.widget.RecyclerView$LayoutManager { *; }
VLayout工具類
object VLayoutHelper {
fun initRecyclerView(rv: RecyclerView,
@OrientationMode orientation: Int,
hasConsistItemType: Boolean,
adapters: List<DelegateAdapter.Adapter<RecyclerView.ViewHolder>>) {
val layoutManager = VirtualLayoutManager(MyAPP.getInstance(), orientation)
rv.setLayoutManager(layoutManager)
val delegateAdapter = DelegateAdapter(layoutManager, hasConsistItemType)
delegateAdapter.setAdapters(adapters as List<DelegateAdapter.Adapter<RecyclerView.ViewHolder>>?)
rv.adapter = delegateAdapter
}
fun initRecyclerView(rv: RecyclerView, @OrientationMode orientation: Int, hasConsistItemType: Boolean, adapter: VirtualLayoutAdapter<RecyclerView.ViewHolder>) {
val layoutManager = VirtualLayoutManager(MyAPP.getInstance(), orientation)
rv.setLayoutManager(layoutManager)
rv.adapter = adapter
}
fun setViewPool(rv: RecyclerView, viewType: Int, max: Int) {
val viewPool = RecyclerView.RecycledViewPool()
rv.setRecycledViewPool(viewPool)
viewPool.setMaxRecycledViews(viewType, max)
}
/**
* 獲取線性佈局Helper
*/
fun getLinearLayoutHelper(count: Int, dividerHeight: Int): LinearLayoutHelper {
val linearLayoutHelper = LinearLayoutHelper()
//線性佈局的Item條目數
linearLayoutHelper.itemCount = count
linearLayoutHelper.setDividerHeight(dividerHeight)
return linearLayoutHelper
}
fun getLinearLayoutHelper(@DimenRes dividerHeightID: Int): LinearLayoutHelper {
return getLinearLayoutHelper(0, Resources.getSystem().getDimensionPixelOffset(dividerHeightID))
}
/**
* 獲取網格佈局
*/
fun getGridLayoutHelper(spanCount: Int, isAutoExpand: Boolean): GridLayoutHelper {
return getGridLayoutHelper(spanCount, isAutoExpand, 0, null, null)
}
fun getGridLayoutHelper(spanCount: Int, position: ((Int) -> Int)?): GridLayoutHelper {
return getGridLayoutHelper(spanCount, false, 0, null, position)
}
fun getGridLayoutHelper(spanCount: Int, itemCount: Int, position: ((Int) -> Int)?): GridLayoutHelper {
return VLayoutHelper.getGridLayoutHelper(spanCount, false, itemCount, null, 0, 0, position)
}
fun getGridLayoutHelper(spanCount: Int, isAutoExpand: Boolean, itemCount: Int, weights: FloatArray?,
position: ((Int) -> Int)?): GridLayoutHelper {
return VLayoutHelper.getGridLayoutHelper(spanCount, isAutoExpand, itemCount, weights, 0, 0, position)
}
fun getGridLayoutHelper(spanCount: Int, isAutoExpand: Boolean, itemCount: Int, weights: FloatArray?, gap: Int,
position: ((Int) -> Int)?): GridLayoutHelper {
return VLayoutHelper.getGridLayoutHelper(spanCount, isAutoExpand, itemCount, weights, gap, gap, position)
}
fun getGridLayoutHelper(spanCount: Int, isAutoExpand: Boolean, itemCount: Int, weights: FloatArray?,
vGap: Int, hGap: Int, position: ((Int) -> Int)?): GridLayoutHelper {
var spanSL: GridLayoutHelper.SpanSizeLookup? = null
if (null != position) {
spanSL = object : GridLayoutHelper.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return position(position)
}
}
}
//網格佈局列數
val gridLayoutHelper = GridLayoutHelper(spanCount)
//是否自動補全佈局(當其設置爲true時,如果一行的Item總佔用列數小於網格列數,那麼Item兩頭的間距將均勻拉伸使之填滿一行)
gridLayoutHelper.setAutoExpand(isAutoExpand)
//網格佈局的Item條目數
gridLayoutHelper.itemCount = itemCount
//設置每列的權重(表示每列佔一行的多大寬度,最大100f,weights的size不能大於網格列數,其值得和不能大於100f)
gridLayoutHelper.setWeights(weights)
//設置Item的水平間距
gridLayoutHelper.hGap = hGap
//設置Item的垂直間距
gridLayoutHelper.vGap = vGap
//指定條目佔用一行的指定列數的規則(指定佔用列數不能大於網格的列數)
gridLayoutHelper.setSpanSizeLookup(spanSL)
return gridLayoutHelper
}
const val TOP_LEFT = 0 //以RecyclerView內左上角爲原點
const val TOP_RIGHT = 1 //以RecyclerView內右上角爲原點
const val BOTTOM_LEFT = 2 //以RecyclerView內左下角爲原點
const val BOTTOM_RIGHT = 3 //以RecyclerView內右下角爲原點
/**
* 以RecyclerView控件內部爲基準
*/
@IntDef(TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT)
@Retention(AnnotationRetention.SOURCE)
private annotation class LayoutHelperAlignType
/**
* 獲取FixLayout固定佈局
*/
fun getFixLayoutHelper(@LayoutHelperAlignType alignType: Int): FixLayoutHelper {
return getFixLayoutHelper(alignType, 0, 0)
}
fun getFixLayoutHelper(@LayoutHelperAlignType alignType: Int, offsetX: Int, offsetY: Int): FixLayoutHelper {
//參數一:設置對齊方式(原點位置) 參數二:相對原點X軸偏移量 參數三:相對原點Y軸偏移量
val fixLayoutHelper = FixLayoutHelper(alignType,offsetX, offsetY)
fixLayoutHelper.itemCount = 1
return fixLayoutHelper
}
const val SHOW_ALWAYS = 0
const val SHOW_ON_ENTER = 1
const val SHOW_ON_LEAVE = 2
@IntDef(SHOW_ALWAYS, SHOW_ON_ENTER, SHOW_ON_LEAVE)
@Retention(AnnotationRetention.SOURCE)
private annotation class ScrollFixLayoutHelperShowType
/**
* 這個也是固定佈局,而且使繼承自FixLayoutHelper的,這個Helper存在一定bug,有可能設置showType無效
*- SHOW_ALWAYS:與FixLayoutHelper的行爲一致,固定在某個位置;
*- SHOW_ON_ENTER:默認不顯示視圖,當頁面滾動到這個視圖的位置的時候,才顯示;
*- SHOW_ON_LEAVE:默認不顯示視圖,當頁面滾出這個視圖的位置的時候顯示;
*/
fun getScrollFixLayoutHelper(@LayoutHelperAlignType alignType: Int, @ScrollFixLayoutHelperShowType showType: Int): ScrollFixLayoutHelper {
return getScrollFixLayoutHelper(alignType, showType, 0, 0)
}
fun getScrollFixLayoutHelper(@LayoutHelperAlignType alignType: Int, @ScrollFixLayoutHelperShowType showType: Int, offsetX: Int, offsetY: Int): ScrollFixLayoutHelper {
val scrollFixLayoutHelper = ScrollFixLayoutHelper(alignType, offsetX, offsetY)
scrollFixLayoutHelper.setShowType(showType)
scrollFixLayoutHelper.itemCount = 1
return scrollFixLayoutHelper
}
/**
* 獲取可拖動佈局
* 設置dragEnable = true可拖動時
* 當其爲adapter的第一個helper時可以直接拖動
* 否則需要其上一個helper顯示後纔可以拖動
*/
fun getFloatLayoutHelper(@LayoutHelperAlignType alignType: Int,dragEnable: Boolean): FloatLayoutHelper {
return getFloatLayoutHelper(TOP_LEFT,0,0,dragEnable)
}
fun getFloatLayoutHelper(posX: Int, posY: Int, dragEnable: Boolean): FloatLayoutHelper {
return getFloatLayoutHelper(TOP_LEFT,posX,posY,dragEnable)
}
fun getFloatLayoutHelper(@LayoutHelperAlignType alignType: Int,posX: Int, posY: Int, dragEnable: Boolean): FloatLayoutHelper {
val floatLayoutHelper = FloatLayoutHelper()
floatLayoutHelper.setAlignType(alignType)
//參數一:相對原點X軸偏移量 參數二:相對原點Y軸偏移量
floatLayoutHelper.setDefaultLocation(posX, posY)
floatLayoutHelper.setDragEnable(dragEnable)
floatLayoutHelper.itemCount = 1
return floatLayoutHelper
}
/**
* 獲取欄格佈局
* 只有一行,可通過itemCount設置多列
*/
fun getColumnLayoutHelper(count: Int, weights: FloatArray?): ColumnLayoutHelper {
val columnLayoutHelper = ColumnLayoutHelper()
columnLayoutHelper.setWeights(weights)
columnLayoutHelper.itemCount = count
return columnLayoutHelper
}
/**
* 獲取通欄佈局
* 只有一個Item
*/
fun getSingleLayoutHelper(): SingleLayoutHelper {
val singleLayoutHelper = SingleLayoutHelper()
return singleLayoutHelper
}
/**
* 獲取一拖N佈局(最多5個Item)
* 佈局方式詳見[GitHub->OnePlusNLayoutHelper.java](https://github.com/alibaba/vlayout/blob/master/vlayout/src/main/java/com/alibaba/android/vlayout/layout/OnePlusNLayoutHelper.java)
*/
fun getOnePlusNLayoutHelper(count: Int, colWeights: FloatArray?, rowWeight: Float): OnePlusNLayoutHelper {
val onePlusNLayoutHelper = OnePlusNLayoutHelper()
onePlusNLayoutHelper.itemCount = if (count > 5) 5 else count
onePlusNLayoutHelper.setColWeights(colWeights)
onePlusNLayoutHelper.setRowWeight(rowWeight)
return onePlusNLayoutHelper
}
fun getOnePlusNLayoutHelper(count: Int): OnePlusNLayoutHelper {
return getOnePlusNLayoutHelper(count, null, Float.NaN)
}
/**
* 獲取一拖N佈局(最多7個Item)
* 佈局方式詳見[GitHub->OnePlusNLayoutHelperEx.java](https://github.com/alibaba/vlayout/blob/master/vlayout/src/main/java/com/alibaba/android/vlayout/layout/OnePlusNLayoutHelperEx.java)
*/
fun getOnePlusNLayoutHelperEx(count: Int): OnePlusNLayoutHelperEx {
return getOnePlusNLayoutHelperEx(count,null, Float.NaN)
}
fun getOnePlusNLayoutHelperEx(count: Int, colWeights: FloatArray?, rowWeight: Float): OnePlusNLayoutHelperEx {
val onePlusNLayoutHelperEx = OnePlusNLayoutHelperEx ()
onePlusNLayoutHelperEx.itemCount = if (count > 7) 7 else count
onePlusNLayoutHelperEx.setColWeights(colWeights)
onePlusNLayoutHelperEx.setRowWeight(rowWeight)
return onePlusNLayoutHelperEx
}
/**
* 獲取吸邊佈局
* stickyStart是否吸頂 true = 組件吸在頂部 false = 組件吸在底部
*/
fun getStickyLayoutHelper(stickyStart: Boolean, offset: Int): StickyLayoutHelper {
val stickyLayoutHelper = StickyLayoutHelper()
stickyLayoutHelper.setStickyStart(stickyStart)
stickyLayoutHelper.setOffset(offset)
return stickyLayoutHelper
}
/**
* 獲取瀑布流佈局
* count item數
* lane 列數(每行條目數)
*/
fun getStaggeredGridLayoutHelper(count: Int, lane: Int, gap: Int): StaggeredGridLayoutHelper {
return getStaggeredGridLayoutHelper(count, lane, gap, 0, 0)
}
fun getStaggeredGridLayoutHelper(count: Int, lane: Int, gap: Int, vGap: Int, hGap: Int): StaggeredGridLayoutHelper {
val staggeredGridLayoutHelper = StaggeredGridLayoutHelper(lane)
staggeredGridLayoutHelper.itemCount = count
if (gap > 0) {
//設置Item的間距
staggeredGridLayoutHelper.setGap(gap)
} else {
//設置Item水平間距
staggeredGridLayoutHelper.hGap = hGap
//設置Item垂直間距
staggeredGridLayoutHelper.vGap = vGap
}
return staggeredGridLayoutHelper
}
}
abstract class BaseVLayoutVirtualLayoutAdapter(private val layoutManager: VirtualLayoutManager, helpers: List<LayoutHelper>) : VirtualLayoutAdapter<BaseVLayoutVirtualLayoutAdapter.BaseViewHolder>(layoutManager) {
init {
layoutHelpers = helpers
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
return BaseViewHolder(LayoutInflater.from(parent.context).inflate(getCustomHelperLayout(viewType), parent, false))
}
abstract fun getCustomHelperLayout(viewType: Int): Int
/**
*獲取指定佈局位置的LayoutHelper
*/
fun getLayoutHelper(position: Int): LayoutHelper? {
return layoutManager.findLayoutHelperByPosition(position)
}
/**
*獲取指定位置的ItemView
*/
fun getItemView(position: Int): View? {
return layoutManager.findViewByPosition(position)
}
override fun getItemCount(): Int {
var count = 0
for (layoutHelper in layoutHelpers){
count += layoutHelper.itemCount
}
return count
}
override fun getItemViewType(position: Int): Int {
return getItemViewType(getLayoutHelper(position)!!, position)
}
fun getItemViewType(@Nullable layoutHelper: LayoutHelper, position: Int): Int {
return super.getItemViewType(position)
}
inner class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}
abstract class BaseVLayoutDelegateAdapter(private val layoutHelper: LayoutHelper) : DelegateAdapter.Adapter<BaseVLayoutDelegateAdapter.BaseViewHolder>() {
override fun onCreateLayoutHelper(): LayoutHelper {
return layoutHelper
}
fun getLayoutHelper(): LayoutHelper {
return layoutHelper
}
override fun getItemCount(): Int {
return layoutHelper.itemCount
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
@LayoutRes var layoutID = getCustomHelperLayout(layoutHelper, viewType)
return BaseViewHolder(LayoutInflater.from(parent.context).inflate(layoutID, parent, false))
}
fun getCustomHelperLayout(layoutHelper: LayoutHelper, viewType: Int): Int {
return 0
}
override fun getItemViewType(position: Int): Int {
return getViewType(position, layoutHelper)
}
fun getViewType(position: Int, layoutHelper: LayoutHelper): Int {
return super.getItemViewType(position)
}
inner class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}
參考
alibaba/vlayout:https://github.com/alibaba/vlayout