Android字体系列(一):Android字体基础知识 2023-10-03 12:29 前言 很高兴认识你~ 最近收到一个请求,大致内容是:全局替换当前项目中的默认字体,并引入UI设计师提供的一些新字体。于是我对字体做了一些研究,把我的一些经验分享给大家。 注:本文展示的系统源码基于Android-30,提取核心部分进行分析 1、Android默认字体介绍 1。 Android系统默认使用一种名为Roboto的字体,这也是Google推荐的字体:https://www.gestaodocondominio.net/specimen/Roboto。它提供了多种字体样式可供选择,例如粗体、斜体等。 2。在Android中,我们一般使用TextView控件来直接或间接显示字体,因为Android提供的用于显示字体的控件都会直接或间接继承TextView,如:EditText、Button等。下面给出一个TextView继承图: 3。 TextView中有3个属性可以设置字体的显示: 1), 文本样式 2)、字体 3)、fontFamily 让我们重点关注这三个属性 2。文字样式 textStyle主要用于设置字体样式。我们来看看它在TextView的自定义属性中的体现://TextView的自定义属性textStyle<attr name="textStyle"><旗帜名称="正常"值="0"/> <旗帜名称= "粗体" 值="1" /> <标志 名称=“斜体” 值="2"/>属性> 从上面的自定义属性我们可以知道: 1。 textStyle主要有3种样式: 正常:默认字体 粗体:粗体 斜体:斜体 2。 textStyle 由 flag 携带。 flag代表的值可以进行OR运算,也就是说我们可以为overlay设置多种字体样式 接下来我们在xml中进行设置,如下图: 如您所见,我们将TextView的textStyle属性设置为粗体和斜体。两种风格叠加,右边可以看到预览效果 同样,我们也可以在代码中设置,但是在代码中设置字体样式时,只能设置一种类型,不能重叠: mTextView.setTypeface(null,字体.BOLD) 三、字体typeface主要用于设置TextView的字体。我们来看看它在TextView的自定义属性中的体现: //TextView的自定义属性字体<attr name="typeface"> <枚举 名称 ="正常"值="0"/> <枚举 名称= "sans" 值="1" /> <枚举名称=“衬线” 值="2" /> <枚举 名称=“等宽字体”值="3"/>属性> 从上面的自定义属性我们可以知道: 1。 typeface提供4种字体: noraml:普通字体,系统默认使用的字体 sans:无衬线字体 serif:衬线字体 monospace:等宽字体 2。字体由枚举携带。 enum 代表枚举类型。一次只能选择一种,所以我们一次只能设置一种字体,不能重叠 接下来我们在xml中进行设置,如下图: 简单介绍一下这些字体的区别: serif(衬线字体):在字符笔画的开头和结尾处有额外的装饰,笔画的粗细会根据垂直和水平笔画的不同而变化 sans(无衬线字体):没有衬线字体的额外装饰,与 noraml 字体相同 monospace(等宽字体):限制每个字符的宽度,使其达到等宽效果 同样我们也可以在代码中设置: mTv.setTypeface(Typeface.SERIF) 4。字体家族 fontFamily相当于typeface的增强版。它代表了android系统支持的一系列字体。每种字体都有一个别名。我们可以通过别名来设置这个字体。看看它的自定义属性在TextView中的一个体现: //TextView的自定义属性fontFamily<attrname="fontFamily" 格式="字符串" /> 从上面的自定义属性我们可以知道: fontFamily接收一个String类型的值,即我们可以通过字体别名来设置这个字体,如下图: 可以看到它仔细区分了各系列字体的风格。我们也在xml中设置: mTv.setTypeface(Typeface.create("sans-serif-medium",Typeface.NORMAL)) 注意的值:fontFamily设置的一些字体存在兼容性问题,比如我上面设置的sans-serif-medium字体。仅当Android系统版本大于等于21时才会生效。如果小于21,则会使用默认字体,所以使用fontFamily属性时需要注意这个问题 至此,影响Android字体的3个属性我们就讲完了,但是我心里有一个疑问🤔️?如果我同时设置这三个属性,它们会一起生效吗? 带着这个问题,我们来探究一下源码吧 5。解析textStyle、typeface、fontFamily之间的关系 TextView在我们使用之前需要初始化,最终会调用参数最多的构造函数:public TextView(上下文上下文,@Nullable AttributeSet attrs,int defStyleAttr,int defStyleRes){ super(context, attrs, defStyleAttr, defStyleRes); //省略大量代码.... //读取设置的属性 readTextAppearance (上下文、外观、属性、false /* styleArray */); //设置字体 applyTextAppearance(属性); }private void applyTextAppearance(TextAppearanceAttributes 属性) { //省略大量代码.... setTypefaceFromAttrs(attributes.mFontTypeface,attributes.mFontFamily,Attributes.mTypefaceIndex,attributes.mTextStyle,attributes.mFontWeight);} 上面的调用链会首先读取TextView设置的相关属性。我们来看几个与字体相关的:private void readTextAppearance(上下文上下文,TypedArray 外观,) TextAppearanceAttributes 属性,boolean styleArray) { //... 开关(索引) { case com.android.internal.R.styleable.TextAppearance_typeface: attribute.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex); if(attributes.mTypefaceIndex != -1& & !attributes.mFontFamilyExplicit) { attributes.mFontFamily = 空; } 断; case com.android.internal.R.styleable.TextAppearance_fontFamily: if (!context.isRestricted() && context.canLoadUnsafeResources()) { try { attributes.mFontTypeface = appearance.getFont(attr); } catch(UnsupportedOperationException | Resources.NotFoundException e) { //如果不是字体资源,则为预期值。 } } if(属性.mFontTypeface == null) { attributes.mFontFamily = appearance.getString(attr); } attributes.mFontFamilyExplic它 = 真; 断; case com.android.internal.R.styleable.TextAppearance_textStyle: attributes.mTextStyle = appearance.getInt (attr, attributes.mTextStyle); break; //... 默认: }}从上述代码中我们可以看到: 1、当我们设置字体属性时,将对应的属性值赋给 mTypefaceIndex ,并把 mFontFamily 设置为 null2。当我们设置fontFamily属性时,我们首先会通过appearance.getFont()方法获取字体文件。如果能获取到,就会赋值给mFontTypeface。如果无法获取,则通过appearance.getString()方法获取。获取当前字体别名并将其分配给mFontFamily 注意:当我们为fontFamily设置了一些第三方字体时,那么appearance.getFont()方法无法获取到字体 3。当我们设置textStyle属性时,获取到的属性值会赋值给mTextStyle 上述方法完成后,会调用setTypefaceFromAttrs()方法。该方法是设置TextView字体的最终方法。我们来分析一下这个方法:private void setTypefaceFromAttrs(@Nullable Typeface 字体,@Nullable String familyName, @XMLTypefaceAttr inttypefaceIndex,@www.gestaodocondominio.net int 样式, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_最大) int重量){ { //从系统字体映射中查找正常字体。 finalTypeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL); resolveStyleAndSetTypeface(normalTypeface, 样式, 权重); } 否则 如果(字体!=null){ resolveStyleAndSetTypeface(字体、样式、粗细); } else { //字体和familyName都为空. 开关(字体索引){ caseSANS :resolveStyleAndSetTypeface(Typeface.SANS_SERIF, 样式, 权重); break; case SERIF: resolveStyleAndSetTypeface(Typeface.SERIF, 样式, 粗细); break; 案例 等宽空间: resolveStyleAndSetTypeface(Typeface.MONOSPACE, 风格, 权重); break; 案例默认类型: 默认: resolveStyleAndSetTypeface(null, 风格, 权重); break; } }}上述代码步骤: 1、当字体为空且familyName不为空时,取familyName的字体 2、当 typeface 不为空且 familyName 为空时,取 typeface 的字体 3、当 typeface 和 familyName 都为空,则根据 typefaceIndex 的值取相应的字体 4、typeface ,familyName 和 typefaceIndex 在我们分析的 readTextAppearance 方法中会被赋值5。 resolveStyleAndSetTypefce方法将设置字体和字体样式 6。 style在readTextAppearance方法中赋值,与设置字体不冲突 好了,现在代码分析已经差不多了,我们来看看上面的问题?我们用假设法推导: 假设我在Xml中设置了typeface、familyName和textStyle,那么根据上面的分析: 1。 textStyle一定会生效 2。设置typeface属性后,typefaceIndex会被赋值,familyName会被设置为空 3。设置familyName属性时,有不同的情况: 1. 如果设置了系统字体,则typeface会被赋值,但familyName仍为空。 2. 如果设置了第三方字体,则typeface为空,familyName赋值为 因此,当我们设置这三个属性时,typeface和familyName其中之一不会为空,所以不会使用第三个条件体,那么typeface设置的属性不会生效,剩下的两个属性都可以取效果 最后,我们来总结一下这三个属性: 1。 fontFamily 和 typeface 属性用于字体设置。如果两者都设置了,会优先使用fontFamily属性,typeface属性不生效 2。 textStyle用于字体样式设置,不会与字体设置冲突 上面的源码分析可能有点混乱。有什么不清楚的可以在评论区留言提问 6。 TextView设置字体属性源码分析 通过上面源码的分析,我们了解了fontFamily、typeface和textStyle之间的关系。接下来我们来研究一下我们设置的属性是如何实现这些效果的?又到了源码分析阶段😂,可能有点枯燥,但是如果你能仔细阅读,一定会收获很多,就去做吧我们上面使用Xml或者代码设置的字体属性最终都会到达TextView的setTypeface重载方法://重载方法一 public void setTypeface(@Nullable Typeface tf) { 如果? //刷新重绘 if (mLayout != null) { nullLayouts(); }}//重载方法二 public void setType Face (@Nullable Typeface tf,@www.gestaodocondominio.net int 风格) { if(风格 > 0){ if(tf == null ? tf //调用重载方法一,设置字体 setTypeface(tf); //经过一番算法 int typefaceStyle = tf != null ? tf.getStyle() : 0;int需要=样式和~typefaceStyle;?字体.BOLD) != 0); : 0); } 其他 mTextPaint.setTextSkewX(0); setTypeface(tf); }} 分析上面的代码: 超载方法一: TextView设置字体其实就是操作mTextPaint。 mTextPaint是TextPaint的类对象,继承自Paint,也就是画笔。因此,我们设置的字体实际上是通过调用画笔方法来绘制的 超载方法二: 与重载方法一相比,方法二传递了一个textStyle参数,主要用于标记粗体和斜体: 1) 如果设置了textStyle,请输入第一个条件正文。情况如下: 1、如果传入的tf为null,则根据传入的style获取Typeface字体。 2、如果不为null,则根据传入的tf和style获取Typeface字体。设置好字体后,还要打开画笔的粗体和斜体设置 2),如果不设置textStyle,则仅设置字体,画笔的粗体斜体设置将设置为false和0 从上面的分析我们可以知道:TextView设置字体和字体样式最终都是通过画笔完成的 7。总结本文主要讲: 1。 Android字体概述 2。关于影响Android字体显示的三个属性 3。 textStyle、typeface、fontFamily是三者之间的关系 4。三组属性是如何达到这些效果的呢? 你可能会问,为什么还没说上面的要求就结束了呢?以我今天讲的知识可能无法实现我的上述要求。别担心,我要写一个关于Android字体的系列,因为内容太多了。这个系列文章不会让你等太久,因为我正在参加掘金六月更新挑战,准备写9篇文章了😄 好了,本文到此结束。如果有任何疑问请给我留言,我们在评论区讨论🤝 感谢您阅读这篇文章