在之前『Android 日期选择器』中介绍了 DatePickerDatePickerDialog 的使用方法,可以满足我们平时开发中对日期选择的需要,日历卡的样式也十分讨喜。

但是,需求是万变的。我们假设这么一种场景,比如说我在做一个软件,用户可以查看以往的月度账单,『支付宝』就有类似的功能:

支付宝的月份选择功能

那么就需要有一个可以选择年份和月份但却没有具体日的功能,怎么实现?

Android 原生并没有类似的控件,所以必须要自己实现。其实对于月份,你完全可以手动把 12 个月都写进去,但是对于年份,你就无能为力了,因为年份理论上来说是一个无穷数,假设你这个软件可以使用 60 年,那么就至少要录入 60 个年份,重复而无意义的工作。

我们能否定制一个类似 DatePicker 的月份选择器呢?

看上去,『支付宝』的月份选择器和 DatePicker 很相像,区别在于去掉了日子列表。二者之间如此相似,是否意味着两者可能并非偶然撞衫,而是本来系出一源呢,我们只需要在 DatePicker 中隐藏日子的滚轮,即可实现移花接木的效果。

public class MonthPicker extends DatePicker {
    public MonthPicker(Context context, AttributeSet attrs) {
        super(context, attrs);
        ViewGroup viewGroup = (ViewGroup) ((ViewGroup) getChildAt(0)).getChildAt(0);
        int groupChildCount = viewGroup.getChildCount();
        if (groupChildCount == 3) {
            viewGroup.getChildAt(1).setVisibility(GONE);
        } else if (groupChildCount == 5) {
            viewGroup.getChildAt(2).setVisibility(GONE);
            viewGroup.getChildAt(3).setVisibility(GONE);
        }
    }
}

定义 MonthPicker 继承自 DatePicker,在构造方法中把显示日子的 View 去掉即可。这里做了一个判断分支,因为不同手机上的可能有不同的显示效果:

不同样式对比

有些手机显示“月 日 年”三列,那我们就把“日”的列去掉,有些手机显示“月 | 日 | 年”五列,也就是每项中间会有一个分割线分开,那么我们就把“日”的列和分割线一起去掉。

在布局文件中,因为 MonthPicker 继承自 DatePicker,所以基本按照 DatePicker 的属性设置即可:

<com.picker.MonthPicker
    android:id="@+id/month_picker"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:calendarViewShown="false"
    android:datePickerMode="spinner" />

这里记得要设置为滚轮样式并隐藏日历卡样式,否则是无法呈现正常效果的。

最终效果如下:

英文语言下 MonthPicker 效果

可以看到,获取到的日期也是和 DatePicker 同样的格式,参照『Android 日期选择器』一文,做好相应的截取操作即可。

但是,“月 日 年”听起来是不是有点别扭,没错,这一般是西方国家的使用方法,中国一般会使用“年 月 日”这种说法,所以,当你把语言切换到中文后,就会出现这种情况:

中文语言下可能出现的 BUG

你会尴尬地发现,我们在上面的方法中截掉了“月份”这一项,这就十分离谱了,所以还需要为 MonthPicker 做一些国际化的适配:

public final class MonthPicker extends DatePicker {
    public MonthPicker(Context context, AttributeSet attrs) {
        super(context, attrs);
        ViewGroup viewGroup = (ViewGroup) ((ViewGroup) getChildAt(0)).getChildAt(0);
        int groupChildCount = viewGroup.getChildCount();
        switch (Locale.getDefault().getLanguage()) {
            case "zh":
                if (groupChildCount == 3) {
                    viewGroup.getChildAt(2).setVisibility(GONE);
                } else if (groupChildCount == 5) {
                    viewGroup.getChildAt(3).setVisibility(GONE);
                    viewGroup.getChildAt(4).setVisibility(GONE);
                }
                break;
            case "en":
                if (groupChildCount == 3) {
                    viewGroup.getChildAt(1).setVisibility(GONE);
                } else if (groupChildCount == 5) {
                    viewGroup.getChildAt(2).setVisibility(GONE);
                    viewGroup.getChildAt(3).setVisibility(GONE);
                }
                break;
            ...
        }
    }
}

我的做法是通过 Locale 获取当前语言名称,如果当前语言为中文,则截掉处在末端的 View,如果当前语言为英文,则截掉处在中间的 View。修改后中文语言下的效果如下:

中文语言下 MonthPicker 效果

如果想适配更多语言,只需继续添加 CASE 即可,如果有国家是用“日 月 年”的写法,则再添加截掉处在首位的 View 的逻辑。