Android 应用开发学习-自定义月份选择器

一、开发需求

我最近在开发安卓应用过程中,需要实现一个选择年和月的月份选择器,获得形如“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);
    }

  • 7
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

武陵悭臾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值