聊聊 Android 中的字體大小適配

本篇文章已授權(quán)微信公眾號(hào) guolin_blog(郭霖)獨(dú)家發(fā)布

前言

雖然去年寫的一篇文章【一種非常好用的Android屏幕適配】就包含字體大小適配,但那篇文章講的是根據(jù)不同屏幕尺寸來適配字體大小的哭廉,接下來我要聊的是字體大小適配中的其他幾種場(chǎng)景炕桨。

場(chǎng)景一

有這樣一個(gè)需求葛作,界面上需要顯示一個(gè)標(biāo)題文本邮屁,但是該標(biāo)題的文案長(zhǎng)度是不固定的,要求標(biāo)題的文案全部顯示出來,不能用省略號(hào)顯示枪蘑,并且標(biāo)題所占的寬高是固定的。例如標(biāo)題的文案為 “這是標(biāo)題岖免,該標(biāo)題的名字比較長(zhǎng)岳颇,產(chǎn)品要求不換行全部顯示出來”,如下圖所示颅湘,第一個(gè)為不符合需求的標(biāo)題话侧,第二個(gè)為符合需求的標(biāo)題。

也就是說TextView控件的寬高需要固定闯参,然后根據(jù)標(biāo)題的文案長(zhǎng)度動(dòng)態(tài)改變字體大小瞻鹏,也就是上圖第二個(gè)標(biāo)題的效果。那是怎么實(shí)現(xiàn)的呢鹿寨?

以前的做法一般是測(cè)量TextView文本所占的寬度與TextView控件的寬度對(duì)比新博,動(dòng)態(tài)改變TextView的字體大小,寫起來即麻煩又耗性能脚草。但是現(xiàn)在不用這么麻煩了赫悄,Android 8.0 新增了用來動(dòng)態(tài)改變TextView字體大小的新特性 Autosizing TextViews,只需要簡(jiǎn)單設(shè)置一下屬性即可。

例如上圖中符合需求的效果可以這樣寫:

xml 方式
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center">

    <TextView
        android:layout_width="340dp"
        android:layout_height="50dp"
        android:background="@drawable/shape_bg_008577"
        android:gravity="center_vertical"
        android:maxLines="1"
        android:text="這是標(biāo)題埂淮,該標(biāo)題的名字比較長(zhǎng)姑隅,產(chǎn)品要求不換行全部顯示出來"
        android:textSize="18sp"
        android:autoSizeTextType="uniform"
        android:autoSizeMaxTextSize="18sp"
        android:autoSizeMinTextSize="10sp"
        android:autoSizeStepGranularity="1sp"/>
</LinearLayout>

可以看到TextView控件多了如下屬性:

  • autoSizeTextType:設(shè)置TextView是否支持自動(dòng)改變字體大小,none表示不支持倔撞,uniform表示支持讲仰。
  • autoSizeMinTextSize:最小字體大小,例如設(shè)置為10sp误窖,表示文字最多只能縮小到10sp叮盘。
  • autoSizeMaxTextSize:最大字體大小秩贰,例如設(shè)置為18sp霹俺,表示文字最多只能放大到18sp。
  • autoSizeStepGranularity:縮放粒度毒费,即每次字體大小變化的數(shù)值丙唧,例如設(shè)置為1sp,表示每次縮小或放大的值為1sp觅玻。

上面的只是針對(duì)于8.0的設(shè)備有效想际,如果想要兼容8.0以下設(shè)備,則需要用AppCompatTextView代替TextView溪厘,并且上面幾個(gè)屬性的命名空間需要用app命名空間胡本。如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:gravity="center">

    <android.support.v7.widget.AppCompatTextView
        android:layout_width="340dp"
        android:layout_height="50dp"
        android:background="@drawable/shape_bg_008577"
        android:gravity="center_vertical"
        android:maxLines="1"
        android:text="這是標(biāo)題,該標(biāo)題的名字比較長(zhǎng)畸悬,產(chǎn)品要求不換行全部顯示出來"
        android:textSize="18sp"
        app:autoSizeTextType="uniform"
        app:autoSizeMaxTextSize="18sp"
        app:autoSizeMinTextSize="10sp"
        app:autoSizeStepGranularity="1sp"/>
</LinearLayout>

肯定很多人說 “為什么自己寫的時(shí)候不用AppCompatTextView也能兼容8.0以下設(shè)備呢侧甫?”,那是因?yàn)槟惝?dāng)前的xml文件對(duì)應(yīng)的Activity繼承的是AppCompatActivity蹋宦,如果繼承的是Activity或FragmentActivity是不能達(dá)到兼容的披粟。這一點(diǎn)其實(shí)官方文檔 Autosizing TextViews 也沒有說清楚,導(dǎo)致很多人誤解了冷冗,各位可以自己驗(yàn)證下守屉。

動(dòng)態(tài)編碼方式

使用 TextViewCompat 的setAutoSizeTextTypeWithDefaults()方法設(shè)置TextView是否支持自動(dòng)改變字體大小,setAutoSizeTextTypeUniformWithConfiguration()方法設(shè)置最小字體大小蒿辙、最大字體大小與縮放粒度拇泛。如下所示:

        TextView tvText = findViewById(R.id.tv_text);
        TextViewCompat.setAutoSizeTextTypeWithDefaults(tvText,TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM);
        TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(tvText,10,18,1, TypedValue.COMPLEX_UNIT_SP);
  • setAutoSizeTextTypeWithDefaults()
    參數(shù)1為需要?jiǎng)討B(tài)改變字體大小的TextView,參數(shù)2為是否支持自動(dòng)改變字體大小的類型思灌,AUTO_SIZE_TEXT_TYPE_UNIFORM表示支持俺叭,AUTO_SIZE_TEXT_TYPE_NONE表示不支持。
  • setAutoSizeTextTypeUniformWithConfiguration()
    參數(shù)1為需要?jiǎng)討B(tài)改變字體大小的TextView习瑰,參數(shù)2绪颖、3、4分別為最小字體大小、最大字體大小與縮放粒度柠横,參數(shù)5為參數(shù)2窃款、3、4的單位牍氛,例如sp 晨继、dp、px等搬俊。

同樣紊扬,如果要兼容8.0以下設(shè)備,要么在xml中用AppCompatTextView代替TextView唉擂,要么當(dāng)前Activity繼承AppCompatActivity餐屎。

小結(jié)

Autosizing TextViews是Android 8.0 新增的特性,可以用來動(dòng)態(tài)改變TextView字體大小玩祟。如果要兼容8.0以下設(shè)備腹缩,則需要滿足以下2個(gè)條件中的其中一個(gè)

  • 在xml中用AppCompatTextView代替TextView空扎,并且上面幾個(gè)屬性的命名空間用app命名空間藏鹊。
  • 當(dāng)前Activity繼承AppCompatActivity,而不是Activity或FragmentActivity转锈。

Autosizing TextViews更多屬性請(qǐng)參考 Autosizing TextViews

場(chǎng)景二

很多人肯定遇到過這種情況盘寡,測(cè)試扔個(gè)圖片過來,然后說怎么運(yùn)行在這個(gè)測(cè)試機(jī)后下面的內(nèi)容都擋住了(如下右圖撮慨,左圖為正常情況)竿痰,你不是說做了屏幕適配的嗎?然后你拿測(cè)試的手機(jī)一看甫煞,設(shè)置里面竟然選了 特大 字體菇曲。

嗯... 經(jīng)過這么一看基本就知道什么問題了。原因是你在xml文件寫死了控件的高度抚吠,并且TextView的字體單位用的是sp常潮,這種情況下到手機(jī)設(shè)置中改變字體大小,那么界面中的字體大小就會(huì)隨系統(tǒng)改變楷力。

那么我們應(yīng)該怎么解決這個(gè)問題呢喊式?這時(shí)候我們可以觀察下微信的做法,經(jīng)過研究發(fā)現(xiàn)微信的字體是不會(huì)隨著系統(tǒng)字體大小的改變而改變的萧朝,并且微信本身是有改變字體大小功能的岔留。微信中改變字體大小后不僅字體大小改變了,控件的寬高也會(huì)跟著改變检柬。所以可以猜到微信的字體適配是如下方式實(shí)現(xiàn)的:

字體大小不隨系統(tǒng)改變

想要實(shí)現(xiàn)字體大小不隨系統(tǒng)改變有兩種方式:

1. xml方式

TextView的字體單位不使用sp献联,而是用dp。因?yàn)閟p單位的字體大小會(huì)隨系統(tǒng)字體大小的改變而改變,而dp單位則不會(huì)里逆。

2. 動(dòng)態(tài)編碼方式

字體大小是否隨系統(tǒng)改變可以通過Configuration類的fontScale變量來控制进胯,fontScale變量默認(rèn)為1,表示字體大小不隨系統(tǒng)字體大小的改變而改變原押,那么我們只需要保證fontScale始終為1即可胁镐。具體代碼如下,一般放在Activity的基類BaseActivity即可诸衔。

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (newConfig.fontScale != 1) { //fontScale不為1盯漂,需要強(qiáng)制設(shè)置為1
            getResources();
        }
    }

    @Override
    public Resources getResources() {
        Resources resources = super.getResources();
        if (resources.getConfiguration().fontScale != 1) { //fontScale不為1,需要強(qiáng)制設(shè)置為1
            Configuration newConfig = new Configuration();
            newConfig.setToDefaults();//設(shè)置成默認(rèn)值笨农,即fontScale為1
            resources.updateConfiguration(newConfig, resources.getDisplayMetrics());
        }
        return resources;
    }

雖然兩種方式都可以解決場(chǎng)景二的問題就缆,但是一般都是使用動(dòng)態(tài)編碼方式,原因如下:

  • 若應(yīng)用需要增加類似微信可以改變字體大小的功能磁餐,如果在xml中用的是dp單位违崇,那么該功能將無法實(shí)現(xiàn)阿弃!
  • 若需求改成字體大小需要隨系統(tǒng)字體大小的改變而改變诊霹,只需要?jiǎng)h掉該段代碼即可。
  • 官方推薦使用sp作為字體單位渣淳。
控件寬高盡量不要固定

原因是如果應(yīng)用需要增加類似微信可以改變字體大小的功能脾还,如果控件寬高固定的話,調(diào)大字體會(huì)導(dǎo)致控件顯示不下入愧,這不是我們需要的效果鄙漏。

場(chǎng)景三

有這樣一種情況,當(dāng)你按照設(shè)計(jì)圖的標(biāo)注去寫一個(gè)TextView控件的時(shí)候棺蛛,寬高用的是wrap_content怔蚌,也沒有設(shè)置任何padding,但是運(yùn)行在手機(jī)上該TextView所占的寬高卻比設(shè)計(jì)圖的要大旁赊。如下圖所示桦踊,字體周圍多了很多空白部分。


這是因?yàn)門extView本身就含有內(nèi)邊距造成的终畅,那么TextView有沒有屬性可以去除內(nèi)邊距呢籍胯?答案是有的,該屬性為 includeFontPadding离福,設(shè)置為false表示不包含字體內(nèi)邊距杖狼,具體代碼如下:

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        android:text="Hello"
        android:textSize="50sp"
        android:includeFontPadding="false"/>

運(yùn)行效果如下圖中的第二個(gè)“Hello”(第一個(gè)“Hello”為普通TextView),看起來好像是可以的妖爷,但是仔細(xì)看發(fā)現(xiàn)還是留有一點(diǎn)內(nèi)邊距的蝶涩。


一般的應(yīng)用可能不在乎那點(diǎn)內(nèi)邊距,但如果做的是TV上的應(yīng)用就要求比較嚴(yán)格了,因?yàn)門V界面一般是不支持上下左右滾動(dòng)的绿聘,如果設(shè)計(jì)圖上的內(nèi)容剛好占滿屏幕暗挑,那么這些內(nèi)邊距就會(huì)導(dǎo)致個(gè)別控件顯示不全。所以在這種情況下是必須要解決的斜友,既然TextView自帶屬性不能解決炸裆,那就只能自定義了。具體代碼如下:

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet;

public class NoPaddingTextView extends AppCompatTextView {
    private Paint   mPaint             = getPaint();
    private Rect    mBounds            = new Rect();
    private Boolean mRemoveFontPadding = false;//是否去除字體內(nèi)邊距鲜屏,true:去除 false:不去除

    public NoPaddingTextView(Context context) {
        super(context);
    }

    public NoPaddingTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initAttributes(context, attrs);
    }

    public NoPaddingTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttributes(context, attrs);
    }

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (mRemoveFontPadding) {
            calculateTextParams();
            setMeasuredDimension(mBounds.right - mBounds.left, -mBounds.top + mBounds.bottom);
        }
    }

    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
    }

    protected void onDraw(Canvas canvas) {
        drawText(canvas);
    }

    /**
     * 初始化屬性
     */
    private void initAttributes(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.NoPaddingTextView);
        mRemoveFontPadding = typedArray.getBoolean(R.styleable.NoPaddingTextView_removeDefaultPadding, false);
        typedArray.recycle();
    }

    /**
     * 計(jì)算文本參數(shù)
     */
    private String calculateTextParams() {
        String text = getText().toString();
        int textLength = text.length();
        mPaint.getTextBounds(text, 0, textLength, mBounds);
        if (textLength == 0) {
            mBounds.right = mBounds.left;
        }
        return text;
    }

    /**
     * 繪制文本
     */
    private void drawText(Canvas canvas) {
        String text = calculateTextParams();
        int left = mBounds.left;
        int bottom = mBounds.bottom;
        mBounds.offset(-mBounds.left, -mBounds.top);
        mPaint.setAntiAlias(true);
        mPaint.setColor(getCurrentTextColor());
        canvas.drawText(text, (float) (-left), (float) (mBounds.bottom - bottom), mPaint);
    }
}

將NoPaddingTextView需要的屬性定義在attr.xml文件中烹看,如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="NoPaddingTextView">
        <attr name="removeDefaultPadding" format="boolean"/>
    </declare-styleable>

</resources>

布局文件中使用,如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="horizontal">

    <com.wildma.fontadaptation.NoPaddingTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        android:text="Hello"
        android:textSize="50sp"
        app:removeDefaultPadding="true"/>

</LinearLayout>

運(yùn)行效果如下圖中的第三個(gè)“Hello”(第一個(gè)為普通TextView洛史,第二個(gè)為加了includeFontPadding屬性的TextView)惯殊,完美解決!


OK也殖!字體大小適配中最常用的三種場(chǎng)景都講了土思,如果還有其他場(chǎng)景歡迎補(bǔ)充~

項(xiàng)目地址:FontAdaptation

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市忆嗜,隨后出現(xiàn)的幾起案子己儒,更是在濱河造成了極大的恐慌,老刑警劉巖捆毫,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闪湾,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡绩卤,警方通過查閱死者的電腦和手機(jī)途样,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來濒憋,“玉大人何暇,你說我怎么就攤上這事×萃裕” “怎么了裆站?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)辐烂。 經(jīng)常有香客問我遏插,道長(zhǎng),這世上最難降的妖魔是什么纠修? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任胳嘲,我火速辦了婚禮,結(jié)果婚禮上扣草,老公的妹妹穿的比我還像新娘了牛。我一直安慰自己颜屠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布鹰祸。 她就那樣靜靜地躺著甫窟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蛙婴。 梳的紋絲不亂的頭發(fā)上粗井,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音街图,去河邊找鬼浇衬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛餐济,可吹牛的內(nèi)容都是我干的耘擂。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼絮姆,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼醉冤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起篙悯,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤蚁阳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后辕近,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體韵吨,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年移宅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片椿疗。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡漏峰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出届榄,到底是詐尸還是另有隱情浅乔,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布铝条,位于F島的核電站靖苇,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏班缰。R本人自食惡果不足惜贤壁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望埠忘。 院中可真熱鬧脾拆,春花似錦馒索、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至渠驼,卻和暖如春蜈块,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背迷扇。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工疯趟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人谋梭。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓信峻,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親瓮床。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盹舞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容