在开发中,我们有时候需要对特定文字加上复数形式,而不同语言下,复数形式是不一样的,比如:

    1. 中文:
    2. 1 本书;
    3. 2 本书;
    4. 3 本书;
    5. ......

    我们发现这里变化的只是前面的数字,后面的文字是完全一样的,也就是说这里的单复数形式是一样的,没必要做区分,因此一个string资源就可以搞定:

    1. <string name="numberOfBooks">%d 本书</string>

    但如果是英文呢?

    1. 英文:
    2. 1 book;
    3. 2 books
    4. 3 books;
    5. ...

    这个时候我们发现变化的不只是前面的数字了,后面的book也发生了变化,也就是说这里的单复数已经是两种形式了,因此要换成字符串资源,就需要两个string了:

    1. <string name="oneBooks">%d book</string>
    2. <string name="manyBooks">%d books</string>

    为了很好的适配这两个string,我们在代码中还要加上相应的判断。这样一来就显得繁琐了,那么有没有一种字符串资源能够自动帮助我们实现单复数切换呢?
    幸运的是,Android里面就提供了这么一种String资源:plurals。接着使用上面的例子,这时英文下只需要这么写:

    1. <plurals name="numberOfBooks" >
    2. <item quantity="one">%d book</item>
    3. <item quantity="other">%d books</item>
    4. </plurals>

    写法类似于string-array,但在使用上却和普通的是string资源有着相似之处,例如:

    1. String one = getResources().getQuantityString(R.plurals.numberOfBooks, 1, 1);
    2. String two = getResources().getQuantityString(R.plurals.numberOfBooks, 2, 2);

    这里通过上面的方法我们就能取到相应的单复数形式的字符串资源了。
    这里的getQuantityString方法有2种重载形式:
    [app]用plurals写复数形式字符串 - 图1
    image.gifgetQuantityString的2种重载形式
    我们可以看到,这2种重载形式中都有2个共同的参数:

    int id,是我们在string.xml里面写的plurals资源的id; int quantity,是数量的意思,也就是我们取具体item的判断依据。

    此外的Object... formatArgs参数应该就不需要解释了。


    上面写我们plurals资源的时候,quantity属性我们只用了其中两个值,实际上它有好几种取值,如下表:

    Value Description
    zero 当前语言需要特别对待0
    one 当前语言需要特别对待1
    two 当前语言需要特别对待2
    few 当前语言需要特别对待few/small,也就是小数量的
    many 当前语言需要特别对待many/large,也就是大数量的
    other 当前语言没有要求对特定资源进行特殊对待

    细心的读者会发现,上面我们反复提到了“当前语言”,为什么呢?
    想知道为什么,看源码就知道了。
    我们跟进getQuantityString的实现源码:

    1. public String getQuantityString(@PluralsRes int id, int quantity, Object... formatArgs)
    2. throws NotFoundException {
    3. String raw = getQuantityText(id, quantity).toString();
    4. return String.format(mConfiguration.locale, raw, formatArgs);
    5. }

    我们发现这个方法调用了String.format方法进行字符串格式化,如下:

    1. String.format(mConfiguration.locale, raw, formatArgs);

    我们知道String.format有2种重载形式,其中一种第一个参数是:Locale locale,这个参数就是机器当前的语言环境,嗯,先记住这个点。
    接下来我们继续跟进mConfiguration.locale的实现源码:

    1. /**
    2. * Current user preference for the locale, corresponding to
    3. * <a href="{@docRoot}guide/topics/resources/providing-resources.html#LocaleQualifier">locale</a>
    4. * resource qualifier.
    5. */
    6. public Locale locale;

    很明显,这里的mConfiguration.locale就是一个Locale对象,so,后面不用我解释了……


    可能有的人尝试写满了zero, one, two, few, many, other,但运行起来,机器并没有按他以为的那样显示相应的item,然后就认为是我在瞎忽悠。这一点,我们一直在强调的“当前语言”就可以解释了,简单的说就是程序运行的时候,具体取的那个item,是取决于当前语言对单复数等形式的定义的,并不是你写的你认为的那样的定义。
    image.png