自定義日曆控制元件
效果圖

DatePickerView.png
使用
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.boildcoffee.datepickview.MainActivity"> <com.boildcoffee.datepicker.widget.DatePicker android:id="@+id/date_picker" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout>
DatePicker datePicker = findViewById(R.id.date_picker); datePicker.putDescText("2018-12-3","5節"); datePicker.putDescText("2018-12-4","4節"); datePicker.putDescText("2018-12-10","3節"); datePicker.setOnSingleSelectedListener(new MonthView.OnSingleSelectedListener() { @Override public void onSingleSelected(MonthView.DayCell dayCell) { Toast.makeText(MainActivity.this,dayCell.getYear() + "-" + dayCell.getMonth() + "-" + dayCell.getDay(),Toast.LENGTH_LONG).show(); } });
實現
MonthView
- 通過
drawHeaderSpace(canvas)
方法繪製頂灰色部間隔 - 通過
drawHeaderYM(canvas)
繪製年月 - 通過
drawCell(canvas)
繪製事先由generateData()
方法計算並生成好的每個日期 - 過載
onTouchEvent(MotionEvent event)
實現單選和多選
public class MonthView extends View{ private final int ROW_CELL_COUNT = 7; //一行7個格子 private int mCellColumn; //列數 private int mCellHorizontalSpace; //水平間距 private int mCellVerticalSpace; //垂直間距 private int mHeaderSpaceHeight; //頭部間隙 private int mHeaderSpaceBgColor; //頭部間隙背景顏色 private int mHeaderYMHeight; //頭部年月高度 private int mHeaderYMFontSize; //頭部年月文字大小 private int mHeaderYMFontColor; //頭部年月文字顏色 private int mGridLineWidth; //網格線寬度 private int mGridLineColor;//網格線顏色 private int mDayFontSize; //普通日期字型大小 private int mDayFontColor; //普通日期字型顏色 private int mSelectedDayFontColor; //選中後普通日期字型顏色 private int mHolidayFontColor; //節假日字型顏色 private int mDescFontSize; //底部描述字型大小 private int mDescFontColor; //底部描述字型顏色 private int mSelectedDescFontColor; //選中後底部描述字型顏色 private int mSelectedCellBgColor; //選中後格子背景顏色 private int mYear; //年 private int mMonth; //月 private int mDay; //日 private Calendar mCalendar; private int mWidth; private int mHeight; private int mCellWidth; //每一個網格的寬度 private int mCellHeight; //每一個網格的高度 private float mDownX; private float mDownY; private OnSingleSelectedListener mOnSingleSelectedListener; private OnMultiSelectedListener mOnMultiSelectedListener; private List<DayCell> mSelectedList = new ArrayList<>(); //選中的集合 private List<DayCell> mDayCells = new ArrayList<>(); private Map<String,String> mDescTextMap; private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.LINEAR_TEXT_FLAG); public MonthView(Context context) { super(context); setAttrDefaultValue(context); init(); } public MonthView(Context context, @Nullable AttributeSet attrs) { this(context,attrs,R.styleable.YJMonthView_month_view_style); init(); } public MonthView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setViewAttributes(context,attrs,defStyleAttr); init(); } /** * 設定年月後重新生成日期資料 * @param year 年 * @param month 月 */ public void setYearAndMonth(int year, int month){ mYear = year; mMonth = month; mCalendar.set(Calendar.YEAR,mYear); mCalendar.set(Calendar.MONTH,mMonth - 1); mDay = mCalendar.getActualMaximum(Calendar.DAY_OF_MONTH); generateData(); postInvalidate(); } /** * 設定描述集合 * @param descTextMap */ public void setDescTextMap(Map<String,String> descTextMap){ mDescTextMap = descTextMap; } /** * 設定單選監聽事件 * @param listener {@see OnSingleSelectedListener} */ public void setOnSingleSelectedListener(OnSingleSelectedListener listener){ mOnSingleSelectedListener = listener; } /** * 設定多選監聽事件 * @param listener {@see OnMultiSelectedListener} */ public void setOnMultiSelectedListener(OnMultiSelectedListener listener){ mOnMultiSelectedListener = listener; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { mWidth = w; mHeight = h; mCellWidth = (mWidth - (ROW_CELL_COUNT + 1) * mCellHorizontalSpace) / ROW_CELL_COUNT; generateData(); } @Override public boolean performClick() { return super.performClick(); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: mDownX = event.getX(); mDownY = event.getY(); break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP: float endX = event.getX(); float enY = event.getY(); int distance = (int) Math.sqrt(Math.pow(mDownX - endX,2) + Math.pow(mDownY - enY,2)); if (distance <= 15){ //點選事件 DayCell collisionDayCell = collisionDayCell(endX,enY); if (collisionDayCell != null && collisionDayCell.day > 0){ if (mOnSingleSelectedListener != null){ mSelectedList.clear(); mOnSingleSelectedListener.onSingleSelected(collisionDayCell); mSelectedList.add(collisionDayCell); }else if (mOnMultiSelectedListener != null){ if (mSelectedList.contains(collisionDayCell)){ mSelectedList.remove(collisionDayCell); }else { mSelectedList.add(collisionDayCell); } mOnMultiSelectedListener.onMultiSelected(mSelectedList); } postInvalidate(); performClick(); } } break; } return true; } @Override protected void onDraw(Canvas canvas) { drawHeaderSpace(canvas); //繪製頂灰色部間隔 drawHeaderYM(canvas); //繪製年月 drawCell(canvas); //繪製日期 } private void init() { mCalendar = Calendar.getInstance(); if (mYear == 0 || mMonth == 0){ //不設定年月,預設獲取當前年月 mYear = mCalendar.get(Calendar.YEAR); mMonth = mCalendar.get(Calendar.MONTH) + 1; } mDay = mCalendar.getActualMaximum(Calendar.DAY_OF_MONTH); //獲取該月的天數 } /** * 繪製頂部間隙 * @param canvas */ private void drawHeaderSpace(Canvas canvas) { mPaint.setColor(mHeaderSpaceBgColor); canvas.drawRect(0,0,mWidth,mHeaderSpaceHeight,mPaint); } private void drawHeaderYM(Canvas canvas) { mPaint.setColor(mHeaderYMFontColor); mPaint.setTextSize(mHeaderYMFontSize); String text = mYear + "年" + mMonth + "月"; Paint.FontMetrics fontMetrics = mPaint.getFontMetrics(); float baseLine = mHeaderSpaceHeight + (mHeaderYMHeight - (Math.abs(fontMetrics.bottom) - Math.abs(fontMetrics.top))) / 2; float x = (mWidth - mPaint.measureText(text)) / 2; canvas.drawText(text,x,baseLine,mPaint); mPaint.setColor(mGridLineColor); mPaint.setStrokeWidth(mGridLineWidth); int lineY = mHeaderSpaceHeight + mHeaderYMHeight; canvas.drawLine(0,lineY,mWidth,lineY,mPaint); } private void drawCell(Canvas canvas) { for (DayCell dayCell : mDayCells){ if (dayCell.day <= 0){ continue; } drawCellBg(dayCell,canvas); drawDayFont(dayCell,canvas); drawDescFont(dayCell,canvas); } } private void drawCellBg(DayCell dayCell, Canvas canvas) { if (!mSelectedList.contains(dayCell)) return; mPaint.setColor(mSelectedCellBgColor); int radius = getContext().getResources().getDimensionPixelSize(R.dimen.dp_5); RectF rect = new RectF(dayCell.left,dayCell.top,dayCell.right,dayCell.bottom); canvas.drawRoundRect(rect,radius,radius,mPaint); } private void drawDayFont(DayCell dayCell, Canvas canvas) { if (dayCell.day <= 0) return; String dayText = String.valueOf(dayCell.day); mPaint.setColor(dayCell.isHoliday ? mHolidayFontColor : mDayFontColor); if (mSelectedList.contains(dayCell)){ mPaint.setColor(mSelectedDayFontColor); } mPaint.setTextSize(mDayFontSize); float x = dayCell.left + (mCellWidth - mPaint.measureText(dayText)) / 2; float baseLine = dayCell.top + mCellHeight * 0.4f; canvas.drawText(String.valueOf(dayCell.day),x,baseLine,mPaint); } private void drawDescFont(DayCell dayCell, Canvas canvas) { final String descText = dayCell.descText; if (TextUtils.isEmpty(descText)) return; mPaint.setColor(mSelectedList.contains(dayCell) ? mSelectedDescFontColor : mDescFontColor); mPaint.setTextSize(mDescFontSize); float x = dayCell.left+ (mCellWidth - mPaint.measureText(descText)) / 2; float baseLine = dayCell.top + mCellHeight * 0.8f; canvas.drawText(dayCell.descText,x,baseLine,mPaint); } private DayCell collisionDayCell(float x,float y){ for (DayCell dayCell : mDayCells){ if (x >= dayCell.left && x <= dayCell.right && y >= dayCell.top && y <= dayCell.bottom){ return dayCell; } } return null; } /** * 生成資料提供canvas繪製日期 */ private void generateData() { mDayCells.clear(); mCalendar.set(Calendar.DAY_OF_MONTH,1); int skipCell = mCalendar.get(Calendar.DAY_OF_WEEK) - 1; if (skipCell == 7){ skipCell = 0; } int totalCell = mDay + skipCell; mCellColumn = totalCell / ROW_CELL_COUNT + (totalCell % ROW_CELL_COUNT == 0 ? 0 : 1); mCellHeight = (mHeight - mHeaderYMHeight - mHeaderSpaceHeight - mCellVerticalSpace * (mCellColumn + 1)) / mCellColumn; for (int i=skipCell; i<mDay + skipCell; i++){ final int top =mHeaderSpaceHeight + mHeaderYMHeight + mCellHeight * (i / ROW_CELL_COUNT) + mCellVerticalSpace * ((i / ROW_CELL_COUNT) + 1); final int left = (i % ROW_CELL_COUNT) * mCellWidth + mCellHorizontalSpace * ((i % ROW_CELL_COUNT) + 1); final int right = left + mCellWidth; final int bottom = top + mCellHeight; final int day = i - skipCell + 1; DayCell dayCell = new DayCell(); dayCell.left = left; dayCell.top = top; dayCell.right = right; dayCell.bottom = bottom; dayCell.day = day; dayCell.descText = getDescText(day); dayCell.isHoliday = isHoliday(i); dayCell.year = mYear; dayCell.month = mMonth; mDayCells.add(dayCell); } } private boolean isHoliday(int cellPosition) { return (cellPosition+1) % ROW_CELL_COUNT == 1 || (cellPosition+1) % ROW_CELL_COUNT == 0; } private String getDescText(int day) { String key = mYear + "-" + mMonth + "-" + day; if (mDescTextMap != null){ return mDescTextMap.get(key); } return null; } private void setAttrDefaultValue(Context context) { mHeaderSpaceHeight = context.getResources().getDimensionPixelSize(R.dimen.dp_13); mHeaderSpaceBgColor = context.getResources().getColor(R.color.bg_ebebeb); mHeaderYMHeight = context.getResources().getDimensionPixelSize(R.dimen.dp_45); mHeaderYMFontSize = context.getResources().getDimensionPixelSize(R.dimen.text_36); mHeaderYMFontColor = context.getResources().getColor(R.color.text_999); mGridLineWidth = 1; mGridLineColor = context.getResources().getColor(R.color.text_cccccc); mDayFontSize = context.getResources().getDimensionPixelSize(R.dimen.text_48); mDayFontColor = context.getResources().getColor(R.color.text_333); mHolidayFontColor = context.getResources().getColor(R.color.text_999); mDescFontSize = context.getResources().getDimensionPixelSize(R.dimen.text_30); mDescFontColor = context.getResources().getColor(R.color.text_39bbfb); mSelectedDayFontColor = Color.WHITE; mSelectedDescFontColor = Color.WHITE; mSelectedCellBgColor = context.getResources().getColor(R.color.text_39bbfb); mCellHorizontalSpace = context.getResources().getDimensionPixelSize(R.dimen.dp_3); mCellVerticalSpace = context.getResources().getDimensionPixelSize(R.dimen.dp_3); } private void setViewAttributes(Context context, @Nullable AttributeSet attrs, int defStyle) { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.YJMonthView,defStyle,R.style.Widget_MonthView); mHeaderYMHeight = a.getDimensionPixelSize(R.styleable.YJMonthView_header_YM_height,context.getResources().getDimensionPixelSize(R.dimen.dp_45)); mHeaderYMFontSize = a.getDimensionPixelSize(R.styleable.YJMonthView_header_YM_font_size,context.getResources().getDimensionPixelSize(R.dimen.text_36)); mHeaderYMFontColor = a.getColor(R.styleable.YJMonthView_header_YM_font_color,context.getResources().getColor(R.color.text_999)); mHeaderSpaceHeight = a.getDimensionPixelSize(R.styleable.YJMonthView_header_space_height,context.getResources().getDimensionPixelSize(R.dimen.dp_13)); mHeaderSpaceBgColor = a.getColor(R.styleable.YJMonthView_header_space_bg_color,context.getResources().getColor(R.color.bg_ebebeb)); mGridLineWidth = a.getDimensionPixelSize(R.styleable.YJMonthView_grid_line_width,1); mGridLineColor = a.getColor(R.styleable.YJMonthView_grid_line_color,context.getResources().getColor(R.color.text_cccccc)); mDayFontSize = a.getDimensionPixelSize(R.styleable.YJMonthView_day_font_size,context.getResources().getDimensionPixelSize(R.dimen.text_48)); mDayFontColor = a.getColor(R.styleable.YJMonthView_day_font_color, context.getResources().getColor(R.color.text_333)); mSelectedDayFontColor = a.getColor(R.styleable.YJMonthView_selected_day_font_color, Color.WHITE); mHolidayFontColor = a.getColor(R.styleable.YJMonthView_holiday_font_color, context.getResources().getColor(R.color.text_999)); mDescFontSize = a.getDimensionPixelSize(R.styleable.YJMonthView_desc_font_size,context.getResources().getDimensionPixelSize(R.dimen.text_30)); mDescFontColor = a.getColor(R.styleable.YJMonthView_desc_font_color, context.getResources().getColor(R.color.text_39bbfb)); mSelectedDescFontColor = a.getColor(R.styleable.YJMonthView_selected_desc_font_color, Color.WHITE); mSelectedCellBgColor = a.getColor(R.styleable.YJMonthView_selected_cell_bg_color,context.getResources().getColor(R.color.text_39bbfb)); mCellHorizontalSpace = a.getDimensionPixelSize(R.styleable.YJMonthView_cell_horizontal_space,context.getResources().getDimensionPixelSize(R.dimen.dp_3)); mCellVerticalSpace = a.getDimensionPixelSize(R.styleable.YJMonthView_cell_horizontal_space,context.getResources().getDimensionPixelSize(R.dimen.dp_3)); a.recycle(); } public final static class DayCell { private int left; private int right; private int top; private int bottom; private String descText; private boolean isHoliday; private int day; private int year; private int month; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; DayCell dayCell = (DayCell) o; if (left != dayCell.left) return false; if (right != dayCell.right) return false; if (top != dayCell.top) return false; if (bottom != dayCell.bottom) return false; if (isHoliday != dayCell.isHoliday) return false; if (day != dayCell.day) return false; if (year != dayCell.year) return false; if (month != dayCell.month) return false; return descText != null ? descText.equals(dayCell.descText) : dayCell.descText == null; } @Override public int hashCode() { int result = left; result = 31 * result + right; result = 31 * result + top; result = 31 * result + bottom; result = 31 * result + (descText != null ? descText.hashCode() : 0); result = 31 * result + (isHoliday ? 1 : 0); result = 31 * result + day; result = 31 * result + year; result = 31 * result + month; return result; } @Override public String toString() { return "year=" + year + "month=" + month + "day=" + day; } public int getDay() { return day; } public int getYear() { return year; } public int getMonth() { return month; } } public interface OnSingleSelectedListener{ void onSingleSelected(DayCell dayCell); } public interface OnMultiSelectedListener { void onMultiSelected(List<DayCell> dayCells); } }
DatePicker
public class DatePicker extends LinearLayout { private static final String[] HEADER_WEEK_TEXT = new String[]{"日","一","二","三","四","五","六"}; private static final int RANGE = 30; //前後30年 private boolean showPastDate = true; //是否顯示以前的日期 private int mCurrentYear; private int mCurrentMonth; private List<String> mYM = new ArrayList<>(); private MonthView.OnSingleSelectedListener mOnSingleSelectedListener; private MonthView.OnMultiSelectedListener mOnMultiSelectedListener; private Map<String,String> mDescTextMap = new HashMap<>(); public DatePicker(Context context) { super(context); init(context); } public DatePicker(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(context); } public DatePicker(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } /** * 新增對應日期的描述文字 * @param date 日期格式 YY-MM-DD * @param descText 描述文字 */ public void putDescText(String date,String descText){ mDescTextMap.put(date,descText); } private void init(Context context) { initData(); initView(context); } private void initView(Context context) { this.setOrientation(VERTICAL); LinearLayout llHeader = new LinearLayout(context); llHeader.setOrientation(HORIZONTAL); LinearLayout.LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,context.getResources().getDimensionPixelSize(R.dimen.dp_25)); llHeader.setLayoutParams(layoutParams); for (String text : HEADER_WEEK_TEXT){ TextView tv = new TextView(context); tv.setTextSize(15); tv.setTextColor(context.getResources().getColor(R.color.text_999)); tv.setText(text); tv.setGravity(Gravity.CENTER); LinearLayout.LayoutParams tvLayoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT); tvLayoutParams.weight = 1; tv.setLayoutParams(tvLayoutParams); llHeader.addView(tv); } addView(llHeader); RecyclerView recyclerView = new RecyclerView(context); recyclerView.setLayoutManager(new LinearLayoutManager(context)); BaseQuickAdapter<String,BaseViewHolder> adapter = new BaseQuickAdapter<String, BaseViewHolder>(R.layout.item_date_pick,mYM) { @Override protected void convert(BaseViewHolder helper, String item) { helper.addOnClickListener(R.id.month_view); MonthView monthView = helper.getView(R.id.month_view); String[] ym = item.split("-"); monthView.setYearAndMonth(Integer.valueOf(ym[0]),Integer.valueOf(ym[1])); monthView.setOnSingleSelectedListener(mOnSingleSelectedListener); monthView.setOnMultiSelectedListener(mOnMultiSelectedListener); monthView.setDescTextMap(mDescTextMap); } }; recyclerView.setAdapter(adapter); int toPosition = mYM.indexOf(mCurrentYear + "-" + mCurrentMonth); recyclerView.scrollToPosition(toPosition); addView(recyclerView); } private void initData() { Calendar calendar = Calendar.getInstance(); mCurrentYear = calendar.get(Calendar.YEAR); mCurrentMonth = calendar.get(Calendar.MONTH) + 1; //月是從0開始需加1 addYM(calendar, mCurrentYear,true); addYM(calendar, mCurrentYear,false); } private void addYM(Calendar calendar, int currentYear,boolean isBefore) { if (isBefore && showPastDate){ for (int y=currentYear - RANGE; y<currentYear; y++){ calendar.set(Calendar.YEAR,y); int month = calendar.get(Calendar.MONTH) + 1; for (int m=1; m<=month; m++){ String yearMonth = y + "-" + m; mYM.add(yearMonth); } } }else { for (int y=currentYear; y<currentYear + RANGE; y++){ calendar.set(Calendar.YEAR,y); int month = calendar.get(Calendar.MONTH) + 1; for (int m=1; m<=month; m++){ String yearMonth = y + "-" + m; mYM.add(yearMonth); } } } } public void setOnSingleSelectedListener(MonthView.OnSingleSelectedListener onSingleSelectedListener) { this.mOnSingleSelectedListener = onSingleSelectedListener; } public void setOnMultiSelectedListener(MonthView.OnMultiSelectedListener onMultiSelectedListener){ this.mOnMultiSelectedListener = onMultiSelectedListener; } }
demo
ofollow,noindex">https://github.com/aii1991/DatePicker