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的逻辑也弄上了,举一反三嘛。

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

相关推荐
CYRUS_STUDIO2 小时前
利用 Linux 信号机制(SIGTRAP)实现 Android 下的反调试
android·安全·逆向
CYRUS_STUDIO2 小时前
Android 反调试攻防实战:多重检测手段解析与内核级绕过方案
android·操作系统·逆向
黄林晴6 小时前
如何判断手机是否是纯血鸿蒙系统
android
火柴就是我6 小时前
flutter 之真手势冲突处理
android·flutter
法的空间6 小时前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
循环不息优化不止6 小时前
深入解析安卓 Handle 机制
android
恋猫de小郭6 小时前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
jctech7 小时前
这才是2025年的插件化!ComboLite 2.0:为Compose开发者带来极致“爽”感
android·开源
用户2018792831677 小时前
为何Handler的postDelayed不适合精准定时任务?
android
叽哥7 小时前
Kotlin学习第 8 课:Kotlin 进阶特性:简化代码与提升效率
android·java·kotlin