继承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系列教程的一些练习题),如果有兴趣欢迎参考

更多相关文章

  1. Android中调用Paint的measureText()方法取得字符串显示的宽度值
  2. Android(安卓)RecyclerView —— 自定义分割线
  3. Android(安卓)TabLayout宽度在平板上未铺满解决方案
  4. Android利用ffmpeg做视频裁剪
  5. Android(安卓)ApiDemos示例解析(73):Graphics->Points
  6. Android下LineLayout实现View自动换行
  7. Android(安卓)Studio图片框
  8. Android中获取屏幕信息的几种方式
  9. android取得系统高度,标题栏和状态高度

随机推荐

  1. Android App Ant打包
  2. Android 软键盘弹出隐藏挤压界面等各种问
  3. Android触摸屏开发知识汇总
  4. android 模拟器创建的sdcard 没有mount
  5. android 手机存储介质大全
  6. Android:TextView属性大全
  7. Mars Android视频学习笔记——01_16_SQLi
  8. Smalidea+IntelliJ IDEA/Android Studio
  9. Android高效编程注意事项
  10. Android(安卓)7.0 Settings Summary 小记