没错,我给androidx修了一个bug!

news2024/9/23 3:07:57

不容易啊,必须先截图留恋😁

这个bug是发生在xml中给AppcompatTextView设置textFontWeight,但是却无法生效。修复bug的代码也很简单,总共就几行代码,但是在找引起这个bug的原因和后面给androidx提pr却花了很久。

//AppcompatTextHelper  
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
          && mFontWeight != TEXT_FONT_WEIGHT_UNSPECIFIED && mFontTypeface != null) {
      mFontTypeface = Api28Impl.create(mFontTypeface, mFontWeight,
              (mStyle & Typeface.ITALIC) != 0);

事情的起因是这样的,在Android开发中,我们经常会遇到设计需要我们让TextView支持mediumbold等不同字重样式,原生TextView给了一个textStyle的属性,但是这个属性只支持bolditalicnormal三种样式,这肯定是满足不了设计小姐姐的需求的,而且很多时候bold的样式都比设计出的样式要粗一些。

以前的老办法就是导入不同字重样式的字体文件到app里面,但是很多字体包要支持的语言比较多的话,字体包文件又会比较大,会造成Apk包体积增大。裁剪字体包也可以,但是样式字体包文件多了,也是一件麻烦事,这些杂事很多又不会算到开发时间里面,我就想还有没有其他的解决办法。

网上搜索了下,大多数都是让使用textFontWeight属性的,这个属性倒是可以支持0-1000的字重设置,但是就是兼容性不好,只支持大于api28的手机使用。

Attribute textFontWeight is only used in API level 28 and higher (current min is 21)

但是从我实际使用来看,好像这个属性在TextView上也并没起作用。

<TextView
    style="@style/TextStyle"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Font100"
    android:textFontWeight="100" />
...
<TextView
    style="@style/TextStyle"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Font100"
    android:textFontWeight="900" />

我用一个LinearLayout装了9个TextView,textFontWeight设置从100-900,然后运行在api34的模拟器上,可以看到每个TextView都没应用上textFontWeight属性。
在这里插入图片描述
然后我就去Read The Fucking Source Code,看看TextViewtextFontWeiget的相关源码。TextView设置textFontWeiget的相关源码就是从setTypefaceFromAttrsresolveStyleAndSetTypeface,最后就是setTypeface

private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName,
        @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style,
        @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
    if (typeface == null && familyName != null) {
        // Lookup normal Typeface from system font map.
        final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
        resolveStyleAndSetTypeface(normalTypeface, style, weight);
    } else if (typeface != null) {
        resolveStyleAndSetTypeface(typeface, style, weight);
    } else {  // both typeface and familyName is null.
        switch (typefaceIndex) {
            case SANS:
                resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight);
                break;
            case SERIF:
                resolveStyleAndSetTypeface(Typeface.SERIF, style, weight);
                break;
            case MONOSPACE:
                resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight);
                break;
            case DEFAULT_TYPEFACE:
            default:
                resolveStyleAndSetTypeface(null, style, weight);
                break;
        }
    }
}
private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style,
        @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
    if (weight >= 0) {
        weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight);
        final boolean italic = (style & Typeface.ITALIC) != 0;
        setTypeface(Typeface.create(typeface, weight, italic));
    } else {
        setTypeface(typeface, style);
    }
}

从代码可以看出来,设置textFontWeight就是靠Typeface.create(typeface, weight, italic)
这样来说,我们的代码应该是没有问题的呀,可是为啥没有起效呢?我想了很久,期间我想用hook的办法,把每个TextViewtextFontWeight都打印出来看看,然后我就写了一个WeightPrinterTextView工具类。

class WeightPrinterTextView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : TextView(context, attrs) {
}

然后我把xml中的TextView都替换成这个WeightPrinterTextView,意想不到的是,textFontWeight这个属性竟然又起效果了😂。

<LinearLayout
    style="@style/WeightLL"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <demo.simple.fontweighttextview.WeightPrinterTextView
        android:id="@+id/textView1"
        style="@style/TextStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Font100"
        android:textFontWeight="100" />

    <demo.simple.fontweighttextview.WeightPrinterTextView
        style="@style/TextStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Font200"
        android:textFontWeight="200" />
    
    ...

    <demo.simple.fontweighttextview.WeightPrinterTextView
        style="@style/TextStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Font800"
        android:textFontWeight="800" />

    <demo.simple.fontweighttextview.WeightPrinterTextView
        style="@style/TextStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Font900"
        android:textFontWeight="900" />
</LinearLayout>

在这里插入图片描述
啊,这?又把我秀到了,不愧是Android啊!不信邪的我,还重写clean了项目,然后xml又换回了TextView,再运行,结论就是TextView就是不行,但是自定义继承TextViewWeightPrinterTextView就是可以,这就奇怪了。

一次偶然的断点调试让我发现了端倪,在xml中声明的TextView竟然变成了AppcompatTextView,我写了几个方法验证了下,发现了如下规律:在xml中定义的TextView会转换成AppcompatTextView,但是自定义继承的TextView是不会转换类型的。
在这里插入图片描述
其实到这里,我大概就明白了应该是AppcompatTextView的问题,因为我之前也看过相关源码,知道如果Activity是继承自AppcompatActivity的,在createView的时候,会自动把相关的基础控件转成Appcompat的相关关联控件。
在这里插入图片描述
接下来又是喜闻乐见的Read The Fucking Source Code时间,首先肯定从View构造函数看起,因为View属性一般都是在构造函数中读取的。

public AppCompatTextView(
        @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(TintContextWrapper.wrap(context), attrs, defStyleAttr);

    ThemeUtils.checkAppCompatTheme(this, getContext());

    mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
    mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);

    mTextHelper = new AppCompatTextHelper(this);
    mTextHelper.loadFromAttributes(attrs, defStyleAttr);
    mTextHelper.applyCompoundDrawablesTints();

    mTextClassifierHelper = new AppCompatTextClassifierHelper(this);

    AppCompatEmojiTextHelper emojiTextViewHelper = getEmojiTextViewHelper();
    emojiTextViewHelper.loadFromAttributes(attrs, defStyleAttr);
}

从代码来看好像也只有loadFromAttributes方法关联性比较大,然后再进入这个方法里面,但是这里嵌套的方法就更多了,就不详细阐述了。如果光是看代码去找问题就可大海捞针了,这里推荐我最喜欢的断点单步调试方法,在你觉得有关系的相关代码上打上断点,然后等着代码执行,一步一步的看调试结果就行了。

但是有一个注意的点就是:你要选择和运行代码匹配的库版本才行,比如你导入了两个appcompat的库,断点打在了1.0.0版本的源码上,但是运行的却是2.0.0的库,或者就是断点打在了Android14的源码上,但是运行的手机或模拟器却是Android13的系统。

反正经过我的各种调试,各种跳转,最终定位到了AppCompatTextHelperupdateTypefaceAndStyle方法。大致解释下造成这个bug的原因:就是updateTypefaceAndStyle被调用了两次,第一次的mFontWeight值是对的,但是mFontTypeface却是fontWeiget不对的,所以第二次就设置成了错误的FontTypeface,所以我们只需要像下面的代码,重新创建一个新的mFontTypeface就好了。

//AppcompatTextHelper  
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
          && mFontWeight != TEXT_FONT_WEIGHT_UNSPECIFIED && mFontTypeface != null) {
      mFontTypeface = Api28Impl.create(mFontTypeface, mFontWeight,
              (mStyle & Typeface.ITALIC) != 0);

本来到这里就应该结束了,我只需要用hook或者反射等相关方法解决掉自己的需求就好了,但是突然想到我好像从来没给Google的仓库提交过代码,之前也就给AndroidUtilCodeijkplayer这种国内开发者的仓库提过pr,不管了,试试就逝世😂!

我去仔细读了androidxreadme文档,大致流程就是先在Android Issue Tracker创建一个issue,这一点很坑,我从它这链接点进去,说我没有create issue的权限,我还以为不用完成这一步,这也导致了最开始这个pr搁置了很久也没人来code review。
在这里插入图片描述其实是需要在下拉框,重新选择子模块的。
在这里插入图片描述

然后再是添加自己的Test方法,覆盖测试用例,完善文档说明,最后push代码,new一个新的pr,选择一个code reviewer。

 @Test
 public void testTextFontWeight() {
     final AppCompatTextView textView = mActivity.findViewById(
             R.id.text_view_text_font_weight);

     if (textView.getTypeface() == null) return;

     final int textFontWeight = textView.getTypeface().getWeight();

     assertEquals(textFontWeight, 900);
 }

还有就是在选择code reviewer可能需要注意一下,能多选几个就多选几个吧,我选择了一个对appcompat贡献最多的一个老哥,结果到现在都还在pending review状态,期间都打算发邮件去沟通一下了,我看其他的pr都是同时选择了好几个code reviewer,最后还是在issue tracker创建了一个issue,才有好心的Google老哥帮忙review了一下,thank you bro😁。

提交pr后,google-cla-bot就会让你同意一个Contributor License,同意之后就等着code review就行了。

code review期间liutikas (Aurimas)大佬帮我完善了测试用例,nona-google (Seigo Nonaka)大佬approved了我的code changes,谢谢他们,Thanks(・ω・)ノ。
在这里插入图片描述
在这里插入图片描述
总的来说,这次给androidx提交代码还是很开心,因为AOSP里面也有我写的代码啦,哈哈哈~ 从readme得知,github上这个androidx仓库只是AOSP里面的androidx一个镜像,在github上合并的pr都会同步到AOSP里面去。

好了这篇文章就到这里结束了~假的,我骗你的。因为我在看androidx里面源码的时候,发现了一个神奇的类,那就是TypefaceCompat。它里面有个create的方法,可以适配不同的系统版本,创建带有weightTypeface

public static Typeface create(@NonNull Context context, @Nullable Typeface family,
        @IntRange(from = 1, to = 1000) int weight, boolean italic) {
    if (context == null) {
        throw new IllegalArgumentException("Context cannot be null");
    }
    Preconditions.checkArgumentInRange(weight, 1, 1000, "weight");
    if (family == null) {
        family = Typeface.DEFAULT;
    }
    return sTypefaceCompatImpl.createWeightStyle(context, family, weight, italic);
}

在这个类初始化的时候,static代码块里面会创建自适应的sTypefaceCompatImpl

static {
    if (Build.VERSION.SDK_INT >= 29) {
        sTypefaceCompatImpl = new TypefaceCompatApi29Impl();
    } else if (Build.VERSION.SDK_INT >= 28) {
        sTypefaceCompatImpl = new TypefaceCompatApi28Impl();
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        sTypefaceCompatImpl = new TypefaceCompatApi26Impl();
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
            && TypefaceCompatApi24Impl.isUsable()) {
        sTypefaceCompatImpl = new TypefaceCompatApi24Impl();
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        sTypefaceCompatImpl = new TypefaceCompatApi21Impl();
    } else {
        sTypefaceCompatImpl = new TypefaceCompatBaseImpl();
    }
}

本来我是打算再提交个pr,让AppcompatTextView能不区分版本的支持textFontWeight,但是在自己写代码的时候好像发现为啥Google的大佬不这样做的原因了,可能是因为Typeface.getWeight这个方法是在Api28添加的,这样就不好写单元测试了。其实要做感觉也能做,就是给AppcompatTextView增加getWeight的方法,但是这样又感觉有点抽象😂。

所以就此,我又搞了一个新的仓库😄,用来适配全版本的textFontWeight
在这里插入图片描述
simplepeng/FontWeightTextView: 让textFontWeight属性支持Api29(Android9-p)以下

代码确实很简单,就读取了下textFontWeight的属性,然后调用了下TypefaceCompat.create,以至于群里老哥都吐槽:就这么几行代码还要搞一个库😂?

就此,本篇文章确实结束啦,完结撒花✿✿ヽ(°▽°)ノ✿。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2156419.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

git学习【完结】

git学习【完结】 文章目录 git学习【完结】一、Git基本操作1.创建本地仓库2.配置本地仓库1.局部配置2.全局配置 3.认识工作区、暂存区、版本库4.添加文件5.修改文件6.版本回退7.撤销修改8.删除文件 二、Git分支管理1.理解分支2.创建、切换、合并分支3.删除分支4.合并冲突5.合并…

【每天学个新注解】Day 2 Lombok注解简解(一)—@Data、@Build、@Value

Data 相当于同时使用了 Getter 、Setter 、RequiredArgsConstructor、ToString、EqualsAndHashCode 1、如何使用 需要同时使用Getter 、Setter 、RequiredArgsConstructor、ToString、EqualsAndHashCode注解一个Bean的时候。 2、代码示例 例&#xff1a; Data public cla…

H5白色大方图形ui设计公司网站HTML模板源码

源码名称&#xff1a;白色大方图形ui设计公司网站模板源码 源码介绍&#xff1a;一款H5自适应白色大方图形ui设计公司官网网站模板源码。源码含有七个页面&#xff0c;可用于各种设计公司官网。 需求环境&#xff1a;H5 下载地址&#xff1a; https://www.51888w.com/369.ht…

基于vue框架的宠物托管系统设计与实现is203(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,宠物种类,商家,咨询商家,用户宠物,宠物托管,宠物状况,宠物用品,用品分类,商家公告,结束托管,账单信息,延长托管 开题报告内容 基于Vue框架的宠物托管系统设计与实现开题报告 一、引言 随着现代生活节奏的加快&#xff0c;越来越…

如何在Linux Centos7系统中挂载群晖共享文件夹

前景&#xff1a;企业信息化各种系统需要上传很多的图片或者是文件&#xff0c;文件如何在群晖中显示&#xff0c;当文件或者图片上传到linux指定文件夹内&#xff0c;而文件夹又与群晖共享文件夹进行挂载&#xff0c;就能保证上传的文件或者图片出现在群晖并在群晖里进行管理。…

分布式安装LNMP

目录 搭建LNMP架构 安装mysql 1.上传mysql软件包&#xff0c;关闭防火墙和核心防护 2.安装环境依赖包&#xff0c;桌面安装可能有自带的数据库除 3.配置软件模块 4.编译及安装 5.创建mysql用户 6.修改mysql 配置文件 7.更改mysql安装目录和配置文件的属主属组 8.设置…

Rumor Mitigation in Social Media Platforms with Deep Reinforcement Learning

ABSTRACT 社交媒体平台已成为人们传播和获取信息的主要渠道之一&#xff0c;其可靠性受到网络谣言的严重威胁。现有的辟谣手段如暂停用户、播放真实信息等&#xff0c;要么成本高&#xff0c;要么扰乱用户。在本文中&#xff0c;我们引入了一种新颖的谣言缓解范例&#xff0c;…

springboot每次都需要重设密码?明明在springboot的配置中设置了密码

第一步&#xff1a;查看当前的密码是什么&#xff1f; 打开redis-cli.exe&#xff0c;输入config get requirepass&#xff0c;查看当前的密码是什么&#xff1f; 接着&#xff0c;修改redis的配置文件&#xff0c;找到redis的安装目录&#xff0c;找到相关的conf文件&#x…

Spring高手之路24——事务类型及传播行为实战指南

文章目录 1. 编程式事务&#xff08;不推荐&#xff09;2. 声明式事务&#xff08;推荐&#xff09;3. 事务的传播行为&#xff08;复杂混合事务场景及时序图说明&#xff09;3.1 NESTED和REQUIRES_NEW传播行为的区别 1. 编程式事务&#xff08;不推荐&#xff09; 定义&#…

如何从 Nutanix 迁移至 SmartX 超融合?解读 4 类迁移方案和 2 例迁移实践

2022 年底&#xff0c;Nutanix&#xff08;路坦力&#xff09;正式宣布将中国市场交由合作伙伴&#xff08;联想&#xff09;主导销售&#xff0c;并于 2023 年 8 月完成全面转型。转型后&#xff0c;虽然中国用户依旧可以使用 Nutanix 产品&#xff0c;但在软件的续保和维保方…

企业EMS -能源管理系统-能源管理系统源码-能源在线监测平台

能源管理系统是以帮助工业生产企业在扩大生产的同时&#xff0c;合理计划和利用能源&#xff0c;降低单位产品能源消耗&#xff0c;提高经济效益&#xff0c;降低CO2排放量为目的信息化管控系统。 我国能源管理从上世纪80年代中期开始&#xff0c;通过“能量平衡测试”、“能源…

安卓数据存储——SharedPreferences

共享参数 SharedPreferences 1、sharedPreferences是Android的一个轻量级存储工具&#xff0c;采用的存储结构是key - value的键值对方式 2、共享参数的存储介质是符合XML规范的配置文件。保存路径是&#xff1a;/data/data/应用包名/shared_prefs/文件名.xml 使用场景&…

【Java】掌握Java:基础概念与核心技能

文章目录 前言&#xff1a;1. 注释2. 字面量3. 变量详解3.1 变量的定义3.2 变量里的数据存储原理3.3 数据类型3.4 关键字、标识符 4. 方法4.1 方法是啥&#xff1f;4.2 方法的完整定义格式4.3 方法如何使用&#xff1a;4.4 方法的其他形式4.5 方法的其他注意事项4.5.1 方法是可…

WebMagic:强大的Java网络爬虫框架

上班苦上班累&#xff0c;上班就想打瞌睡。 在当今信息爆炸的时代&#xff0c;数据的获取和处理变得越来越重要。网络爬虫作为获取网络数据的重要工具&#xff0c;已经成为许多开发者和数据科学家的必备技能。今天&#xff0c;我们将介绍一个广受欢迎的Java网络爬虫框架——We…

2024PDF内容修改秘籍:工具推荐与技巧分享

现在我们使用PDF文档的频率越来越高了&#xff0c;很多时候收到的表格之类的资料也都是PDF格式的&#xff0c;如果进行转换之后编辑再转换为PDF格式还是有点麻烦的&#xff0c;那么pdf怎么编辑修改内容呢&#xff1f;这篇文章我将介绍几款可以直接编辑PDF文件的工具来提高我们的…

鸿蒙开发(NEXT/API 12)【跨设备互通NDK开发】协同服务

跨设备互通提供跨设备的相机、扫描、图库访问能力&#xff0c;平板或2in1设备可以调用手机的相机、扫描、图库等功能。 说明 本章节以拍照为例展开介绍&#xff0c;扫描、图库功能的使用与拍照类似。 用户在平板或2in1设备上使用富文本类编辑应用&#xff08;如&#xff1a;…

JVM —— 类加载器的分类,双亲委派机制

文章目录 一、类加载器的分类【理解】1.1 概述1.2 JDK8及之前的版本1.2.1 启动类加载器1.2.2 扩展类加载器和应用程序类加载器扩展类加载器应用程序类加载器 1.3 JDK9之后的类加载器1.4 ClassLoader 中的两个方法【应用】 二、双亲委派模型【理解】2.1 什么是双亲委派机制面试题…

在vue中嵌入vitepress,基于markdown文件生成静态网页从而嵌入社团周报系统的一些想法和思路

什么是vitepress vitepress是一种将markdown文件渲染成静态网页的技术 其使用仅需几行命令即可 //在根目录安装vitepress npm add -D vitepress //初始化vitepress&#xff0c;添加相关配置文件&#xff0c;选择主题&#xff0c;描述&#xff0c;框架等 npx vitepress init //…

9.20日学习记录及相关问题解答

部分一 今天看了一本古老的书。学到了一些有关计算机的远古的知识。弥补了一些之前没有意识到的空白点。 原来上个世纪就有AI这个东西了 现阶段的主流模式&#xff0c;在许多年前其实是将来要发展的对象。 B/S指的是客户机/服务器结构模式 C/S是在B/S基础上发展过来的。三层结…

兼容多个AI应用接口,支持用户自定义切换AI接口

项目背景 2023年ChatGPT横空出世&#xff0c;给IT行业造成了巨大的反响。我第一次发现这个ChatGPT有着如此神奇的功能&#xff08;智能对话&#xff0c;知识问答&#xff0c;代码生成&#xff0c;逻辑推理等&#xff09;&#xff0c;我感到非常吃惊&#xff01;经过一番学习和…