Android:自定义可设置最大最小长度和宽度的LinearLayout

需求原因需要一个本身自适应长宽,但有最大值或者最小值限制的布局.

所以需要自定义View,重新onMeasure进行布局绘制。
我们继承LinearLayout,准备重写onMeasure方法

class AdaptiveLinearLayout
@JvmOverloads
constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        //todo
    }
}

看的出,onMeasure需要传入MeasureSpec,那么MeasureSpec是个什么东西呢?

MeasureSpec是什么?

MeasureSpec 封装了父布局传递给子布局的布局要求,每个 MeasureSpec modesize 组成,包含了父布局对子布局相应的宽高要求。

MeasureSpec 有三种模式,UNSPECIFIEDEXACTLYAT_MOST

UNSPECIFIED:父布局不对子布局做任何限制,它想多大就多大;一般自定义 View 中用不到;(常见于系统内部控件,例如 ListView、ScrollView)

EXACTLY:父布局对子布局的宽高大小有明确的要求,不管子布局想要多大,它都不能超过父布局对它的限制;(一般指具体的大小如 100dp,或者 match_parent,都是确切的尺寸)

AT_MOST:子布局想要多大就可以多大,但是一般来说不会超过父布局的尺寸;(一般对应的父布局尺寸为 wrap_content,父布局无法确定子布局的尺寸)

怎么获取到MeasureSpec?

可以通过调用View.MeasureSpec.makeMeasureSpec()传入size和mode 来获取一个MeasureSpec。具体的大家可以看源码噢,这里就不做详述了。

val size = 100dp
val mode = MeasureSpec.AT_MOST
val measureSpec = View.MeasureSpec.makeMeasureSpec(size,mode )

大致思路

  • 调用super.onMeasure()之前获取到,倘若设置了最大尺寸,那么此时判断父布局给的mode如果是AT_MOST,那么就以最大尺寸和AT_MOST重新获取对应的measureSpec 否则就以最大尺寸和EXACTLY重新获取measureSpec 。然后测量。(这样就可以做到,所以xml文件设置的是wrapcontent,就可以自适应且最大尺寸为设置的最大尺寸。如果父布局设置的是match_parent或者固定值,就直接已最大尺寸作为宽高)。
  • 调用super.onMeasure()之后判断测量后的宽高有没有小于设置的宽高,如果小于,则直接按照上述方式重新获取measureSpec ,然后测量将宽高限定到最小值。注意:此时也需要判断原来的模式是否为EXACTLY,如果是EXACTLY,则不做操作(按照不觉设置的最小值来),否则将模式设置为EXACTLY,将最小值传入,直接设置宽高为最小值

测量代码如下

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        //测量最大值
        val widthMS = getMaxMeasureSpec(widthMeasureSpec, maxWidth)
        val heightMS = getMaxMeasureSpec(heightMeasureSpec, maxHeight)
        super.onMeasure(widthMS, heightMS)
        //测量最小值
        val minW = getMinWidthMeasureSpec(widthMS)
        val minH = getMinHeightMeasureSpec(heightMS)
        if (widthMS != minW || heightMS != minH) {
            super.onMeasure(minW, minH)
        }
    }

    /**
     * 得到最大值的MeasureSpec
     * AT_MOST 子布局想要多大就可以多大,但是一般来说不会超过父布局的尺寸;(一般对应的父布局尺寸为 wrap_content,父布局无法确定子布局的尺寸)所以用来设置最大值
     */
    private fun getMaxMeasureSpec(measureSpec: Int, value: Int): Int {
        if (value > 0 && MeasureSpec.getSize(measureSpec) > value) {
            return if (MeasureSpec.getMode(measureSpec) == MeasureSpec.AT_MOST) {
                MeasureSpec.makeMeasureSpec(value, MeasureSpec.AT_MOST)
            } else {
                MeasureSpec.makeMeasureSpec(value, MeasureSpec.EXACTLY)
            }
        }
        return measureSpec
    }

    /**
     * 得到最小宽度MeasureSpec
     */
    private fun getMinWidthMeasureSpec(measureSpec: Int): Int {
        return if (MeasureSpec.getMode(measureSpec) != MeasureSpec.EXACTLY
                && measuredWidth < minWidth) {
            MeasureSpec.makeMeasureSpec(minWidth, MeasureSpec.EXACTLY)
        } else measureSpec
    }


    /**
     * 得到最小高度MeasureSpec
     */
    private fun getMinHeightMeasureSpec(measureSpec: Int): Int {
        //父布局没有设置EXACTLY,那么此时判断测量后的高度小于最小高度,直接明确设置.如果父布局明确设置为EXACTLY 则直接设置为按照父布局的设置来
        return if (MeasureSpec.getMode(measureSpec) != MeasureSpec.EXACTLY
                && measuredHeight < minHeight) {
            MeasureSpec.makeMeasureSpec(minHeight, MeasureSpec.EXACTLY)
        } else measureSpec
    }

上面的测量是核心的代码思路了,下面放上完整的代码。

设置xml 属性 在attrs.xml文件中新增 所需的属性

<declare-styleable name="AdaptiveLinearLayout">
        <!--最小宽度-->
        <attr name="min_width" format="dimension" />
        <!--对打宽度-->
        <attr name="max_width" format="dimension" />
        <!--最小高度-->
        <attr name="min_height" format="dimension" />
        <!--最大高度-->
        <attr name="max_height" format="dimension" />
    </declare-styleable>

AdaptiveLinearLayout的整体代码

class AdaptiveLinearLayout
@JvmOverloads
constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {

    /**
     * 最小宽度
     */
    var minWidth: Int = 0
        set(value) {
            field = value
            requestLayoutAndInvalidate()
        }

    /**
     * 最大宽度
     */
    var maxWidth: Int = 0
        set(value) {
            field = value
            requestLayoutAndInvalidate()
        }

    /**
     * 最小高度
     */
    var minHeight: Int = 0
        set(value) {
            field = value
            requestLayoutAndInvalidate()
        }

    /**
     * 最大高度
     */
    var maxHeight: Int = 0
        set(value) {
            field = value
            requestLayoutAndInvalidate()
        }

    init {
        //解析xml属性
        val styleAttr = context.obtainStyledAttributes(attrs, R.styleable.AdaptiveLinearLayout)
        minWidth = styleAttr.getDimension(R.styleable.AdaptiveLinearLayout_min_width, 0F).toInt()
        maxWidth = styleAttr.getDimension(R.styleable.AdaptiveLinearLayout_max_width, 0F).toInt()
        minHeight = styleAttr.getDimension(R.styleable.AdaptiveLinearLayout_min_height, 0F).toInt()
        maxHeight = styleAttr.getDimension(R.styleable.AdaptiveLinearLayout_max_height, 0F).toInt()
        styleAttr.recycle()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        //测量最大值
        val widthMS = getMaxMeasureSpec(widthMeasureSpec, maxWidth)
        val heightMS = getMaxMeasureSpec(heightMeasureSpec, maxHeight)
        super.onMeasure(widthMS, heightMS)
        //测量最小值
        val minW = getMinWidthMeasureSpec(widthMS)
        val minH = getMinHeightMeasureSpec(heightMS)
        if (widthMS != minW || heightMS != minH) {
            super.onMeasure(minW, minH)
        }
    }

    /**
     * 得到最大值的MeasureSpec
     * AT_MOST 子布局想要多大就可以多大,但是一般来说不会超过父布局的尺寸;(一般对应的父布局尺寸为 wrap_content,父布局无法确定子布局的尺寸)所以用来设置最大值
     */
    private fun getMaxMeasureSpec(measureSpec: Int, value: Int): Int {
        if (value > 0 && MeasureSpec.getSize(measureSpec) > value) {
            return if (MeasureSpec.getMode(measureSpec) == MeasureSpec.AT_MOST) {
                MeasureSpec.makeMeasureSpec(value, MeasureSpec.AT_MOST)
            } else {
                MeasureSpec.makeMeasureSpec(value, MeasureSpec.EXACTLY)
            }
        }
        return measureSpec
    }

    /**
     * 得到最小宽度MeasureSpec
     */
    private fun getMinWidthMeasureSpec(measureSpec: Int): Int {
        return if (MeasureSpec.getMode(measureSpec) != MeasureSpec.EXACTLY
                && measuredWidth < minWidth) {
            MeasureSpec.makeMeasureSpec(minWidth, MeasureSpec.EXACTLY)
        } else measureSpec
    }


    /**
     * 得到最小高度MeasureSpec
     */
    private fun getMinHeightMeasureSpec(measureSpec: Int): Int {
        //父布局没有设置EXACTLY,那么此时判断测量后的高度小于最小高度,直接明确设置.如果父布局明确设置为EXACTLY 则直接设置为按照父布局的设置来
        return if (MeasureSpec.getMode(measureSpec) != MeasureSpec.EXACTLY
                && measuredHeight < minHeight) {
            MeasureSpec.makeMeasureSpec(minHeight, MeasureSpec.EXACTLY)
        } else measureSpec
    }

    /**
     * 布局重新请求绘制
     */
    private fun requestLayoutAndInvalidate() {
        requestLayout()
        invalidate()
    }
}

在布局文件中直接使用例子

<com.widget.AdaptiveLinearLayout
                android:id="@+id/ll_phone"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center_vertical"
                android:orientation="horizontal"
                app:min_height="50dp"
                app:min_width ="50dp"
				app:max_height="500dp"
                app:max_width ="500dp">

            
            </com.widget.AdaptiveLinearLayout>
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pumpkin的玄学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值