一、开发需求
我最近在开发安卓应用过程中,需要实现一个选择年和月的月份选择器,获得形如“2023-11”的数据,以便按照月份来筛查数据。但安卓提供的DatePicker和DatePickerDialog都是选择年、月、日三项的,没有只选择年和月选择器的。虽然也可以通过对获取到的日期进行处理来得到我想要的数据,但总是让我感觉别扭。
二、参考资料
还好在欧阳燊先生编著的《Android App开发入门与项目实战》一书的第10章 “自定义控件”里找到了通过改造DatePicker得到自定义月份选择器的方法和源码。该方法需要自定义一个继承自DatePicker的MonthPicker.java放置项目的widget文件夹下。该java文件对选择器中多余的日期控件进行隐藏,来达到只显示年和月选择控件的目的。
然后在布局文件中添加自定义的MonthPicker(需要使用全路径)且添加以下两个属性才能正常使用:
android:datePickerMode="spinner" (下拉框模式) android:calendarViewShown="false" (不是日历视图)
我参照教材,做了一个MonthPickerActivity实现月份选择。在需要使用月份选择器的Activity中通过startActivityForResult()方法调用MonthPickerActivity来选择月份。实现的效果如下图:
(MonthPickerActivity的实现效果)
(调用MonthPickerActivity的Activity中的月份选择和显示样式)
三、进行改进
在真机上测试代码的时候,我发现一个小问题,那就是,每次调用MonthPickerActivity时,月份选择器上都是显示的当前月份,而我希望是显示之前已经选择的月份。比如用户之前选择的是“2023-01”,再次调用月份选择器时,选择器上显示的月份仍是当前的“2023-11”。而用户一般的操作习惯大概率是要查看“2023-01”前后月份的数据,那么月份选择器最好是显示之前选择的“2023-01”才更方便用户的后续操作。因此,需要对这个月份选择器进行改进。
通过对DatePicker进行研究,发现它有一个init()方法可以设置显示的年、月、日。
public void init(int year, int monthOfYear, int dayOfMonth, DatePicker.OnDateChangedListener onDateChangedListener) {
throw new RuntimeException("Stub!");
}
知道了这点,我就有了解决办法:
在用startActivityForResult()方法调用MonthPickerActivity时,通过Intent将已经选择的月份数据发送给MonthPickerActivity。
在MonthPickerActivity中从Intent获取上一个活动发送过来的月份数据。对数据进行处理,获得year和monthOfYear数据,调用init()方法设置选择器打开时显示的月份。
四、源码
相关的代码如下:
1. 自定义月份选择器组件MonthPicker.java ,该文件存放在项目的widget文件夹下。
(如 ...\src\main\java\com\....\wiget\MonthPicker.java)
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.DatePicker;
// 由日期选择器派生出月份选择器
public class MonthPicker extends DatePicker {
public MonthPicker(Context context, AttributeSet attrs) {
super(context, attrs);
// 获取年月日的下拉列表项
ViewGroup vg = ((ViewGroup) ((ViewGroup) getChildAt(0)).getChildAt(0));
if (vg.getChildCount() == 3) {
// 有的机型显示格式为“年月日”,此时隐藏第三个控件
vg.getChildAt(2).setVisibility(View.GONE);
} else if (vg.getChildCount() == 5) {
// 有的机型显示格式为“年|月|日”,此时隐藏第四个和第五个控件(即“|日”)
vg.getChildAt(3).setVisibility(View.GONE);
vg.getChildAt(4).setVisibility(View.GONE);
}
}
}
2. 创建MonthPickerActivity.java和activity_month_picker.xml文件,实现月份选择器。
xml布局文件代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="请选择月份"
android:textColor="@color/black"
android:textSize="17sp" />
<!-- 自定义的月份选择器,需要使用全路径,MonthPicker前是你自己项目的包名 -->
<com.bahamutj.easyinventory.widget.MonthPicker
android:id="@+id/mp_month"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:calendarViewShown="false"
android:datePickerMode="spinner"
android:gravity="center" />
<Button
android:id="@+id/btn_confirm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:gravity="center"
android:text="确定"
android:textColor="@color/black"
android:textSize="17sp" />
</LinearLayout>
java代码
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import com.bahamutj.easyinventory.R;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class MonthPickerActivity extends AppCompatActivity implements View.OnClickListener {
private final static String TAG = "MonthPickerActivity";
private MonthPicker mp_month; // 声明一个月份选择器对象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_month_picker);
// 从Intent中获取传入的月份(格式为 yyyy-MM 的字符串)
Bundle bundle = getIntent().getExtras();
String month = bundle.getString("month");
String[] ym = month.split("-");
int year = Integer.parseInt(ym[0]);
// 因为1月份对应的monthOfYear是0,因此要减1
int monthOfYear = Integer.parseInt(ym[1]) -1;
// 从布局文件中获取名叫mp_month的月份选择器
mp_month = findViewById(R.id.mp_month);
// 调用init方法设置日期选择器的初始日期
mp_month.init(year, monthOfYear, 1, null);
// 为确定按钮设置监听器
findViewById(R.id.btn_confirm).setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_confirm) {
String ym = String.format(Locale.CHINESE, "%d-%d",mp_month.getYear(),mp_month.getMonth() + 1);
// 因获取到的数据中1-9月份前是没有0的(yyyy-M格式),需要转换成yyyy-MM格式
// 创建解析格式和格式化格式
@SuppressLint("SimpleDateFormat") SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-M");
@SuppressLint("SimpleDateFormat") SimpleDateFormat outputFormat = new SimpleDateFormat("yyyy-MM");
// 解析输入的日期字符串
Date date = null;
try {
date = inputFormat.parse(ym);
} catch (ParseException e) {
e.printStackTrace();
}
// 格式化日期对象为输出的日期字符串
assert date != null;
String month = outputFormat.format(date);
// 回传数据
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putString("month", month);
intent.putExtras(bundle);
setResult(Activity.RESULT_OK, intent);
finish();
}
}
}
3. 调用MonthPickerActivity.java以及从MonthPickerActivity.java获取返回值的代码
private final int MONTH_CODE = 1; // 选择月份意图代码
private TextView tv_month; // 显示形式如“2023-11”的年月的文本控件,点击该控件并可选择月份
private final Calendar calendar = Calendar.getInstance(); // 获取日历实例及当前的年月日
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_records); // 该Activity的布局文件
// 设置月份选择器
tv_month = findViewById(R.id.tv_month);
tv_month.setOnClickListener(this);
tv_month.setText(getMonth(calendar)); // 在TextView控件中显示当前月份,getMonth方法 为自定义,见下方
// 点击监听
@Override
public void onClick(View v) {
if (v.getId() == R.id.tv_month) { // 点击了选择月份的TextView对象
String month = tv_month.getText().toString(); // 从TextView获取月份,格式“”
Intent intent = new Intent(this, MonthPickerActivity.class);
Bundle bundle = new Bundle();
bundle.putString("month", month);
intent.putExtras(bundle);
startActivityForResult(intent, MONTH_CODE);
}
}
// 获取选择的月份数据
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (intent != null && resultCode == RESULT_OK && requestCode == MONTH_CODE) {
// 从Intent中获取月份选择器传回的数据
Bundle bundle = intent.getExtras();
String month = bundle.getString("month");
tv_month.setText(month); // 将获取的结果显示在TextView对象中
}
}
// 对从日历实例获取的日期数据进行格式化,转为成字符串
private String getMonth(Calendar calendar) {
Date date = calendar.getTime();
// 创建一个日期格式化的工具
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM");
// 将当前日期时间按照指定格式输出格式化后的日期时间字符串
return sdf.format(date);
}