Android自定义View使用Icon Font中遇到NewStringUTF的解决过程

508 查看

原文地址:http://www.codefrom.com/c/249

当我第一次接触到Icon Font时,我就喜欢上了它。用字体文件取代图片,对于个人开发者或者缺少UI的项目,无疑是雪中送炭!本文将对自己在Android开发中使用Icon Font遇到的问题及解决方法做一个记录,同时希望能够帮助可能有需要的你。

准备工作

首先给出我看的比较多两个网站:

字体图标在Web端使用的比较多了,而关于在Android上使用Icon Font可以参考http://www.iconfont.cn/help/iconuse.html这个地方,可以下载Android的demo运行一下。

简单使用

  1. 复制字体文件到项目 assets 目录;

  2. 打开 iconfont 目录中的 demo.html,找到图标相对应的 HTML 实体字符码;

  3. 打开 res/values/strings.xml,添加 string 值;

        <string name="icons">&#x3605; &#x35ad; &#x35ae; &#x35af;</string>
  4. 打开 activity_main.xml,添加 string 值到 TextView:

    <TextView
        android:id="@+id/like"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/icons" />
  5. 为 TextView 指定文字:

    import android.graphics.Typeface;
    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        Typeface iconfont = Typeface.createFromAsset(getAssets(), "iconfont/iconfont.ttf");
        TextView textview = (TextView)findViewById(R.id.like);
        textview.setTypeface(iconfont);
    }

    以上步骤来自:http://www.iconfont.cn/help/iconuse.html,我也是根据上面的步骤学习的

问题来了

如果上面的使用方法已经满足了你的需求,那么下面的问题你可能不会遇到。

在我的项目中,用到了Android的自定义View来制作公共的头部Topbar,这个Topbar上就用到了一些小图标(恐怕现在很少看到没有公用头部或者头部没有小图标的应用了吧~-.-)。

在意识到Android可以使用Icon Font之前,我就是发挥了我艺术家潜力的PS功底……它的效果是这样的(求不吐槽!):

正如你们看到的,不光是图标画的不怎么好(其实感觉还可以…),配上上问题也很大(之前没想使用蓝色作为背景的)……因此,当我长了见识之后,果断选择了Icon Font,原因是:1、图标库的比我画的好看,获取方便,勾选->下载一步到位;2、颜色/大小都可以通过代码轻松控制。

那么,前面一些很普通的自定义View的步骤就不多说了:定义attr、继承RelativeLayout等……毕竟是个改造过程。

先看看我选的一些图标:

strings.xml是这样写的:

    <string name="menu">&#xf0184;</string>
    <string name="search">&#xf017c;</string>
    <string name="message">&#xf016c;</string>
    <string name="share">&#xf016d;</string>
    <string name="backtop">&#xf012c;</string>
    <string name="setting">&#xf021b;</string>
    <string name="fire">&#xf01f3;</string>
    <string name="left">&#xf0115;</string>
    <string name="right">&#xf0116;</string>
    <string name="up">&#xf0117;</string>
    <string name="down">&#xf0118;</string>
    <string name="refresh">&#xf017d;</string>
    <string name="liked">&#xf0170;</string>
    <string name="unliked">&#xf0054;</string>
    <string name="logout">&#xe733;</string>
    <string name="me">&#xf0142;</string>
    <string name="comment">&#xf0217;</string>
    <string name="delete">&#xf020a;</string>
    <string name="update">&#xe70c;</string>
    <string name="about">&#xe613;</string>
    <string name="commit">&#xe848;</string>
    <string name="original">&#xe609;</string>
    <string name="hot">&#xe642;</string>
    <string name="choice">&#xe66d;</string>

接下来就是Topbar的修改了,因为要写的是公用的头部,所以头部左右的TextView的Text需要通过自定义属性设置并获取。

以首页头部的左边Menu图标为例,调用的布局文件中时这样写的custom:LeftText="@string/menu",对应的是上面我选的第一个图标。

在Topbar中,通过TypedArray获取LeftText属性,代码是这样的:

ta.getText(R.styleable.Topbar_LeftText)

改动并不算大,完了之后运行起来,期待着Icon Font的特效!But……是的,报错了,并且控制台打印了一段很奇怪的log:

这个问题困扰了我很久,网上查到的都是JNI什么鬼相关的,接触Android以来,仅仅看过一两篇JNI的文章,远谈不上去写这个东西了。所以,这个问题是内部机制导致的异常,于是,又是网上找了好久,始终得不到答案,包括尝试字符转换,但是只要程序获取这个string时,就是异常。

初步解决

后来,我看着这段logstring: ' ',想着能否试试把引号部分的内容复制,使用在线Unicode转码试试,得到转码后的值,得到了这个东西\uDB80\uDD84。我将这个值设置给我Topbar左边的TextView的Text上,上面的menu图标显示了。

然后,我发现将引号部分的内容复制,在java代码中使用“”将他们包起来,奇迹发生了,看到了"\uDB80\uDD84"这个东西。

接着,我尝试着使用\uDB80\uDD84去替换strings.xml中的&#xf0184;,也就是这样:

<string name="menu">\uDB80\uDD84</string>

再次运行,成功了!

这时,我想到了一个办法:先将我所有的Icont Font写到一个string,然后必然得到一串很长的Unicode码的报错。那么我在调用布局文件这样写:custom:LeftText="menu",然后在获取到这个值的时候,弄一个switch,如果是menu那么我就赋值为\uDB80\uDD84


    public static String getFontIcon(CharSequence name) {
        String res = name.toString();
        switch (res) {
            case "menu":
                return "\uDB80\uDD84";
            case "search":
                return "\uDB80\uDD7C";
            case "message":
                return "\uDB80\uDD6C";
            case "share":
                return "\uDB80\uDD6D";
            case "backtop":
                return "\uDB80\uDD2C";
            case "setting":
                return "\uDB80\uDE1B";
            case "fire":
                return "\uDB80\uDDF3";
            case "left":
                return "\uDB80\uDD15";
            case "right":
                return "\uDB80\uDD16";
            case "up":
                return "\uDB80\uDD17";
            case "down":
                return "\uDB80\uDD18";
            case "refresh":
                return "\uDB80\uDD7D";
            case "liked":
                return "";
            case "unliked":
                return "\uDB80\uDC54";
            case "logout":
                return "\uE733";
            case "me":
                return "\uDB80\uDD42";
            case "comment":
                return "\uDB80\uDE17";
            case "delete":
                return "\uDB80\uDE0A";
            case "update":
                return "\uE70C";
            case "about":
                return "\uE613";
            case "commit":
                return "\uE848";
            case "original":
                return "\uE609";
            case "hot":
                return "\uE642";
            case "choice":
                return "\uE66D";
            case "article":
                return "\uE60B";
            case "video":
                return "\uE674";
            case "topic":
                return "\uDB80\uDD84";
            case "more":
                return "\uDB80\uDD70";
            default:
                break;
        }
        return res;
    }

上面的这段代码并没有写完整比如liked没有值,因为,我本以为每个icon都是\uDB80\uDD84这样两段组成的,但是如你所见,里面出现了\uE70C这样的(这是我通过手动赋值得到的……)。同时,如果我到时候增加了icon,我的代码又需要修改,这是个很蠢的办法。

深入探讨

上面发现update图标对应的是Unicode编码是\uE70C,而strings.xml中它是&#xe70c;,相信你也发现了,他们的后面部分是一样的E70C,而对于menu图标strings.xml中对应的是&#xf0184;。结合其他几个,可以发现一个共通点:&#xe开头的string值和&#xf开头的string值不同,并且&#xe对应的Unicode值与自己后半部分一样。

做个猜想, 开头的string值不需要转换即可使用!!!

试验了下,和猜想的一样,结果如下:

那么,为了避免上面那种愚蠢的办法,我多么希望所有的Icon都能使以 &#xe开头!

但是阿里的Icon Font生成的TTF似乎并不支持自己定义string值,也不知道他们是怎么想的!

而[icomoon]()在这方面就做的很好,不仅生成的都是开头的,还支持自定义编辑:

不过,两个网站都使用过的话,我发现阿里的图标更多,并且更适用于国内的开发。

终极解决

当然,要使用阿里的Icon Font库,也不是完全没办法。我的意思是,如果你运气好,生成的都是&#xe开头的话…也不是没有可能的…

不过不靠运气的方法就是,你先要拥有一款字体编辑软件,我使用的是FontLab Studio(Mac版 隐蔽的下载地址,win版应该也有)。

那么,打开ttf文件之后,

  1. 选择Names mode

  2. 选择不为E开头的字符(一般不是E就是F)

  3. 按照E开头的方法修改,别忘记点Apply(这个地方请参考下面的 <自带strong标签>写时感</strong>,我有话要说)

然后点击File -> Generate Font 将生成的font覆盖assert下面的原字体文件

这时候,以图中的“原创”图标为例,你可以使用\uE609设置为string值,也可以写成&#xe609;的形式。

写时感

昨晚只是初略解决了这个问题,进入到深入探讨阶段就想着写这篇文章了,一个是为了记录,一个是可能其他人也会碰到这个问题(至少我没找到网上其他人的分析和我需要的解决方案)。庆幸的是,写文章之前搞定了 终极解决方案。

之所以称之为伪解决方案,是因为我在写这篇文章的时候才进一步发现问题的原因:&#xe开头的stirng值比&#xf少一位。

这是我在进行FontLab Studio部分截图的时候观察到的。并且“突发奇想”地,尝试将setting图标的F0218改成F001,同时,strings.xml中使用\uF001或者&#xf001;作为setting的string值,最后一样显示了!(如你所见上面截图中还有一个E001的setting图标,那是我尝试伪终极解决方案时测试用的。)

所以,一切的根本原因在于我使用的ttf字体中的Unicode值的位数差别,也许这是手机的限制导致的问题,更深入的原因我不想继续研究也应该研究不出个所以然来。

当然,这并不代表出现NewStringUTF的问题就是我上面的出的那个“结论”,上面的结论暂且只适合我在使用阿里的Icon Font时出现的问题总结。

有时间会往git上传一个demo,虽然之前某一篇我也说过类似的话却无动于衷。

看来,写文章不光可以装x,写的时候也可能更深入的看到一些东西、学到一些东西!
什么?通篇不见装x情节?那么……昨晚为了这个问题到了4点半,能解决实在值得;今天得到终极解决方案,并领悟到了一些东西等等的什么鬼,这些都不是重点。

重点是,希望这篇文章能解决你所碰到的问题或者对你有任何帮助。

本文后续变更请关注:http://www.codefrom.com/c/249
更多技术文章,欢迎访问http://www.codefrom.com/获取