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

  1. <string name="icons"></string>

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

[html] view plain copy

  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

  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

  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

  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

  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

  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

  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

  1. public IconFontTextView(Context context) {
  2. super(context);
  3. }
  4. public IconFontTextView(Context context, AttributeSet attrs) {
  5. super(context, attrs);
  6. }

在第二个构造方法里处理属性值,具体代码如下:

[java] view plain copy

  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. break;
  25. }
  26. }
  27. a.recycle();
  28. }

其实很简单,这样我们就完成了自定义 IconFontTextView 了,接下来讲下怎么使用。在布局文件中,跟普通的 TextView 一样,添加 IconFontTextVIew,

[html] view plain copy

  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

  1. app:value=""
  2. <!--当然,你也可以在 res/values/strings.xml 文件上添加 string 再来引用-->
  3. app:value="@string/arrow";

这个值对应的图标是一个箭头,

这样直接跑程序就可以看到效果了,是不是方便了很多?因为一般我们不使用 TextView 来监听事件,更多的是让它去负责 View 层的一些展示,而且这里我们也不再需要在 java 代码中去指定加载字体文件,所以根本不需要再去 findViewById 获取实例了,所以可以省去下面的一段代码,

[java] view plain copy

  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

  1. setText(getString(R.string.wifi));

难道 getString 里面做了什么处理吗?看了源码,没发现什么特别的地方。那好吧,我直接通过 log 打印出来,看看 getString 返回了什么。

我在 string.xml 上添加了

[html] view plain copy

  1. <string name="iconfont"></string>

然后在 Activity 上添加下面两行测试代码,

[java] view plain copy

  1. Log.d("tag", getString(R.string.iconfont));
  2. Log.d("tag", "");

按照以往的经验,这八九不离十跟 unicode 字符有关。把代码稍改一下

[java] view plain copy

  1. setText("\ue6f2");// "&#x" 替换成 "\u",用 unicode 字符来表示

这样问题就解决了。

OK,最后附上我实现的效果图,

时间: 2024-09-30 07:02:48

Android 上使用 iconfont 的一种便捷方案的相关文章

Android上发送带附件的邮件

准备工作-下载最新版本的JMail https://java.net/projects/javamail/pages/Home#Download_JavaMail_1.5.2_Release http://www.oracle.com/technetwork/java/javase/downloads/index-135046.html 在android上发送邮件方式: 第一种:借助GMail APP客户端,缺点是必须使用GMail帐号,有点是比较方便 不需要写很多代码,但是不是很灵活. 第二种

android开发中监听器的三种实现方法(OnClickListener)

Android开发中监听器的实现有三种方法,对于初学者来说,能够很好地理解这三种方法,将能更好地增进自己对android中监听器的理解. 一.什么是监听器. 监听器是一个存在于View类下的接口,一般以On******Llistener命名,实现该接口需要复写相应的on****(View v)方法(如onClick(View v)). 二.监听器的三种实现方法 (以OnClickListener为例) 方法一:在Activity中定义一个内部类继承监听器接口(这里是OnClickListener

Android JSON原生解析的几种思路,以号码归属地,笑话大全,天气预报为例演示

Android JSON原生解析的几种思路,以天气预报为例 今天项目中要实现一个天气的预览,加载的信息很多,字段也很多,所以理清了一下思路,准备独立出来写一个总结,这样对大家还是很有帮助的,老司机要开车了 涉及到网络,你就一定要先添加权限,准没错 <!--网络权限--> <uses-permission android:name="android.permission.INTERNET" /> 一.归属地查询(JSONObject) 这个也是最简单的一类Json

在Android上运用Anko和Kotlin开发数据库:SQLite从来不是一件轻松的事(KAD25)

作者:Antonio Leiva 时间:Mar 30, 2017 原文链接:https://antonioleiva.com/databases-anko-kotlin/ 事实告诉我们:在Android中编写数据库是相当无聊的. 使用SQLite时,所需的所有模板在当今世界上都不是一件最令人愉快的事情. 所幸的是,在最新一次Google I / O会议上,它们宣布的其中一项事项(称其为:Room),就是为简化这项工作,开发出足够的库. 然而,运用Anko,我们仍可以继续像使用低级别框架一样工作,

如何调试 Android 上 HTTP(S) 流量

转自: http://greenrobot.me/devpost/how-to-debug-http-and-https-traffic-on-android/ 如何调试 Android 上 HTTP(S) 流量 前面的话 在Android开发中我们常常会和API 打交道,可能你不想,但是这是避不开的.大部分情况下,调试发送网络请求和接收响应的过程都是十分痛苦的. 有多少次我们经过调试发现API的调用失败仅仅是因为我们的编码错了或者丢失了一个HTTP头部参数?在调试的过程中,我们发现出现错误的原

android中bitmap压缩的几种方法的解读

最近在研究微信的sdk,在缩略图这遇到了一点问题. 微信的缩略图要求是不大于32k,这就需要对我的图片进行压缩.试了几种方法,一一道来. 1.质量压缩法: 代码如下 ByteArrayOutputStream baos = new ByteArrayOutputStream(); image.compress(Bitmap.CompressFormat.JPEG, 100, baos); int options = 100; while ( baos.toByteArray().length /

android httpClient 支持HTTPS的2种处理方式

摘自: http://www.kankanews.com/ICkengine/archives/9634.shtml 项目中Android https或http请求地址重定向为HTTPS的地址,相信很多人都遇到了这个异常(无终端认证): javax.net.ssl.SSLPeerUnverifiedException: No peer certificate 1.没遇到过的问题,搜索吧,少年 log里出现这个异常,作者第一次遇到,不知道啥意思.看下字面意思,是ssl协议中没有终端认证.SSL?作

Dagger——Android上的依赖注入框架

* 你也可以去Github查看这片文章 简介 在开发程序的时候,会用到各种对象,很多对象在使用之前都需要进行初始化.例如你要操作一个SharedPreference,你需要调用getSharedPreferences(String name,int mode)来获取一个对象,然后才能使用它.而如果这个对象会在多个Activity中被使用,你就需要在每个使用的场景中都写下同样的代码.这不仅麻烦,而且增加了出错的可能.dagger的用途就是:让你不需要初始化对象.换句话说,任何对象声明完了就能直接用

*Android跨进程通信的四种方式

由于android系统中应用程序之间不能共享内存.因此,在不同应用程序之间交互数据(跨进程通讯)就稍微麻烦一些.在android SDK中提供了4种用于跨进程通讯的方式.这4种方式正好对应于android系统中4种应用程序组件:Activity.Content Provider.Broadcast和Service.其中Activity可以跨进程调用其他应用程序的Activity:Content Provider可以跨进程访问其他应用程序中的数据(以Cursor对象形式返回),当然,也可以对其他应