动态长度控件

166 阅读4分钟

源码

package com.community.mobile.widget

import android.content.Context
import android.util.AttributeSet
import android.view.Gravity
import android.view.View
import android.view.View.OnClickListener
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import com.safframework.log.L


/**
 * 自动换行布局
 * Created by Went_Gone on 2016/9/20.
 */
class WrapLayout : ViewGroup {
    /**
     * TextView的style
     */
    var TEXTVIEW_STYLE = 0

    /**
     * Button的style
     */
    var BUTTON_STYLE = 1
    private var style = 0
    private var btn: View? = null

    constructor(context: Context?) : super(context) {}
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {}
    constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(
        context,
        attrs,
        defStyle
    ) {
    }

    /**
     * 设置数据
     * @param data  文字
     * @param context 上下文
     * @param textSize 文字大小
     * @param pl 左内边距
     * @param pt 上内边距
     * @param pr 右内边距
     * @param pb 下内边距
     * @param ml 左外边距
     * @param mt 上外边距
     * @param mr 右外边距
     * @param mb 下外边距
     */
    fun setData(
        data: Array<String?>?,
        context: Context,
        textSize: Int,
        pl: Int,
        pt: Int,
        pr: Int,
        pb: Int,
        ml: Int,
        mt: Int,
        mr: Int,
        mb: Int
    ) {
        createChild(data, context, textSize, pl, pt, pr, pb, ml, mt, mr, mb)
    }

    fun setData(
        data: List<String?>?,
        context: Context,
        textSize: Int,
        pl: Int,
        pt: Int,
        pr: Int,
        pb: Int,
        ml: Int,
        mt: Int,
        mr: Int,
        mb: Int
    ) {
        var mydata: Array<String?>? = null
        if (data != null) {
            val length = data.size
            mydata = arrayOfNulls(length)
            for (i in 0 until length) {
                mydata[i] = data[i]
            }
        }
        setData(mydata, context, textSize, pl, pt, pr, pb, ml, mt, mr, mb)
    }


    fun setView(textView : TextView){
        this.addView(textView)



    }



    private fun createChild(
        data: Array<String?>?,
        context: Context,
        textSize: Int,
        pl: Int,
        pt: Int,
        pr: Int,
        pb: Int,
        ml: Int,
        mt: Int,
        mr: Int,
        mb: Int
    ) {
        val size = data!!.size
        for (i in 0 until size) {
            val text = data[i]
            //通过判断style是TextView还是Button进行不同的操作,还可以继续添加不同的view
            if (style == TEXTVIEW_STYLE) {
                btn = TextView(context)
                (btn as TextView).gravity = Gravity.CENTER
                (btn as TextView).text = text
                (btn as TextView).textSize = textSize.toFloat()
            } else if (style == BUTTON_STYLE) {
                btn = Button(context)
                (btn as Button).setGravity(Gravity.CENTER)
                (btn as Button).setText(text)
                (btn as Button).setTextSize(textSize.toFloat())
            }
            btn?.setClickable(true)
            btn?.setPadding(
                dip2px(context, pl.toFloat()),
                dip2px(context, pt.toFloat()),
                dip2px(context, pr.toFloat()),
                dip2px(context, pb.toFloat())
            )
            val params =
                MarginLayoutParams(MarginLayoutParams.WRAP_CONTENT, MarginLayoutParams.WRAP_CONTENT)
            params.setMargins(ml, mt, mr, mb)
            btn?.setLayoutParams(params)
            //给每个view添加点击事件
            btn?.setOnClickListener(OnClickListener { markClickListener!!.clickMark(i) })
            this.addView(btn)
        }
    }

    private var markClickListener: MarkClickListener? = null
    fun setMarkClickListener(markClickListener: MarkClickListener?) {
        this.markClickListener = markClickListener
    }

    interface MarkClickListener {
        fun clickMark(position: Int)
    }

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    private fun dip2px(context: Context, dpValue: Float): Int {
        val scale: Float = context.getResources().getDisplayMetrics().density
        return (dpValue * scale + 0.5f).toInt()
    }

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

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        //获取view测量宽度大小
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        //获取view测量高度大小
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)
        val childCount = childCount
        var lineWidth = 0
        var lineHeight = 0
        var width = 0 //warpcontet是需要记录的宽度
        var height = 0
        for (i in 0 until childCount) {
            val child: View = getChildAt(i)
            // 测量每一个child的宽和高
            measureChild(child, widthMeasureSpec, heightMeasureSpec)
            val lp = child.layoutParams as MarginLayoutParams
            val childWidth: Int = child.measuredWidth + lp.leftMargin + lp.rightMargin
            val childHeight: Int = child.measuredHeight + lp.topMargin + lp.bottomMargin
            //            Log.e(TAG, "onMeasure: lineHeight = "+lineHeight+" childHeight = "+childHeight );
            if (lineWidth + childWidth > widthSize) {
                width = Math.max(lineWidth, childWidth)
                //这种情况就是排除单个标签很长的情况
                lineWidth = childWidth

                //记录总行高
                lineHeight = childHeight

                //开启新行
                height += lineHeight



                //因为开了新行,所以这行的高度要记录一下
            } else {
                lineWidth += childWidth
                //lineHeight = Math.max(lineHeight, childHeight);
                // 记录行高
                lineHeight = Math.max(height, childHeight) //记录行高
            }
            // 如果是最后一个,则将当前记录的最大宽度和当前lineWidth做比较
            if (i == childCount - 1) {
                width = Math.max(width, lineWidth) //宽度
                height += childHeight //
            }
        }
        L.i("lineHeight","${lineHeight}")
        L.i("height","${height}")
        setMeasuredDimension(
            if (widthMode == MeasureSpec.EXACTLY) widthSize else width,
            if (heightMode == MeasureSpec.EXACTLY) heightSize else height
        )
        /*   int width1 = (widthMode == MeasureSpec.EXACTLY)? widthSize:width;
        int height1 = (heightMode == MeasureSpec.EXACTLY)? heightSize:height;
        Log.e(TAG, "onMeasure: widthSize ="+widthSize+" heightSize = "+heightSize );
        Log.e(TAG, "onMeasure: width ="+width+" height = "+height );
        Log.e(TAG, "onMeasure: widthEnd ="+width1+" heightEnd = "+height1 );*/
    }

    /**
     * 存储所有的View,按行记录
     */
    private val mAllViews: MutableList<MutableList<View>> = ArrayList<MutableList<View>>()

    /**
     * 记录每一行的最大高度
     */
    private val mLineHeight: MutableList<Int> = ArrayList()

    //onLayout中完成对所有childView的位置以及大小的指定
    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        mAllViews.clear() //清空子控件列表
        mLineHeight.clear() //清空高度记录列表
        val width = width //得到当前控件的宽度(在onmeasure方法中已经测量出来了)
        val childCount = childCount
        // 存储每一行所有的childView
        var lineViews: MutableList<View> = ArrayList<View>()
        var lineWidth = 0 //行宽
        var lineHeight = 0 //总行高
        run {
            var i = 0
            while (i < childCount) {
                val child: View = getChildAt(i)
                val lp = child.getLayoutParams() as MarginLayoutParams //得到属性参数
                val childWidth: Int = child.getMeasuredWidth()
                val childHeight: Int = child.getMeasuredHeight()
                // 如果已经需要换行
                if (i == 3) {
                    i = 3
                }
                if (childWidth + lp.leftMargin + lp.rightMargin + lineWidth > width) //大于父布局的宽度
                {
                    // 记录这一行所有的View以及最大高度
                    mLineHeight.add(lineHeight)
                    // 将当前行的childView保存,然后开启新的ArrayList保存下一行的childView
                    mAllViews.add(lineViews)
                    lineWidth = 0 // 重置行宽
                    lineViews = ArrayList<View>()
                }
                /**
                 * 如果不需要换行,则累加
                 */
                lineWidth += childWidth + lp.leftMargin + lp.rightMargin
                lineHeight = Math.max(
                    lineHeight, childHeight + lp.topMargin
                            + lp.bottomMargin
                )
                lineViews.add(child)
                i++
            }
        }
        // 记录最后一行  (因为最后一行肯定大于父布局的宽度,所以添加最后一行是必要的)
        mLineHeight.add(lineHeight)
        mAllViews.add(lineViews)
        var left = 0
        var top = 0
        val lineNums = mAllViews.size
        for (i in 0 until lineNums) {
            // 每一行的所有的views
            lineViews = mAllViews[i]
            // 当前行的最大高度  每一行的高度都相同  所以使用(i+1)进行设置高度
            lineHeight = (i + 1) * mLineHeight[i]
            for (j in lineViews.indices) {
                val lineChild: View = lineViews[j]
                if (lineChild.getVisibility() === View.GONE) {
                    continue
                }
                val lp = lineChild.getLayoutParams() as MarginLayoutParams
                //开始画标签了。左边和上边的距离是要根据累计的数确定的。
                val lc = left + lp.leftMargin
                val tc = top + lp.topMargin
                val rc: Int = lc + lineChild.getMeasuredWidth()
                val bc: Int = tc + lineChild.getMeasuredHeight()
                lineChild.layout(lc, tc, rc, bc)
                left += lineChild.getMeasuredWidth() + lp.rightMargin + lp.leftMargin
            }
            left = 0 //将left归零
            top = lineHeight
        }
    }

    fun setStyle(style: Int) {
        this.style = style
    }

    companion object {
        private const val TAG = "WrapLayout"
    }
}

布局

<com.community.mobile.widget.WrapLayout
                        android:id="@+id/layout_wrap"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="6dp"
                        android:layout_marginEnd="10dp"
                        />

数据渲染

    private val oldIdentitysList = ArrayList<String>()

Handler().post {
                binding.layoutWrap.removeAllViews()
                identitysList.forEach {
                    val textView = TextView(context)
                    textView.setTextColor(context!!.resources.getColor(R.color.colorPrimary))
                    textView.textSize = 12f
                    textView.setBackgroundResource(R.drawable.shape_tab_identity_in_user_center)
                    textView.text = it.replace("\n", "").trim()
                    textView.tag = textView.text
                    textView.setPadding(15, 5, 15, 5)
                    val params =
                        ViewGroup.MarginLayoutParams(
                            ViewGroup.MarginLayoutParams.WRAP_CONTENT,
                            ViewGroup.MarginLayoutParams.WRAP_CONTENT
                        )
                    params.setMargins(5, 5, 5, 5)
                    textView.layoutParams = params
                    binding.layoutWrap.setView(textView)
                }
            }

最终效果