如下图:
之前写过一个可以双向滑动的和这个view的类似,那个滑动的view处理的onTouch事件,以及判断了我们应该滑动哪个小球,有兴趣的可以看下之前的连接
双向滑动的SeekBarhttp://blog.csdn.net/givemeacondom/article/details/52397589
这个就比较简单了,都是静态的绘制,唯一的交互就是UI中的签到按钮,点击一次通知自定义view绘制;
上面就是实现的思路,我们一步步看下代码,最后会奉上源代码的下载链接;
<declare-styleable name="SignInView">
<attr name="sign_in_bg_clor" format="color" />
<attr name="sign_in_pb_clor" format="color" />
<attr name="sign_in_check_clor" format="color" />
<attr name="sign_in_text_clor" format="color" />
<attr name="sign_in_text_size" format="dimension" />
declare-styleable>
private void initAttrs(Context context, AttributeSet attrs, int defStyleAttr) {
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SignInView, defStyleAttr, R.style.def_sign_in_style);
int indexCount = typedArray.getIndexCount();
for (int i = 0; i < indexCount; i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.SignInView_sign_in_bg_clor:
signInBgColor = typedArray.getColor(attr, Color.BLACK);
break;
case R.styleable.SignInView_sign_in_pb_clor:
signInPbColor = typedArray.getColor(attr, Color.BLACK);
break;
case R.styleable.SignInView_sign_in_check_clor:
signInCheckColor = typedArray.getColor(attr, Color.BLACK);
break;
case R.styleable.SignInView_sign_in_text_clor:
singInTextColor = typedArray.getColor(attr, Color.BLACK);
break;
case R.styleable.SignInView_sign_in_text_size:
singInTextSize = typedArray.getDimensionPixelSize(attr, 0);
break;
}
}
typedArray.recycle();
}
根据需求我们的这个view默认充满屏幕,所以只需要处理height的测量模式即可
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.e("--TAG---", "onMeasure--->>");
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int measureHeight;
if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
measureHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEF_HEIGHT, getResources().getDisplayMetrics());
heightMeasureSpec = MeasureSpec.makeMeasureSpec(measureHeight, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
Log.e("--TAG---", "onSizeChanged--->>");
viewPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEF_PADDING, getResources().getDisplayMetrics());
int textMarginTop = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, TEXT_MARGIN_TOP, getResources().getDisplayMetrics());
// 在回调中获取view的宽度和高度,并计算小球的半径
viewWidth = w;
viewHeight = h;
signInBallRadio = (int) (viewHeight * SIGN_IN_BALL_SCALE / 2);
signRectHeight = (int) (signInBallRadio * SIGN_BG_RECT_SCALE);
// 矩形的背景区域,
signInBgRectF = new RectF(viewPadding + signInBallRadio, viewHeight * SECTION_SCALE - signInBallRadio - signRectHeight, viewWidth - viewPadding - signInBallRadio, viewHeight * SECTION_SCALE - signInBallRadio);
circleY = (int) (signInBgRectF.top + signRectHeight / 2);
descY = (int) (viewHeight * SECTION_SCALE) + signInBallRadio + textMarginTop;
// 核心代码就一个方法
calculateCirclePoints(viewData);
progressRectF = signInPbRectFs.get(0);
}
private void calculateCirclePoints(List viewData) {
//获取数据源中分开的份数,比如一周7天,中间的小段是不是有6段,所以是size-1
if (null != viewData) {
int intervalSize = viewData.size() - 1;
// 计算一份的长度,view的宽度减去两边的padding,以及总共小球的半径的综合然后除以,一共多少份
int onePiece = (viewWidth - 2 * viewPadding - signInBallRadio * 2 * viewData.size()) / intervalSize;
// 计算每个小球的坐标,x 和y,
for (int i = 0; i < viewData.size(); i++) {
// 这里其实很简单,就是一个小小的规律,当前的小球坐标
// 第一个小球 0个onePiece + 1个小球半径
// 第二个小球 1个onePiece + 3个小球半径
// 第三个小球 2个onePiece + 5个小球半径
// ####........求第N个的距离......?是不是一个送分题
// 第N个的公式: i * onePiece + ((i + 1) * 2 - 1)*R
Point circleP = new Point(viewPadding + i * onePiece + ((i + 1) * 2 - 1) * signInBallRadio, circleY);
Point descP = new Point((int) (viewPadding + i * onePiece + ((i + 1) * 2 - 1) * signInBallRadio - signInTextPaint.measureText(viewData.get(i)) / 2), descY);
// 计算对勾的路径
Path signInPath = new Path();
signInPath.moveTo(circleP.x - signInBallRadio / 2, circleP.y);
signInPath.lineTo(circleP.x, circleP.y + signInBallRadio / 2);
signInPath.lineTo(circleP.x + signInBallRadio / 2, circleP.y - signInBallRadio + signInBallRadio / 2);
RectF signInPbRectF = new RectF(viewPadding + signInBallRadio, viewHeight * SECTION_SCALE - signInBallRadio - signRectHeight, circleP.x, viewHeight * SECTION_SCALE - signInBallRadio);
// 保存计算的坐标;
signInPaths.add(signInPath);
circlePoints.add(circleP);
descPoints.add(descP);
signInPbRectFs.add(signInPbRectF);
}
}
}
/**
* 属性动画的形式绘制进度条,暂时未启用,效果预览可以的
*
* @param canvas
*/
private void drawSignInPbRectWithAnim(final Canvas canvas) {
if (isNeedReturn()) {
return;
}
final RectF targetRectF = signInPbRectFs.get(currentSignTag);
RectF beforeRectF;
if (currentSignTag >= 1) {
beforeRectF = signInPbRectFs.get(currentSignTag - 1);
progressRectF.left = beforeRectF.left;
}
progressRectF.right = targetRectF.right * persent;
canvas.drawRect(progressRectF, signInPbPaint);
}
// 绘制签到进度的方法,如果想看动画的可以自己把ondraw中的方法替换成上面的即可
private void drawSignInPbRectNoAnim(final Canvas canvas) {
if (isNeedReturn()) {
return;
}
canvas.drawRect(signInPbRectFs.get(currentSignTag), signInPbPaint);
}
private boolean isNeedReturn() {
return currentSignTag < 0 || currentSignTag >= viewData.size();
}
// 绘制选中的签到状态下的圆圈以及白色的对勾路径
private void drawSingInCheckCircle(Canvas canvas) {
if (isNeedReturn()) {
return;
}
for (int i = -1; i < currentSignTag; i++) {
Point p = circlePoints.get(i + 1);
Path path = signInPaths.get(i + 1);
canvas.drawCircle(p.x, p.y, signInBallRadio, signInPbPaint);
canvas.drawPath(path, signInCheckPaint);
}
}
// 绘制签到信息描述
private void drawSignDesc(Canvas canvas) {
for (int i = 0; i < viewData.size(); i++) {
Point p = descPoints.get(i);
canvas.drawText(viewData.get(i), p.x, p.y, signInTextPaint);
}
}
// 绘制白色的未签到背景
private void drawSignInNormalCircle(Canvas canvas) {
for (Point p : circlePoints) {
canvas.drawCircle(p.x, p.y, signInBallRadio, signInBgPaint);
}
}
private void drawSignInBgRect(Canvas canvas) {
canvas.drawRect(signInBgRectF, signInBgPaint);
}