ps: 前段时间遇到一个需求,需可以选月份和号数切换且年份不循环的时间选择器。
解决方式:采用NumberPicker自定义个性的DatePicker
先从官网看下 NumberPicker的属性:https://developer.android.com/reference/android/widget/NumberPicker.html
NumberPicker的api:
setFormatter(NumberPicker.Formatter formatter):设置内容的格式
setMaxValue(int maxValue):设置最大值
setMinValue(int minValue):设置最小值
setValue(int value): 根据索引来显示内容
setWrapSelectorWheel(boolean wrapSelectorWheel):设置是否在循环
setOnScrollListener(NumberPicker.OnScrollListener onScrollListener):设置滚动的监听器
setOnValueChangedListener(NumberPicker.OnValueChangeListener onValueChangedListener): 设置内容改变的监控器
注意点:
1.maxValues=内容长度-1
2.若是maxValues发生改变,setWrapSelectorWheel()需要重新设置
为什么maxValues=内容长度-1,源码给了答案:
开始动手啦!
(一)自定义NumberPicker:改变字体颜色
CustormNumberPicker.java:
public class CustormNumberPicker extends NumberPicker {
public CustormNumberPicker(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustormNumberPicker(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs,defStyleAttr);
}
@Override
public void addView(View child) {
super.addView(child);
updateView(child);
}
@Override
public void addView(View child, int index,
android.view.ViewGroup.LayoutParams params) {
super.addView(child, index, params);
updateView(child);
}
@Override
public void addView(View child, android.view.ViewGroup.LayoutParams params) {
super.addView(child, params);
updateView(child);
}
public void updateView(View view) {
if (view instanceof EditText) {
//这里修改字体的属性
((EditText) view).setTextColor(Color.parseColor("#e68f17"));
}
}
}
(二)改变NumberPicker的间隔线颜色,关闭editext的光标:
官方没有提供改变NumberPicker的分割线颜色的api,这里就采用反射来突破Private私有化。(ps:从网上找到的解决方式)
public void setNumberPickerCong(NumberPicker picker, int maxValues,
String[] values, int currentPosition) {
picker.setMinValue(0);
picker.setMaxValue(maxValues);
if(picker.getId()==R.id.selectdate_dialog_year){//年份不循环
picker.setWrapSelectorWheel(false);
}
picker.setDisplayedValues(values);
picker.setValue(currentPosition);
picker.setOnValueChangedListener(this);
//反射设置分割线颜色:
Field[] pickerFields = NumberPicker.class.getDeclaredFields();
for (Field f : pickerFields) {
if (f.getName().equals("mSelectionDivider")) {
f.setAccessible(true);
try {
//设置需求的颜色
f.set(picker,
new ColorDrawable(context.getResources().getColor(
R.color.gasstation_selectdate_gray)));
} catch (Exception e) {
e.printStackTrace();
}
break;
}
}
//反射设置分割线高度:
for (Field f : pickerFields) {
if (f.getName().equals("mSelectionDividerHeight")) {
f.setAccessible(true);
try {
f.set(picker, 1);
} catch (Exception e) {
e.printStackTrace();
}
break;
}
}
//关掉编辑模式
picker.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS);
}
(三)计算每年每个月的最大天数:
Calendar可以获取当前年,月,分。还可以获取某年某月中的最大天数代码如下:
private Calendar calendar;
/**
* 获取当前的年,月,号,当前月最大号数
*/
public void getCurrentDate() {
calendar= Calendar.getInstance(Locale.getDefault());
currentYear = calendar.get(Calendar.YEAR);
currentMonth = calendar.get(Calendar.MONTH);
currentDay = calendar.get(Calendar.DAY_OF_MONTH);
currentMaxDay = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
}
/**
* 计算某月最大天数
* @param year
* @param month
* @return
*/
public int getMaxDay(int year, int month) {
calendar.clear();//清空默认时间
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, month - 1);// Calendar中,月份从0开始算起
int day = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
return day;
}
(四)初始化年,月,天的数据:
这里采用四个数组来装天数(28,29,30,31),一个数组来装月的数据(1,2….,12),一个数组装年
private String[] years, months, days;
private List<String> dayList;
private String[] day_28, day_29, day_30, day_31;
private void initData() {
dayList = new ArrayList<>();
years = new String[5];
months = new String[12];
//初始化,月份的数据,四种天数的数据(28,29,30,31)
for (int i = 1; i < 32; i++) {
int position = i - 1;
String content = String.valueOf(i);
setYearCong(position);
setMonthCong(position,content);
dayList.add(content);
setDayCong(i,dayList);
}
recycleList();
}
/**
* 设置当前年份的前后两年,比如2014,2015,2016,2017,2018
* @param position
*/
public void setYearCong(int position){
if(position<5){
years[position]=String.valueOf((currentYear+2)-4+position);
}
}
/**
* 设置月份,1~12
* @param position
* @param month
*/
public void setMonthCong(int position,String month){
if (position < 12) {
months[position] = month;
}
}
/**
* 设置天数四个数组,(28,29,30,31)
* @param index
* @param dayList
*/
public void setDayCong(int index,List<String> dayList){
switch (index){
case 28:
day_28= new String[28];
listCopyArray(dayList, day_28);
break;
case 29:
day_29= new String[29];
listCopyArray(dayList, day_29);
break;
case 30:
day_30= new String[30];
listCopyArray(dayList, day_30);
break;
case 31:
day_31= new String[31];
listCopyArray(dayList, day_31);
break;
}
}
/**
* 回收list
*/
private void recycleList() {
if(dayList!=null){
dayList.clear();
dayList=null;
}
}
(五)监控滑动年或者月的选中状态改变时,这个月中的最大天数也随着改变:
在NumberPicker的setOnValueChangedListener()中监控滑动导致值改变:
/**
* 监控NumberPicker 的滑动值改变:
* 当年份或者月份改变时,每个月的最大天数必须随着改变
* @param picker
* @param oldVal
* @param newVal
*/
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
switch (picker.getId()) {
case R.id.selectdate_dialog_year:
currentMaxDay=getMaxDay(Integer.valueOf(years[newVal]),
Integer.valueOf( months[month_NumberPicker.getValue()] ) );
setDay_NnmberPicekerCong(currentMaxDay);
break;
case R.id.selectdate_dialog_month:
currentMaxDay=getMaxDay(Integer.valueOf( years[year_NumberPicker.getValue() ]),
Integer.valueOf( months[ newVal ] ));
setDay_NnmberPicekerCong(currentMaxDay);
break;
}
}
/**
* 根据每个月最大天数,来设置显示day的NumberPicker
* @param index
*/
public void setDay_NnmberPicekerCong(int index) {
switch (index) {
case 28:
days = day_28;
break;
case 29:
days = day_29;
break;
case 30:
days = day_30;
break;
case 31:
days = day_31;
break;
}
if(isFirt){ //设置当前的号数,即今天是几号
setNumberPickerCong(day_NnmberPiceker, days.length - 1, days, currentDay - 1);
isFirt=false;
}
else{ //滑动年,月导致最大号数的改变(即28或者29或者30或者31)
int before_Size= day_NnmberPiceker.getMaxValue()+1;
if(before_Size<=(days.length-1)){//原本的length< 即将设置max(特殊情况:b_length=m_max)
day_NnmberPiceker.setDisplayedValues(days);
day_NnmberPiceker.setMaxValue(days.length-1);
day_NnmberPiceker.setMinValue(0);
}else{ //(原本的max+1)>=即将设置的length
day_NnmberPiceker.setMinValue(0);
day_NnmberPiceker.setMaxValue(days.length-1);
day_NnmberPiceker.setDisplayedValues(days);
}
}
}
在更新NumberPicker的时候遇到一个很奇特的问题,拿出来分享下:
操作 length maxValues
curent 30 29
update 31 30
先设置setMaxValues(),再设置setDisplayedValues()
运行结果却报异常: error: java.lang.ArrayIndexOutOfBoundsException: length=30; index=30
截图如下:
经过多次尝试,总结规律:原本的maxValues>=即将设置的maxValues,那先设置setMax,再设置content.反之,先设置content,在来设置max
ps:将三个numberPicker(年,月,天)封装在Dialog中,对外提供选择时间的方法,在下载的项目中都有,偷懒不写了
经历一番波折,填坑,最终完成需求,展示成果: