专栏首页androidBlogAndroid 自定义最大宽度,高度, 宽高比例 Layout

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

前言

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

使用说明

常用的自定义属性

    <attr name="ml_maxWidth" format="dimension" />
    <attr name="ml_maxheight" format="dimension" />
    <attr name="ml_ratio" format="float" />
    <attr name="ml_ratio_standard">
        <enum name="w_h" value="1" />
        <enum name="h_w" value="2" />
    </attr>
</declare-styleable>

key

含义

补充说明

ml_maxWidth

最大宽度

ml_maxheight

最大高度

ml_ratio_standard

指定比例的模式,即是宽高比还是高宽比

w_h,宽高比, h_w 高宽比

ml_ratio

比例值

只有比例模式是 w_h 或者 h_w,该值才会生效

指定最大宽度,高度

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

<com.xj.maxlayout.MaxLayout
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:layout_marginTop="15dp"
    android:background="@android:color/holo_blue_light"
    app:ml_maxWidth="200dp">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:padding="10dp"
        android:text="指定最大宽度,指定最大宽度,指定最大宽度" />

</com.xj.maxlayout.MaxLayout>

<com.xj.maxlayout.MaxLayout
    android:layout_width="200dp"
    android:layout_height="match_parent"
    android:layout_marginTop="15dp"
    android:background="@android:color/holo_blue_light"
    app:ml_maxheight="200dp">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:gravity="center"
        android:padding="10dp"
        android:text="指定最大高度" />

</com.xj.maxlayout.MaxLayout>

<com.xj.maxlayout.MaxLayout
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_marginTop="15dp"
    android:background="@android:color/holo_blue_light"
    app:ml_maxWidth="200dp"
    app:ml_maxheight="150dp">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:padding="10dp"
        android:text="同时指定最大宽度和最大高度" />

</com.xj.maxlayout.MaxLayout>

指定宽高比

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

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

<com.xj.maxlayout.MaxLayout
    android:id="@+id/ml_1"
    android:layout_width="100dp"
    android:layout_height="wrap_content"
    android:background="@color/colorAccent"
    app:ml_ratio="2.0"
    app:ml_ratio_standard="w_h">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        android:src="@mipmap/jsy03" />


</com.xj.maxlayout.MaxLayout>

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

<com.xj.maxlayout.MaxLayout
    android:id="@+id/ml_2"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:layout_marginLeft="20dp"
    android:layout_toRightOf="@id/ml_1"
    android:background="@android:color/holo_blue_light"
    app:ml_ratio="0.8"
    app:ml_ratio_standard="h_w">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        android:src="@mipmap/jsy04" />

</com.xj.maxlayout.MaxLayout>

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

<com.xj.maxlayout.MaxLayout
    android:id="@+id/ml_03"
    android:layout_width="match_parent"
    android:layout_height="220dp"
    android:layout_below="@id/ml_1"
    android:layout_marginTop="15dp"
    android:background="@android:color/holo_blue_light"
    app:ml_maxWidth="150dp">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        android:src="@mipmap/jsy05" />

</com.xj.maxlayout.MaxLayout>

原理介绍

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

思路大概如下

  • 没有设置最大宽度,高度,宽高比例,不需要调整,直接返回
  • 先拿到原来的 mode 和 size,暂存起来
  • 根据宽高的比例进行相应的调整
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 是否设置了比例
    boolean setRatio = isSetRatio();
    // 没有设置最大宽度,高度,宽高比例,不需要调整,直接返回
    if (mMaxWidth <= DEF_VALUE && mMaxHeight <= DEF_VALUE && !setRatio) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        return;
    }

    // 拿到原来宽度,高度的 mode 和 size
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);

    Log.d(TAG, "origin onMeasure: widthSize =" + widthSize + "heightSize = " + heightSize);

    if (mRatioStandrad == W_H) { // 当模式已宽度为基准
        widthSize = getWidth(widthSize);

        if (mRatio >= 0) {
            heightSize = (int) (widthSize * mRatio);
        }

        heightSize = getHeight(heightSize);
        int maxHeightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
        int maxWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
        super.onMeasure(maxWidthMeasureSpec, maxHeightMeasureSpec);

    } else if (mRatioStandrad == H_W) { // 当模式已高度为基准
        heightSize = getHeight(heightSize);

        if (mRatio >= 0) {
            widthSize = (int) (heightSize * mRatio);
        }

        widthSize = getWidth(widthSize);

        int maxHeightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
        int maxWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
        super.onMeasure(maxWidthMeasureSpec, maxHeightMeasureSpec);

    } else { // 当没有设定比例的时候
        widthSize = getWidth(widthSize);
        heightSize = getHeight(heightSize);
        int maxHeightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
        int maxWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
        super.onMeasure(maxWidthMeasureSpec, maxHeightMeasureSpec);

    }

    Log.d(TAG, "adjust onMeasure: widthSize =" + widthSize + "heightSize = " + heightSize);
    
}

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

  1. 当模式已宽度为基准的时候,我们首先对宽度进行调整,是否超出最大宽度,超出取最大宽度,没超出,取原来的值。接着,高度按照 mRatio 进行调整,接着判断高度是否超出最大高度,超出取最大高度,没超出,取原来的值。最后,根据相应的 size,mode 生成相应的 MeasureSpec
  2. 当模式已高度为基准的时候,我们首先对高度进行调整,是否超出最大高度,超出取最大高度,没超出,取原来的值。接着,宽度按照 mRatio 进行调整,接着判断宽度是否超出最大宽度,超出取最大宽度,没超出,取原来的值。最后,根据相应的 size,mode 生成相应的 MeasureSpec
  3. 当模式是默认,没有指定宽度或者高度作为基准的时候,直接判断宽高度是否超出最大的宽高度,制定相应的 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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 使用CoordinatorLayout打造各种炫酷的效果

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details...

    用户2965908
  • 自定义Behavior —— 仿知乎,FloatActionButton隐藏与展示

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details/...

    用户2965908
  • 自定义 behavior - 完美仿 QQ 浏览器首页,美团商家详情页

    记得两年前的时候,曾写过自定义 behavior 的文章 自定义 Behavior -仿新浪微博发现页的实现,到现在差不多有一万多的阅读量吧。

    用户2965908
  • 安卓开发_浅谈Android动画(三)

    听着music睡
  • Android开发之给你的Button加个背景

    在Android应用中,绝大部分情况下,按钮都有按下变色的效果,这种效果主要都是借助于Android里面的 StateListDrawable来实现的,它可以设...

    YungFan
  • Android解决ScrollView下嵌套ListView和GridView中内容显示不全的问题

    最近为公司做的一个Demo里面用到了ScrollView嵌套了GridView和ListView,然而在嵌套的时候我发现GridView和ListView都是不...

    砸漏
  • Android 逐帧动画创建实例详解

    我们看早期电影的时候,电影通常是一张一张播放,用我们现在专有名词来说,就是一帧帧来,安卓同样有这样动画效果的编排形式。

    砸漏
  • Android自定义Dialog实现加载对话框效果

    最近开发中用到许多对话框,之前都是在外面的代码中创建AlertDialog并设置自定义布局实现常见的对话框,诸如更新提示等含有取消和删除两个按钮的对话框我们可以...

    砸漏
  • 《Monkey Android》第5课之剖析第一个App

    我们的第一个项目FirstDemo.java建立以后,Android Studio首先展示给我们的就是activity_main.xml和MainAcivity...

    GitOPEN
  • RecyclerView实现拖拽排序效果

    砸漏

扫码关注云+社区

领取腾讯云代金券