zoukankan      html  css  js  c++  java
  • Android 上使用 iconfont 的一种便捷方案

    最近在学习 AIOSO(Alibaba Internal Open Source Organization,即阿里巴巴内部开源组织) 的一个子项目MMCherryUI,这是一个流式布局,可以在运行时做动态改变子元素的个数(增删查改), 并内建动画效果,先贴一张效果图出来

    我们学习代码,最重要的就是动手实践。于是,我想自己去实现一个类似上面效果的页面。首先,我需要页面上的几张 icon 图标,去哪里找?上 iconfont.cn 找,里面的 icon 最全了。这时候我脑子里浮现了一个问题,我是使用 png 格式的 icon 图片还是使用 iconfont 呢?如果使用 png 我还得考虑图片的分辨率(当然,我完全可以不必考虑这个问题,毕竟我只是为了学习而去写的这个页面),但强迫症迫使我最终选择了 iconfont,因为 iconfont 可以不用考虑分辨率等适配问题。说到这里,终于进入正题了 -_- 。

    iconfont 其实说白了就是一种特殊的字体,那么问题就转变为怎么在 Android 上使用自定义字体的问题了。以 iconfont 为例,一般你需要下面几个步骤:

    • 准备字体文件

      在 iconfont.cn 上选好图片,然后打包下载,解压文件,得到字体文件 iconfont.ttf。

    导入字体文件

    在工程 assets 目录下创建一个文件夹,名字随便取,然后把字体文件放进去。

    • 使用字体

    打开工程目录下 res/values/strings.xml 文件,添加 string

    [html] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. <string name="icons">�</string>  

    然后打开 Activity 对应的布局文件,比如 activity_main.xml,添加 string 值到 TextView 中,

    [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,最后附上我实现的效果图,

  • 相关阅读:
    [SDOI2013]直径(树的直径)
    [ZJOI2012]旅游(树的直径)
    [SDOI2011]消防(树的直径)
    【模板】2-SAT 问题(2-SAT)
    [HNOI2006]公路修建问题
    速度限制(分层图)
    [JLOI2011]飞行路线(分层图)
    【洛谷 P3194】 [HNOI2008]水平可见直线 (单调栈)
    【洛谷 P3187】 [HNOI2007]最小矩形覆盖 (二维凸包,旋转卡壳)
    【洛谷 P1452】 Beauty Contest (二维凸包,旋转卡壳)
  • 原文地址:https://www.cnblogs.com/dongweiq/p/5730212.html
Copyright © 2011-2022 走看看