当前位置: 首页 > 工具软件 > vlayout > 使用案例 >

阿里VLayout学习笔记(Kotlin)

包德业
2023-12-01

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

 类似资料: