android自定义时间选择

自定义时间选择器,可以更改到年月日,时分秒

一、自定义DatePicker

public class CustomDatePicker {/*** 定义结果回调接口*/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 DatePickerView year_pv, month_pv, day_pv, hour_pv, minute_pv;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 int lastMonthDays; //上一个被选中的月份天数private String title;private String currentMon, currentDay, currentHour, currentMin; //当前选中的月、日、时、分private boolean spanYear, spanMon, spanDay, spanHour, spanMin;private Calendar selectedCalender, startCalendar, endCalendar;private TextView tv_title, tv_cancle, tv_select, hour_text, minute_text;public CustomDatePicker(Context context, String title, 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;this.title = title;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.TimePickerDialog);datePickerDialog.setCancelable(true);datePickerDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);datePickerDialog.setContentView(R.layout.custom_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() {year_pv = (DatePickerView) datePickerDialog.findViewById(R.id.year_pv);month_pv = (DatePickerView) datePickerDialog.findViewById(R.id.month_pv);day_pv = (DatePickerView) datePickerDialog.findViewById(R.id.day_pv);hour_pv = (DatePickerView) datePickerDialog.findViewById(R.id.hour_pv);minute_pv = (DatePickerView) datePickerDialog.findViewById(R.id.minute_pv);tv_title = (TextView) datePickerDialog.findViewById(R.id.tv_title);tv_cancle = (TextView) datePickerDialog.findViewById(R.id.tv_cancle);tv_select = (TextView) datePickerDialog.findViewById(R.id.tv_select);hour_text = (TextView) datePickerDialog.findViewById(R.id.hour_text);minute_text = (TextView) datePickerDialog.findViewById(R.id.minute_text);tv_title.setText(title);tv_cancle.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {datePickerDialog.dismiss();}});tv_select.setOnClickListener(new View.OnClickListener() {@Overridepublic 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() {year_pv.setData(year);month_pv.setData(month);day_pv.setData(day);hour_pv.setData(hour);minute_pv.setData(minute);year_pv.setSelected(0);month_pv.setSelected(0);day_pv.setSelected(0);hour_pv.setSelected(0);minute_pv.setSelected(0);executeScroll();}private void addListener() {year_pv.setOnSelectListener(new DatePickerView.onSelectListener() {@Overridepublic void onSelect(String text) {selectedCalender.set(Calendar.YEAR, Integer.parseInt(text));monthChange();}});month_pv.setOnSelectListener(new DatePickerView.onSelectListener() {@Overridepublic void onSelect(String text) {selectedCalender.set(Calendar.DAY_OF_MONTH, 1);selectedCalender.set(Calendar.MONTH, Integer.parseInt(text) - 1);currentMon = text; //保存选择的月份dayChange();}});day_pv.setOnSelectListener(new DatePickerView.onSelectListener() {@Overridepublic void onSelect(String text) {selectedCalender.set(Calendar.DAY_OF_MONTH, Integer.parseInt(text));currentDay = text;//保存选择的日期hourChange();}});hour_pv.setOnSelectListener(new DatePickerView.onSelectListener() {@Overridepublic void onSelect(String text) {selectedCalender.set(Calendar.HOUR_OF_DAY, Integer.parseInt(text));currentHour = text; //保存选择的小时minuteChange();}});minute_pv.setOnSelectListener(new DatePickerView.onSelectListener() {@Overridepublic void onSelect(String text) {selectedCalender.set(Calendar.MINUTE, Integer.parseInt(text));currentMin = 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);month_pv.setData(month);if (month.size() < MAX_MONTH && Integer.valueOf(currentMon) > month.size()) {month_pv.setSelected(month.size() - 1);selectedCalender.set(Calendar.DAY_OF_MONTH, 1);selectedCalender.set(Calendar.MONTH, month.size() - 1);} else {month_pv.setSelected(currentMon);selectedCalender.set(Calendar.DAY_OF_MONTH, 1);selectedCalender.set(Calendar.MONTH, Integer.valueOf(currentMon) - 1);}executeAnimator(month_pv);month_pv.postDelayed(new Runnable() {@Overridepublic 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));}}day_pv.setData(day);
//        selectedCalender.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day.get(0)));
//        day_pv.setSelected(0);if (day.size() < lastMonthDays && Integer.valueOf(currentDay) > day.size()) {day_pv.setSelected(day.size() - 1);currentDay = formatTimeUnit(day.size());} else {day_pv.setSelected(currentDay);}selectedCalender.set(Calendar.DAY_OF_MONTH, Integer.parseInt(currentDay));//重新赋值lastMonthDays = day.size();executeAnimator(day_pv);day_pv.postDelayed(new Runnable() {@Overridepublic 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)));
//            hour_pv.setSelected(0);hour_pv.setData(hour);if (hour.size() < 24 && Integer.valueOf(currentHour) > hour.size()) {hour_pv.setSelected(hour.size() - 1);selectedCalender.set(Calendar.HOUR_OF_DAY, hour.size());currentHour = formatTimeUnit(hour.size());} else {hour_pv.setSelected(currentHour);selectedCalender.set(Calendar.HOUR_OF_DAY, Integer.valueOf(currentHour));}executeAnimator(hour_pv);}hour_pv.postDelayed(new Runnable() {@Overridepublic 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));}}minute_pv.setData(minute);
//            selectedCalender.set(Calendar.MINUTE, Integer.parseInt(minute.get(0)));
//            minute_pv.setSelected(0);if (minute.size() < 60 && minute.size() < Integer.valueOf(currentMin)) {minute_pv.setSelected(minute.size() - 1);selectedCalender.set(Calendar.MINUTE, minute.size());//改变当前选择的分钟currentMin = formatTimeUnit(minute.size());} else {minute_pv.setSelected(currentMin);selectedCalender.set(Calendar.MINUTE, Integer.parseInt(currentMin));}executeAnimator(minute_pv);}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() {year_pv.setCanScroll(year.size() > 1);month_pv.setCanScroll(month.size() > 1);day_pv.setCanScroll(day.size() > 1);hour_pv.setCanScroll(hour.size() > 1 && (scrollUnits & SCROLL_TYPE.HOUR.value) == SCROLL_TYPE.HOUR.value);minute_pv.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();hour_pv.setVisibility(View.VISIBLE);hour_text.setVisibility(View.VISIBLE);minute_pv.setVisibility(View.VISIBLE);minute_text.setVisibility(View.VISIBLE);} else {disScrollUnit(SCROLL_TYPE.HOUR, SCROLL_TYPE.MINUTE);hour_pv.setVisibility(View.GONE);hour_text.setVisibility(View.GONE);minute_pv.setVisibility(View.GONE);minute_text.setVisibility(View.GONE);}}}/*** 设置日期控件是否可以循环滚动*/public void setIsLoop(boolean isLoop) {if (canAccess) {this.year_pv.setIsLoop(isLoop);this.month_pv.setIsLoop(isLoop);this.day_pv.setIsLoop(isLoop);this.hour_pv.setIsLoop(isLoop);this.minute_pv.setIsLoop(isLoop);}}public void setYearIsLoop(boolean isLoop) {if (canAccess) {this.year_pv.setIsLoop(isLoop);}}public void setMonIsLoop(boolean isLoop) {if (canAccess) {this.month_pv.setIsLoop(isLoop);}}public void setDayIsLoop(boolean isLoop) {if (canAccess) {this.day_pv.setIsLoop(isLoop);}}public void setHourIsLoop(boolean isLoop) {if (canAccess) {this.hour_pv.setIsLoop(isLoop);}}public void setMinIsLoop(boolean isLoop) {if (canAccess) {this.minute_pv.setIsLoop(isLoop);}}/*** 设置日期控件默认选中的时间*/public void setSelectedTime(String time) {if (canAccess) {String[] str = time.split(" ");String[] dateStr = str[0].split("-");year_pv.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));}}month_pv.setData(month);month_pv.setSelected(dateStr[1]);currentMon = dateStr[1]; //保存选择的月份selectedCalender.set(Calendar.MONTH, Integer.parseInt(dateStr[1]) - 1);executeAnimator(month_pv);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));}}lastMonthDays = day.size();day_pv.setData(day);day_pv.setSelected(dateStr[2]);currentDay = dateStr[2]; //保存选择的日selectedCalender.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateStr[2]));executeAnimator(day_pv);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));}}hour_pv.setData(hour);hour_pv.setSelected(timeStr[0]);currentHour = timeStr[0]; //保存选择的小时selectedCalender.set(Calendar.HOUR_OF_DAY, Integer.parseInt(timeStr[0]));executeAnimator(hour_pv);}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));}}minute_pv.setData(minute);minute_pv.setSelected(timeStr[1]);currentMin = timeStr[1]; //保存选择的分钟selectedCalender.set(Calendar.MINUTE, Integer.parseInt(timeStr[1]));executeAnimator(minute_pv);}}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/01format.setLenient(false);format.parse(date);} catch (Exception e) {// 如果throw java.text.ParseException或者NullPointerException,就说明格式不对convertSuccess = false;}return convertSuccess;}
}

二、自定义PickerView

public class DatePickerView 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() {@Overridepublic 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 DatePickerView(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(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++;}}}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);}}@Overrideprotected 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<>();//第一个paintmPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mPaint.setStyle(Style.FILL);mPaint.setTextAlign(Align.CENTER);//被选中的数字颜色mPaint.setColor(ContextCompat.getColor(context, R.color.color_gold));//第二个paintnPaint = new Paint(Paint.ANTI_ALIAS_FLAG);nPaint.setStyle(Style.FILL);nPaint.setTextAlign(Align.CENTER);//未选中的数字颜色nPaint.setColor(ContextCompat.getColor(context, R.color.color_text_unselected));}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 根据index绘制viewif (isInit) {drawData(canvas);}}private void drawData(Canvas canvas) {// 先绘制选中的text再往上往下绘制其余的textfloat 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));if (mDataList.size() > 0){canvas.drawText(mDataList.get(mCurrentSelected), x, baseline, mPaint);}// 绘制上方datafor (int i = 1; (mCurrentSelected - i) >= 0; i++) {drawOtherText(canvas, i, -1);}// 绘制下方datafor (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;}@Overridepublic 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;}@Overridepublic void run() {handler.sendMessage(handler.obtainMessage());}}public interface onSelectListener {void onSelect(String text);}public void setCanScroll(boolean canScroll) {this.canScroll = canScroll;}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {return canScroll && super.dispatchTouchEvent(event);}/*** 控制内容是否首尾相连*/public void setIsLoop(boolean isLoop) {loop = isLoop;}

三、对应的custom_date_picker.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="300dp"
android:background="@drawable/btn_white_bg"android:orientation="vertical"><RelativeLayoutandroid:paddingTop="5dp"android:paddingBottom="5dp"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingRight="10dp"android:paddingLeft="10dp"><TextViewandroid:id="@+id/tv_cancle"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:padding="10dp"android:text=""android:textColor="@color/color_gold"android:textSize="16sp" /><TextViewandroid:id="@+id/tv_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:text="Reminder"android:textColor="#FFEF863B"android:textSize="20sp" /></RelativeLayout><Viewandroid:layout_width="match_parent"android:layout_height="0.5dp"/><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><LinearLayoutandroid:id="@+id/ll_sel_time"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginBottom="5dp"android:orientation="horizontal"android:paddingBottom="15dp"android:paddingLeft="20dp"android:paddingRight="20dp"android:paddingTop="15dp"><com.top.note.havenrba.util.DatePickerViewandroid:id="@+id/year_pv"android:layout_width="0dp"android:layout_height="160dp"android:visibility="gone"android:layout_weight="3" /><TextViewandroid:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:gravity="center"android:text="@string/year"android:visibility="gone"android:textColor="@color/color_unit"android:textSize="18sp" /><com.top.note.havenrba.util.DatePickerViewandroid:id="@+id/month_pv"android:layout_width="0dp"android:layout_height="160dp"android:layout_weight="2" /><TextViewandroid:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:gravity="center"android:text="@string/month"android:textColor="@color/color_unit"android:textSize="18sp" /><com.top.note.havenrba.util.DatePickerViewandroid:id="@+id/day_pv"android:layout_width="0dp"android:layout_height="160dp"android:layout_weight="2" /><TextViewandroid:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:gravity="center"android:text="@string/day"android:textColor="@color/color_unit"android:textSize="18sp" /><com.top.note.havenrba.util.DatePickerViewandroid:id="@+id/hour_pv"android:layout_width="0dp"android:layout_height="160dp"android:layout_weight="2" /><TextViewandroid: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/color_unit"android:textSize="18sp" /><com.top.note.havenrba.util.DatePickerViewandroid:id="@+id/minute_pv"android:layout_width="0dp"android:layout_height="160dp"android:layout_weight="2" /><TextViewandroid: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/color_unit"android:textSize="18sp" /></LinearLayout><TextViewandroid:id="@+id/tv_select"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:layout_marginHorizontal="16dp"android:layout_centerVertical="true"android:background="@drawable/bg_btn"android:layout_below="@+id/ll_sel_time"android:layout_marginBottom="16dp"android:gravity="center"android:padding="10dp"android:text="@string/commit"android:textColor="@color/color_gold"android:textSize="16sp" /></RelativeLayout>
</LinearLayout>

四、定义TimePickerDialog

 <style name="TimePickerDialog" 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">@color/color_9f9</item></style>

 

                                                           End

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/610509.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Web】CTFSHOW命令执行刷题记录1

目录 web29 web30 web31 web32 web33 web34 web35 web36 web37-39 web40 web41 &#xff08;y4✌脚本&#xff09; web42 -44 web45 web46 -49 web50 web51 web52 web53 web54 期末复习不了一点&#xff0c;不如做点旧题醒一醒手感。每一题都尽量用不同payl…

@FunctionalSpringBootTest 和@SpringBootTest注解的区别

FunctionalSpringBootTest 和 SpringBootTest 是Spring框架中用于测试的两个不同注解。下面是它们之间的主要区别&#xff1a; 用途和范围&#xff1a; SpringBootTest&#xff1a;这个注解用于需要测试Spring应用程序上下文的场合。它会加载完整的应用程序上下文&#xff0c;适…

插件和工具汇总

插件和工具汇总 【一】MyBatis Log插件【二】热部署【三】一些快捷键 【一】MyBatis Log插件 能够自动拼接参数生成执行的SQL语句&#xff0c;可以更清晰看到执行本次接口调用的所有sql执行条数。在我们执行myabtis的时候&#xff0c;有的时候报错知道哪里错了&#xff0c;但是…

亚马逊,速卖通,美客多卖家怎么才能安全及有效的积累产品的评论

测评补单对于亚马逊、速卖通等平台卖家来说&#xff0c;是一种重要的运营手段之一&#xff0c;通过测评补单快速增加产品的销量、评论数量&#xff0c;提升排名&#xff0c;从而打造爆款产品。 测评养号的好处包括&#xff1a; 1. 提升店铺信誉&#xff0c;制造爆款&#xff…

【大模型】大型模型飞跃升级—文档图像识别领域迎来技术巨变

写在前面 2023年12月31日&#xff0c;第十九届中国图象图形学学会青年科学家会议在广州举行&#xff0c;由中国图象图形学学会主办。 该会议的目标是促进青年科学家之间的交流与合作&#xff0c;以提升我国在图像图形领域的科研水平和创新能力。 由中国图象图形学学会和上海合合…

我在考 pmp,想知道如何去学习 pmbok?

PMP新版大纲加入了ACP敏捷管理的内容&#xff0c;而且还不少&#xff0c;敏捷混合题型占到了 50%&#xff0c;很多人都觉得考试难度提升了&#xff0c;我最开始备考的时候也被折磨过一段时间&#xff0c;但是后面还是找到了方法&#xff0c;3A通过了考试&#xff0c;也算有点经…

【EAI 005】EmbodiedGPT:通过具身思维链进行视觉语言预训练的具身智能大模型

论文描述&#xff1a;EmbodiedGPT: Vision-Language Pre-Training via Embodied Chain of Thought 论文作者&#xff1a;Yao Mu, Qinglong Zhang, Mengkang Hu, Wenhai Wang, Mingyu Ding, Jun Jin, Bin Wang, Jifeng Dai, Yu Qiao, Ping Luo 作者单位&#xff1a;The Universi…

MyBatis:自定义 typeHandler 处理枚举类型

MyBatis 枚举类型typeHandler 枚举类型 枚举类型&#xff0c;在 Java 中属于基本数据类型&#xff0c;而不是构造数据类型&#xff0c;用于声明一组命名的常数。枚举可以根据 Integer 、Long 、Short 或 Byte 中的任意一种数据类型来创建一种新型变量。这种变量可以设置为已经…

npm安装vue,添加淘宝镜像

如果是第一次使用命令栏可能会遇到权限问题。 解决vscode无法运行npm和node.js命令的问题-CSDN博客 安装 在vscode上面的导航栏选择terminal打开新的命令栏 另外可能会遇到网络或者其他的问题&#xff0c;可以添加淘宝镜像 npm install -g cnpm --registryhttps://registry.…

Appium,多应用程序平台的 UI 自动化

Appium是一个开源的移动应用程序自动化测试工具&#xff0c;可以用于跨平台的UI自动化&#xff0c;包括iOS、Android、Web和Windows应用程序。它基于WebDriver协议&#xff0c;支持多种编程语言&#xff0c;如Java、Python、Ruby,Javascript、C#等。 Appium的设计理念是“一次…

websocket: 了解并利用nodejs实现webSocket前后端通信

目录 第一章 前言 1.1 起源 1.2 短轮询与长轮询 1.2.1 短轮询 1.2.2 长轮询 1.2.3 长连接&#xff08;SSE&#xff09; 1.2.4 websocket 第二章 利用Node以及ws创建webSocket服务器 2.1 创建ws服务器&#xff08;后端部分&#xff09; 2.1.1 了解一下 2.1.2 代创建W…

“To-Do Master“ GPTs:重塑任务管理的趣味与效率

有 GPTs 访问权限的可以点击链接进行体验&#xff1a;https://chat.openai.com/g/g-IhGsoyIkP-to-do-master 部署私人的 To-Do Master 教程&#xff1a;https://github.com/Reborn14/To-Do-Master/tree/main 引言 在忙碌的日常生活中&#xff0c;有效地管理日常任务对于提高生…

K2P路由器刷OpenWrt官方最新版本固件OpenWrt 23.05.2方法 其他型号的智能路由器OpenWrt固件刷入方法也基本上适用

最近路由器在开机时总出问题,于是就那他来开刀,直接刷一个OpenWrt官方最新版本的固件, 刷其他第三方的固件总是觉得不安全, 而且很多第三方固件都带了些小工具,始终会有安全隐患, 而且占用内存空间太多,本来这个东西就没有多少内存,于是就干脆刷一个官方的原始固件(才6.3M, 相…

HarmonyOS应用开发学习笔记 UI布局学习 相对布局 (RelativeContainer)

UI布局学习 之 相对布局 &#xff08;RelativeContainer&#xff09; 官方文档 一、关键字 RelativeContainer&#xff0c; alignRules&#xff08;适配规则&#xff09; Text(Text02).alignRules({left: { anchor: text01, align: HorizontalAlign.Start },top: { anchor: t…

增广路算法 DFS求解 最大网络流问题

最大网络流问题 最大网络流问题是这样的&#xff0c;有一个有向图&#xff0c;假定有一个源点&#xff0c;有一个汇点&#xff0c;源点有流量出来&#xff0c;汇点有流量进入&#xff0c;有向图上的边的权重为该条边可通过的最大流量(方向为边的方向)&#xff0c;问从源点到汇…

的修平台——院校智能报修的强大助手,轻松解决报修难题!

在当今信息化时代&#xff0c;智能化的后勤管理成为了提升院校服务水平的关键。其中&#xff0c;报修流程的智能化改革更是重中之重。面对传统报修方式存在的种种问题&#xff0c;如报修流程冗长、信息沟通不畅、进度难以跟踪等&#xff0c;的修平台应运而生&#xff0c;为院校…

自动化测试框架pytest系列之基础概念介绍(一)

如果你要打算学习自动化测试 &#xff0c;无论是web自动化、app自动化还是接口自动化 &#xff0c;在学习的道路上&#xff0c;你几乎会遇到pytest这个测试框架&#xff0c;因为自动化编写没有测试框架&#xff0c;根本玩不了 。 如果你已经是一位自动化测试人员 &#xff0c;…

c++|关键字extern

一个C语言项目往往由多个文件组合而成。而对于多个文件来说&#xff0c;它们可能会共用到一些相同的变量。而有些情况下&#xff0c;这些相同的变量并没有出现在本文件内&#xff0c;有可能在其他文件内。而一个文件可能只会搜寻该文件内部是否有该变量。 所以&#xff0c;需要…

c++的构造函数

目录 构造函数 1.构造函数&#xff1a; 2.构造函数的特点&#xff1a; 默认构造函数 -- 没有参数的构造函数 1. 合成(自动)的默认构造函数(一般不常用) 1&#xff09; 介绍&#xff0c;以及为什么不使用 2&#xff09;可以使用合成默认构造函数的情况 2. 自定义的默认…

xss-labs(6-9)

level6:欢迎来到level6 老规矩还是先看看输入框的闭合情况 尝试事件函数绕过 test" onclick="alert(欢迎来钓鱼) 既然事件函数被转义了,那就使用我们第二关用过的绕过方法插入标签看看 test"><script>alert(欢迎来钓鱼)</script>// <