Android 上使用 iconfont 的一種便捷方案

最近在學習 AIOSO(Alibaba Internal Open Source Organization,即阿里巴巴內部開源組織) 的一個子項目MMCherryUI,這是一個流式佈局,能夠在運行時作動態改變子元素的個數(增刪查改), 並內建動畫效果,先貼一張效果圖出來html

 

咱們學習代碼,最重要的就是動手實踐。因而,我想本身去實現一個相似上面效果的頁面。首先,我須要頁面上的幾張 icon 圖標,去哪裏找?上 iconfont.cn 找,裏面的 icon 最全了。這時候我腦子裏浮現了一個問題,我是使用 png 格式的 icon 圖片仍是使用 iconfont 呢?若是使用 png 我還得考慮圖片的分辨率(固然,我徹底能夠沒必要考慮這個問題,畢竟我只是爲了學習而去寫的這個頁面),但強迫症迫使我最終選擇了 iconfont,由於 iconfont 能夠不用考慮分辨率等適配問題。說到這裏,終於進入正題了 -_- 。java

iconfont 其實說白了就是一種特殊的字體,那麼問題就轉變爲怎麼在 Android 上使用自定義字體的問題了。以 iconfont 爲例,通常你須要下面幾個步驟:android

  • 準備字體文件ios

    在 iconfont.cn 上選好圖片,而後打包下載,解壓文件,獲得字體文件 iconfont.ttf。git

導入字體文件程序員

在工程 assets 目錄下建立一個文件夾,名字隨便取,而後把字體文件放進去。app

 

 

  • 使用字體

 

打開工程目錄下 res/values/strings.xml 文件,添加 stringide

 

 

[html]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. <string name="icons"></string>  

而後打開 Activity 對應的佈局文件,好比 activity_main.xml,添加 string 值到 TextView 中,gitlab

 

 

[html]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. TextView  
  2. android:id="@+id/like"  
  3. android:layout_width="wrap_content"  
  4. android:layout_height="wrap_content"  
  5. android:text="@string/icons" />  


最後,爲 TextView 指定字體佈局

 

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. import android.graphics.Typeface;  
  2. ...  
  3. protected void onCreate(Bundle savedInstanceState) {  
  4. super.onCreate(savedInstanceState);  
  5. // 加載佈局文件  
  6. setContentView(R.layout.activity_main);  
  7. // 加載字體文件  
  8. Typeface iconfont = Typeface.createFromAsset(getAssets(), "iconfont/iconfont.ttf");  
  9. // 獲取 textview 實例  
  10. TextView textview = (TextView)findViewById(R.id.like);  
  11. // 設置 typeface, 就是字體文件  
  12. textview.setTypeface(iconfont);  
  13. }  
  14. ...  

 

 

在單個 View 的狀況下,上面的步驟看起來也沒有那麼可怕,可是在不少 TextView、Button 等文本組件的狀況下,這就是一種體力活了。

舉例:

我有這麼一個需求,首頁底部欄須要用到四個 iconfont 的圖標,


我得在 res/values/strings.xml 文件上添加四個 string (先不考慮選中狀態的狀況),

 

[html]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. <string name="nj_main_bottom_tab_1_icon"></string>  
  2. <string name="nj_main_bottom_tab_2_icon"></string>  
  3. <string name="nj_main_bottom_tab_3_icon"></string>  
  4. <string name="nj_main_bottom_tab_4_icon"></string>  


而後在佈局文件上分別添加 string 到 TextView,最後爲 TextView 指定字體

 

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. Typeface iconfont = Typeface.createFromAsset(getAssets(), "iconfont/iconfont.ttf");  
  2. TextView textview1 = (TextView)findViewById(R.id.icon_1);  
  3. textview1.setTypeface(iconfont);  
  4. TextView textview2 = (TextView)findViewById(R.id.icon_2);  
  5. textview2.setTypeface(iconfont);  
  6. TextView textview3 = (TextView)findViewById(R.id.icon_3);  
  7. textview3.setTypeface(iconfont);  
  8. TextView textview4 = (TextView)findViewById(R.id.icon_4);  
  9. textview4.setTypeface(iconfont);  

 

 

這只是同一個頁面上有四個 iconfont 的狀況,若是是多頁面多處地方使用到 iconfont,那就可怕了,難以接受。身爲程序員,除了會寫代碼,還要講究效率和質量。我開始在想,是否有更好的方案來使用 iconfont。

因爲 iconfont 屬於一種字體,因此我開始從自定義字體的方向上去探索。首先想到的就是自定義 TextView 組件,這也是我最終選擇的方案。但在此以前,我想跟你們分享一個國外的方案,他不考慮界面的佈局層級,經過遍歷當前頁面上全部基於 TextView 的文本組件來設置字體。

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. /** 
  2. * Apply specified font for all text views (including nested ones) in the specified 
  3. * root view. 
  4. *  
  5. * @param context 
  6. *            Context to fetch font asset. 
  7. * @param root 
  8. *            Root view that should have specified font for all it's nested text 
  9. *            views. 
  10. * @param fontPath 
  11. *            Font path related to the assets folder (e.g. "fonts/YourFontName.ttf"). 
  12. */  
  13. public static void applyFont(final Context context, final View root, final String fontName) {  
  14.     try {  
  15.         if (root instanceof ViewGroup) {  
  16.             ViewGroup viewGroup = (ViewGroup) root;  
  17.             for (int i = 0; i < viewGroup.getChildCount(); i++)  
  18.                 applyFont(context, viewGroup.getChildAt(i), fontName);  
  19.         } else if (root instanceof TextView)  
  20.             ((TextView) root).setTypeface(Typeface.createFromAsset(context.getAssets(), fontName));  
  21.     } catch (Exception e) {  
  22.         Log.e(TAG, String.format("Error occured when trying to apply %s font for %s view", fontName, root));  
  23.         e.printStackTrace();  
  24.     }  
  25. }  

 

使用方法:

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. FontHelper.applyFont(context, findViewById(R.id.activity_root), "fonts/YourCustomFont.ttf");  

 

一行代碼就好了,傳入上下文,根佈局和字體文件路徑三個參數,很是簡單粗暴。剛看到這個方法時,我有些驚訝,之因此在這裏拿出來跟你們分享,並非想說這種方法有多好,而是由於我被做者的這種活躍的思惟所震撼,這很值得咱們去學習,不侷限於傳統,大膽敢於創新。

好了,再也不廢話了,說說個人方案——自定義 TextView。

我建立了一個 View,繼承系統的 TextView,將其命名爲 IconFontTextView。

而後在 res/values 目錄下,新建一個 attrs.xml 的資源文件,若是已經存在,就不須要重複建立了。這是個人 attrs.xml 的內容:

[html]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2.  <resources>  
  3.      <declare-styleable name="IconFontTextView">  
  4.          <attr name="value" format="string"/>  
  5.          <attr name="fontFile" format="string"/>  
  6.      </declare-styleable>  
  7.  </resources>  

我定義了兩個屬性,value 填寫 iconfont 圖標對應的值,fontFile 是字體文件路徑。

而後在 IconFontTextView 裏添加兩個構造方法

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. public IconFontTextView(Context context) {  
  2.       super(context);  
  3.   }  
  4.   
  5.   public IconFontTextView(Context context, AttributeSet attrs) {  
  6.       super(context, attrs);  
  7.   }  


在第二個構造方法裏處理屬性值,具體代碼以下:

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. public IconFontTextView(Context context, AttributeSet attrs) {  
  2.           super(context, attrs);  
  3.           if (attrs == null) {  
  4.               return;  
  5.           }  
  6.           TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.IconFontTextView);  
  7.           final int N = a.getIndexCount();  
  8.           for (int i = 0; i < N; i++) {  
  9.               int attr = a.getIndex(i);  
  10.               switch (attr) {  
  11.                   case R.styleable.IconFontTextView_value:  
  12.                       value = a.getString(attr);  
  13.                       setText(value);  
  14.                       Log.d(TAG, "value : " + value);  
  15.                       break;  
  16.                   case R.styleable.IconFontTextView_fontFile:  
  17.                       fontFile = a.getString(attr);  
  18.                       Log.d(TAG, "fontFile : " + fontFile);  
  19.                       try {  
  20.                           Typeface typeface = Typeface.createFromAsset(context.getAssets(), fontFile);  
  21.                           setTypeface(typeface);  
  22.                       } catch (Throwable e) {  
  23.   
  24.                       }  
  25.                       break;  
  26.               }  
  27.           }  
  28.           a.recycle();  
  29.       }  

其實很簡單,這樣咱們就完成了自定義 IconFontTextView 了,接下來說下怎麼使用。在佈局文件中,跟普通的 TextView 同樣,添加 IconFontTextVIew,

[html]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2.  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.                xmlns:app="http://schemas.android.com/apk/res-auto"  
  4.                android:layout_width="match_parent"  
  5.                android:layout_height="wrap_content"  
  6.                android:orientation="vertical">  
  7.    <com.xhj.huijian.mmcherryuidemo.view.IconFontTextView  
  8.                android:layout_width="wrap_content"  
  9.                android:layout_height="wrap_content"  
  10.                app:fontFile="iconfont/iconfont.ttf"  
  11.                android:id="@+id/iconfont_view"  
  12.                app:value=""  
  13.                />  
  14.    <TextView  
  15.                android:layout_width="wrap_content"  
  16.                android:layout_height="wrap_content"  
  17.                android:layout_gravity="center"  
  18.                android:text="搜索"/>  
  19.  </LinearLayout>  

記得在根佈局添加這一行 xmlns:app="http://schemas.android.com/apk/res-auto" ,這樣纔可使用自定義屬性。緊接着,指定字體文件 fontFile="iconfont/iconfont.ttf",告訴 IconFontTextView 字體文件路徑位於 assets 目錄下的 iconfont/iconfont.ttf,並給它一個 iconfont 的值,

 

[html]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. app:value=""  
  2. <!--固然,你也能夠在 res/values/strings.xml 文件上添加 string 再來引用-->  
  3. app:value="@string/arrow";  

這個值對應的圖標是一個箭頭,

 



這樣直接跑程序就能夠看到效果了,是否是方便了不少?由於通常咱們不使用 TextView 來監聽事件,更多的是讓它去負責 View 層的一些展現,並且這裏咱們也再也不須要在 java 代碼中去指定加載字體文件,因此根本不須要再去 findViewById 獲取實例了,因此能夠省去下面的一段代碼,

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. Typeface iconfont = Typeface.createFromAsset(getAssets(), "iconfont/iconfont.ttf");  
  2. TextView textview1 = (TextView)findViewById(R.id.icon_1);  
  3. textview1.setTypeface(iconfont);  
  4. TextView textview2 = (TextView)findViewById(R.id.icon_2);  
  5. textview2.setTypeface(iconfont);  
  6. ...  

 

 

IconFontTextView 咱們能夠抽出來當作一個組件使用,這樣之後使用 iconfont 時,將它當作普通的 TextView 來使用,並指定字體文件和圖標值,就能夠了。

也許你有這樣的需求,我想要在運行時去動態的設置 IconFont 的值。就拿我上面那底部欄四個 tab 來講吧,當我選中其中一個 tab 時,我想要它顯示選中狀態,若是隻是改變顏色那就方便多了,若是在改變顏色的同時,還須要改變圖標呢?你可能會說,這個問題很容易解決啊,由於 IconFontTextView 是 TextView 的子類,我從新給它一個 value 不就行了嗎?是的,但是當你經過 setText("&#xe6f2") 後你會發現,圖標成這樣了



這跟你想要的結果徹底不一樣

這是爲何呢?我就遇到了這個問題,我還發現,當我在 res/values/strings.xml 文件上添加 string 再來使用時卻不會出現這個問題,像下面這樣就什麼問題都沒有:

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. setText(getString(R.string.wifi));  

 

難道 getString 裏面作了什麼處理嗎?看了源碼,沒發現什麼特別的地方。那好吧,我直接經過 log 打印出來,看看 getString 返回了什麼。

我在 string.xml 上添加了

 

[html]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. <string name="iconfont"></string>  

而後在 Activity 上添加下面兩行測試代碼,

 

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. Log.d("tag", getString(R.string.iconfont));  
  2. Log.d("tag", "");  



按照以往的經驗,這八九不離十跟 unicode 字符有關。把代碼稍改一下

 

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. setText("\ue6f2");// "&#x" 替換成 "\u",用 unicode 字符來表示  

 

 

這樣問題就解決了。

OK,最後附上我實現的效果圖,

相關文章
相關標籤/搜索