先将代码附上,抽空再细讲内部实现
主要实现了流式布局,并且考虑了子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属性
布局中使用