之前学过的东西隔了很长一段时间现在又忘了,原来已经不打算做码农了,但是好像没有什么选择了,先试试看自己合不合适做程序猿吧。今天来学一下,自定义View。我们看看今天要做的东西
这是我在某个地方看到的一张图,不知道在哪个地方了。我们先来分析一下,如果画出类似于这种效果。首先确定当前月的第一天是星期几,以及当前月的天数。然后用一个循环来画每一个日期,当然这要计算每个日期所在的位置。宽度是平均分很容易,高度的话看个人想要的了,我这里是总高度的1/6。判断是否和当前日期相等,如果相等则画出背景。接着便是用一个Map集合来存储每个日期所在的位置,以便用于监听是否点击了日期,然后做出相应的重绘。要监听事件,所以重写了onTounch方法。要注意的一点是绘制文字是基于文字的底部的,所以应该加上字体高度的一半。
- 自定义属性。分别是日期的背景颜色,日期表头的背景颜色,正常的日期字体颜色,当前日期的颜色,当前日期的背景颜色,选择的日期的背景颜色,日期的字体大小,日期表头的字体大小。
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="SunshineView"> <attr name="android:background"/> <attr name="titleBackgroundColor" format="color"/> <attr name="normalDateColor" format="color"/> <attr name="currentDateColor" format="color"/> <attr name="currentDateBackgroundColor" format="color"/> <attr name="selectedDateBackgroundColor" format="color"/> <attr name="dateSize" format="dimension"/> <attr name="titleSize" format="dimension"/> </declare-styleable> </resources>
- 获取自定义的属性
//默认的字体大小 defaultSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 19, getResources().getDisplayMetrics()); /** * 获取自定义属性 */ TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.SunshineView,defStyleAttr,0); int attrCount = typedArray.getIndexCount(); for(int i=0; i<attrCount; i++) { int attr = typedArray.getIndex(i); switch (attr){ case R.styleable.SunshineView_android_background: background = typedArray.getColor(attr,Color.parseColor("#ffec00")); break; case R.styleable.SunshineView_titleBackgroundColor: titleBackgroundColor = typedArray.getColor(attr,0xff0000); break; case R.styleable.SunshineView_normalDateColor: normalColor = typedArray.getColor(attr, 0x232323); break; case R.styleable.SunshineView_currentDateColor: currentColor = typedArray.getColor(attr, 0xffffff); break; case R.styleable.SunshineView_currentDateBackgroundColor: currentBgColor = typedArray.getColor(attr, 0xFFDA4336); break; case R.styleable.SunshineView_selectedDateBackgroundColor: selectedBgColor = typedArray.getColor(attr, 0x7fd2d2d2); break; case R.styleable.SunshineView_dateSize: dateSize = typedArray.getDimensionPixelSize(attr, defaultSize); break; case R.styleable.SunshineView_titleSize: titleSize = typedArray.getDimensionPixelSize(attr,defaultSize); break; } } //记得要调用,回收原来的属性 typedArray.recycle();
- 初始化画笔
private void initPaint(){ //星期几标题字体大小 titleSize = (int) 1.5*defaultSize; mTitlePaint = new TextPaint(); mTitlePaint.setColor(Color.parseColor("#ffffff")); mTitlePaint.setTextSize(titleSize); mTitlePaint.setStyle(Paint.Style.STROKE); mTitlePaint.setTextAlign(Paint.Align.CENTER); mTitlePaint.setFlags(Paint.ANTI_ALIAS_FLAG); mTitlePaint.setAntiAlias(true); mTitlePaint.getTextBounds("22", 0, 2, new Rect()); //日期的字体大小 mPaint = new TextPaint(); mPaint.setColor(normalColor); mPaint.setTextSize(dateSize); mPaint.setStyle(Paint.Style.STROKE); mPaint.setFlags(Paint.ANTI_ALIAS_FLAG); mPaint.setTextAlign(Paint.Align.CENTER); mPaint.setAntiAlias(true); mPaint.getTextBounds("22", 0, 2, new Rect()); }
- 测量View的大小
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); /** * 设置宽度 */ int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); //match_parent if (widthMode == MeasureSpec.EXACTLY){ width = widthSize; } else if(widthMode == MeasureSpec.AT_MOST){ width = getResources().getDisplayMetrics().widthPixels - getPaddingLeft() - getPaddingRight(); } /*** * 设置高度 */ int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); //match_parent if(heightMode == MeasureSpec.EXACTLY){ height = heightSize; }else if(heightMode == MeasureSpec.AT_MOST){ height = getResources().getDisplayMetrics().heightPixels - getPaddingTop() - getPaddingBottom(); } setMeasuredDimension(width, height); }
- 接着就是开始画了
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); drawTitle(canvas); drawDate(canvas); } /** * 画表头 * @param canvas */ private void drawTitle(Canvas canvas){ float titleHeight = mTitlePaint.measureText("二"); //画背景 Paint bgPaint = new Paint(); bgPaint.setColor(titleBackgroundColor); bgPaint.setStyle(Paint.Style.FILL); canvas.drawRect(getPaddingLeft(), getPaddingTop(), width, height / 7, bgPaint); //日期表头 for(int i=0; i<7; i++){ canvas.drawText(mTitles[i],(2*i+1)*width/14,height/14+titleHeight/2,mTitlePaint); } } /** * 画日期 * @param canvas */ private void drawDate(Canvas canvas){ /** * 画背景 */ Paint paint = new Paint(); paint.setColor(background); paint.setTextSize(titleSize); paint.setStyle(Paint.Style.STROKE); paint.setFlags(Paint.ANTI_ALIAS_FLAG); paint.setAntiAlias(true); canvas.drawRect(getPaddingLeft(),height /7,getPaddingRight(),height,paint); //当前的日期 Calendar calendar = Calendar.getInstance(); int mCurrentDay = calendar.get(Calendar.DAY_OF_MONTH); //当前天是星期几 int currentDayIndex = calendar.get(Calendar.DAY_OF_WEEK)-1; //获取当月的第一天是星期几 int firstDayOfMonthIndex = currentDayIndex - (mCurrentDay-1)%7; if(firstDayOfMonthIndex<=0){ firstDayOfMonthIndex = firstDayOfMonthIndex + 7; } //标记所描绘的日期是星期几 int dayWeekIndex = firstDayOfMonthIndex; //画到了第几行 int rowIndex = 1; int dateHeight = (int) (mPaint.getFontMetrics().ascent+mPaint.getFontMetrics().descent); int paddingTop = height/14+height /7; int monthDays = calendar.getActualMaximum(Calendar.DAY_OF_MONTH); for(int i=1; i<=monthDays; i++){ if(dayWeekIndex>7){ dayWeekIndex=1; rowIndex= rowIndex+1; } //先计算这天所要显示的位置 int positionX = (2*dayWeekIndex-1)*width/14; int positionY = (2*rowIndex-1)*height/12 + dateHeight/2 + paddingTop; //判断日期是否是当前日期,是的话则画背景的圆,并且设置相应的日期颜色 if(i!=mCurrentDay){ //选择其它日期时的背景颜色 if(i==Integer.parseInt(selectedDate)){ drawCircle(canvas, positionX, positionY+dateHeight/2, height / 13, selectedBgColor); mPaint.setColor(Color.parseColor("#232323")); } }else { drawCircle(canvas, positionX, positionY+dateHeight/2, height / 13, currentBgColor); mPaint.setColor(currentColor); } //画日期和重置字体颜色 canvas.drawText(i+"",positionX,positionY,mPaint); mPaint.setColor(Color.parseColor("#232323")); //星期几递增1 dayWeekIndex++; //将日期的信息添加进Map,用来监听点击事件的触发条件 String dateKey = rowIndex+"_"+dayWeekIndex; datePositionMap.put(dateKey, i + ""); } } /** * 画圆形的背景 * @param canvas * @param positionX * @param positionY * @param radius * @param color */ private void drawCircle(Canvas canvas,int positionX, int positionY, int radius,int color){ mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(color); canvas.drawCircle(positionX, positionY, radius,mPaint); }
- 事件监听
/**用来监听点击事件 * * @param event * @return */ @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); if(action==MotionEvent.ACTION_DOWN){ selectedDate = selectedDate(event); if(selectedDate!=null){ invalidate(); }else { selectedDate="0"; } } return true; } /** * 处理单击事件,重绘选中的日期的背景 * @param event * @return */ private String selectedDate(MotionEvent event){ int paddingTop = height/14+height /7; int column = (int)Math.ceil(event.getX()/(width/7))+1; int row = (int)Math.ceil((event.getY()-paddingTop)/(height/6)); String key = row+"_"+column; String clickDay = datePositionMap.get(key); return clickDay; }
- 布局文件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:sunshine="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.sharemebook.myapplication.SunshineView android:layout_width="match_parent" android:layout_height="282dp" android:background="#ffec00" sunshine:titleBackgroundColor="#ff0000" sunshine:dateSize="15sp" sunshine:normalDateColor="#232323" sunshine:currentDateColor="#ffffff" sunshine:selectedDateBackgroundColor="#7fd2d2d2" sunshine:currentDateBackgroundColor="#ff0000"/> </LinearLayout>
- 效果图
完整的代码
/**
* Created by Sanisy on 2016/3/22.
*/
public class SunshineView extends View{
private Paint mTitlePaint;
private Paint mPaint;
private int normalColor;
private int currentColor;
private int currentBgColor;
private int selectedBgColor;
private int dateSize;
private int background;
//星期几标签字体设定的大小和默认的字体大小
private int titleBackgroundColor;
private int titleSize;
private int defaultSize;
private String mTitles[] ={"一","二","三","四","五","六","日"};
//日历整体的宽度和高度
private int width;
private int height;
//被点击的日期
private String selectedDate = "0";
private Map<String,String> datePositionMap = new HashMap<>();
public SunshineView(Context context) {
this(context, null);
}
public SunshineView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SunshineView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//默认的字体大小
defaultSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 19, getResources().getDisplayMetrics());
/**
* 获取自定义属性
*/
TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.SunshineView,defStyleAttr,0);
int attrCount = typedArray.getIndexCount();
for(int i=0; i<attrCount; i++) {
int attr = typedArray.getIndex(i);
switch (attr){
case R.styleable.SunshineView_android_background:
background = typedArray.getColor(attr,Color.parseColor("#ffec00"));
break;
case R.styleable.SunshineView_titleBackgroundColor:
titleBackgroundColor = typedArray.getColor(attr,0xff0000);
break;
case R.styleable.SunshineView_normalDateColor:
normalColor = typedArray.getColor(attr, 0x232323);
break;
case R.styleable.SunshineView_currentDateColor:
currentColor = typedArray.getColor(attr, 0xffffff);
break;
case R.styleable.SunshineView_currentDateBackgroundColor:
currentBgColor = typedArray.getColor(attr, 0xFFDA4336);
break;
case R.styleable.SunshineView_selectedDateBackgroundColor:
selectedBgColor = typedArray.getColor(attr, 0x7fd2d2d2);
break;
case R.styleable.SunshineView_dateSize:
dateSize = typedArray.getDimensionPixelSize(attr, defaultSize);
break;
case R.styleable.SunshineView_titleSize:
titleSize = typedArray.getDimensionPixelSize(attr,defaultSize);
break;
}
}
//记得要调用,回收原来的属性
typedArray.recycle();
initPaint();
}
private void initPaint(){
//星期几标题字体大小
titleSize = (int) 1.5*defaultSize;
mTitlePaint = new TextPaint();
mTitlePaint.setColor(Color.parseColor("#ffffff"));
mTitlePaint.setTextSize(titleSize);
mTitlePaint.setStyle(Paint.Style.STROKE);
mTitlePaint.setTextAlign(Paint.Align.CENTER);
mTitlePaint.setFlags(Paint.ANTI_ALIAS_FLAG);
mTitlePaint.setAntiAlias(true);
mTitlePaint.getTextBounds("22", 0, 2, new Rect());
//日期的字体大小
mPaint = new TextPaint();
mPaint.setColor(normalColor);
mPaint.setTextSize(dateSize);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setAntiAlias(true);
mPaint.getTextBounds("22", 0, 2, new Rect());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/**
* 设置宽度
*/
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
//match_parent
if (widthMode == MeasureSpec.EXACTLY){
width = widthSize;
} else if(widthMode == MeasureSpec.AT_MOST){
width = getResources().getDisplayMetrics().widthPixels - getPaddingLeft() - getPaddingRight();
}
/***
* 设置高度
*/
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//match_parent
if(heightMode == MeasureSpec.EXACTLY){
height = heightSize;
}else if(heightMode == MeasureSpec.AT_MOST){
height = getResources().getDisplayMetrics().heightPixels - getPaddingTop() - getPaddingBottom();
}
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawTitle(canvas);
drawDate(canvas);
}
/**
* 画表头
* @param canvas
*/
private void drawTitle(Canvas canvas){
float titleHeight = mTitlePaint.measureText("二");
//画背景
Paint bgPaint = new Paint();
bgPaint.setColor(titleBackgroundColor);
bgPaint.setStyle(Paint.Style.FILL);
canvas.drawRect(getPaddingLeft(), getPaddingTop(), width, height / 7, bgPaint);
//日期表头
for(int i=0; i<7; i++){
canvas.drawText(mTitles[i],(2*i+1)*width/14,height/14+titleHeight/2,mTitlePaint);
}
}
/**
* 画日期
* @param canvas
*/
private void drawDate(Canvas canvas){
/**
* 画背景
*/
Paint paint = new Paint();
paint.setColor(background);
paint.setTextSize(titleSize);
paint.setStyle(Paint.Style.STROKE);
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
paint.setAntiAlias(true);
canvas.drawRect(getPaddingLeft(),height /7,getPaddingRight(),height,paint);
//当前的日期
Calendar calendar = Calendar.getInstance();
int mCurrentDay = calendar.get(Calendar.DAY_OF_MONTH);
//当前天是星期几
int currentDayIndex = calendar.get(Calendar.DAY_OF_WEEK)-1;
//获取当月的第一天是星期几
int firstDayOfMonthIndex = currentDayIndex - (mCurrentDay-1)%7;
if(firstDayOfMonthIndex<=0){
firstDayOfMonthIndex = firstDayOfMonthIndex + 7;
}
//标记所描绘的日期是星期几
int dayWeekIndex = firstDayOfMonthIndex;
//画到了第几行
int rowIndex = 1;
int dateHeight = (int) (mPaint.getFontMetrics().ascent+mPaint.getFontMetrics().descent);
int paddingTop = height/14+height /7;
int monthDays = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
for(int i=1; i<=monthDays; i++){
if(dayWeekIndex>7){
dayWeekIndex=1;
rowIndex= rowIndex+1;
}
//先计算这天所要显示的位置
int positionX = (2*dayWeekIndex-1)*width/14;
int positionY = (2*rowIndex-1)*height/12 + dateHeight/2 + paddingTop;
//判断日期是否是当前日期,是的话则画背景的圆,并且设置相应的日期颜色
if(i!=mCurrentDay){
//选择其它日期时的背景颜色
if(i==Integer.parseInt(selectedDate)){
drawCircle(canvas, positionX, positionY+dateHeight/2, height / 13, selectedBgColor);
mPaint.setColor(Color.parseColor("#232323"));
}
}else {
drawCircle(canvas, positionX, positionY+dateHeight/2, height / 13, currentBgColor);
mPaint.setColor(currentColor);
}
//画日期和重置字体颜色
canvas.drawText(i+"",positionX,positionY,mPaint);
mPaint.setColor(Color.parseColor("#232323"));
//星期几递增1
dayWeekIndex++;
//将日期的信息添加进Map,用来监听点击事件的触发条件
String dateKey = rowIndex+"_"+dayWeekIndex;
datePositionMap.put(dateKey, i + "");
}
}
/**
* 画圆形的背景
* @param canvas
* @param positionX
* @param positionY
* @param radius
* @param color
*/
private void drawCircle(Canvas canvas,int positionX, int positionY, int radius,int color){
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(color);
canvas.drawCircle(positionX, positionY, radius,mPaint);
}
/**用来监听点击事件
*
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
if(action==MotionEvent.ACTION_DOWN){
selectedDate = selectedDate(event);
if(selectedDate!=null){
invalidate();
}else {
selectedDate="0";
}
}
return true;
}
/**
* 处理单击事件,重绘选中的日期的背景
* @param event
* @return
*/
private String selectedDate(MotionEvent event){
int paddingTop = height/14+height /7;
int column = (int)Math.ceil(event.getX()/(width/7))+1;
int row = (int)Math.ceil((event.getY()-paddingTop)/(height/6));
String key = row+"_"+column;
String clickDay = datePositionMap.get(key);
return clickDay;
}
}