淺扒Android動態(tài)設(shè)置字體大小的示例
說點廢話
Android開發(fā)中,TextView類的控件應(yīng)該說是很常用了。一般來說我們是通過android:textSize="20sp"
來設(shè)置字體大小,但是很多時候也需要動態(tài)設(shè)置字體大小,調(diào)用也很簡單:
textView.setTextSize(textSize);
為了適配各種各樣的型號,我們一般會將字體大小定義到dimens.xml之中:
<dimen name="text_size">16sp</dimen>
然后在java代碼中設(shè)置定義好的字體大?。?/p>
float dimen = getResources().getDimension(R.dimen.text_size); textView.setTextSize(dimen);
滿心歡喜的運行一下,看一效果,結(jié)果發(fā)現(xiàn)字體奇大無比?。。∵h(yuǎn)非16sp?。?!難道不應(yīng)該通過getDimension()取值嗎?通過logcat我發(fā)現(xiàn),在Nexus 6p并且<dimen name="text_size">16sp</dimen>下,在通過getDimension(R.dimen.text_size)得到返回值是56.0!
實際上,在java代碼中取在dimens.xml中定義的值一共有三種:
- getDimension()
- getDimensionPixelOffset()
- getDimensionPixelSize()
看到這三個函數(shù)的名稱時,還是會有點不知所云。本著“不求甚解,遍歷式開發(fā)”的原則,我把這三種方式都試了一遍,結(jié)果發(fā)現(xiàn)字體大小沒一個是對的,這就詭異了。難道這里有平行宇宙?至此,我只能翻出我的英漢大詞典,讓我們?nèi)ヌ綄ひ幌耫ocs吧。
getDimension()
/** * Retrieve a dimensional for a particular resource ID. Unit * conversions are based on the current {@link DisplayMetrics} associated * with the resources. * * @param id The desired resource identifier, as generated by the aapt * tool. This integer encodes the package, type, and resource * entry. The value 0 is an invalid identifier. * * @return Resource dimension value multiplied by the appropriate * metric. */ public float getDimension(@DimenRes int id) throws NotFoundException { }
通過注釋我們不難發(fā)現(xiàn),getDimension()是根據(jù)指定id獲取一個基于當(dāng)前DisplayMetrics的值。這個值究竟是什么也沒有說,只知道是float,并且單位轉(zhuǎn)換是基于當(dāng)前資源的,但肯定不是像素,如果是像素應(yīng)該是int。
getDimensionPixelSize
/** * Retrieve a dimensional for a particular resource ID for use * as a size in raw pixels. This is the same as * {@link #getDimension}, except the returned value is converted to * integer pixels for use as a size. A size conversion involves * rounding the base value, and ensuring that a non-zero base value * is at least one pixel in size. * * @param id The desired resource identifier, as generated by the aapt * tool. This integer encodes the package, type, and resource * entry. The value 0 is an invalid identifier. * * @return Resource dimension value multiplied by the appropriate * metric and truncated to integer pixels. */ public int getDimensionPixelSize(@DimenRes int id) throws NotFoundException { }
getDimensionPixelSize()的功能與getDimension()類似,不同的是將結(jié)果轉(zhuǎn)換為int,并且小數(shù)部分四舍五入,這個結(jié)果將作為尺寸。getDimensionPixelSize()進行了尺寸轉(zhuǎn)換,這個轉(zhuǎn)換實際是上四舍五入的結(jié)果,并且保證返回值是一個至少是1像素的非零數(shù)值。
getDimensionPixelOffset()
/** * Retrieve a dimensional for a particular resource ID for use * as an offset in raw pixels. This is the same as * {@link #getDimension}, except the returned value is converted to * integer pixels for you. An offset conversion involves simply * truncating the base value to an integer. * * @param id The desired resource identifier, as generated by the aapt * tool. This integer encodes the package, type, and resource * entry. The value 0 is an invalid identifier. * * @return Resource dimension value multiplied by the appropriate * metric and truncated to integer pixels. */ public int getDimensionPixelOffset(@DimenRes int id) throws NotFoundException { }
getDimensionPixelOffset()與getDimension()功能類似,不同的是將結(jié)果轉(zhuǎn)換為int,這個結(jié)果將用作原始像素的偏移量。偏移轉(zhuǎn)換(offset conversion,函數(shù)命名中的offset是這個意思)的作用之一是將基礎(chǔ)值簡單地截短為整數(shù),注意直接截斷小數(shù)位,即取整(其實就是把float強制轉(zhuǎn)化為int,注意不是四舍五入)。
階段性總結(jié)
由此可見,這三個函數(shù)返回的都是絕對尺寸,而不是相對尺寸(dp\sp等)。如果getDimension()返回結(jié)果是30.5f,那么getDimensionPixelSize()返回結(jié)果就是31,getDimensionPixelOffset()返回結(jié)果就是30。
至此,應(yīng)該說getDimensionPixelSize() getDimension() getDimensionPixelOffset()我們已經(jīng)大致有所了解了,但是如果想更深入了解一下,就需要深入源碼以驗證上述解釋。
扒源碼
深入源碼,我們可以發(fā)現(xiàn)其實這三個函數(shù)的實現(xiàn)大同小異,以getDimension()
為例:
public float getDimension(@DimenRes int id) throws NotFoundException { final TypedValue value = obtainTempTypedValue(); try { final ResourcesImpl impl = mResourcesImpl; impl.getValue(id, value, true); if (value.type == TypedValue.TYPE_DIMENSION) { return TypedValue.complexToDimension(value.data, impl.getDisplayMetrics()); } throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id) + " type #0x" + Integer.toHexString(value.type) + " is not valid"); } finally { releaseTempTypedValue(value); } }
類TypedValue是動態(tài)類型數(shù)據(jù)的容器,其主要用于盛放Resources的值。上述代碼第7行就是根據(jù)id獲取TypedValue的值,getDimension()、getDimensionPixelOffset()和getDimensionPixelSize()函數(shù)體唯一的不同就是第7行:
- getDimension()調(diào)用的是TypedValue的complexToDimension()方法
- getDimensionPixelSize調(diào)用的是TypedValue的complexToDimensionPixelSize()方法
- getDimensionPixelOffset調(diào)用的是TypedValue的complexToDimensionPixelOffset()方法
順藤摸瓜,我們繼續(xù)深入ypedValue,查看complexToDimension()、complexToDimensionPixelSize()和complexToDimensionPixelOffset()函數(shù)的區(qū)別,會發(fā)現(xiàn)這三個函數(shù)體內(nèi)容依舊大同小異,以complexToDimension()為例:
public static float complexToDimension(int data, DisplayMetrics metrics) { return applyDimension( (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK, complexToFloat(data), metrics); }
complexToDimensionPixelOffset()與complexToDimension()不同的是將結(jié)果進行了強轉(zhuǎn),實際上相當(dāng)直接截斷小數(shù)部分;
complexToDimensionPixelSize()是將結(jié)果進行四舍五入,并取整。這里的四舍五入實際上就是把結(jié)果加上0.5f然后進行強轉(zhuǎn)。
applyDimension()
各位看官,源碼已經(jīng)看到了這里,是否已感覺很無趣?但applyDimension()的實現(xiàn)已經(jīng)脫光了在等著你呢:
public static float applyDimension(int unit, float value,DisplayMetrics metrics) { switch (unit) { case COMPLEX_UNIT_PX: return value; case COMPLEX_UNIT_DIP: return value * metrics.density; case COMPLEX_UNIT_SP: return value * metrics.scaledDensity; case COMPLEX_UNIT_PT: return value * metrics.xdpi * (1.0f/72); case COMPLEX_UNIT_IN: return value * metrics.xdpi; case COMPLEX_UNIT_MM: return value * metrics.xdpi * (1.0f/25.4f); } return 0; }
在上述代碼中,我們發(fā)現(xiàn)在applyDimension()中根據(jù)單位的不同,將float乘上不同的系數(shù)。如dip/dp需乘上屏幕系數(shù),sp則需乘上字號的縮放系數(shù),pt、in、mm等也是根據(jù)相應(yīng)的算法進行換算(從COMPLEX_UNIT_PX直接返回float可以看出,該方法是將數(shù)值轉(zhuǎn)成像素數(shù))。
再次總結(jié)
通過上述探索,我們不難發(fā)現(xiàn),在Adroid并沒有在java代碼中直接獲取dimens.xml中定義的dp(dip)/sp的值的API,只有g(shù)etDimension()、getDimensionPixelOffset()和getDimensionPixelSize()這個三個方法來獲取絕對尺寸。但有時候我們確實需要動態(tài)獲取dimen.xml中的值,并為TextView設(shè)置字體大小。而這種方法直接應(yīng)用在textView.setTextSize(dimen);都是有問題的。那我們將從TextView入手,尋找一個正確的姿勢來設(shè)置字體大小。
setTextSize()
首先把代碼端上來:
public void setTextSize(float size) { setTextSize(TypedValue.COMPLEX_UNIT_SP, size); }
原來setTextSize(float)調(diào)用了他的重載方法setTextSize(int,float),并且第一個參數(shù)傳的默認(rèn)值是TypedValue.COMPLEX_UNIT_SP,眼熟嗎,沒錯就是之前提到的。那么,我們繼續(xù)看看一下setTextSize(int,float)做了什么:
public void setTextSize(int unit, float size) { if (!isAutoSizeEnabled()) { setTextSizeInternal(unit, size, true /* shouldRequestLayout */); } }
很顯然是調(diào)用了setTextSizeInternal(unit, size, true /* shouldRequestLayout */);。看到這累不,不過看都看了就再看看唄,說不定比蒼老師好看:
private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) { Context c = getContext(); Resources r; if (c == null) { r = Resources.getSystem(); } else { r = c.getResources(); } setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()),shouldRequestLayout); }
高能?。?!TypedValue.applyDimension(unit, size, r.getDisplayMetrics())是不是很眼熟???還記得applyDimension()是怎么處理數(shù)據(jù)的嗎?
我們發(fā)現(xiàn)在applyDimension()中根據(jù)單位的不同,將float乘上不同的系數(shù)。如dip/dp需乘上屏幕系數(shù),sp則需乘上字號的縮放系數(shù),pt、in、mm等也是根據(jù)相應(yīng)的算法進行換算(從COMPLEX_UNIT_PX直接返回float可以看出,該方法是將數(shù)值轉(zhuǎn)成像素數(shù))
綜上,setTextSize(float)給傳的值的單位其實是SP,但通過getDimension()取的值卻不是這樣的。為了證實默認(rèn)單位是SP,各位看官可以直接傳個16,看看和16sp是不是一樣的。所以問題是不得到了解決?
結(jié)論
Android中并不提供直接從dimens.xml獲取dp/sp數(shù)值的方法,通過getDimensionPixelSize() getDimension() getDimensionPixelOffset()獲取的值是經(jīng)過處理的。所以正確地動態(tài)設(shè)置TextView字體大小的姿勢應(yīng)該是:
int dimen = getResources().getDimensionPixelSize(R.dimen.text_size); textView.setTextSize(TypedValue.COMPLEX_UNIT_PX,dimen);
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- android textview設(shè)置字體的行距和字間距
- Android TextView 設(shè)置字體大小的方法
- Android Dialog 設(shè)置字體大小的具體方法
- Flutter中嵌入Android 原生TextView實例教程
- android使用TextView實現(xiàn)跑馬燈效果
- android TextView中識別多個url并分別點擊跳轉(zhuǎn)方法詳解
- 詳解Android TextView屬性ellipsize多行失效的解決思路
- Android自定義TextView仿微信朋友圈文字展開全文功能
- Android使用TypeFace設(shè)置TextView的文字字體
相關(guān)文章
android創(chuàng)建和刪除文件夾和文件的實現(xiàn)方法
下面小編就為大家?guī)硪黄猘ndroid創(chuàng)建和刪除文件夾和文件的實現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-03-03SurfaceView開發(fā)[捉小豬]手機游戲 (二)
這篇文章主要介紹了用SurfaceView開發(fā)[捉小豬]手機游戲 (二)本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08Android EditText實現(xiàn)關(guān)鍵詞批量搜索示例
本篇文章主要介紹了Android EditText實現(xiàn)關(guān)鍵詞批量搜索示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02Android Studio實現(xiàn)華為手機的充電動畫效果
本篇文章介紹了我參照華為手機的充電動畫來仿照實現(xiàn)的樣例,這個動畫并不難實現(xiàn),不過案例精簡具有參考意義,需要的朋友快往下看吧2021-10-10Android5.1系統(tǒng)通過包名給應(yīng)用開放系統(tǒng)權(quán)限的方法
這篇文章主要介紹了Android5.1系統(tǒng)通過包名給應(yīng)用開放系統(tǒng)權(quán)限的方法,此文介紹一種通過修改Android平臺系統(tǒng)層代碼,根據(jù)指定的應(yīng)用包名給對應(yīng)的應(yīng)用在該平臺上開放系統(tǒng)權(quán)限,需要的朋友可以參考下2017-11-11