Android自定义View第四弹(Kotlin流式布局)
程序员文章站
2022-03-21 07:54:38
拖更了1年半,工作太忙了,最近有时间练习一下Kotlin,2017年Google Android开发大会上宣布Kotlin为Android开发第一语言。相信以后用Kotlin会越来越多,之前写过一篇流式布局,这次用Kotlin写一遍,废话不多说,上图:下面是实现这个流式布局的核心代码class FlowLayout(context: Context) : ViewGroup(context) { private val TAG = "FlowLayout" /** * 在布...
拖更了1年半,工作太忙了,最近有时间练习一下Kotlin,2017年Google Android开发大会上宣布Kotlin为Android开发第一语言。相信以后用Kotlin会越来越多,之前写过一篇流式布局,这次用Kotlin写一遍,废话不多说,上图:
下面是实现这个流式布局的核心代码
class FlowLayout(context: Context) : ViewGroup(context) {
private val TAG = "FlowLayout"
/**
* 在布局文件里创建view的时候调用这个构造函数
* @param context context
* @param attrs xml属性
*/
constructor(context: Context, attrs: AttributeSet) : this(context)
/**
* 在布局文件创建view的时候并且设置了自定义的属性(attribute)
* @param context
* @param attrs
* @param defStyleAttr 自定义属性
*/
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : this(context, attrs)
/**
* 在布局文件创建view的时候并且,设置了自定义的属性(attribute)或者资源文件 用的少
* @param context
* @param attrs
* @param defStyleAttr
* @param defStyleRes 自定义资源文件
*/
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : this(
context,
attrs,
defStyleAttr
)
//每个item横向间距
private val mHorizontalSpacing = dp2px(10)
//item的行间距
private val mVerticalSpacing = dp2px(8)
// 记录所有的行,一行一行的存储,用于layout
private val allLines = mutableListOf<List<View>>()
// 记录每一行的行高,用于layout
private var lineHeights = ArrayList<Int>()
//用 clear 不用 new 防止频繁GC产生大量内存碎片
private fun clearMeasureParams() {
allLines.clear()
lineHeights.clear()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
//父View会多次调用onMeasure
clearMeasureParams()
//获取父View的padding
val paddingLeft = paddingLeft
val paddingRight = paddingRight
val paddingTop = paddingTop
val paddingBottom = paddingBottom
//ViewGroup解析的父View给的宽度
val selfWidth = MeasureSpec.getSize(widthMeasureSpec)
//ViewGroup解析的父View给我的高度
val selfHeight = MeasureSpec.getSize(heightMeasureSpec)
//保存一行中的所有的view
var lineViews = mutableListOf<View>()
//记录这行已经使用了多宽
var lineWidthUsed = 0
// 一行的行高
var lineHeight = 0
// measure过程中,子View要求的父ViewGroup的宽
var parentNeededWidth = 0
// measure过程中,子View要求的父ViewGroup的高
var parentNeededHeight = 0
for (i in 0 until childCount) {
val childView = getChildAt(i)
//子View告诉父View,自己要如何布局
val childLP = childView.layoutParams
if (childView.visibility != View.GONE) {
//getChildMeasureSpec在于结合我们从子视图的LayoutParams所给出的MeasureSpec信息来获取最合适的结果
val childWidthMeasureSpec =
getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, childLP.width)
/**
* 参数:
* spec 父窗口传递给子视图的大小和模式
* padding 父窗口的边距,也就是xml中的android:padding
* childDimension 子视图想要绘制的准确大小,但最终不一定绘制此值
*/
val childHeightMeasureSpec =
getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, childLP.height)
//调用子view的measure方法
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec)
//获取子view的度量宽高
val childMeasuredWidth=childView.measuredWidth
val childMeasuredHeight=childView.measuredHeight
//换行操作 自view的宽度+已经用过的宽度+行间距>父View给的宽度则换行
if (childMeasuredWidth + lineWidthUsed + mHorizontalSpacing > selfWidth) {
//一旦换行,我们就可以判断当前行需要的宽和高了,所以此时要记录下来
allLines.add(lineViews)
lineHeights.add(lineHeight)
//ViewGroup 的实际高度 = 每一行的高度+行间距
parentNeededHeight += lineHeight + mVerticalSpacing
//ViewGroup 的实际宽度 = 自view布局产生的最大宽度
parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing)
//换行之后重置
lineViews = ArrayList()
lineWidthUsed = 0
lineHeight = 0
}
// view 是分行layout的,所以要记录每一行有哪些view,这样可以方便layout布局
lineViews.add(childView)
//每行都会有自己的宽和高 每行已经用过的宽度 = 子view宽度+行间距
lineWidthUsed += childMeasuredWidth + mHorizontalSpacing
//行高
lineHeight = Math.max(lineHeight, childMeasuredHeight)
//处理最后一行数据
if (i == childCount - 1) {
allLines.add(lineViews)
lineHeights.add(lineHeight)
parentNeededHeight += lineHeight + mVerticalSpacing
parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing)
}
}
}
//再度量自己的高度保存
//根据子View的度量结果,来重新度量自己ViewGroup
//作为一个ViewGroup,它自己也是一个View,它的大小也需要根据它的父View给它提供的宽高来度量
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
/**
* UNSPECIFIED:不对View大小做限制,如:ListView,ScrollView
* EXACTLY:确切的大小,如:100dp或者march_parent 只有确切的大小采用父View给的大小,否则用自己的大小
* AT_MOST:大小不可超过某数值,如:wrap_content
*/
val realWidth = if (widthMode == MeasureSpec.EXACTLY) selfWidth else parentNeededWidth
val realHeight = if (heightMode == MeasureSpec.EXACTLY) selfHeight else parentNeededHeight
setMeasuredDimension(realWidth, realHeight)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
//行数
val lineCount = allLines.size
//去掉父view的Padding的实际的左上起点
var curL = paddingLeft
var curT = paddingTop
for (i in 0 until lineCount){
//获取每一行的view
val lineViews = allLines[i]
//获取每一行的高度
val lineHeight = lineHeights[i]
lineViews.forEach { view ->
//每个view 的上下左右点
val left = curL
val top = curT
val right = left + view.measuredWidth
val bottom = top + view.measuredHeight
//获取view的上下左右去布局
view.layout(left,top,right,bottom)
//下一个view的左起点 上一个view的右起点+行间距
curL = right + mHorizontalSpacing
}
//下一次的启动起点 = 行高 + 行间距
curT += lineHeight + mVerticalSpacing
//重置左起点
curL = paddingLeft
}
}
private fun dp2px(dp: Int): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp.toFloat(),
Resources.getSystem().displayMetrics
).toInt()
}
}
这篇文章主要是关于Kotlin的应用,关于自定义view构造函数,以及三种测量模式UNSPECIFIED,EXACTLY,AT_MOST,以及一些主要方法的用途以及参数注释里面都有,用法我就不多说了,大家应该都会,不会的话可以看一下我的这篇博客:Android自定义流式布局
哈哈,预祝股市大涨