今天给大家奉上一篇Android 自定义view实现签到功能
程序截图
点击签到
点击签到,实现签到动画,且点亮签到图标。不允许多次签到,大致上功能就是这样
项目结构
大致上就两个核心文件一个自定义view,一个单位换算工具类
工具类
package cn.llwy.com.signin.utils;
import android.content.Context;
import java.util.List;
import cn.llwy.com.signin.bean.SigninBean;
/**
* description: 单位换算工具.
*
* @author 刘明昆.
* @date 2018/8/30.
*/
public class CalcUtils {
public static int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
public static int dp2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* 寻到最大两个值的位置
*/
public static int[] findMax(List steps) {
int[] value = new int[2];
int[] position = new int[2];
int temValue;
int temPosition;
for (int i = 0; i < steps.size(); i++) {
if (steps.get(i).getNumber() > value[1]) {
//比较出大的放到value[0]中
value[1] = steps.get(i).getNumber();
position[1] = i;
}
if (value[1] > value[0]) {
//把最大的放到value[0]中,交换位置
temValue = value[0];
value[0] = value[1];
value[1] = temValue;
temPosition = position[0];
position[0] = position[1];
position[1] = temPosition;
}
}
return position;
}
}
自定义view
package cn.llwy.com.signin.View;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
import cn.llwy.com.signin.R;
import cn.llwy.com.signin.bean.SigninBean;
import cn.llwy.com.signin.utils.CalcUtils;
public class MysigninView extends View {
//初始化签到动画执行时间为300毫秒
private final static int ANIMATION_TIME=300;
//动画执行间隔次数
private final static int ANINMATION_INTERVAL=10;
//线段高度
private float mCompletedHeight = CalcUtils.dp2px(getContext(), 2f);
//图标的宽度
private float mIconWeight = CalcUtils.dp2px(getContext(), 21.5f);
//图标的高度
private float mIconHeight = CalcUtils.dp2px(getContext(), 24f);
//up的宽度
private float mUpWeight = CalcUtils.dp2px(getContext(), 20.5f);
//up的高度
private float mUpHeight = CalcUtils.dp2px(getContext(), 20.5f);
//线段的长度
private float mLineWeight = CalcUtils.dp2px(getContext(), 23f);
//已经完成的图标
private Drawable mCompleteIcon;
//正在进行的图标
private Drawable mAttentionIcon;
//默认的图标
private Drawable mDefaultIcon;
//UP图标
private Drawable mUpIcon;
//图标中心点Y
private float mCenterY;
//线段的左上方Y
private float mLeftY;
//线段的右下方
private float mRightY;
//数据源
private List list;
private int mSinginNum=0;
//图标中心点位置
private List mCircleCenterPointPositionList;
//未完成的线段Paint
private Paint mUnCompletedPaint;
// 完成的线段paint
private Paint mCompletedPaint;
//未完成颜色
private int mUnCompletedLineColor = ContextCompat.getColor(getContext(), R.color.c_999999);
// 天数颜色
private int mUnCompletedTextColor = ContextCompat.getColor(getContext(), R.color.c_cccccc);
//up魅力值颜色
private int mCurrentTextColor = ContextCompat.getColor(getContext(), R.color.c_f7b93c);
//完成的颜色
private int mCompletedLineColor = ContextCompat.getColor(getContext(), R.color.c_41c961);
private Paint mTextNumberPaint;
//是否执行动画
private boolean isAnimation = false;
//记录重绘次数
private int mCount = 0;
// 执行动画线段每次绘制的长度,线段的总长度除以总共执行的时间乘以每次执行的间隔时间
private float mAnimationWeight = (mLineWeight / ANIMATION_TIME) * ANINMATION_INTERVAL;
// 执行动画的位置
private int mPosition;
private int[] mMax;
public MysigninView(Context context) {
this(context, null);
}
public MysigninView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MysigninView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
/**
* init
*/
private void init() {
list = new ArrayList<>();
mCircleCenterPointPositionList = new ArrayList<>();
//未完成文字画笔
mUnCompletedPaint = new Paint();
mUnCompletedPaint.setAntiAlias(true);
mUnCompletedPaint.setColor(mUnCompletedLineColor);
mUnCompletedPaint.setStrokeWidth(2);
mUnCompletedPaint.setStyle(Paint.Style.FILL);
//已完成画笔文字
mCompletedPaint = new Paint();
mCompletedPaint.setAntiAlias(true);
mCompletedPaint.setColor(mCompletedLineColor);
mCompletedPaint.setStrokeWidth(2);
mCompletedPaint.setStyle(Paint.Style.FILL);
//number paint
mTextNumberPaint = new Paint();
mTextNumberPaint.setAntiAlias(true);
mTextNumberPaint.setColor(mUnCompletedTextColor);
mTextNumberPaint.setStyle(Paint.Style.FILL);
mTextNumberPaint.setTextSize(CalcUtils.sp2px(getContext(), 8f));
//已经完成的icon
mCompleteIcon = ContextCompat.getDrawable(getContext(), R.mipmap.signed);
//正在进行的icon
mAttentionIcon = ContextCompat.getDrawable(getContext(), R.mipmap.unsigned);
//未完成的icon
mDefaultIcon = ContextCompat.getDrawable(getContext(), R.mipmap.unsigned);
//UP的icon
mUpIcon = ContextCompat.getDrawable(getContext(), R.mipmap.up);
}
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//图标的中中心Y点
mCenterY = CalcUtils.dp2px(getContext(), 28f) + mIconHeight / 2;
//获取左上方Y的位置,获取该点的意义是为了方便画矩形左上的Y位置
mLeftY = mCenterY - (mCompletedHeight / 2);
//获取右下方Y的位置,获取该点的意义是为了方便画矩形右下的Y位置
mRightY = mCenterY + mCompletedHeight / 2;
//计算图标中心点
mCircleCenterPointPositionList.clear();
//第一个点距离父控件左边14.5dp
float size = mIconWeight / 2 + CalcUtils.dp2px(getContext(), 14.5f);
mCircleCenterPointPositionList.add(size);
for (int i = 1; i < mSinginNum; i++) {
//从第二个点开始,每个点距离上一个点为图标的宽度加上线段的23dp的长度
size = size + mIconWeight + mLineWeight;
mCircleCenterPointPositionList.add(size);
}
}
@SuppressLint("DrawAllocation")
@Override
protected synchronized void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isAnimation) {
drawSign(canvas);
} else {
drawUnSign(canvas);
}
}
/**
* 绘制签到(伴随签到动画)
*/
@SuppressLint("DrawAllocation")
private void drawSign(Canvas canvas) {
for (int i = 0; i < mCircleCenterPointPositionList.size(); i++) {
//绘制线段
float preComplectedXPosition = mCircleCenterPointPositionList.get(i) + mIconWeight / 2;
if (i != mCircleCenterPointPositionList.size() - 1) {
//最后一条不需要绘制
if (list.get(i + 1).getState() == SigninBean.STEP_COMPLETED) {
//下一个是已完成,当前才需要绘制绿色
canvas.drawRect(preComplectedXPosition, mLeftY, preComplectedXPosition + mLineWeight,
mRightY, mCompletedPaint);
} else {
//其余绘制灰色
//当前位置执行动画
if (i == mPosition - 1) {
//绿色开始绘制的地方,
float endX = preComplectedXPosition + mAnimationWeight * (mCount / ANINMATION_INTERVAL);
//绘制绿色
canvas.drawRect(preComplectedXPosition, mLeftY, endX, mRightY, mCompletedPaint);
//绘制灰色
canvas.drawRect(endX, mLeftY, preComplectedXPosition + mLineWeight,
mRightY, mUnCompletedPaint);
} else {
canvas.drawRect(preComplectedXPosition, mLeftY, preComplectedXPosition + mLineWeight,
mRightY, mUnCompletedPaint);
}
}
}
//绘制图标
float currentComplectedXPosition = mCircleCenterPointPositionList.get(i);
Rect rect = new Rect((int) (currentComplectedXPosition - mIconWeight / 2),
(int) (mCenterY - mIconHeight / 2),
(int) (currentComplectedXPosition + mIconWeight / 2),
(int) (mCenterY + mIconHeight / 2));
SigninBean signinBean = list.get(i);
if (i == mPosition && mCount == ANIMATION_TIME) {
//当前需要绘制成绿色了
mCompleteIcon.setBounds(rect);
mCompleteIcon.draw(canvas);
} else {
if (signinBean.getState() == SigninBean.STEP_UNDO) {
mDefaultIcon.setBounds(rect);
mDefaultIcon.draw(canvas);
} else if (signinBean.getState() == SigninBean.STEP_CURRENT) {
mAttentionIcon.setBounds(rect);
mAttentionIcon.draw(canvas);
} else if (signinBean.getState() == SigninBean.STEP_COMPLETED) {
mCompleteIcon.setBounds(rect);
mCompleteIcon.draw(canvas);
}
}
//绘制图标
if (signinBean.getState() == SigninBean.STEP_COMPLETED || (i == mPosition
&& mCount == ANIMATION_TIME)) {
//已经完成了或者是当前动画完成并且需要当前位置需要改变
if (i == mMax[0] || i == mMax[1]) {
//是up的需要橙色
mTextNumberPaint.setColor(mCurrentTextColor);
} else {
//普通完成的颜色
mTextNumberPaint.setColor(mCompletedLineColor);
}
} else {
//还没签到的,颜色均为灰色
mTextNumberPaint.setColor(mUnCompletedLineColor);
}
canvas.drawText("+" + signinBean.getNumber(),
currentComplectedXPosition + CalcUtils.dp2px(getContext(), 2f),
mCenterY - mIconHeight / 2 - CalcUtils.dp2px(getContext(), 0.5f),
mTextNumberPaint);
//绘制UP
if (i == mMax[0] || i == mMax[1]) {
//需要UP才进行绘制
Rect rectUp =
new Rect((int) (currentComplectedXPosition - mUpWeight / 2),
(int) (mCenterY - mIconHeight / 2 - CalcUtils.dp2px(getContext(), 8f) - mUpHeight),
(int) (currentComplectedXPosition + mUpWeight / 2),
(int) (mCenterY - mIconHeight / 2 - CalcUtils.dp2px(getContext(), 8f)));
mUpIcon.setBounds(rectUp);
mUpIcon.draw(canvas);
}
}
//记录重绘次数
mCount = mCount + ANINMATION_INTERVAL;
if (mCount <= ANIMATION_TIME) {
//引起重绘
postInvalidate();
} else {
//重绘完成
isAnimation = false;
mCount = 0;
}
}
/**
* 绘制初始状态的view
*/
@SuppressLint("DrawAllocation")
private void drawUnSign(Canvas canvas) {
for (int i = 0; i < mCircleCenterPointPositionList.size(); i++) {
//绘制线段
float preComplectedXPosition = mCircleCenterPointPositionList.get(i) + mIconWeight / 2;
if (i != mCircleCenterPointPositionList.size() - 1) {
//最后一条不需要绘制
if (list.get(i + 1).getState() == SigninBean.STEP_COMPLETED) {
//下一个是已完成,当前才需要绘制绿色
canvas.drawRect(preComplectedXPosition, mLeftY, preComplectedXPosition + mLineWeight,
mRightY, mCompletedPaint);
} else {
//其余绘制灰色
canvas.drawRect(preComplectedXPosition, mLeftY, preComplectedXPosition + mLineWeight,
mRightY, mUnCompletedPaint);
}
}
//绘制图标
float currentComplectedXPosition = mCircleCenterPointPositionList.get(i);
Rect rect = new Rect((int) (currentComplectedXPosition - mIconWeight / 2),
(int) (mCenterY - mIconHeight / 2),
(int) (currentComplectedXPosition + mIconWeight / 2),
(int) (mCenterY + mIconHeight / 2));
SigninBean signinBean = list.get(i);
if (signinBean.getState() == SigninBean.STEP_UNDO) {
mDefaultIcon.setBounds(rect);
mDefaultIcon.draw(canvas);
} else if (signinBean.getState() == SigninBean.STEP_CURRENT) {
mAttentionIcon.setBounds(rect);
mAttentionIcon.draw(canvas);
} else if (signinBean.getState() == SigninBean.STEP_COMPLETED) {
mCompleteIcon.setBounds(rect);
mCompleteIcon.draw(canvas);
}
//绘制增加的分数数目
if (signinBean.getState() == SigninBean.STEP_COMPLETED) {
//已经完成了
if (i == mMax[0] || i == mMax[1]) {
//是up的需要橙色
mTextNumberPaint.setColor(mCurrentTextColor);
} else {
//普通完成的颜色
mTextNumberPaint.setColor(mCompletedLineColor);
}
} else {
//还没签到的,颜色均为灰色
mTextNumberPaint.setColor(mUnCompletedLineColor);
}
canvas.drawText("+" + signinBean.getNumber(),
currentComplectedXPosition + CalcUtils.dp2px(getContext(), 2f),
mCenterY - mIconHeight / 2 - CalcUtils.dp2px(getContext(), 0.5f),
mTextNumberPaint);
//绘制UP
if (i == mMax[0] || i == mMax[1]) {
//需要UP才进行绘制
Rect rectUp =
new Rect((int) (currentComplectedXPosition - mUpWeight / 2),
(int) (mCenterY - mIconHeight / 2 - CalcUtils.dp2px(getContext(), 8f) - mUpHeight),
(int) (currentComplectedXPosition + mUpWeight / 2),
(int) (mCenterY - mIconHeight / 2 - CalcUtils.dp2px(getContext(), 8f)));
mUpIcon.setBounds(rectUp);
mUpIcon.draw(canvas);
}
}
}
/**
* 设置流程步数
*
* @param lists 流程步数
*/
public void setStepNum(List lists) {
if (list == null) {
return;
}
list = lists;
mSinginNum = list.size();
//找出最大的两个值的位置
mMax = CalcUtils.findMax(lists);
//引起重绘
postInvalidate();
}
/**
* 执行签到动画
*
* @param position 执行的位置
*/
public void startSignAnimation(int position) {
//线条从灰色变为绿色
isAnimation = true;
mPosition = position;
//引起重绘
postInvalidate();
}
}
MainActivity
package cn.llwy.com.signin;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import cn.llwy.com.signin.View.MysigninView;
import cn.llwy.com.signin.bean.SigninBean;
public class MainActivity extends AppCompatActivity {
private MysigninView msigninView;
private TextView txt;
private ArrayList signinBen = new ArrayList<>();
boolean flag=true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Init();
InitData();
InitListtener();
}
private void InitListtener() {
txt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(flag) {
msigninView.startSignAnimation(2);
flag=false;
txt.setBackground(getResources().getDrawable(R.mipmap.signed));
}else{
Toast.makeText(MainActivity.this,"当前已签到,不允许重复签到",Toast.LENGTH_LONG).show();
}
}
});
}
private void InitData() {
signinBen.add(new SigninBean(SigninBean.STEP_COMPLETED, 1));
signinBen.add(new SigninBean(SigninBean.STEP_COMPLETED, 2));
signinBen.add(new SigninBean(SigninBean.STEP_UNDO, 5));
signinBen.add(new SigninBean(SigninBean.STEP_UNDO, 5));
signinBen.add(new SigninBean(SigninBean.STEP_UNDO, 5));
signinBen.add(new SigninBean(SigninBean.STEP_UNDO, 5));
signinBen.add(new SigninBean(SigninBean.STEP_UNDO, 10));
msigninView.setStepNum(signinBen);
}
private void Init() {
msigninView = findViewById(R.id.signin_view);
txt = findViewById(R.id.txt_click);
}
}
最后感谢大家使用宝贵的时间来观看我的博客。谢谢大家!