阿里VLayout學習筆記(Kotlin)

阿里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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章