平常都是用的系统的view,不过有时候需要自己去自定义,这次我也遇到要自定义签到view。
先来看下效果图
第一张是今天没签到的,点击今天就会签到。
主要实现类:
SignUtil:用来获取日历的相关操作。我们先来看下 SignEntity里面的代码SignEntity:定义签到的type的实体类。SignView:具体的绘制代码,自定义的view
public class SignEntity {
private String day;//代表的日期
private int dayType;//用来判断是否签到 0 表示已签到 1未签到 2 今天可以签到 3今天以后的日期 4超出当前月份的日期
public String getDay() {
return day;
}
public void setDay(String day) {
this.day = day;
}
public int getDayType() {
return dayType;
}
public void setDayType(int dayType) {
this.dayType = dayType;
}
}
SignUtil代码
public class SignUtil {
/**
* 获取当前天的下标
*/
public int getTodayPostion(){
int today=getToday();
String s1= getWeek(1);//获取到星期
int upWeek=getUpShowDay(s1);// 把星期转换成天数 上个月要显示几天
return today+upWeek-1;
}
/**
* 获取总共要显示的天数
*/
public int getAllDay(){
int day=getDay();
String s1= getWeek(1);//这个月第一天是星期几 0代表 星期日
String s2=getWeek(day);//这个月最后一天是星期几
int upWeek=getUpShowDay(s1);
int nextWeek=getNextShowDay(s2);//把星期转换成天数
return day+upWeek+nextWeek;
}
/**
* 获取当前月份的天数
*/
public int getDay() {
Calendar a = Calendar.getInstance();
a.set(Calendar.MONTH, getMonth());
int maxDate = a.getActualMaximum(Calendar.DATE);
return maxDate;
}
/**
* 获取指定月份1号是星期几 用来确定上个月要在签到表里面站几个位置
*/
public String getWeek(int day) {
String str=getYear()+"-"+getMonth()+"-"+day;
Date date=null;
SimpleDateFormat dateFm = new SimpleDateFormat("yyyy-MM-dd");
try {
date=dateFm.parse(str);
SimpleDateFormat sdf = new SimpleDateFormat("E");
String s = sdf.format(date);
return s;
} catch (ParseException e) {
e.printStackTrace();
}
return "";
}
/**
* 上月要显示的天数
*/
public int getUpShowDay(String s){
int week=0;
if (s.equals("周一")){
week=1;
}else if (s.equals("周二")){
week=2;
}else if (s.equals("周三")){
week=3;
}else if (s.equals("周四")){
week=4;
}else if (s.equals("周五")){
week=5;
}else if (s.equals("周六")){
week=6;
}else if (s.equals("周日")){
week=0;
}
return week;
}
/**
* 下月要显示的天数
*/
public int getNextShowDay(String s){
int week=0;
if (s.equals("周一")){
week=5;
}else if (s.equals("周二")){
week=4;
}else if (s.equals("周三")){
week=3;
}else if (s.equals("周四")){
week=2;
}else if (s.equals("周五")){
week=1;
}else if (s.equals("周六")){
week=0;
}else if (s.equals("周日")){
week=6;
}
return week;
}
/**
* 获取指定月份的前一个月的天数
*/
public int getUpDay() {
Calendar a = Calendar.getInstance();
a.set(Calendar.MONTH,getMonth()-1);
int maxDate = a.getActualMaximum(Calendar.DATE);
return maxDate;
}
/**
* 获取当月
*/
public int getMonth() {
Calendar cal = Calendar.getInstance();
return cal.get(Calendar.MONTH)+1;
}
/**
* 获取当前年
*/
public int getYear() {
Calendar cal = Calendar.getInstance();
return cal.get(Calendar.YEAR);
}
/**
* 获取当前天
*/
public int getToday(){
Calendar cal = Calendar.getInstance();
return cal.get(Calendar.DATE);
}
/**
* 获取到我默认的数据 可以不用 然后自己写数据
*/
public List<SignEntity> getListData(){
List<SignEntity> listData = new ArrayList<>();
int upDar = getUpDay();//上个月是多少天 用来计算 上个月要显示的时间
int day = getDay();//当前月要显示的天数
String s1= getWeek(1);//获取到星期
String s2=getWeek(day);
int upWeek=getUpShowDay(s1);// 把星期转换成天数 上个月要显示几天
int nextWeek=getNextShowDay(s2);//下个月要显示几天
int today = getToday();//当前天
Random ran = new Random();
for (int i = 1; i <= getAllDay(); i++) {
SignEntity entity = new SignEntity();
if (i <= upWeek) {//上月的日期
entity.setDay("" + (upDar - upWeek + i));//日期是从最后开始的显示的
entity.setDayType(4);
} else if (i <= day + upWeek) {//这个月的日期
if (i < today+upWeek) {
entity.setDayType(ran.nextInt(10) % 2);//随机生成是否签到过
} else if (i == today+upWeek) {
entity.setDayType(2);//只有今天是可以签到的
} else {
entity.setDayType(3);
}
entity.setDay("" + (-upWeek + i));//日期从1开始的
} else {//下个月的日期
entity.setDay("" + (-(upWeek + day) + i));//日期是从1开始的
entity.setDayType(4);
}
listData.add(entity);
}
return listData;
}
}
SignView的代码
还有一个颜色配置public class SignView extends View { private int xian = getResources().getColor(R.color.sign_xian);//线的颜色 private int c0 = getResources().getColor(R.color.sign_c0);//这个颜色对应 SignEntity 对应这个签到类的type private int c1 = getResources().getColor(R.color.sign_c1); private int c2 = getResources().getColor(R.color.sign_c2); private int c3 = getResources().getColor(R.color.sign_c3); private int c4 = getResources().getColor(R.color.sign_c4); private int cbj0 = getResources().getColor(R.color.sign_bj0); private int cbj2 = getResources().getColor(R.color.sign_bj2); private String[] mWeeks = {"日", "一", "二", "三", "四", "五", "六"};//这个显示要和我们的工具类里面星期对应的天数 的方法相同 private int mMargin = 6;//默认二个格子之间的边距是 6 private int mWidth = 60;//设置每个小控件的大小默认是 60 private List<SignEntity> listData; private int size;//字体大小 private Paint pWeek;//用来画上面的星期 private Paint mPaint_Xian;//画笔 来画线 private Rect waitRect;//今天签到的点击事件 的可点击范围 private Rect mBound;//处理文字居中用的 private Paint p0;//SignEntity 对应这个签到类的type private Paint p1; private Paint p2; private Paint p3; private Paint p4; private Paint bj0, bj1, bj2, bj3, bj4; private boolean is_off = true;//控制是否显示边框 默认显示 public SignView(Context context) { this(context, null); } public SignView(Context context, AttributeSet attrs) { this(context, attrs, -1); } public SignView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initData(); initPaint(); initView(context); } private void initData() { size = 24; } private void initPaint() { p0 = new Paint(); p1 = new Paint(); p2 = new Paint(); p3 = new Paint(); p4 = new Paint(); p0.setTextSize(size); p0.setColor(c0); p1.setTextSize(size); p1.setColor(c1); p2.setTextSize(size); p2.setColor(c2); p3.setTextSize(size); p3.setColor(c3); p4.setTextSize(size); p4.setColor(c4); bj0 = new Paint(); bj1 = new Paint(); bj2 = new Paint(); bj3 = new Paint(); bj4 = new Paint(); bj0.setAntiAlias(true); bj0.setColor(cbj0); bj0.setStrokeWidth(2); bj0.setStyle(Paint.Style.FILL); bj2.setAntiAlias(true); bj2.setColor(cbj2); bj2.setStrokeWidth(1); bj2.setStyle(Paint.Style.STROKE); pWeek = new Paint(); pWeek.setTextSize(24); pWeek.setColor(getResources().getColor(R.color.sidn_week)); mBound = new Rect(); mPaint_Xian = new Paint(); mPaint_Xian.setStrokeWidth(1); mPaint_Xian.setColor(xian); } private void initView(Context context) { // 将DIP单位默认值转为PX DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int ww = 0; int hh = 0; int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width; int height; if (widthMode == MeasureSpec.EXACTLY) {// width = widthSize; Log.i("gdx", "wws " + width); ww = width; } else { width = 7 * mWidth; } if (heightMode == MeasureSpec.EXACTLY) { height = heightSize; hh = height; } else { height = listData.size() / 7 * mWidth + mWidth; } setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { if (listData.size() == 0) return; if (is_off) { mPaint_Xian.setStyle(Paint.Style.STROKE); canvas.drawRect(1, 0, getWidth(), getHeight() - 1, mPaint_Xian); } mDrawWeek(canvas); mDrawText(canvas); } private void mDrawWeek(Canvas canvas) { for (int i = 0; i < 7; i++) { pWeek.getTextBounds(mWeeks[i], 0, mWeeks[i].length(), mBound); float textWidth = pWeek.measureText(mWeeks[i]);//文字自己的宽度 float startX = i * mWidth + mWidth / 2 - textWidth / 2;//宽度居中 FontMetricsInt fm = pWeek.getFontMetricsInt(); int startY = mWidth / 2 - fm.descent + (fm.bottom - fm.top) / 2;//高度居中 这个公式是网上找的 canvas.drawText(mWeeks[i], startX, startY, pWeek);//画星期 } mPaint_Xian.setStyle(Paint.Style.FILL); canvas.drawLine(0, mWidth, getWidth(), mWidth, mPaint_Xian);//横线 } private void mDrawText(Canvas canvas) { int count = 0;//取listdata里面的数据 for (int i = 1; i <= listData.size() / 7; i++) { for (int j = 0; j < 7; j++) { int x = j * mWidth + mWidth / 3; int y = i * mWidth + mWidth * 3 / 4; int bjX = j * mWidth + mWidth / 2; int bjY = i * mWidth + mWidth / 2; switch (listData.get(count).getDayType()) { case 0: canvas.drawCircle(bjX, bjY, mWidth / 2 - mMargin, bj0);//背景 mDrawTexts(canvas, listData.get(count).getDay(), j, i, count, p0); break; case 1: mDrawTexts(canvas, listData.get(count).getDay(), j, i, count, p1); break; case 2: waitRect = new Rect(j * mWidth, i * mWidth, j * mWidth + mWidth, i * mWidth + mWidth); canvas.drawCircle(bjX, bjY, mWidth / 2 - mMargin, bj2); mDrawTexts(canvas, listData.get(count).getDay(), j, i, count, p2); break; case 3: mDrawTexts(canvas, listData.get(count).getDay(), j, i, count, p3); break; case 4: mDrawTexts(canvas, listData.get(count).getDay(), j, i, count, p4); break; } count++; } } } private void mDrawTexts(Canvas canvas, String str, int width, int height, int count, Paint p) {//画日期 并且居中 p.getTextBounds(listData.get(count).getDay(), 0, listData.get(count).getDay().length(), mBound); float textWidth = p.measureText(listData.get(count).getDay());//文字自己的宽度 float startX = width * mWidth + mWidth / 2 - textWidth / 2;//宽度居中 先加上前面有几个 mwidth 然后 + mwidth的一半 - 自己的宽度 FontMetricsInt fm = p.getFontMetricsInt(); int startY = height * mWidth + mWidth / 2 - fm.descent + (fm.bottom - fm.top) / 2;//高度居中 这个公式是网上找的 高度差不多也在这样的算法 canvas.drawText(listData.get(count).getDay(), startX, startY, p); } public void setListData(List<SignEntity> listData) { if (listData != null) this.listData = listData; else this.listData = new ArrayList<>(); } public void notifyDataSetChanged() { invalidate(); }//重绘 private OnTodayClickListener onTodayClickListener; public void setOnTodayClickListener(OnTodayClickListener onTodayClickListener) { this.onTodayClickListener = onTodayClickListener; } public interface OnTodayClickListener { void onTodayClick(); } /** * 是否设置边框 */ public void setBober(boolean b) { is_off = b; } /** * 设置字体大小 */ public void setTextSize(int size) { this.size = size; } /** * 设置每个格子的大小 */ public void setWidthSize(int width) {// mWidth = width; } /** * 设置每个格子之间的边距 */ public void setWidthMargin(int margin) { mMargin = margin; } @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { float x = event.getX(); float y = event.getY(); SignUtil signUtil = new SignUtil(); if (waitRect != null) {//用来给今天签到的 if (listData.get(signUtil.getTodayPostion()).getDayType() == 2) {//必须要没签过到才可以 if (x >= waitRect.left && x <= waitRect.right && y <= waitRect.bottom && y >= waitRect.top) {//判断范围 if (onTodayClickListener != null) { onTodayClickListener.onTodayClick(); } } } } } return true; } }
<color name="sidn_week">#1d3048</color>
<color name="sign_c0">#FFF</color>
<color name="sign_c1">#4d6687</color>
<color name="sign_c2">#ee4d4e</color>
<color name="sign_c3">#84B0E8</color>
<color name="sign_c4">#cdcfd1</color>
<color name="sign_bj0">#5d9ae5</color>
<color name="sign_bj2">#00CC00</color>
<color name="sign_xian" >#e1e1e1</color>
使用SignView
MainActivity使用<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.qiang.demo.activity.MainActivity"> <com.qiang.demo.ui.SignView android:id="@+id/signview1" android:layout_centerInParent="true" android:layout_width="wrap_content" //这里有一个问题就是这个宽度只能设置成 wrap_content 高度也是 android:layout_margin="10dp" android:layout_height="wrap_content" /> </RelativeLayout>
public class MainActivity extends Activity {
private SignView signview;
private List<SignEntity> listData;//初始化数据
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
signview = (SignView) findViewById(R.id.signview1);
signview.setListData(listData);
signview.setWidthSize(60);
// signview.setWidthMargin(6);二个格子之间默认的边距
// signview.setTextSize(); 字体大小
// signview.setBober(true); 是否显示边框
signview.setOnTodayClickListener(new SignView.OnTodayClickListener() {
@Override
public void onTodayClick() {
SignUtil signUtil = new SignUtil();
listData.get(signUtil.getTodayPostion()).setDayType(0);
signview.notifyDataSetChanged();
;
}
});
}
private void initData() {
SignUtil signUtil = new SignUtil();
listData = signUtil.getListData();//我在工具类初始化的数据 可以根据自己的去修改
}
}
里面注释很详细,可以根据自己的需求去修改