Android自定义滚动日期时间选择器
基本使用
private TextView mTvBirthday;
private MyDatePicker mBirthdayPicker;
private String today;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTvBirthday = findViewById(R.id.tv_birthday);
mTvBirthday.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mBirthdayPicker.show(today);
}
});
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.CHINA);
String now = sdf.format(new Date());
today = now.split(" ")[0];
mTvBirthday.setText(today);
mBirthdayPicker = new MyDatePicker(this, new MyDatePicker.ResultHandler() {
@Override
public void handle(String time) { // 回调接口, 获得选中的时间
mTvBirthday.setText(time.split(" ")[0]);
}
}, "1900-01-01 00:00", now); // 初始化日期格式请用: yyyy-MM-dd HH:mm, 否则不能正常运行
mBirthdayPicker.showSpecificTime(false); // 不显示时和分
mBirthdayPicker.setIsLoop(false); // 不允许循环滚动
}
自定义MyDatePicker
MyDatePicker.java
public class MyDatePicker {
/**
* 定义结果回调接口
*/
public interface ResultHandler {
void handle(String time);
}
public enum SCROLL_TYPE {
HOUR(1),
MINUTE(2);
SCROLL_TYPE(int value) {
this.value = value;
}
public int value;
}
private int scrollUnits = SCROLL_TYPE.HOUR.value + SCROLL_TYPE.MINUTE.value;
private ResultHandler handler;
private Context context;
private boolean canAccess = false;
private Dialog datePickerDialog;
private WheelPickerView yearPickerView, monthPickerView, dayPickerView, hourPickerView, minutePickerView;
private static final int MAX_MINUTE = 59;
private static final int MAX_HOUR = 23;
private static final int MIN_MINUTE = 0;
private static final int MIN_HOUR = 0;
private static final int MAX_MONTH = 12;
private ArrayList<String> year, month, day, hour, minute;
private int startYear, startMonth, startDay, startHour, startMinute, endYear, endMonth, endDay, endHour, endMinute;
private boolean spanYear, spanMon, spanDay, spanHour, spanMin;
private Calendar selectedCalender, startCalendar, endCalendar;
private TextView tvCancle, tvSelect, tvHour, tvMinute;
public MyDatePicker(Context context, ResultHandler resultHandler, String startDate, String endDate) {
if (isValidDate(startDate, "yyyy-MM-dd HH:mm") && isValidDate(endDate, "yyyy-MM-dd HH:mm")) {
canAccess = true;
this.context = context;
this.handler = resultHandler;
selectedCalender = Calendar.getInstance();
startCalendar = Calendar.getInstance();
endCalendar = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.CHINA);
try {
startCalendar.setTime(sdf.parse(startDate));
endCalendar.setTime(sdf.parse(endDate));
} catch (ParseException e) {
e.printStackTrace();
}
initDialog();
initView();
}
}
private void initDialog() {
if (datePickerDialog == null) {
datePickerDialog = new Dialog(context, R.style.date_picker_dialog);
datePickerDialog.setCancelable(false);
datePickerDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
datePickerDialog.setContentView(R.layout.layout_date_picker);
Window window = datePickerDialog.getWindow();
window.setGravity(Gravity.BOTTOM);
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
manager.getDefaultDisplay().getMetrics(dm);
WindowManager.LayoutParams lp = window.getAttributes();
lp.width = dm.widthPixels;
window.setAttributes(lp);
}
}
private void initView() {
yearPickerView = datePickerDialog.findViewById(R.id.year_pv);
monthPickerView = datePickerDialog.findViewById(R.id.month_pv);
dayPickerView = datePickerDialog.findViewById(R.id.day_pv);
hourPickerView = datePickerDialog.findViewById(R.id.hour_pv);
minutePickerView = datePickerDialog.findViewById(R.id.minute_pv);
tvCancle = datePickerDialog.findViewById(R.id.tv_cancle);
tvSelect = datePickerDialog.findViewById(R.id.tv_select);
tvHour = datePickerDialog.findViewById(R.id.hour_text);
tvMinute = datePickerDialog.findViewById(R.id.minute_text);
tvCancle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
datePickerDialog.dismiss();
}
});
tvSelect.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.CHINA);
handler.handle(sdf.format(selectedCalender.getTime()));
datePickerDialog.dismiss();
}
});
}
private void initParameter() {
startYear = startCalendar.get(Calendar.YEAR);
startMonth = startCalendar.get(Calendar.MONTH) + 1;
startDay = startCalendar.get(Calendar.DAY_OF_MONTH);
startHour = startCalendar.get(Calendar.HOUR_OF_DAY);
startMinute = startCalendar.get(Calendar.MINUTE);
endYear = endCalendar.get(Calendar.YEAR);
endMonth = endCalendar.get(Calendar.MONTH) + 1;
endDay = endCalendar.get(Calendar.DAY_OF_MONTH);
endHour = endCalendar.get(Calendar.HOUR_OF_DAY);
endMinute = endCalendar.get(Calendar.MINUTE);
spanYear = startYear != endYear;
spanMon = (!spanYear) && (startMonth != endMonth);
spanDay = (!spanMon) && (startDay != endDay);
spanHour = (!spanDay) && (startHour != endHour);
spanMin = (!spanHour) && (startMinute != endMinute);
selectedCalender.setTime(startCalendar.getTime());
}
private void initTimer() {
initArrayList();
if (spanYear) {
for (int i = startYear; i <= endYear; i++) {
year.add(String.valueOf(i));
}
for (int i = startMonth; i <= MAX_MONTH; i++) {
month.add(formatTimeUnit(i));
}
for (int i = startDay; i <= startCalendar.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
day.add(formatTimeUnit(i));
}
if ((scrollUnits & SCROLL_TYPE.HOUR.value) != SCROLL_TYPE.HOUR.value) {
hour.add(formatTimeUnit(startHour));
} else {
for (int i = startHour; i <= MAX_HOUR; i++) {
hour.add(formatTimeUnit(i));
}
}
if ((scrollUnits & SCROLL_TYPE.MINUTE.value) != SCROLL_TYPE.MINUTE.value) {
minute.add(formatTimeUnit(startMinute));
} else {
for (int i = startMinute; i <= MAX_MINUTE; i++) {
minute.add(formatTimeUnit(i));
}
}
} else if (spanMon) {
year.add(String.valueOf(startYear));
for (int i = startMonth; i <= endMonth; i++) {
month.add(formatTimeUnit(i));
}
for (int i = startDay; i <= startCalendar.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
day.add(formatTimeUnit(i));
}
if ((scrollUnits & SCROLL_TYPE.HOUR.value) != SCROLL_TYPE.HOUR.value) {
hour.add(formatTimeUnit(startHour));
} else {
for (int i = startHour; i <= MAX_HOUR; i++) {
hour.add(formatTimeUnit(i));
}
}
if ((scrollUnits & SCROLL_TYPE.MINUTE.value) != SCROLL_TYPE.MINUTE.value) {
minute.add(formatTimeUnit(startMinute));
} else {
for (int i = startMinute; i <= MAX_MINUTE; i++) {
minute.add(formatTimeUnit(i));
}
}
} else if (spanDay) {
year.add(String.valueOf(startYear));
month.add(formatTimeUnit(startMonth));
for (int i = startDay; i <= endDay; i++) {
day.add(formatTimeUnit(i));
}
if ((scrollUnits & SCROLL_TYPE.HOUR.value) != SCROLL_TYPE.HOUR.value) {
hour.add(formatTimeUnit(startHour));
} else {
for (int i = startHour; i <= MAX_HOUR; i++) {
hour.add(formatTimeUnit(i));
}
}
if ((scrollUnits & SCROLL_TYPE.MINUTE.value) != SCROLL_TYPE.MINUTE.value) {
minute.add(formatTimeUnit(startMinute));
} else {
for (int i = startMinute; i <= MAX_MINUTE; i++) {
minute.add(formatTimeUnit(i));
}
}
} else if (spanHour) {
year.add(String.valueOf(startYear));
month.add(formatTimeUnit(startMonth));
day.add(formatTimeUnit(startDay));
if ((scrollUnits & SCROLL_TYPE.HOUR.value) != SCROLL_TYPE.HOUR.value) {
hour.add(formatTimeUnit(startHour));
} else {
for (int i = startHour; i <= endHour; i++) {
hour.add(formatTimeUnit(i));
}
}
if ((scrollUnits & SCROLL_TYPE.MINUTE.value) != SCROLL_TYPE.MINUTE.value) {
minute.add(formatTimeUnit(startMinute));
} else {
for (int i = startMinute; i <= MAX_MINUTE; i++) {
minute.add(formatTimeUnit(i));
}
}
} else if (spanMin) {
year.add(String.valueOf(startYear));
month.add(formatTimeUnit(startMonth));
day.add(formatTimeUnit(startDay));
hour.add(formatTimeUnit(startHour));
if ((scrollUnits & SCROLL_TYPE.MINUTE.value) != SCROLL_TYPE.MINUTE.value) {
minute.add(formatTimeUnit(startMinute));
} else {
for (int i = startMinute; i <= endMinute; i++) {
minute.add(formatTimeUnit(i));
}
}
}
loadComponent();
}
/**
* 将“0-9”转换为“00-09”
*/
private String formatTimeUnit(int unit) {
return unit < 10 ? "0" + String.valueOf(unit) : String.valueOf(unit);
}
private void initArrayList() {
if (year == null) year = new ArrayList<>();
if (month == null) month = new ArrayList<>();
if (day == null) day = new ArrayList<>();
if (hour == null) hour = new ArrayList<>();
if (minute == null) minute = new ArrayList<>();
year.clear();
month.clear();
day.clear();
hour.clear();
minute.clear();
}
private void loadComponent() {
yearPickerView.setData(year);
monthPickerView.setData(month);
dayPickerView.setData(day);
hourPickerView.setData(hour);
minutePickerView.setData(minute);
yearPickerView.setSelected(0);
monthPickerView.setSelected(0);
dayPickerView.setSelected(0);
hourPickerView.setSelected(0);
minutePickerView.setSelected(0);
executeScroll();
}
private void addListener() {
yearPickerView.setOnSelectListener(new WheelPickerView.OnSelectListener() {
@Override
public void onSelect(int index, String text) {
selectedCalender.set(Calendar.YEAR, Integer.parseInt(text));
monthChange();
}
});
monthPickerView.setOnSelectListener(new WheelPickerView.OnSelectListener() {
@Override
public void onSelect(int index, String text) {
selectedCalender.set(Calendar.DAY_OF_MONTH, 1);
selectedCalender.set(Calendar.MONTH, Integer.parseInt(text) - 1);
dayChange();
}
});
dayPickerView.setOnSelectListener(new WheelPickerView.OnSelectListener() {
@Override
public void onSelect(int index, String text) {
selectedCalender.set(Calendar.DAY_OF_MONTH, Integer.parseInt(text));
hourChange();
}
});
hourPickerView.setOnSelectListener(new WheelPickerView.OnSelectListener() {
@Override
public void onSelect(int index, String text) {
selectedCalender.set(Calendar.HOUR_OF_DAY, Integer.parseInt(text));
minuteChange();
}
});
minutePickerView.setOnSelectListener(new WheelPickerView.OnSelectListener() {
@Override
public void onSelect(int index, String text) {
selectedCalender.set(Calendar.MINUTE, Integer.parseInt(text));
}
});
}
private void monthChange() {
month.clear();
int selectedYear = selectedCalender.get(Calendar.YEAR);
if (selectedYear == startYear) {
for (int i = startMonth; i <= MAX_MONTH; i++) {
month.add(formatTimeUnit(i));
}
} else if (selectedYear == endYear) {
for (int i = 1; i <= endMonth; i++) {
month.add(formatTimeUnit(i));
}
} else {
for (int i = 1; i <= MAX_MONTH; i++) {
month.add(formatTimeUnit(i));
}
}
selectedCalender.set(Calendar.MONTH, Integer.parseInt(month.get(0)) - 1);
monthPickerView.setData(month);
monthPickerView.setSelected(0);
executeAnimator(monthPickerView);
monthPickerView.postDelayed(new Runnable() {
@Override
public void run() {
dayChange();
}
}, 100);
}
private void dayChange() {
day.clear();
int selectedYear = selectedCalender.get(Calendar.YEAR);
int selectedMonth = selectedCalender.get(Calendar.MONTH) + 1;
if (selectedYear == startYear && selectedMonth == startMonth) {
for (int i = startDay; i <= selectedCalender.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
day.add(formatTimeUnit(i));
}
} else if (selectedYear == endYear && selectedMonth == endMonth) {
for (int i = 1; i <= endDay; i++) {
day.add(formatTimeUnit(i));
}
} else {
for (int i = 1; i <= selectedCalender.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
day.add(formatTimeUnit(i));
}
}
selectedCalender.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day.get(0)));
dayPickerView.setData(day);
dayPickerView.setSelected(0);
executeAnimator(dayPickerView);
dayPickerView.postDelayed(new Runnable() {
@Override
public void run() {
hourChange();
}
}, 100);
}
private void hourChange() {
if ((scrollUnits & SCROLL_TYPE.HOUR.value) == SCROLL_TYPE.HOUR.value) {
hour.clear();
int selectedYear = selectedCalender.get(Calendar.YEAR);
int selectedMonth = selectedCalender.get(Calendar.MONTH) + 1;
int selectedDay = selectedCalender.get(Calendar.DAY_OF_MONTH);
if (selectedYear == startYear && selectedMonth == startMonth && selectedDay == startDay) {
for (int i = startHour; i <= MAX_HOUR; i++) {
hour.add(formatTimeUnit(i));
}
} else if (selectedYear == endYear && selectedMonth == endMonth && selectedDay == endDay) {
for (int i = MIN_HOUR; i <= endHour; i++) {
hour.add(formatTimeUnit(i));
}
} else {
for (int i = MIN_HOUR; i <= MAX_HOUR; i++) {
hour.add(formatTimeUnit(i));
}
}
selectedCalender.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hour.get(0)));
hourPickerView.setData(hour);
hourPickerView.setSelected(0);
executeAnimator(hourPickerView);
}
hourPickerView.postDelayed(new Runnable() {
@Override
public void run() {
minuteChange();
}
}, 100);
}
private void minuteChange() {
if ((scrollUnits & SCROLL_TYPE.MINUTE.value) == SCROLL_TYPE.MINUTE.value) {
minute.clear();
int selectedYear = selectedCalender.get(Calendar.YEAR);
int selectedMonth = selectedCalender.get(Calendar.MONTH) + 1;
int selectedDay = selectedCalender.get(Calendar.DAY_OF_MONTH);
int selectedHour = selectedCalender.get(Calendar.HOUR_OF_DAY);
if (selectedYear == startYear && selectedMonth == startMonth && selectedDay == startDay && selectedHour == startHour) {
for (int i = startMinute; i <= MAX_MINUTE; i++) {
minute.add(formatTimeUnit(i));
}
} else if (selectedYear == endYear && selectedMonth == endMonth && selectedDay == endDay && selectedHour == endHour) {
for (int i = MIN_MINUTE; i <= endMinute; i++) {
minute.add(formatTimeUnit(i));
}
} else {
for (int i = MIN_MINUTE; i <= MAX_MINUTE; i++) {
minute.add(formatTimeUnit(i));
}
}
selectedCalender.set(Calendar.MINUTE, Integer.parseInt(minute.get(0)));
minutePickerView.setData(minute);
minutePickerView.setSelected(0);
executeAnimator(minutePickerView);
}
executeScroll();
}
private void executeAnimator(View view) {
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("alpha", 1f, 0f, 1f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("scaleX", 1f, 1.3f, 1f);
PropertyValuesHolder pvhZ = PropertyValuesHolder.ofFloat("scaleY", 1f, 1.3f, 1f);
ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY, pvhZ).setDuration(200).start();
}
private void executeScroll() {
yearPickerView.setCanScroll(year.size() > 1);
monthPickerView.setCanScroll(month.size() > 1);
dayPickerView.setCanScroll(day.size() > 1);
hourPickerView.setCanScroll(hour.size() > 1 && (scrollUnits & SCROLL_TYPE.HOUR.value) == SCROLL_TYPE.HOUR.value);
minutePickerView.setCanScroll(minute.size() > 1 && (scrollUnits & SCROLL_TYPE.MINUTE.value) == SCROLL_TYPE.MINUTE.value);
}
private int disScrollUnit(SCROLL_TYPE... scroll_types) {
if (scroll_types == null || scroll_types.length == 0) {
scrollUnits = SCROLL_TYPE.HOUR.value + SCROLL_TYPE.MINUTE.value;
} else {
for (SCROLL_TYPE scroll_type : scroll_types) {
scrollUnits ^= scroll_type.value;
}
}
return scrollUnits;
}
public void show(String time) {
if (canAccess) {
if (isValidDate(time, "yyyy-MM-dd")) {
if (startCalendar.getTime().getTime() < endCalendar.getTime().getTime()) {
canAccess = true;
initParameter();
initTimer();
addListener();
setSelectedTime(time);
datePickerDialog.show();
}
} else {
canAccess = false;
}
}
}
/**
* 设置日期控件是否显示时和分
*/
public void showSpecificTime(boolean show) {
if (canAccess) {
if (show) {
disScrollUnit();
hourPickerView.setVisibility(View.VISIBLE);
tvHour.setVisibility(View.VISIBLE);
minutePickerView.setVisibility(View.VISIBLE);
tvMinute.setVisibility(View.VISIBLE);
} else {
disScrollUnit(SCROLL_TYPE.HOUR, SCROLL_TYPE.MINUTE);
hourPickerView.setVisibility(View.GONE);
tvHour.setVisibility(View.GONE);
minutePickerView.setVisibility(View.GONE);
tvMinute.setVisibility(View.GONE);
}
}
}
/**
* 设置日期控件是否可以循环滚动
*/
public void setIsLoop(boolean isLoop) {
if (canAccess) {
this.yearPickerView.setIsLoop(isLoop);
this.monthPickerView.setIsLoop(isLoop);
this.dayPickerView.setIsLoop(isLoop);
this.hourPickerView.setIsLoop(isLoop);
this.minutePickerView.setIsLoop(isLoop);
}
}
/**
* 设置日期控件默认选中的时间
*/
public void setSelectedTime(String time) {
if (canAccess) {
String[] str = time.split(" ");
String[] dateStr = str[0].split("-");
yearPickerView.setSelected(dateStr[0]);
selectedCalender.set(Calendar.YEAR, Integer.parseInt(dateStr[0]));
month.clear();
int selectedYear = selectedCalender.get(Calendar.YEAR);
if (selectedYear == startYear) {
for (int i = startMonth; i <= MAX_MONTH; i++) {
month.add(formatTimeUnit(i));
}
} else if (selectedYear == endYear) {
for (int i = 1; i <= endMonth; i++) {
month.add(formatTimeUnit(i));
}
} else {
for (int i = 1; i <= MAX_MONTH; i++) {
month.add(formatTimeUnit(i));
}
}
monthPickerView.setData(month);
monthPickerView.setSelected(dateStr[1]);
selectedCalender.set(Calendar.MONTH, Integer.parseInt(dateStr[1]) - 1);
executeAnimator(monthPickerView);
day.clear();
int selectedMonth = selectedCalender.get(Calendar.MONTH) + 1;
if (selectedYear == startYear && selectedMonth == startMonth) {
for (int i = startDay; i <= selectedCalender.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
day.add(formatTimeUnit(i));
}
} else if (selectedYear == endYear && selectedMonth == endMonth) {
for (int i = 1; i <= endDay; i++) {
day.add(formatTimeUnit(i));
}
} else {
for (int i = 1; i <= selectedCalender.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
day.add(formatTimeUnit(i));
}
}
dayPickerView.setData(day);
dayPickerView.setSelected(dateStr[2]);
selectedCalender.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateStr[2]));
executeAnimator(dayPickerView);
if (str.length == 2) {
String[] timeStr = str[1].split(":");
if ((scrollUnits & SCROLL_TYPE.HOUR.value) == SCROLL_TYPE.HOUR.value) {
hour.clear();
int selectedDay = selectedCalender.get(Calendar.DAY_OF_MONTH);
if (selectedYear == startYear && selectedMonth == startMonth && selectedDay == startDay) {
for (int i = startHour; i <= MAX_HOUR; i++) {
hour.add(formatTimeUnit(i));
}
} else if (selectedYear == endYear && selectedMonth == endMonth && selectedDay == endDay) {
for (int i = MIN_HOUR; i <= endHour; i++) {
hour.add(formatTimeUnit(i));
}
} else {
for (int i = MIN_HOUR; i <= MAX_HOUR; i++) {
hour.add(formatTimeUnit(i));
}
}
hourPickerView.setData(hour);
hourPickerView.setSelected(timeStr[0]);
selectedCalender.set(Calendar.HOUR_OF_DAY, Integer.parseInt(timeStr[0]));
executeAnimator(hourPickerView);
}
if ((scrollUnits & SCROLL_TYPE.MINUTE.value) == SCROLL_TYPE.MINUTE.value) {
minute.clear();
int selectedDay = selectedCalender.get(Calendar.DAY_OF_MONTH);
int selectedHour = selectedCalender.get(Calendar.HOUR_OF_DAY);
if (selectedYear == startYear && selectedMonth == startMonth && selectedDay == startDay && selectedHour == startHour) {
for (int i = startMinute; i <= MAX_MINUTE; i++) {
minute.add(formatTimeUnit(i));
}
} else if (selectedYear == endYear && selectedMonth == endMonth && selectedDay == endDay && selectedHour == endHour) {
for (int i = MIN_MINUTE; i <= endMinute; i++) {
minute.add(formatTimeUnit(i));
}
} else {
for (int i = MIN_MINUTE; i <= MAX_MINUTE; i++) {
minute.add(formatTimeUnit(i));
}
}
minutePickerView.setData(minute);
minutePickerView.setSelected(timeStr[1]);
selectedCalender.set(Calendar.MINUTE, Integer.parseInt(timeStr[1]));
executeAnimator(minutePickerView);
}
}
executeScroll();
}
}
/**
* 验证字符串是否是一个合法的日期格式
*/
private boolean isValidDate(String date, String template) {
boolean convertSuccess = true;
// 指定日期格式
SimpleDateFormat format = new SimpleDateFormat(template, Locale.CHINA);
try {
// 设置lenient为false. 否则SimpleDateFormat会比较宽松地验证日期,比如2015/02/29会被接受,并转换成2015/03/01
format.setLenient(false);
format.parse(date);
} catch (Exception e) {
// 如果throw java.text.ParseException或者NullPointerException,就说明格式不对
convertSuccess = false;
}
return convertSuccess;
}
}
date_picker_dialog.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFF"
android:padding="10dp">
<TextView
android:id="@+id/tv_cancle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:padding="10dp"
android:text="@string/cancel"
android:textColor="@color/text1"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/date_picker_title"
android:textColor="@color/text1"
android:textSize="20sp" />
<TextView
android:id="@+id/tv_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:padding="10dp"
android:text="@string/ok"
android:textColor="@color/text1"
android:textSize="16sp" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#11112233" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:background="#FFF"
android:orientation="horizontal"
android:paddingBottom="15dp"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:paddingTop="15dp">
<me.yezhou.lib.widget.WheelPickerView
android:id="@+id/year_pv"
android:layout_width="0dp"
android:layout_height="160dp"
android:layout_weight="2" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1.3"
android:gravity="center"
android:text="@string/year"
android:textColor="@color/text2"
android:textSize="18sp" />
<me.yezhou.lib.widget.WheelPickerView
android:id="@+id/month_pv"
android:layout_width="0dp"
android:layout_height="160dp"
android:layout_weight="2" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1.7"
android:gravity="center"
android:text="@string/month"
android:textColor="@color/text2"
android:textSize="18sp" />
<me.yezhou.lib.widget.WheelPickerView
android:id="@+id/day_pv"
android:layout_width="0dp"
android:layout_height="160dp"
android:layout_weight="2" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="@string/day"
android:textColor="@color/text2"
android:textSize="18sp" />
<me.yezhou.lib.widget.WheelPickerView
android:id="@+id/hour_pv"
android:layout_width="0dp"
android:layout_height="160dp"
android:layout_weight="2" />
<TextView
android:id="@+id/hour_text"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="@string/hour"
android:textColor="@color/text2"
android:textSize="18sp" />
<me.yezhou.lib.widget.WheelPickerView
android:id="@+id/minute_pv"
android:layout_width="0dp"
android:layout_height="160dp"
android:layout_weight="2" />
<TextView
android:id="@+id/minute_text"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="@string/minute"
android:textColor="@color/text2"
android:textSize="18sp" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>
styles.xml
<style name="date_picker_dialog" parent="android:style/Theme.Dialog">
<item name="android:windowFrame">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowBackground">@android:color/white</item>
</style>
colors.xml
<color name="text1">#59B29C</color>
<color name="text2">#333333</color>
strings.xml
<string name="confirm">确定</string>
<string name="cancel">取消</string>
<string name="date_picker_title">请选择日期</string>
<string name="year">年</string>
<string name="month">月</string>
<string name="day">日</string>
<string name="hour">时</string>
<string name="minute">分</string>
自定义WheelPickerView
public class WheelPickerView extends View {
private Context context;
/**
* 新增字段 控制是否首尾相接循环显示 默认为循环显示
*/
private boolean loop = true;
/**
* text之间间距和minTextSize之比
*/
public static final float MARGIN_ALPHA = 2.8f;
/**
* 自动回滚到中间的速度
*/
public static final float SPEED = 10;
private List<String> mDataList;
/**
* 选中的位置,这个位置是mDataList的中心位置,一直不变
*/
private int mCurrentSelected;
private Paint mPaint, nPaint;
private float mMaxTextSize = 80;
private float mMinTextSize = 40;
private float mMaxTextAlpha = 255;
private float mMinTextAlpha = 120;
private int mViewHeight;
private int mViewWidth;
private float mLastDownY;
/**
* 滑动的距离
*/
private float mMoveLen = 0;
private boolean isInit = false;
private boolean canScroll = true;
private OnSelectListener mSelectListener;
private Timer timer;
private MyTimerTask mTask;
private Handler updateHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (Math.abs(mMoveLen) < SPEED) {
mMoveLen = 0;
if (mTask != null) {
mTask.cancel();
mTask = null;
performSelect();
}
} else {
// 这里mMoveLen / Math.abs(mMoveLen)是为了保有mMoveLen的正负号,以实现上滚或下滚
mMoveLen = mMoveLen - mMoveLen / Math.abs(mMoveLen) * SPEED;
}
invalidate();
}
};
public WheelPickerView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
init();
}
public void setOnSelectListener(OnSelectListener listener) {
mSelectListener = listener;
}
private void performSelect() {
if (mSelectListener != null) {
mSelectListener.onSelect(mCurrentSelected, mDataList.get(mCurrentSelected));
}
}
public void setData(List<String> datas) {
mDataList = datas;
mCurrentSelected = datas.size() / 4;
invalidate();
}
/**
* 选择选中的item的index
*/
public void setSelected(int selected) {
mCurrentSelected = selected;
if (loop) {
int distance = mDataList.size() / 2 - mCurrentSelected;
if (distance < 0) {
for (int i = 0; i < -distance; i++) {
moveHeadToTail();
mCurrentSelected--;
}
} else if (distance > 0) {
for (int i = 0; i < distance; i++) {
moveTailToHead();
mCurrentSelected++;
}
}
}
Log.i("yezhou", "setSelected: " + mCurrentSelected);
invalidate();
}
/**
* 选择选中的内容
*/
public void setSelected(String mSelectItem) {
for (int i = 0; i < mDataList.size(); i++) {
if (mDataList.get(i).equals(mSelectItem)) {
setSelected(i);
break;
}
}
}
private void moveHeadToTail() {
if (loop) {
String head = mDataList.get(0);
mDataList.remove(0);
mDataList.add(head);
}
}
private void moveTailToHead() {
if (loop) {
String tail = mDataList.get(mDataList.size() - 1);
mDataList.remove(mDataList.size() - 1);
mDataList.add(0, tail);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mViewHeight = getMeasuredHeight();
mViewWidth = getMeasuredWidth();
// 按照View的高度计算字体大小
mMaxTextSize = mViewHeight / 7f;
mMinTextSize = mMaxTextSize / 2.2f;
isInit = true;
invalidate();
}
private void init() {
timer = new Timer();
mDataList = new ArrayList<>();
//第一个paint
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Style.FILL);
mPaint.setTextAlign(Align.CENTER);
mPaint.setColor(ContextCompat.getColor(context, R.color.text1));
//第二个paint
nPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
nPaint.setStyle(Style.FILL);
nPaint.setTextAlign(Align.CENTER);
nPaint.setColor(ContextCompat.getColor(context, R.color.text2));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 根据index绘制view
if (isInit) {
drawData(canvas);
}
}
private void drawData(Canvas canvas) {
// 先绘制选中的text再往上往下绘制其余的text
float scale = parabola(mViewHeight / 4.0f, mMoveLen);
float size = (mMaxTextSize - mMinTextSize) * scale + mMinTextSize;
mPaint.setTextSize(size);
mPaint.setAlpha((int) ((mMaxTextAlpha - mMinTextAlpha) * scale + mMinTextAlpha));
// text居中绘制,注意baseline的计算才能达到居中,y值是text中心坐标
float x = (float) (mViewWidth / 2.0);
float y = (float) (mViewHeight / 2.0 + mMoveLen);
FontMetricsInt fmi = mPaint.getFontMetricsInt();
float baseline = (float) (y - (fmi.bottom / 2.0 + fmi.top / 2.0));
canvas.drawText(mDataList.get(mCurrentSelected), x, baseline, mPaint);
// 绘制上方data
for (int i = 1; (mCurrentSelected - i) >= 0; i++) {
drawOtherText(canvas, i, -1);
}
// 绘制下方data
for (int i = 1; (mCurrentSelected + i) < mDataList.size(); i++) {
drawOtherText(canvas, i, 1);
}
}
/**
* @param position 距离mCurrentSelected的差值
* @param type 1表示向下绘制,-1表示向上绘制
*/
private void drawOtherText(Canvas canvas, int position, int type) {
float d = MARGIN_ALPHA * mMinTextSize * position + type * mMoveLen;
float scale = parabola(mViewHeight / 4.0f, d);
float size = (mMaxTextSize - mMinTextSize) * scale + mMinTextSize;
nPaint.setTextSize(size);
nPaint.setAlpha((int) ((mMaxTextAlpha - mMinTextAlpha) * scale + mMinTextAlpha));
float y = (float) (mViewHeight / 2.0 + type * d);
FontMetricsInt fmi = nPaint.getFontMetricsInt();
float baseline = (float) (y - (fmi.bottom / 2.0 + fmi.top / 2.0));
canvas.drawText(mDataList.get(mCurrentSelected + type * position),
(float) (mViewWidth / 2.0), baseline, nPaint);
}
/**
* 抛物线
*
* @param zero 零点坐标
* @param x 偏移量
*/
private float parabola(float zero, float x) {
float f = (float) (1 - Math.pow(x / zero, 2));
return f < 0 ? 0 : f;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
doDown(event);
break;
case MotionEvent.ACTION_MOVE:
mMoveLen += (event.getY() - mLastDownY);
if (mMoveLen > MARGIN_ALPHA * mMinTextSize / 2) {
if (!loop && mCurrentSelected == 0) {
mLastDownY = event.getY();
invalidate();
return true;
}
if (!loop) {
mCurrentSelected--;
}
// 往下滑超过离开距离
moveTailToHead();
mMoveLen = mMoveLen - MARGIN_ALPHA * mMinTextSize;
} else if (mMoveLen < -MARGIN_ALPHA * mMinTextSize / 2) {
if (mCurrentSelected == mDataList.size() - 1) {
mLastDownY = event.getY();
invalidate();
return true;
}
if (!loop) {
mCurrentSelected++;
}
// 往上滑超过离开距离
moveHeadToTail();
mMoveLen = mMoveLen + MARGIN_ALPHA * mMinTextSize;
}
mLastDownY = event.getY();
invalidate();
break;
case MotionEvent.ACTION_UP:
doUp();
break;
}
return true;
}
private void doDown(MotionEvent event) {
if (mTask != null) {
mTask.cancel();
mTask = null;
}
mLastDownY = event.getY();
}
private void doUp() {
// 抬起手后mCurrentSelected的位置由当前位置move到中间选中位置
if (Math.abs(mMoveLen) < 0.0001) {
mMoveLen = 0;
return;
}
if (mTask != null) {
mTask.cancel();
mTask = null;
}
mTask = new MyTimerTask(updateHandler);
timer.schedule(mTask, 0, 10);
}
class MyTimerTask extends TimerTask {
Handler handler;
public MyTimerTask(Handler handler) {
this.handler = handler;
}
@Override
public void run() {
handler.sendMessage(handler.obtainMessage());
}
}
public interface OnSelectListener {
void onSelect(int index, String text);
}
public void setCanScroll(boolean canScroll) {
this.canScroll = canScroll;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
return canScroll && super.dispatchTouchEvent(event);
}
/**
* 控制内容是否首尾相连
*/
public void setIsLoop(boolean isLoop) {
loop = isLoop;
}
}
效果图
版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/25/android-custom-scrolling-date-time-selector/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。
THE END
0
二维码
打赏
海报
Android自定义滚动日期时间选择器
基本使用
private TextView mTvBirthday;
private MyDatePicker mBirthdayPicker;
private String today;
@Override
protected void onCreate(Bundle savedIn……
文章目录
关闭
共有 0 条评论