Android 自定义最大宽度,高度, 宽高比例 Layout

1,949 阅读9分钟
原文链接: mp.weixin.qq.com

前言

这篇博客主要介绍的是怎样自定义一个可以指定最大宽度,高度,以及宽高比的 Layout。原理其实很简单,就是通过重写 onMeasure 方法,重新制定 MeasureSpec。

使用说明

常用的自定义属性

1    <attr name="ml_maxWidth" format="dimension" />2    <attr name="ml_maxheight" format="dimension" />3    <attr name="ml_ratio" format="float" />4    <attr name="ml_ratio_standard">5        <enum name="w_h" value="1" />6        <enum name="h_w" value="2" />7    </attr>8</declare-styleable>
image

指定最大宽度,高度

指定最大宽度,最大高度,我们值需要使用 ml_maxWidth,ml_maxheight 属性即可,当然我们也可以同时指定最大宽度和最大高度。如下

image
 1<com.xj.maxlayout.MaxLayout 2    android:layout_width="match_parent" 3    android:layout_height="100dp" 4    android:layout_marginTop="15dp" 5    android:background="@android:color/holo_blue_light" 6    app:ml_maxWidth="200dp"> 7 8    <TextView 9        android:layout_width="match_parent"10        android:layout_height="wrap_content"11        android:layout_gravity="center"12        android:padding="10dp"13        android:text="指定最大宽度,指定最大宽度,指定最大宽度" />1415</com.xj.maxlayout.MaxLayout>1617<com.xj.maxlayout.MaxLayout18    android:layout_width="200dp"19    android:layout_height="match_parent"20    android:layout_marginTop="15dp"21    android:background="@android:color/holo_blue_light"22    app:ml_maxheight="200dp">2324    <TextView25        android:layout_width="match_parent"26        android:layout_height="match_parent"27        android:layout_gravity="center"28        android:gravity="center"29        android:padding="10dp"30        android:text="指定最大高度" />3132</com.xj.maxlayout.MaxLayout>3334<com.xj.maxlayout.MaxLayout35    android:layout_width="wrap_content"36    android:layout_height="match_parent"37    android:layout_marginTop="15dp"38    android:background="@android:color/holo_blue_light"39    app:ml_maxWidth="200dp"40    app:ml_maxheight="150dp">4142    <TextView43        android:layout_width="match_parent"44        android:layout_height="wrap_content"45        android:layout_gravity="center"46        android:padding="10dp"47        android:text="同时指定最大宽度和最大高度" />4849</com.xj.maxlayout.MaxLayout>

指定宽高比

指定宽高比,我们需要设置两个属性,ml_ratio_standard 和 ml_ratio。ml_ratio_standard 有两个值,w_h 代表已宽度为基准,h_w 代表已高度为基准。

比如,我们要指定高度是宽度的某个比例的时候,如,高度是宽度的两倍,可以这样写

 1<com.xj.maxlayout.MaxLayout 2    android:id="@+id/ml_1" 3    android:layout_width="100dp" 4    android:layout_height="wrap_content" 5    android:background="@color/colorAccent" 6    app:ml_ratio="2.0" 7    app:ml_ratio_standard="w_h"> 8 9    <ImageView10        android:layout_width="match_parent"11        android:layout_height="match_parent"12        android:scaleType="centerCrop"13        android:src="@mipmap/jsy03" />141516</com.xj.maxlayout.MaxLayout>
image

比如,我们要指定宽度是高度的某个比例的时候,如,宽度是高度的 0.8,可以这样写

 1<com.xj.maxlayout.MaxLayout 2    android:id="@+id/ml_2" 3    android:layout_width="match_parent" 4    android:layout_height="200dp" 5    android:layout_marginLeft="20dp" 6    android:layout_toRightOf="@id/ml_1" 7    android:background="@android:color/holo_blue_light" 8    app:ml_ratio="0.8" 9    app:ml_ratio_standard="h_w">1011    <ImageView12        android:layout_width="match_parent"13        android:layout_height="match_parent"14        android:scaleType="centerCrop"15        android:src="@mipmap/jsy04" />1617</com.xj.maxlayout.MaxLayout>18
image

当然,也可以同时指定比例和最大宽度,高度。

 1<com.xj.maxlayout.MaxLayout 2    android:id="@+id/ml_03" 3    android:layout_width="match_parent" 4    android:layout_height="220dp" 5    android:layout_below="@id/ml_1" 6    android:layout_marginTop="15dp" 7    android:background="@android:color/holo_blue_light" 8    app:ml_maxWidth="150dp"> 910    <ImageView11        android:layout_width="match_parent"12        android:layout_height="match_parent"13        android:scaleType="centerCrop"14        android:src="@mipmap/jsy05" />1516</com.xj.maxlayout.MaxLayout>17
image

原理介绍

原理其实很简单,对自定义 View 有基本了解的人都知道,View 的宽度和高度,是在 onMeasure 方法中进行测量的,他们的大小受 MeasureSpec 的影响。既然如此,那么我们在继承 FrameLayout,重写它的 onMeasure 方法。在 onMeasure 方法中根据我们指定的最大宽度,高度和比例对 MeasureSpec 进行调整即可。

思路大概如下没有设置最大宽度,高度,宽高比例,不需要调整,直接返回先拿到原来的 mode 和 size,暂存起来 根据宽高的比例进行相应的调整

 1@Override 2protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 3    // 是否设置了比例 4    boolean setRatio = isSetRatio(); 5    // 没有设置最大宽度,高度,宽高比例,不需要调整,直接返回 6    if (mMaxWidth <= DEF_VALUE && mMaxHeight <= DEF_VALUE && !setRatio) { 7        super.onMeasure(widthMeasureSpec, heightMeasureSpec); 8        return; 9    }1011    // 拿到原来宽度,高度的 mode 和 size12    int heightMode = MeasureSpec.getMode(heightMeasureSpec);13    int heightSize = MeasureSpec.getSize(heightMeasureSpec);1415    int widthMode = MeasureSpec.getMode(widthMeasureSpec);16    int widthSize = MeasureSpec.getSize(widthMeasureSpec);1718    Log.d(TAG, "origin onMeasure: widthSize =" + widthSize + "heightSize = " + heightSize);1920    if (mRatioStandrad == W_H) { // 当模式已宽度为基准21        widthSize = getWidth(widthSize);2223        if (mRatio >= 0) {24            heightSize = (int) (widthSize * mRatio);25        }2627        heightSize = getHeight(heightSize);28        int maxHeightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);29        int maxWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);30        super.onMeasure(maxWidthMeasureSpec, maxHeightMeasureSpec);3132    } else if (mRatioStandrad == H_W) { // 当模式已高度为基准33        heightSize = getHeight(heightSize);3435        if (mRatio >= 0) {36            widthSize = (int) (heightSize * mRatio);37        }3839        widthSize = getWidth(widthSize);4041        int maxHeightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);42        int maxWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);43        super.onMeasure(maxWidthMeasureSpec, maxHeightMeasureSpec);4445    } else { // 当没有设定比例的时候46        widthSize = getWidth(widthSize);47        heightSize = getHeight(heightSize);48        int maxHeightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);49        int maxWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);50        super.onMeasure(maxWidthMeasureSpec, maxHeightMeasureSpec);5152    }5354    Log.d(TAG, "adjust onMeasure: widthSize =" + widthSize + "heightSize = " + heightSize);5556}

我们来看一下,有三种模式:

当模式已宽度为基准的时候,我们首先对宽度进行调整,是否超出最大宽度,超出取最大宽度,没超出,取原来的值。接着,高度按照 mRatio 进行调整,接着判断高度是否超出最大高度,超出取最大高度,没超出,取原来的值。最后,根据相应的 size,mode 生成相应的 MeasureSpec

当模式已高度为基准的时候,我们首先对高度进行调整,是否超出最大高度,超出取最大高度,没超出,取原来的值。接着,宽度按照 mRatio 进行调整,接着判断宽度是否超出最大宽度,超出取最大宽度,没超出,取原来的值。最后,根据相应的 size,mode 生成相应的 MeasureSpec

当模式是默认,没有指定宽度或者高度作为基准的时候,直接判断宽高度是否超出最大的宽高度,制定相应的 MeasureSpec 即可。

源码到此分析为止


题外话

宽高比例的,其实在 2015 的时候,google 已经推出了 PercentFrameLayout,PercentRelativeLayout,可以很好得进行宽高比例的调整。在 API level 26.1.0 的时候,上述的 PercentFrameLayout,PercentRelativeLayout 背标记为过时,并推荐使用 ConstraintLayout。

写这一篇博客,主要是有时候一些旧项目里面,有时候需要设置最大宽度,高度,或者比例,并没有使用最新的一些控件 ConstraintLayout,如果不进行封装,经常需要在代码里面动态设置,这样比较麻烦。再者,这一篇可以帮助大家更好得理解 onMeasue 方法,尤其是 MeasureSpec。即 MeasureSpec 决定了 width, height。想调整 width, height 的话,可以通过调整 MeasureSpec 实现。

同时,这里还有一个坑,如果在代码里面直接设置 width 的话,当 TextView 超过设置的 width 的时候,textView 显示的文字会被截断。


Github MaxLayout Sample

Android 技术人,希望让你看到程序猿不同的一面,除了分享 Coding,,还有职场心得,面试经验,学习心得,人生感悟等等。希望通过该公众号,让大家看到,我们不只会敲代码,我们还会。。。。。。

Android 技术人