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);  preImgBtn.setOnClickListener(this);  nextImgBtn.setOnClickListener(this);  closeImgBtn.setOnClickListener(this);    CalendarCard[] views = new CalendarCard[3];  for (int i = 0; i < 3; i++) {   views[i] = new CalendarCard(this, this);  }  adapter = new CalendarViewAdapter<>(views);  setViewPager(); } private void setViewPager() {  mViewPager.setAdapter(adapter);  mViewPager.setCurrentItem(498);  mViewPager.setOnPageChangeListener(new OnPageChangeListener() {      @Override   public void onPageSelected(int position) {    measureDirection(position);    updateCalendarView(position);       }      @Override   public void onPageScrolled(int arg0, float arg1, int arg2) {       }      @Override   public void onPageScrollStateChanged(int arg0) {       }  }); } @Override public void onClick(View v) {  switch (v.getId()) {  case R.id.btnPreMonth:   mViewPager.setCurrentItem(mViewPager.getCurrentItem() - 1);   break;  case R.id.btnNextMonth:   mViewPager.setCurrentItem(mViewPager.getCurrentItem() + 1);   break;  case R.id.btnClose:   finish();   break;  default:   break;  } } @Override public void clickDate(CustomDate date) {   } @Override public void changeDate(CustomDate date) {  monthText.setText(date.month + "月"); } /**  * 计算方向  *   * @param arg0  */ private void measureDirection(int arg0) {  if (arg0 > mCurrentIndex) {   mDirection = SildeDirection.RIGHT;  } else if (arg0 < mCurrentIndex) {   mDirection = SildeDirection.LEFT;  }  mCurrentIndex = arg0; } // 更新日历视图 private void updateCalendarView(int arg0) {  mShowViews = adapter.getAllItems();  if (mDirection == SildeDirection.RIGHT) {   mShowViews[arg0 % mShowViews.length].rightSlide();  } else if (mDirection == SildeDirection.LEFT) {   mShowViews[arg0 % mShowViews.length].leftSlide();  }  mDirection = SildeDirection.NO_SILDE; } }


用到的资源:

/CustomCalendarView/res/values/color.xml

<?xml version="1.0" encoding="utf-8"?><resources>    <color name="white">#ffffff</color>     <color name="canlendar_text_color">#323232</color></resources>


/CustomCalendarView/res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?><resources>    <string name="app_name">CustomCalendarView</string>    <string name="hello_world">Hello world!</string>    <string name="action_settings">Settings</string>        <string name="sunday"></string>    <string name="monday"></string>    <string name="thesday"></string>    <string name="wednesday"></string>    <string name="thursday"></string>    <string name="friday"></string>    <string name="saturday"></string>    </resources>

/CustomCalendarView/res/values/styles.xml

<resources>    <!--        Base application theme, dependent on API level. This theme is replaced        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.    -->    <style name="AppBaseTheme" parent="android:Theme.Light">        <!--            Theme customizations available in newer API levels can go in            res/values-vXX/styles.xml, while customizations related to            backward-compatibility can go here.        -->    </style>    <!-- Application theme. -->    <style name="AppTheme" parent="AppBaseTheme">        <!-- All customizations that are NOT specific to a particular API-level can go here. -->    </style>        <style name="dateStyle">        <item name="android:layout_width">fill_parent</item>        <item name="android:layout_height">fill_parent</item>        <item name="android:layout_weight">1</item>        <item name="android:gravity">center</item>        <item name="android:textSize">16sp</item>    </style></resources>


源码下载:http://download.csdn.net/detail/wwj_748/8312233













           

给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow

这里写图片描述

猜你喜欢

转载自blog.csdn.net/gfjggtf/article/details/83857730