Android继承ViewGroup自定义流式布局
16lz
2021-01-26
继承ViewGroup自定义布局主要分两步
1.重写onMeasure()方法,自己根据子view的大小测量容器的宽高
2.重写onLayout()方法,自己对每一个子view进行布局
下面分享一下自己继承View Group实现流式布局的过程
import android.content.Contextimport android.util.AttributeSetimport android.view.Viewimport android.view.ViewGroupclass MyFlowLayout : ViewGroup { //存放所有View的容器 private val allViewLists = ArrayList>() //实例一个List 用于存放一行子View private val singleLineView = ArrayList() @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0): super(context, attrs, defStyleAttr) override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams { return MarginLayoutParams(context,attrs) } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { allViewLists.clear() singleLineView.clear() //获取自身的模式和宽高 val widthModel = MeasureSpec.getMode(widthMeasureSpec) val heightModel = MeasureSpec.getMode(heightMeasureSpec) val widthSize = MeasureSpec.getSize(widthMeasureSpec) val heightSize = MeasureSpec.getSize(heightMeasureSpec) //测量子view的大小 //注意测量了所有可见View,如果view 的Visiable 为GONE则不测量 measureChildren(widthMeasureSpec, heightMeasureSpec) //自身的测量宽高 var myWidth = 0 var myHeight = 0 //子View的测量数据 var cParams: MarginLayoutParams? //计算单行每添加一个子view的宽度 var singleLineChildrenWidth = 0 for (i in 0 until childCount){ val cView = getChildAt(i) //此处不用判断子View是否可见,不可见的子View在measureChildren()的时候已经被忽略掉了 cParams = cView.layoutParams as MarginLayoutParams //当前所有View的宽度 大于 父View的可用宽度时 换行 if (singleLineChildrenWidth + cView.measuredWidth + cParams.marginStart + cParams.marginEnd > widthSize - paddingStart - paddingEnd){ //获取上一行的宽度,每次换行的时候,取出上一行最大的宽度作为父容器的宽度 myWidth = myWidth.coerceAtLeast(getSingleLineWidth(singleLineView)) //获取上一行的高度 val lastLineHeight = getSingleLineHeight(singleLineView) //自身高度叠加 myHeight += lastLineHeight //将上一行的View 添加到总的容器中,注意一定要copy一份,因为下面要清空singleLineView,否则将会只看到最后一行的View allViewLists.add(copyLineViews(singleLineView)) //清空上一行的容器,和记录行宽行高的值 singleLineView.clear() singleLineChildrenWidth = 0 } //计算当前宽度 singleLineChildrenWidth += cView.measuredWidth + cParams.marginStart + cParams.marginEnd singleLineView.add(cView) } //循环结束之后要计算最后一行的宽高,并决定父容器的宽高 myWidth = myWidth.coerceAtLeast(getSingleLineWidth(singleLineView)) myHeight += getSingleLineHeight(singleLineView) //把最后一行放入总容器 allViewLists.add(singleLineView) setMeasuredDimension( if (widthModel == MeasureSpec.EXACTLY) widthSize else myWidth, if (heightModel == MeasureSpec.EXACTLY) heightSize else myHeight ) } //拷贝一行view private fun copyLineViews(singleLineView: ArrayList): ArrayList { val copiedViews = ArrayList() for (i in 0 until singleLineView.size){ copiedViews.add(singleLineView[i]) } return copiedViews } //获取单行的宽度 private fun getSingleLineWidth(singleLineView: ArrayList): Int{ var singLineWidth = 0 for (cView in singleLineView){ val cParams = cView.layoutParams as MarginLayoutParams singLineWidth += cView.measuredWidth + cParams.marginStart + cParams.marginEnd } return singLineWidth } //获取单行高度 private fun getSingleLineHeight(singleLineView: ArrayList): Int { var singleLineHeight = 0 for (view in singleLineView){ val cParams = view.layoutParams as MarginLayoutParams singleLineHeight = singleLineHeight.coerceAtLeast(view.measuredHeight + cParams.topMargin + cParams.bottomMargin) } return singleLineHeight } override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { //如果思路不清晰这个会比较麻烦 //思路: //单个子View的left 和top是不断变化的,搞两个变量单独记录每一个子View的位置状态 //在双层for循环中先对每一行布局,换行后记得重置left,增加top var left = paddingStart var top = paddingTop for (i in 0 until allViewLists.size){ val thisLineViews = allViewLists[i] val thisLineHeight = getSingleLineHeight(thisLineViews) for (j in 0 until thisLineViews.size){ val thisView = thisLineViews[j] val thisLp = thisView.layoutParams as MarginLayoutParams val cl = left + thisLp.marginStart val ct = top + thisLp.topMargin val cr = cl + thisView.measuredWidth val cb = ct + thisView.measuredHeight thisView.layout(cl,ct,cr,cb) left += thisView.measuredWidth + thisLp.marginEnd + thisLp.marginStart } //单行View循环完毕,走出来准备进入下一行之前,要重置left left = paddingStart top += thisLineHeight } }}
代码中注释的比较清除了,这里就不再分析了,另外送上我最近学习AndroidView绘制的练习写的demo(主要是在知乎上HenCoder大佬发布的AndroidView系列教程的一些练习题),如果有兴趣欢迎参考
更多相关文章
- Android中调用Paint的measureText()方法取得字符串显示的宽度值
- Android(安卓)RecyclerView —— 自定义分割线
- Android(安卓)TabLayout宽度在平板上未铺满解决方案
- Android利用ffmpeg做视频裁剪
- Android(安卓)ApiDemos示例解析(73):Graphics->Points
- Android下LineLayout实现View自动换行
- Android(安卓)Studio图片框
- Android中获取屏幕信息的几种方式
- android取得系统高度,标题栏和状态高度