1. 程式人生 > >Android UI-自定義日曆控制元件

Android UI-自定義日曆控制元件

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

Android UI-自定義日曆控制元件


 2014年部落格之星,投票地址點選開啟連結

 本篇部落格筆者給大家分享一個日曆控制元件,這裡有個需求:要求顯示當前月的日期,左右可以切換月份來檢視日期。

 我們想一想會如何去實現這樣的一個控制元件,有開源的,但可能不太滿足我們的特定的需求,這裡筆者自定義了一個,讀者可以根據自己的需求來修改程式碼。下面來說一下實現的思路:

 首先我們要顯示當前月份,自然我們要計算出當前的日期,並且把每一天對應到具體的星期,我們會有以下效果:

 

 我們先想一下這樣的效果用什麼控制元件可以實現?很自然可以想到用網格檢視GridView,但這裡筆者使用的不是GridView, 因為使用GridView可能無法實現那個紅色的圈圈,所以筆者決定自定義View,通過繪製來達到這樣的效果。

 這裡我們定於一個日曆卡,每一個月代表一個日曆卡,我們通過計算每個月的日期,然後根據計算出來的位置繪製我們的數字。

 我們知道,一個星期有七天,分別為星期日、星期一、星期二、星期三、星期四、星期五、星期六,這裡有7列,一個月至少有28天,最多31天,所以至少應該有6行。組成6*7的方格圖。

 直接上程式碼:

 

package com.xiaowu.calendar;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.util.AttributeSet;import
android.view.MotionEvent;import android.view.View;import android.view.ViewConfiguration;/** * 自定義日曆卡 *  * @author wuwenjie *  */public class CalendarCard extends View private static final int TOTAL_COL = 7; // 7列 private static final int TOTAL_ROW = 6; // 6行 private Paint mCirclePaint; // 繪製圓形的畫筆 private Paint mTextPaint; // 繪製文字的畫筆 private int mViewWidth; // 檢視的寬度 private int mViewHeight; // 檢視的高度 private int mCellSpace; // 單元格間距 private Row rows[] = new Row[TOTAL_ROW]; // 行陣列,每個元素代表一行 private static CustomDate mShowDate; // 自定義的日期,包括year,month,day private OnCellClickListener mCellClickListener; // 單元格點選回撥事件 private int touchSlop; // private boolean callBackCellSpace; private Cell mClickCell; private float mDownX; private float mDownY; /**  * 單元格點選的回撥介面  *   * @author wuwenjie  *   */ public interface OnCellClickListener {  void clickDate(CustomDate date); // 回撥點選的日期  void changeDate(CustomDate date); // 回撥滑動ViewPager改變的日期 } public CalendarCard(Context context, AttributeSet attrs, int defStyleAttr) {  super(context, attrs, defStyleAttr);  init(context); } public CalendarCard(Context context, AttributeSet attrs) {  super(context, attrs);  init(context); } public CalendarCard(Context context) {  super(context);  init(context); } public CalendarCard(Context context, OnCellClickListener listener) {  super(context);  this.mCellClickListener = listener;  init(context); } private void init(Context context) {  mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);  mCirclePaint.setStyle(Paint.Style.FILL);  mCirclePaint.setColor(Color.parseColor("#F24949")); // 紅色圓形  touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();  initDate(); } private void initDate() {  mShowDate = new CustomDate();  fillDate();// } private void fillDate() {  int monthDay = DateUtil.getCurrentMonthDay(); // 今天  int lastMonthDays = DateUtil.getMonthDays(mShowDate.year,    mShowDate.month - 1); // 上個月的天數  int currentMonthDays = DateUtil.getMonthDays(mShowDate.year,    mShowDate.month); // 當前月的天數  int firstDayWeek = DateUtil.getWeekDayFromDate(mShowDate.year,    mShowDate.month);  boolean isCurrentMonth = false;  if (DateUtil.isCurrentMonth(mShowDate)) {   isCurrentMonth = true;  }  int day = 0;  for (int j = 0; j < TOTAL_ROW; j++) {   rows[j] = new Row(j);   for (int i = 0; i < TOTAL_COL; i++) {    int position = i + j * TOTAL_COL; // 單元格位置    // 這個月的    if (position >= firstDayWeek      && position < firstDayWeek + currentMonthDays) {     day++;     rows[j].cells[i] = new Cell(CustomDate.modifiDayForObject(       mShowDate, day), State.CURRENT_MONTH_DAY, i, j);     // 今天     if (isCurrentMonth && day == monthDay ) {      CustomDate date = CustomDate.modifiDayForObject(mShowDate, day);      rows[j].cells[i] = new Cell(date, State.TODAY, i, j);     }     if (isCurrentMonth && day > monthDay) { // 如果比這個月的今天要大,表示還沒到      rows[j].cells[i] = new Cell(        CustomDate.modifiDayForObject(mShowDate, day),        State.UNREACH_DAY, i, j);     }     // 過去一個月    } else if (position < firstDayWeek) {     rows[j].cells[i] = new Cell(new CustomDate(mShowDate.year,       mShowDate.month - 1, lastMonthDays         - (firstDayWeek - position - 1)),       State.PAST_MONTH_DAY, i, j);     // 下個月    } else if (position >= firstDayWeek + currentMonthDays) {     rows[j].cells[i] = new Cell((new CustomDate(mShowDate.year,       mShowDate.month + 1, position - firstDayWeek         - currentMonthDays + 1)),       State.NEXT_MONTH_DAY, i, j);    }   }  }  mCellClickListener.changeDate(mShowDate); } @Override protected void onDraw(Canvas canvas) {  super.onDraw(canvas);  for (int i = 0; i < TOTAL_ROW; i++) {   if (rows[i] != null) {    rows[i].drawCells(canvas);   }  } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {  super.onSizeChanged(w, h, oldw, oldh);  mViewWidth = w;  mViewHeight = h;  mCellSpace = Math.min(mViewHeight / TOTAL_ROW, mViewWidth / TOTAL_COL);  if (!callBackCellSpace) {   callBackCellSpace = true;  }  mTextPaint.setTextSize(mCellSpace / 3); } @Override public boolean onTouchEvent(MotionEvent event) {  switch (event.getAction()) {  case MotionEvent.ACTION_DOWN:   mDownX = event.getX();   mDownY = event.getY();   break;  case MotionEvent.ACTION_UP:   float disX = event.getX() - mDownX;   float disY = event.getY() - mDownY;   if (Math.abs(disX) < touchSlop && Math.abs(disY) < touchSlop) {    int col = (int) (mDownX / mCellSpace);    int row = (int) (mDownY / mCellSpace);    measureClickCell(col, row);   }   break;  default:   break;  }  return true; } /**  * 計算點選的單元格  * @param col  * @param row  */ private void measureClickCell(int col, int row) {  if (col >= TOTAL_COL || row >= TOTAL_ROW)   return;  if (mClickCell != null) {   rows[mClickCell.j].cells[mClickCell.i] = mClickCell;  }  if (rows[row] != null) {   mClickCell = new Cell(rows[row].cells[col].date,     rows[row].cells[col].state, rows[row].cells[col].i,     rows[row].cells[col].j);   CustomDate date = rows[row].cells[col].date;   date.week = col;   mCellClickListener.clickDate(date);   // 重新整理介面   update();  } } /**  * 組元素  *   * @author wuwenjie  *   */ class Row {  public int j;  Row(int j) {   this.j = j;  }  public Cell[] cells = new Cell[TOTAL_COL];  // 繪製單元格  public void drawCells(Canvas canvas) {   for (int i = 0; i < cells.length; i++) {    if (cells[i] != null) {     cells[i].drawSelf(canvas);    }   }  } } /**  * 單元格元素  *   * @author wuwenjie  *   */ class Cell {  public CustomDate date;  public State state;  public int i;  public int j;  public Cell(CustomDate date, State state, int i, int j) {   super();   this.date = date;   this.state = state;   this.i = i;   this.j = j;  }  public void drawSelf(Canvas canvas) {   switch (state) {   case TODAY: // 今天    mTextPaint.setColor(Color.parseColor("#fffffe"));    canvas.drawCircle((float) (mCellSpace * (i + 0.5)),      (float) ((j + 0.5) * mCellSpace), mCellSpace / 3,      mCirclePaint);    break;   case CURRENT_MONTH_DAY: // 當前月日期    mTextPaint.setColor(Color.BLACK);    break;   case PAST_MONTH_DAY: // 過去一個月   case NEXT_MONTH_DAY: // 下一個月    mTextPaint.setColor(Color.parseColor("#fffffe"));    break;   case UNREACH_DAY: // 還未到的天    mTextPaint.setColor(Color.GRAY);    break;   default:    break;   }   // 繪製文字   String content = date.day + "";   canvas.drawText(content,     (float) ((i + 0.5) * mCellSpace - mTextPaint       .measureText(content) / 2), (float) ((j + 0.7)       * mCellSpace - mTextPaint       .measureText(content, 0, 1) / 2), mTextPaint);  } } /**  *   * @author wuwenjie 單元格的狀態 當前月日期,過去的月的日期,下個月的日期  */ enum State {  TODAY,CURRENT_MONTH_DAY, PAST_MONTH_DAY, NEXT_MONTH_DAY, UNREACH_DAY; } // 從左往右劃,上一個月 public void leftSlide() {  if (mShowDate.month == 1) {   mShowDate.month = 12;   mShowDate.year -= 1;  } else {   mShowDate.month -= 1;  }  update(); } // 從右往左劃,下一個月 public void rightSlide() {  if (mShowDate.month == 12) {   mShowDate.month = 1;   mShowDate.year += 1;  } else {   mShowDate.month += 1;  }  update(); } public void update() {  fillDate();  invalidate(); }}

   

 /CustomCalendarView/src/com/xiaowu/calendar/DateUtil.java

 

package com.xiaowu.calendar;import android.annotation.SuppressLint;import android.util.Log;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Calendar;import java.util.Date;import java.util.GregorianCalendar;public class DateUtil public static String[] weekName = { "週日", "週一", "週二", "週三", "週四", "週五","週六" }; public static int getMonthDays(int year, int month) {  if (month > 12) {   month = 1;   year += 1;  } else if (month < 1) {   month = 12;   year -= 1;  }  int[] arr = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };  int days = 0;  if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {   arr[1] = 29; // 閏年2月29天  }  try {   days = arr[month - 1];  } catch (Exception e) {   e.getStackTrace();  }  return days; }  public static int getYear() {  return Calendar.getInstance().get(Calendar.YEAR); } public static int getMonth() {  return Calendar.getInstance().get(Calendar.MONTH) + 1; } public static int getCurrentMonthDay() {  return Calendar.getInstance().get(Calendar.DAY_OF_MONTH); } public static int getWeekDay() {  return Calendar.getInstance().get(Calendar.DAY_OF_WEEK); } public static int getHour() {  return Calendar.getInstance().get(Calendar.HOUR_OF_DAY); } public static int getMinute() {  return Calendar.getInstance().get(Calendar.MINUTE); } public static CustomDate getNextSunday() {    Calendar c = Calendar.getInstance();  c.add(Calendar.DATE, 7 - getWeekDay()+1);  CustomDate date = new CustomDate(c.get(Calendar.YEAR),    c.get(Calendar.MONTH)+1, c.get(Calendar.DAY_OF_MONTH));  return date; } public static int[] getWeekSunday(int year, int month, int day, int pervious) {  int[] time = new int[3];  Calendar c = Calendar.getInstance();  c.set(Calendar.YEAR, year);  c.set(Calendar.MONTH, month);  c.set(Calendar.DAY_OF_MONTH, day);  c.add(Calendar.DAY_OF_MONTH, pervious);  time[0] = c.get(Calendar.YEAR);  time[1] = c.get(Calendar.MONTH )+1;  time[2] = c.get(Calendar.DAY_OF_MONTH);  return time; } public static int getWeekDayFromDate(int year, int month) {  Calendar cal = Calendar.getInstance();  cal.setTime(getDateFromString(year, month));  int week_index = cal.get(Calendar.DAY_OF_WEEK) - 1;  if (week_index < 0) {   week_index = 0;  }  return week_index; } @SuppressLint("SimpleDateFormat"public static Date getDateFromString(int year, int month) {  String dateString = year + "-" + (month > 9 ? month : ("0" + month))    + "-01";  Date date = null;  try {   SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");   date = sdf.parse(dateString);  } catch (ParseException e) {   System.out.println(e.getMessage());  }  return date; } public static boolean isToday(CustomDate date){  return(date.year == DateUtil.getYear() &&    date.month == DateUtil.getMonth()     && date.day == DateUtil.getCurrentMonthDay()); }  public static boolean isCurrentMonth(CustomDate date){  return(date.year == DateUtil.getYear() &&    date.month == DateUtil.getMonth()); }}


/CustomCalendarView/src/com/xiaowu/calendar/CustomDate.java、

package com.xiaowu.calendar;import java.io.Serializable;public class CustomDate implements Serializable{   private static final long serialVersionUID = 1Lpublic int year; public int month; public int day; public int week;  public CustomDate(int year,int month,int day){  if(month > 12){   month = 1;   year++;  }else if(month <1){   month = 12;   year--;  }  this.year = year;  this.month = month;  this.day = day; }  public CustomDate(){  this.year = DateUtil.getYear();  this.month = DateUtil.getMonth();  this.day = DateUtil.getCurrentMonthDay(); }  public static CustomDate modifiDayForObject(CustomDate date,int day){  CustomDate modifiDate = new CustomDate(date.year,date.month,day);  return modifiDate; } @Override public String toString() {  return year+"-"+month+"-"+day; } public int getYear() {  return year; } public void setYear(int year) {  this.year = year; } public int getMonth() {  return month; } public void setMonth(int month) {  this.month = month; } public int getDay() {  return day; } public void setDay(int day) {  this.day = day; } public int getWeek() {  return week; } public void setWeek(int week) {  this.week = week; }}



   所有繪製的操作在onDraw方面裡實現,我這裡定於了一個組物件Row、單元格元素Cell,通過Row[row].cell[col]來確定一個單元格,每次呼叫invalidate重繪檢視。

 接著,我們有一個需求需要左右切換,我們選用最熟悉的ViewPager,但這裡有個問題,怎麼實現無限迴圈呢,

這裡我們傳入一個日曆卡陣列,讓ViewPager迴圈複用這幾個日曆卡,避免消耗記憶體。

/CustomCalendarView/src/com/xiaowu/calendar/CalendarViewAdapter.java

package com.xiaowu.calendar;import android.support.v4.view.PagerAdapter;import android.support.v4.view.ViewPager;import android.view.View;import android.view.ViewGroup;public class CalendarViewAdapter<V extends View> extends PagerAdapter public static final String TAG = "CalendarViewAdapter"private V[] views; public CalendarViewAdapter(V[] views) {  super();  this.views = views; }  @Override public Object instantiateItem(ViewGroup container, int position) {     if (((ViewPager) container).getChildCount() == views.length) {   ((ViewPager) container).removeView(views[position % views.length]);  }    ((ViewPager) container).addView(views[position % views.length], 0);  return views[position % views.length]; } @Override public int getCount() {  return Integer.MAX_VALUE; } @Override public boolean isViewFromObject(View view, Object object) {  return view == ((View) object); } @Override public void destroyItem(ViewGroup container, int position, Object object) {  ((ViewPager) container).removeView((View) container); }  public V[] getAllItems() {  return views; }}

佈局檔案:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="@color/white"    android:orientation="vertical" >    <RelativeLayout        android:layout_width="match_parent"        android:layout_height="50dp"        android:background="#f6f1ea"         >        <ImageButton            android:id="@+id/btnPreMonth"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_centerVertical="true"            android:layout_marginRight="33dip"            android:layout_toLeftOf="@+id/tvCurrentMonth"            android:background="@drawable/ic_before" />        <ImageButton            android:id="@+id/btnNextMonth"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_centerVertical="true"            android:layout_marginLeft="33dip"            android:layout_toRightOf="@+id/tvCurrentMonth"            android:background="@drawable/ic_next" />        <TextView            android:id="@+id/tvCurrentMonth"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_centerInParent="true"            android:layout_centerVertical="true"            android:text="11月"            android:textColor="#323232"            android:textSize="22sp" />        <ImageButton            android:id="@+id/btnClose"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_alignParentRight="true"            android:layout_centerVertical="true"            android:layout_marginRight="15dp"            android:background="@drawable/ic_close" />    </RelativeLayout>    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginTop="15dp"        android:orientation="vertical"         >        <TableLayout            android:layout_width="match_parent"            android:layout_height="20dip"            android:layout_marginBottom="2dip"            android:layout_marginTop="2dip" >            <TableRow>                <TextView                    style="@style/dateStyle"                    android:text="@string/sunday"                    android:textColor="@color/canlendar_text_color" />                <TextView                    style="@style/dateStyle"                    android:text="@string/monday"                    android:textColor="@color/canlendar_text_color" />                <TextView                    style="@style/dateStyle"                    android:text="@string/thesday"                    android:textColor="@color/canlendar_text_color" />                <TextView                    style="@style/dateStyle"                    android:text="@string/wednesday"                    android:textColor="@color/canlendar_text_color" />                <TextView                    style="@style/dateStyle"                    android:text="@string/thursday"                    android:textColor="@color/canlendar_text_color" />                <TextView                    style="@style/dateStyle"                    android:text="@string/friday"                    android:textColor="@color/canlendar_text_color" />                <TextView                    style="@style/dateStyle"                    android:text="@string/saturday"                    android:textColor="@color/canlendar_text_color" />            </TableRow>        </TableLayout>    </LinearLayout>    <LinearLayout        android:layout_width="match_parent"        android:layout_height="0dp"        android:orientation="vertical"         android:layout_weight="1"        android:layout_marginTop="15dp">        <android.support.v4.view.ViewPager            android:id="@+id/vp_calendar"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:layout_gravity="center"            android:background="@color/white" >        </android.support.v4.view.ViewPager>    </LinearLayout></LinearLayout>


/CustomCalendarView/src/com/xiaowu/calendar/MainActivity.java 

package com.xiaowu.calendar;import android.app.Activity;import android.os.Bundle;import android.support.v4.view.ViewPager;import android.support.v4.view.ViewPager.OnPageChangeListener;import android.view.View;import android.view.View.OnClickListener;import android.view.Window;import android.widget.ImageButton;import android.widget.TextView;import com.xiaowu.calendar.CalendarCard.OnCellClickListener;public class MainActivity extends Activity implements OnClickListener, OnCellClickListenerprivate ViewPager mViewPager; private int mCurrentIndex = 498private CalendarCard[] mShowViews; private CalendarViewAdapter<CalendarCard> adapter; private SildeDirection mDirection = SildeDirection.NO_SILDE; enum SildeDirection {  RIGHT, LEFT, NO_SILDE; }  private ImageButton preImgBtn, nextImgBtn; private TextView monthText; private ImageButton closeImgBtn; @Override protected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  requestWindowFeature(Window.FEATURE_NO_TITLE);  setContentView(R.layout.activity_main);  mViewPager = (ViewPager) this.findViewById(R.id.vp_calendar);  preImgBtn = (ImageButton) this.findViewById(R.id.btnPreMonth);  nextImgBtn = (ImageButton) this.findViewById(R.id.btnNextMonth);  monthText = (TextView) this.findViewById(R.id.tvCurrentMonth);  closeImgBtn = (ImageButton) this.findViewById(R.id.btnClose); &