自定义FlowLayout

先将代码附上,抽空再细讲内部实现

主要实现了流式布局,并且考虑了子View的margin和gravity,自定义了LayoutParams。

自定义FlowLayout主要代码

package net.test.kotlintest.flow

import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import androidx.annotation.InspectableProperty
import net.test.kotlintest.R
import java.util.ArrayList
import kotlin.math.max

class FlowLayout(context:Context) : ViewGroup(context) {
    private val lines = ArrayList()

    constructor(context: Context, attrs: AttributeSet?): this(context)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int): this(context)

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        var left=paddingLeft
        var top=paddingTop
        var childTop=0
        for(line in lines){
            val views = line.getViews()
            for(view in views){
                val layoutParams:LayoutParams= view.layoutParams as LayoutParams

                Log.e("man",layoutParams.rightMargin.toString())

                var gravity = layoutParams.gravity
                if (gravity == -1) {
                    gravity = DEFAULT_CHILD_GRAVITY
                }
                Log.e("man", "before gravity$gravity")
                val verticalGravity = gravity?.and(Gravity.VERTICAL_GRAVITY_MASK)

                childTop = when (verticalGravity) {
                    Gravity.TOP -> top+layoutParams.topMargin
                    Gravity.CENTER_VERTICAL -> (line.getHeight()-view.measuredHeight)/2+top+layoutParams.topMargin
                    Gravity.BOTTOM -> line.getHeight()-view.measuredHeight+top+layoutParams.topMargin
                    else -> top+layoutParams.topMargin
                }

                Log.e("man", "gravity$verticalGravity")

                view.layout(left+layoutParams.leftMargin,childTop,left+view.measuredWidth+layoutParams.leftMargin,view.measuredHeight+childTop)

                left+=view.measuredWidth+ HORIZONTAL_SIZE+layoutParams.leftMargin+layoutParams.rightMargin
            }

            left=paddingLeft
            top+=line.getHeight()+ VERTICAL_SIZE
        }

    }


    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        lines.clear()
        val childCount:Int=childCount
        val leftPadding:Int=paddingLeft
        val rightPadding:Int=paddingRight
        val topPadding:Int=paddingTop
        val bottomPadding:Int=paddingBottom

        var useWidth=0
        val selfHeight=MeasureSpec.getSize(heightMeasureSpec)
        val selfWidth=MeasureSpec.getSize(widthMeasureSpec);
        var line= Line()

        var totalHeight=0
        var totalWidth=0

        for (i in 0 until childCount){
            val view:View=getChildAt(i)

            if (view.visibility==View.GONE){
                continue
            }
            val layoutParams:LayoutParams = view.layoutParams as LayoutParams

            val childWidthMeasureSpec = getChildMeasureSpec(
                widthMeasureSpec,
                leftPadding + rightPadding,
                layoutParams.width
            )

            val childHeightMeasureSpec = getChildMeasureSpec(
                heightMeasureSpec,
                topPadding + bottomPadding,
                layoutParams.height
            )

            view.measure(childWidthMeasureSpec,childHeightMeasureSpec)

            if (useWidth+view.measuredWidth+ Companion.HORIZONTAL_SIZE+layoutParams.leftMargin+layoutParams.rightMargin + leftPadding + rightPadding >selfWidth){
                totalWidth= max(totalWidth,useWidth)
                totalHeight+=line.getHeight()+ Companion.VERTICAL_SIZE
                lines.add(line)
                line= Line()
                useWidth=0
            }
            line.setHeight(max(line.getHeight(),view.measuredHeight+layoutParams.topMargin+layoutParams.bottomMargin))
            line.addView(view)
            useWidth += view.measuredWidth + Companion.HORIZONTAL_SIZE+layoutParams.leftMargin+layoutParams.rightMargin


            if (i==childCount-1){
                totalWidth= max(totalWidth,useWidth)
                totalHeight+=line.getHeight()+ Companion.VERTICAL_SIZE
                lines.add(line)
            }
        }


        val height=if (MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.EXACTLY) selfHeight     else totalHeight+topPadding + bottomPadding
        val width=if (MeasureSpec.getMode(widthMeasureSpec)==MeasureSpec.EXACTLY) selfWidth else totalWidth+leftPadding + rightPadding


        setMeasuredDimension(width,height)


    }

    override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
        return LayoutParams(context,attrs)
    }


    override fun generateLayoutParams(p: ViewGroup.LayoutParams?): ViewGroup.LayoutParams {
        return super.generateLayoutParams(p)
    }

    internal class Line{
        private var lineHeight:Int=0

        private val views = ArrayList()


        fun addView(view: View){
            views.add(view)
        }

        fun getViews(): ArrayList {
            return views
        }

        fun setHeight(height:Int){
            this.lineHeight=height
        }

        fun getHeight():Int{
            return lineHeight
        }
    }

    companion object {
        private const val VERTICAL_SIZE:Int=10
        private const val HORIZONTAL_SIZE:Int=10
        private const val DEFAULT_CHILD_GRAVITY = Gravity.TOP or Gravity.START
    }

    class LayoutParams : MarginLayoutParams {

//        @InspectableProperty(
//            name = "layout_gravity",
//            valueType = InspectableProperty.ValueType.GRAVITY)
        var gravity:Int? = UNSPECIFIED_GRAVITY

        constructor(c: Context?, attrs: AttributeSet?) : super(c, attrs){
            val a = c?.obtainStyledAttributes(attrs, R.styleable.FlowLayout_Layout)
            gravity = a?.getInt(R.styleable.FlowLayout_Layout_layout_gravity,
                Companion.UNSPECIFIED_GRAVITY
            )
            a?.recycle()
        }

        constructor(width: Int, height: Int) : super(width, height)
        constructor(source: MarginLayoutParams?) : super(source)
        constructor(source: ViewGroup.LayoutParams?) : super(source)

        constructor(source: LayoutParams): super(source) {
            this.gravity = source.gravity
        }

        constructor(width: Int, height: Int, gravity: Int): this(width, height) {
            this.gravity = gravity
        }
        companion object {
            const val UNSPECIFIED_GRAVITY = -1
        }


    }

}

自定义layout_gravity属性


        
            
            
            
        
    

布局中使用




    
        
        
        
        
        
        
        
        
        

        
    

最终结果

运行结果.png

你可能感兴趣的:(自定义FlowLayout)