日曆控件你們應該不陌生,github 上面一搜一大堆,可是咱們拿到 github 上面的一個日曆控件,想動手改改功能改改需求,有時可能會以爲無從下手,(固然了,老司機就忽略我說的 —。—)那麼,若是想知道一個日曆控件是如何從無到有構建起來的,不妨各位看官快速瀏覽一下個人這篇文章。 文章主要是帶你們一步一步熟悉構建的流程,並無什麼特別酷炫狂拽的效果。java
先上一個效果圖鎮鎮樓。 android
/** * Created by deeson.woo */ public class MyCalendarBean { private int year; private int month;//1-12 private int day;//1-31 public MyCalendarBean(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } public int getYear() { return year; } public int getMonth() { return month; } public int getDay() { return day; } } 複製代碼
/** * 構建具體一天的對象 * @param year * @param month * @param day * @return */ public MyCalendarBean generateCalendarBean(int year, int month, int day) { Calendar calendar = Calendar.getInstance(); calendar.set(year, month - 1, day); year = calendar.get(Calendar.YEAR); month = calendar.get(Calendar.MONTH) + 1; day = calendar.get(Calendar.DATE); return new MyCalendarBean(year, month, day); } 複製代碼
/** * 獲取當前月份的日期列表 * @param year * @param month * @return */ public List<MyCalendarBean> getDaysListOfMonth(int year, int month) { List<MyCalendarBean> list = new ArrayList<>(); int daysOfMonth = getDaysOfCertainMonth(year, month); for (int i = 0; i < daysOfMonth; i++) { MyCalendarBean bean = generateCalendarBean(year, month, i + 1); list.add(bean); } return list; } /** * 獲取具體月份的最大天數 * * @param year * @param month * @return */ public static int getDaysOfCertainMonth(int year, int month) { Calendar calendar = Calendar.getInstance(); calendar.set(year, month - 1, 1); return calendar.getActualMaximum(Calendar.DATE); } 複製代碼
//測試打印2018年2月份 printDaysList(getDaysListOfMonth(2018, 2)); 複製代碼
2018年2月 = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28 }
git
自定義ViewGroup(MonthCalendarView)github
/**
* Created by deeson.woo
* 月份視圖
*/
public class MonthCalendarView extends ViewGroup {
private int column = 7;
public MonthCalendarView(Context context) {
super(context);
}
public MonthCalendarView(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
複製代碼
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int parentWidth = MeasureSpec.getSize(MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpec.EXACTLY)); //將寬度平均分紅七份,每一個item的寬高都等於它 int itemWidth = parentWidth / column; int itemHeight = itemWidth; int parentHeight = 0; for (int i = 0; i < getChildCount(); i++) { View childView = getChildAt(i); childView.measure(MeasureSpec.makeMeasureSpec(itemWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(itemHeight, MeasureSpec.EXACTLY)); //計算控件所需的高度 if (i % column == 0) { parentHeight += childView.getMeasuredHeight(); } } setMeasuredDimension(parentWidth, parentHeight); } 複製代碼
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { for (int i = 0; i < getChildCount(); i++) { View itemView = getChildAt(i); int columnCount = i % column; int rowCount = i / column; int itemWidth = itemView.getMeasuredWidth(); int itemHeight = itemView.getMeasuredHeight(); left = columnCount * itemWidth; top = rowCount * itemHeight; right = left + itemWidth; bottom = top + itemHeight; itemView.layout(left, top, right, bottom); } } 複製代碼
public void setMonth(int year, int month) { mList = calendarUtils.getDaysListOfMonth(year, month); addAllItem(); requestLayout(); } private void addAllItem() { for (int i = 0; i < mList.size(); i++) { MyCalendarBean bean = mList.get(i); View itemView = generateDateView(bean); addViewInLayout(itemView, i, itemView.getLayoutParams(), true); } } private View generateDateView(MyCalendarBean bean) { View itemView = LayoutInflater.from(getContext()).inflate(R.layout.item_date_view, null); if (bean.isCurrentMonth()) { TextView date = itemView.findViewById(R.id.date); date.setText(String.valueOf(bean.getDay())); } return itemView; } 複製代碼
把 item_date_view.xml 佈局也放出來bash
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/date" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center" android:textSize="14sp" android:textColor="@color/text_black" /> </RelativeLayout> 複製代碼
<?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="match_parent" android:orientation="vertical" android:background="@color/background_white" > <com.example.deesonwoo.mysimplecalendar.calendar.MonthCalendarView android:id="@+id/month_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/colorAccent"> </com.example.deesonwoo.mysimplecalendar.calendar.MonthCalendarView> </LinearLayout> 複製代碼
package com.example.deesonwoo.mysimplecalendar; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import com.example.deesonwoo.mysimplecalendar.calendar.MonthCalendarView; import com.example.deesonwoo.mysimplecalendar.calendar.MyCalendarBean; import com.example.deesonwoo.mysimplecalendar.calendar.MyCalendarUtils; import java.util.List; public class MainActivity extends AppCompatActivity { MonthCalendarView monthView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); monthView = findViewById(R.id.month_view); initCalendar(); } private void initCalendar() { //測試顯示2018年3月 monthView.setMonth(2018, 3); } } 複製代碼
效果展現以下:markdown
<?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="match_parent" android:orientation="vertical" android:background="@color/background_white" > <LinearLayout android:id="@+id/week_layout" android:layout_width="match_parent" android:layout_height="40dp" android:orientation="horizontal" android:background="@color/white" > <TextView android:id="@+id/week_00" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:text="週日" android:textColor="@color/text_black" android:textSize="12sp" /> <TextView android:id="@+id/week_01" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:text="週一" android:textColor="@color/text_black" android:textSize="12sp" /> <TextView android:id="@+id/week_02" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:text="週二" android:textColor="@color/text_black" android:textSize="12sp" /> <TextView android:id="@+id/week_03" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:text="週三" android:textColor="@color/text_black" android:textSize="12sp" /> <TextView android:id="@+id/week_04" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:text="週四" android:textColor="@color/text_black" android:textSize="12sp" /> <TextView android:id="@+id/week_05" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:text="週五" android:textColor="@color/text_black" android:textSize="12sp" /> <TextView android:id="@+id/week_06" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:text="週六" android:textColor="@color/text_black" android:textSize="12sp" /> </LinearLayout> <com.example.deesonwoo.mysimplecalendar.calendar.MonthCalendarView android:id="@+id/month_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/colorAccent"> </com.example.deesonwoo.mysimplecalendar.calendar.MonthCalendarView> </LinearLayout> 複製代碼
效果以下: app
相信大家已經發現,上面展現的效果中,日期與星期並無進行一一對應的排布。接下來,一塊兒咱們優化一下。ide
/** * 獲取具體一天對應的星期 * * @param year * @param month * @param day * @return 1-7(週日-週六) */ private int getWeekDayOnCertainDate(int year, int month, int day) { Calendar calendar = Calendar.getInstance(); calendar.set(year, month - 1, day); return calendar.get(Calendar.DAY_OF_WEEK); } 複製代碼
/** * 獲取當前月份的日期列表 * * @param year * @param month * @return */ public List<MyCalendarBean> getDaysListOfMonth(int year, int month) { List<MyCalendarBean> list = new ArrayList<>(); int daysOfMonth = getDaysOfCertainMonth(year, month); //找到當前月第一天的星期,計算出前面空缺的上個月的日期個數,填充到當月日期列表中 int weekDayOfFirstDay = getWeekDayOnCertainDate(year, month, 1); int preMonthDays = weekDayOfFirstDay - 1; for (int i = preMonthDays; i > 0; i--) { MyCalendarBean preMonthBean = generateCalendarBean(year, month, 1 - i); list.add(preMonthBean); } for (int i = 0; i < daysOfMonth; i++) { MyCalendarBean monthBean = generateCalendarBean(year, month, i + 1); list.add(monthBean); } return list; } 複製代碼
展現效果以下:工具
顯然,上一個月的日期在這裏是須要區別展現或者須要隱藏的,否則會給用戶形成視覺上的困擾,這裏,我直接作隱藏操做。oop
實體類以下:
/** * Created by deeson.woo */ public class MyCalendarBean { private int year; private int month;//1-12 private int day;//1-31 private boolean isCurrentMonth = true;//是否爲當前月份的日期 public MyCalendarBean(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } public int getYear() { return year; } public int getMonth() { return month; } public int getDay() { return day; } public boolean isCurrentMonth() { return isCurrentMonth; } public void setCurrentMonth(boolean currentMonth) { isCurrentMonth = currentMonth; } } 複製代碼
給標識賦值,在 getDaysListOfMonth()中賦值:
/** * 獲取當前月份的日期列表 * * @param year * @param month * @return */ public List<MyCalendarBean> getDaysListOfMonth(int year, int month) { List<MyCalendarBean> list = new ArrayList<>(); int daysOfMonth = getDaysOfCertainMonth(year, month); //找到當前月第一天的星期,計算出前面空缺的上個月的日期個數,填充到當月日期列表中 int weekDayOfFirstDay = getWeekDayOnCertainDate(year, month, 1); int preMonthDays = weekDayOfFirstDay - 1; for (int i = preMonthDays; i > 0; i--) { MyCalendarBean preMonthBean = generateCalendarBean(year, month, 1 - i); preMonthBean.setCurrentMonth(false); list.add(preMonthBean); } for (int i = 0; i < daysOfMonth; i++) { MyCalendarBean monthBean = generateCalendarBean(year, month, i + 1); monthBean.setCurrentMonth(true); list.add(monthBean); } return list; } 複製代碼
最後修改咱們自定義月曆類中 generateDateView()方法,不顯示上個月的日期:
private View generateDateView(MyCalendarBean bean) { View itemView = LayoutInflater.from(getContext()).inflate(R.layout.item_date_view, null); if(bean.isCurrentMonth()){ TextView date = itemView.findViewById(R.id.date); date.setText(String.valueOf(bean.getDay())); } return itemView; } 複製代碼
效果以下:
<RelativeLayout android:layout_width="match_parent" android:layout_height="50dp" android:background="@color/background_blue" > <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:textSize="20sp" android:textColor="@color/white" /> <ImageView android:id="@+id/pre_month" android:layout_width="40dp" android:layout_height="40dp" android:layout_centerVertical="true" android:layout_alignParentLeft="true" android:src="@mipmap/btn_preview" /> <ImageView android:id="@+id/next_month" android:layout_width="40dp" android:layout_height="40dp" android:layout_centerVertical="true" android:layout_alignParentRight="true" android:src="@mipmap/btn_next" /> </RelativeLayout> 複製代碼
#####(2)修改自定義月曆類
private int mYear, mMonth;
public MonthCalendarView(Context context) { super(context); calendarUtils = new MyCalendarUtils(context); } public MonthCalendarView(Context context, AttributeSet attrs) { super(context, attrs); calendarUtils = new MyCalendarUtils(context); } public void setMonth(int year, int month) { this.mYear = year; this.mMonth = month; invalidateMonth(); } private void invalidateMonth() { mList = calendarUtils.getDaysListOfMonth(mYear, mMonth); removeAllViews(); addAllItem(); requestLayout(); } 複製代碼
/** * 展現上一個月 */ public void moveToPreMonth() { mMonth -= 1; invalidateMonth(); } /** * 展現下一個月 */ public void moveToNextMonth() { mMonth += 1; invalidateMonth(); } 複製代碼
public String getCurrentYearAndMonth() { return MyCalendarUtils.formatYearAndMonth(mYear, mMonth); } 複製代碼
/** * 格式化標題展現 * @param year * @param month * @return */ public static String formatYearAndMonth(int year, int month) { Calendar calendar = Calendar.getInstance(); calendar.set(year, month - 1, 1); year = calendar.get(Calendar.YEAR); month = calendar.get(Calendar.MONTH) + 1; return year + "年" + month + "月"; } /** * 獲取系統當前年月日 * * @return */ public static int[] getNowDayFromSystem() { Calendar cal = Calendar.getInstance(); cal.setTime(new Date()); return new int[]{cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DATE)}; } 複製代碼
package com.example.deesonwoo.mysimplecalendar; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.ImageView; import android.widget.TextView; import com.example.deesonwoo.mysimplecalendar.calendar.MonthCalendarView; public class MainActivity extends AppCompatActivity implements View.OnClickListener { MonthCalendarView monthView; ImageView btnPreMonth, btnNextMonth; TextView title; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.activity_main); monthView = findViewById(R.id.month_view); initCalendar(); initTitleView(); updateTitle(); } private void initCalendar() { int[] nowDay = MyCalendarUtils.getNowDayFromSystem(); monthView.setMonth(nowDay[0], nowDay[1]); } private void initTitleView() { title = findViewById(R.id.title); btnPreMonth = findViewById(R.id.pre_month); btnNextMonth = findViewById(R.id.next_month); btnPreMonth.setOnClickListener(this); btnNextMonth.setOnClickListener(this); } /** * 刷新標題顯示年月 */ private void updateTitle() { String yearAndMonth = monthView.getCurrentYearAndMonth(); title.setText(yearAndMonth); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.pre_month: monthView.moveToPreMonth(); updateTitle(); break; case R.id.next_month: monthView.moveToNextMonth(); updateTitle(); break; } } } 複製代碼
最後顯示的效果以下動圖:
/** * 判斷是否爲系統當天 * @param bean * @return */ public static boolean isToday(MyCalendarBean bean) { int[] nowDay = getNowDayFromSystem(); return bean.getYear() == nowDay[0] && bean.getMonth() == nowDay[1] && bean.getDay() == nowDay[2]; } 複製代碼
private View generateDateView(MyCalendarBean bean) { View itemView = LayoutInflater.from(getContext()).inflate(R.layout.item_date_view, null); if (bean.isCurrentMonth()) { TextView date = itemView.findViewById(R.id.date); if (MyCalendarUtils.isToday(bean)) { date.setBackgroundResource(R.drawable.item_today_bg); } date.setText(String.valueOf(bean.getDay())); } return itemView; } 複製代碼
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> <solid android:color="@color/theme_color" /> </shape> 複製代碼
效果以下:
private View generateDateView(MyCalendarBean bean) { View itemView = LayoutInflater.from(getContext()).inflate(R.layout.item_date_view, null); if (bean.isCurrentMonth()) { TextView date = itemView.findViewById(R.id.date); if (MyCalendarUtils.isToday(bean)) { date.setBackgroundResource(R.drawable.item_today_bg); } else { date.setBackgroundResource(R.drawable.item_pick_up); } date.setText(String.valueOf(bean.getDay())); } return itemView; } 複製代碼
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_selected="true"> <shape android:shape="oval"> <stroke android:color="@color/theme_color" android:width="1dp"/> </shape> </item> <item android:drawable="@android:color/transparent" /> </selector> 複製代碼
private OnDatePickUpListener onDatePickUpListener; public void setOnDatePickUpListener(OnDatePickUpListener onDatePickUpListener) { this.onDatePickUpListener = onDatePickUpListener; } public interface OnDatePickUpListener { void onDatePickUp(MyCalendarBean bean); } 複製代碼
private void addAllItem() { for (int i = 0; i < mList.size(); i++) { final MyCalendarBean bean = mList.get(i); final View itemView = generateDateView(bean); addViewInLayout(itemView, i, itemView.getLayoutParams(), true); final int position = i; itemView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (pickUpPosition == position) { return; } if (pickUpPosition != -1) { getChildAt(pickUpPosition).setSelected(false); } itemView.setSelected(true); if (null != onDatePickUpListener) { onDatePickUpListener.onOnDatePickUp(bean); } pickUpPosition = position; } }); } } 複製代碼
private void initCalendar() { int[] nowDay = MyCalendarUtils.getNowDayFromSystem(); monthView.setMonth(nowDay[0], nowDay[1]); monthView.setOnDatePickUpListener(new MonthCalendarView.OnDatePickUpListener() { @Override public void onDatePickUp(MyCalendarBean bean) { Toast.makeText(MainActivity.this, bean.toString(), Toast.LENGTH_SHORT).show(); } }); } 複製代碼
效果以下動圖:
你們可能以爲咱們的自定義控件到這裏就完結了,可是young、simple、naive......(瞎bb) 秉着高內聚低耦合的原則(再次瞎bb),我將剛剛出現的操做所有整合到一個控件SimpleCalendarView 中。 直接上代碼吧,也沒幾行,就不作什麼解釋了。
package com.example.deesonwoo.mysimplecalendar.calendar; import android.content.Context; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import com.example.deesonwoo.mysimplecalendar.R; public class SimpleCalendarView extends LinearLayout implements View.OnClickListener, MonthCalendarView.OnDatePickUpListener { private MonthCalendarView monthCalendarView;// 月曆 private OnDatePickListener onDatePickListener; private TextView title; public SimpleCalendarView(Context context) { this(context, null); } public SimpleCalendarView(Context context, AttributeSet attrs) { super(context, attrs); setOrientation(VERTICAL); setBackgroundColor(context.getResources().getColor(R.color.white)); // 年月標題、翻頁按鈕 LayoutParams titleParams = new LayoutParams(LayoutParams.MATCH_PARENT, MyCalendarUtils.dp2px(context, 50)); RelativeLayout titleLayout = (RelativeLayout) LayoutInflater.from(context).inflate(R.layout.title_layout, null); title = titleLayout.findViewById(R.id.title); ImageView preMonth = titleLayout.findViewById(R.id.pre_month); ImageView nextMonth = titleLayout.findViewById(R.id.next_month); preMonth.setOnClickListener(this); nextMonth.setOnClickListener(this); addView(titleLayout, titleParams); //星期佈局 LayoutParams weekParams = new LayoutParams(LayoutParams.MATCH_PARENT, MyCalendarUtils.dp2px(context, 40)); LinearLayout weekLayout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.week_layout, null); addView(weekLayout, weekParams); //月曆視圖 LayoutParams monthParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); monthCalendarView = new MonthCalendarView(context); initCalendarDate(); monthCalendarView.setOnDatePickUpListener(this); addView(monthCalendarView, monthParams); } private void initCalendarDate() { int[] nowDay = MyCalendarUtils.getNowDayFromSystem(); monthCalendarView.setMonth(nowDay[0], nowDay[1]); updateTitle(); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.pre_month: if (null != monthCalendarView) { monthCalendarView.moveToPreMonth(); } updateTitle(); break; case R.id.next_month: if (null != monthCalendarView) { monthCalendarView.moveToNextMonth(); } updateTitle(); break; } } private void updateTitle() { if (null != title && null != monthCalendarView) { title.setText(monthCalendarView.getCurrentYearAndMonth()); } } @Override public void onDatePickUp(MyCalendarBean bean) { if (null != onDatePickListener) { onDatePickListener.onDatePick(bean); } } public void setOnDatePickListener(OnDatePickListener onDatePickListener) { this.onDatePickListener = onDatePickListener; } public interface OnDatePickListener { void onDatePick(MyCalendarBean bean); } } 複製代碼
<?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="match_parent" android:background="@color/background_white" android:orientation="vertical"> <com.example.deesonwoo.mysimplecalendar.calendar.SimpleCalendarView android:id="@+id/calendarView" android:layout_width="match_parent" android:layout_height="wrap_content"> </com.example.deesonwoo.mysimplecalendar.calendar.SimpleCalendarView> </LinearLayout> 複製代碼
package com.example.deesonwoo.mysimplecalendar; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.Window; import android.view.WindowManager; import android.widget.Toast; import com.example.deesonwoo.mysimplecalendar.calendar.MyCalendarBean; import com.example.deesonwoo.mysimplecalendar.calendar.SimpleCalendarView; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.activity_main); SimpleCalendarView calendarView = findViewById(R.id.calendarView); calendarView.setOnDatePickListener(new SimpleCalendarView.OnDatePickListener() { @Override public void onDatePick(MyCalendarBean bean) { Toast.makeText(MainActivity.this, bean.toString(), Toast.LENGTH_SHORT).show(); } }); } } 複製代碼
最後的效果就是文章開頭的動態圖。