animalize

导航

Android上显示生僻字的方法

安卓5.0+是可以显示所有(8万多个)Unicode汉字的,本文介绍显示生僻汉字的方法,这个方法也适用于其它特殊字符。

Unicode值在0xFFFF以下的(2万多个简体、繁体)汉字早已被广泛支持,所以本文不把它们看作生僻字。
本文说的生僻字是指Unicode值在0xFFFF以上的汉字,这个范围也叫non-BMP。

可以看这篇文章了解汉字的大致分布。

主要有4个关键点:

一、需使用Android 5.0以上系统。

经过我的测试,在Android 4.3.1模拟器、4.4.2模拟器上会出现问题,而在Android 5.0.2模拟器、6.0真机、7.0模拟器上正常。

问题见本文最后部分的两张截图:附1、附2。

二、确保字符集能覆盖生僻字。

也就是在文件、数据库保存字符串时使用的字符集,应该是:GB18030、UTF-8、UTF-16、UTF-32等能支持所有Unicode字符的字符集。

注意a:不要使用GB2312、GBK或BIG5字符集,它们的范围有限,都不支持生僻字(Unicode值大于0xFFFF)。
注意b:不要在Android程序的配置xml文件里写生僻字,Android在用UTF-8解码这些xml时,支持的范围并不完整。这个bug在Android 6.0里被修复。见此帖

按如下方法把字符串读入String后,我们进入下一个关键点。

byte[] b = read_bytes_from_somewhere();
String str = new String(b, "GB18030");


三、String在处理生僻字时会使用surrogate对。

Java的String使用的是UTF-16编码。String的一个char占两个字节,能表示0~0xFFFF之间的数值范围。

于是,根据UTF-16的规则,在String里:常用字符(Unicode值小于等于0xFFFF)用一个char表示;剩下的字符(Unicode值大于0xFFFF)用两个char组合表示,这两个char叫surrogate对儿。所以我们的生僻字符须要用两个char组合表示

surrogate对的原理是:两个char各有一个保留区域,这个保留区域有1,024个位置,于是1,024x1,024等于1,048,576,再加上单个char包含的字符,正好能覆盖Unicode所有的1,114,112个位置点。

在这里,搞清楚两个概念就可以了:

  1. char,一个char就是一个两字节整数。它是String的最小单位,一个String包含0个或若干个char。String对象的length()函数返回的就是char的个数。
  2. code point,一个code point就是一个Unicode字符,一个实际的字符。在String里,它可能用1个char表示,也可能用2个char组合表示,没有第三种可能。

所以,在处理生僻字时,String的length()函数的返回值可能大于实际字符数,想知道String的实际字符数可以用codePointCount()函数。同理,charAt()等基于char操作的函数可能也不再适用。

四、使用支持生僻字的字体。

就算程序内在支持生僻字,字体不支持也显示不出来。包括默认字体在内的很多字体都不支持生僻字。
我们可以找一个支持生僻字的字体,然后放到程序的资源目录里,指定程序使用它而不是系统的默认字体。

先找一个支持生僻字的字体,比如这个开心宋体帖子网盘

然后按以下方法使用该字体就可以了(出处):

//得到TextView控件对象
TextView textView = (TextView) findViewById(R.id.custom);

//将字体文件保存在assets/fonts/目录下,创建Typeface对象
Typeface typeFace = Typeface.createFromAsset(getAssets(), "fonts/KaiXinSong.ttf");

//使用字体
textView.setTypeface(typeFace);

注意a:打包了字体的程序会大很多,这也是没有办法的事。除非做成按需下载字体,这就依赖网络了;或者生成一个只包含所需汉字的字体文件。(2017年发布的Android Support Library v26增强了对字体的支持,见此视频。)
注意b:各字体可能会有自己的侧重点,比如支持生僻汉字的字体可能不支持emoji,支持emoji的字体可能不支持生僻汉字。
注意c:可以用SpannableString为一个字符串的部分字符使用指定的字体,见为部分字符指定字体在SpannableString使用指定字体

在按如上方法使用了开心宋体后,已经可以在Android 5.0.2模拟器上显示各区汉字:

在Android 5.0.2模拟器上默认字体的效果,可以看到CJK统一汉字扩展B、C、D、E区的汉字都无法显示:

 

附1,在Android 4.4.2模拟器上,使用开心宋体后,常用字反倒显示不出来了。
在4.3.1、4.4.2上,使用Simsun-ExtB字体也有类似情况,所以这应该是安卓4.x的问题。
难道是系统不支持多于65536个字符的字体?我没有去验证,仅仅是猜测。

附2,在Android 4.4.2模拟器上,使用默认字体的效果:

posted on 2017-02-16 21:39  animalize  阅读(2283)  评论(0编辑  收藏  举报