Android---View中的setMinWidth与setMinimumWidth的踩坑记录

平时做有文字显示的需求的时候,UI给的图的样子通常都是一个带有圆角加背景色的TextView,里面的文字宽度自适应,但是当文字很少的时候,宽度就不会减少了。

那么理所当然的就想到了使用android:minWidth这个属性。

但是我在做适配的时候发现,设置了这个值之后,并没有和UI要的那样的效果,TextView的宽度仿佛被固定住了。于是就去搜了一下,发现网上的解决办法就是手动调用一下setMinimumWidth。试了一下,确实解决了。

但是由于整个项目的适配都需要用到,所以我觉得需要搞清楚是为什么,而不是简单的找到解决方法,毕竟万一有坑,那就是大问题了。 那么就来研究一下吧!

整理一下目前的问题:在xml里设置TextView的minWidth之后,这个值不会随着分辨率的改变而改变。

首先,我尝试着重写了setMinWidth与setMinimumWidth,将里面的参数都进行了适配,并打印了日志,大致代码就是这样。

kotlin 复制代码
class MyTextView:androidx.appcompat.widget.AppCompatTextView {
    constructor(context: Context) : this(context,null)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs,0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )
    
    //模拟适配
    private fun adapter(size:Int) = (size*0.75f).toInt()

    override fun setMinWidth(minPixels: Int) {
        val adapterSize = adapter(minPixels)
        Log.d("MyTextView","setMinWidth--minPixels:$minPixels,adapter:$adapterSize")
        super.setMinWidth(adapterSize)
   }

    override fun setMinimumWidth(minWidth: Int) {
        val adapterSize = adapter(minWidth)
        Log.d("MyTextView","setMinWidth--minWidth:$minWidth,adapterSize:$adapterSize")
        super.setMinimumWidth(adapterSize)
    }
}
ini 复制代码
<com.lyr.myapplication2.MyTextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/tv_test"
    android:textColor="@color/white"
    android:background="@color/black"
    android:gravity="center"
    android:textSize="20sp"
    android:text="666"
    android:minWidth="150dp"/>

然后我运行了一下,我发现只有setMinWidth的日志被打印了,而且打印出来的adapterSize也是按照模拟适配的数值来的。

但是此时我还不能确定,因为我之前确实也出现过执行了逻辑但是日志没被打印的问题,所以我觉得应该要关注的是数值是否被正确设置了,于是我在设置完之后又加了几行日志。

kotlin 复制代码
override fun setMinWidth(minPixels: Int) {
    val adapterSize = adapter(minPixels)
    Log.d("MyTextView","setMinWidth--minPixels:$minPixels,adapter:$adapterSize")
    super.setMinWidth(adapter(adapterSize))
    Log.d("MyTextView","setMinWidth--minWidth:${this.minWidth},minimumWidth:${this.minimumWidth}")
}

override fun setMinimumWidth(minWidth: Int) {
    val adapterSize = adapter(minWidth)
    Log.d("MyTextView","setMinWidth--minWidth:$minWidth,adapterSize:$adapterSize")
    super.setMinimumWidth(adapterSize)
    Log.d("MyTextView","setMinWidth--minWidth:${this.minWidth},minimumWidth:${this.minimumWidth}")
}

然后再运行一遍,结果发现确实没有执行setMinimumWidth这个方法,因为minimumWidth这个值没有变化,依然是我们设置的150。

那么此时问题就变了:

  1. 在xml里设置TextView的minWidth之后,minWidth与minimumWidth都被设置成了150,也就是我们在xml里设定的数值
  2. 重写的setMinWidth有被调用,minWidth改变了,minimumWidth没有改变。但是setMinimumWidth没有被调用。
  3. 运行的机器上的TextView的宽度没有缩小(没截图,但是就是这个现象)

打开源码看看这两个方法的逻辑,看看有没有什么思路。

首先是setMinWidth,这个是TextView里面特有的方法

可以看到里面的逻辑首先是赋值保存了我们设定的值,随后请求重新布局,再让我们看看注释里的信息,用红色方框圈出来的。这里的意思就是,在TextView里的minWidth不同于minimumWidth,他们两个的中的最大的那个才会被用于决定最后的宽度!

再去看看setMinimumWidth,这个是View都有的方法

这里面的逻辑也是先赋值保存了我们设定的值,随后请求重新布局。

看到这里大概就懂了,本来按照我们的需求,我们在xml里使用android:minWidth="150dp"设定最小宽度为150dp时,我们肯定是希望随着屏幕分辨率的变化而去适配的。但是我们发现运行的结果无论是日志,还是机器上的现象都不符合。

这其中的原因就是,重写的setMinWidth有被调用,minWidth改变了,minimumWidth没有改变,但是由于决定整个宽度的因素是minWidth和minimumWidth两个中的最大的那个,所以就一直是150dp了。

那么现在我要思考的就是:为什么重写的setMinWidth有被调用,但是setMinimumWidth没有被调用。还是从源码入手,找到这两个方法的调用时机。

TextView中的构造函数里,在获取xml中设定的值时,会调用这个方法

而setMinimumWidth的调用就很奇怪了,没有调用这个方法

那就让我们看看mMinWidth是在哪里赋值的

也是在View的构造函数里赋值的。

那看到这里就懂了,因为TextView的构造函数里是调用setMinWidth赋值的,所以我们重写这个方法可以覆盖,而View的构造函数里,是直接赋值的!所以我们重写setMinimumWidth这个方法并不会让他在初始化的时候被调用到,这也是网上提出的手动调用才有用的解决方法的依据吧!

那么现在再来重新梳理一遍:

我们在xml里设置android:minWidth="150dp"时,View的构造函数和TextView的构造函数都会去获取这个值,不同的是View里是直接通过赋值保存,TextView里会调用对应的set方法赋值保存,这也就导致了我们重写的setMinWidth有被调用,minWidth改变了,但是setMinimumWidth没有被调用。而整个TextView的宽度最后是由这两个中最大的决定的,所以也就导致了我们虽然做了适配,但是如果是需要按比例缩小的时候并不会起作用!

那有什么解决办法呢? 首先当然我们可以手动调用setMinimumWidth去调用,但是需要注意的是,我们最好判断一下minimumWidth是否大于0,如果大于0再去调用。因为这是一个基础属性,我们不能确定如果这样不加判断的调用是否会影响其他属性。 举个例子,刚刚看源码的时候,我选择性忽略了一行代码:

这个mode是什么意思呢?我们再看看它赋值的PIXELS在哪:

在这里需要牵扯到TextView的其他用法,比如限制文本字符,我可以在xml里规定整个TextView只能显示5个字符,那么此时这个mMinWidthMode就会被设置成EMS。

想象一下,假如我想要使用这个限制文本字符的功能,但是此时不需要设置最小宽度,我却手动调用了setMinWidth,这就会导致mMinWidthMode这个属性被设置成PIXELS,结果就很有可能不一样!

其次,既然决定整个View的宽度的逻辑是最大的,那我们可以根据我们想要的去修改这个逻辑不就好了吗?官方也是提供了getSuggestedMinimumWidth这个方法来给我们重写,我们就可以在这里把原先的逻辑覆盖掉,但是这里面的逻辑估计会比较复杂。

我的选择是在构造函数里判断是否大于0,大于0就调用,并且我也把minHeight的逻辑也弄上了,举一反三嘛。

所以还是要多看源码多思考啊!周末快乐!

相关推荐
闲暇部落26 分钟前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
诸神黄昏EX2 小时前
Android 分区相关介绍
android
大白要努力!3 小时前
android 使用SQLiteOpenHelper 如何优化数据库的性能
android·数据库·oracle
Estar.Lee4 小时前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
Winston Wood4 小时前
Perfetto学习大全
android·性能优化·perfetto
Dnelic-7 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
Eastsea.Chen9 小时前
MTK Android12 user版本MtkLogger
android·framework
长亭外的少年16 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿19 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神20 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri