0%

Android-Custom-View-two

前言

魅族手机的闹钟应用中有个倒计时,这个控件还是蛮有趣的。左边是魅族闹钟,右边是我们最终实现的效果,虽然有些细节还需优化,不过基本上已经达到了想要的效果,我们先来就来看看如何实现吧。

图1
图2

分析

确定宽高

对一个Android自定义控件来说,一般都经过三个步骤

  • onLayout()
  • onMeasure()
  • onDraw()

onLayout明确子控件在父控件中的位置(本控件不需要重写),onMeasure是确定控件的大小(宽、高),而onDraw是我们重点关注的方法,我们需要在这个方法中写入显示View的逻辑代码。
对于本控件,控件的高度 应该等于细线的高度(mLineHeight)加上数字的高度(mFontHeight),当然为了好看,中间需要设上一些边距(mPadding),因此本控件的高度应该为 mFontHeight + mLineHeight + 10 + mPadding,测量代码如下

1
2
3
4
5
6
7
8
9
10
11
private int measureHeight(int heightMeasureSpec) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
switch (mode) {
case MeasureSpec.EXACTLY:
return size ;
case MeasureSpec.AT_MOST:
return Math.min(size, mFontHeight + mLineHeight + 10 + mPadding) ;
}
return size ;
}

同样地,控件的宽度其实就是0~1000的间隔,测量代码如下

1
2
3
4
5
6
7
8
9
10
11
12
private int measureWidth(int widthMeasureSpec) {
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
switch (mode) {
case MeasureSpec.EXACTLY:
return size ;
case MeasureSpec.AT_MOST:
int result = getPaddingLeft() + mContentWidth + getPaddingRight() ;
return Math.min(size, result) ;
}
return size ;
}

画刻度尺

重点在于刻度尺的计算。思路是先draw上头的数字,然后再draw下边的线条,判断位置确定是否需要draw上头的数字即可。其实就是坐标的计算。代码如下

1
2
3
4
5
6
7
8
9
10
int startX = mPadding;
int stopX = mPadding;
int stopY =mHeight - mPadding;
for (int i = 0 ; i<=mContentWidth ; i += mFontWidth)
if (i % (mFontWidth *10) == 0) {
canvas.drawLine(startX + i, mFontHeight + mPadding + 5 , stopX + i, stopY, mTextPaint);
canvas.drawText(i + "", startX + i, mFontHeight + mPadding, mTextPaint);
} else if (i % mFontWidth == 0) {
canvas.drawLine(startX + i, mFontHeight + mPadding + 10, stopX + i, stopY, mPaint);
}

让View动起来

Android本身提供了移动View的API,因此让View动起来也是不难的。两种思路

  • 监听Touch事件,当Touch坐标变化时,计算坐标位置,不断调用scrollTo(x,0)达到变换坐标的目的
  • 监听Touch事件,记录上次横坐标和本次横坐标的差值,然后调用scrollBy(delta, 0) 即可移动

其实两种方法本质上都是一样的。ScrollBy其实也是调用了scrollTo方法。本文采用方法二。其代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = (int) event.getX();
break;
case MotionEvent.ACTION_MOVE:
int x = (int) event.getX();
int deltaX = x - mLastX;
scrollBy(-deltaX, 0);
mLastX = x;
break;
}
return true;
}

当然了,我们是不能让View无限移动的,因此需要重写scrollBy方法,限制View不能超过边界。
代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void scrollBy(int x, int y) {
super.scrollBy(x, y);
if (x < 0) { // drag to left
if (getScrollX() < -getCenter() + mPadding) {
scrollTo(-getCenter() + mPadding, 0);
}
} else
if (x >0) { // drag to right
if (mContentWidth - getScrollX() + x < getCenter()) {
scrollTo(mContentWidth - getCenter() + mFontWidth, 0);
}
}
}

当超过边界时,直接调用scrollTo,让View停留在特定的位置即可。需要注意的一点是,View往左滑动时,ScrollX的值是负的。

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package com.nancyyihao.demo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;
public class RulerView extends View {
private static final String TAG = RulerView.class.getSimpleName() ;
private TextPaint mTextPaint;
private Paint mPaint ;
private int mWidth;
private int mHeight;
private int mPadding = 10;
private Scroller mScroller ;
private int mLastX;
private int mContentWidth = 1000;
private int mLineHeight = 50;
private int mFontHeight;
private int mFontWidth = 10;
private onValueChangedListener mValueChangedListener;
public interface onValueChangedListener {
void onValueChanged(int newValue);
}
public RulerView(Context context) {
super(context);
init(context);
}
public RulerView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public RulerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
public void setOnValueChangedListener(onValueChangedListener listener) {
this.mValueChangedListener = listener ;
}
private void init(Context context) {
mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.LINEAR_TEXT_FLAG);
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setColor(Color.parseColor("#FF4081"));
mTextPaint.setTextSize(30);
mTextPaint.setStrokeWidth(2f);
Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
mFontHeight = Math.round(Math.abs(fontMetrics.top) + Math.abs(fontMetrics.bottom)) ;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mPaint.setColor(Color.DKGRAY);
mPaint.setStrokeWidth(2f);
mPaint.setTextSize(30);
mPaint.setTextAlign(Paint.Align.CENTER);
//mScroller = new Scroller(context) ;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mWidth = measureWidth(widthMeasureSpec);
mHeight = measureHeight(heightMeasureSpec);
setMeasuredDimension(mWidth , mHeight );
}
private int measureWidth(int widthMeasureSpec) {
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
switch (mode) {
case MeasureSpec.EXACTLY:
return size ;
case MeasureSpec.AT_MOST:
int result = getPaddingLeft() + mContentWidth + getPaddingRight() ;
return Math.min(size, result) ;
}
return size ;
}
private int measureHeight(int heightMeasureSpec) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
switch (mode) {
case MeasureSpec.EXACTLY:
return size ;
case MeasureSpec.AT_MOST:
return Math.min(size, mFontHeight + mLineHeight + 10 + mPadding) ;
}
return size ;
}
// private void smoothScrollTo(int destX, int destY) {
// int scrollX = getScrollX() ;
// int delta = destX - scrollX ;
// mScroller.startScroll(scrollX, 0, delta, 0 , 1000);
// invalidate();
// }
//
// @Override
// public void computeScroll() {
// super.computeScroll();
// if (mScroller.computeScrollOffset()) {
// smoothScrollTo(mScroller.getCurrX(), mScroller.getCurrY());
// postInvalidate();
//// ((View) getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
//// invalidate();
// }
// }
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int startX = mPadding;
int stopX = mPadding;
int stopY =mHeight - mPadding;
for (int i = 0 ; i<=mContentWidth ; i += mFontWidth)
if (i % (mFontWidth *10) == 0) {
canvas.drawLine(startX + i, mFontHeight + mPadding + 5 , stopX + i, stopY, mTextPaint);
canvas.drawText(i + "", startX + i, mFontHeight + mPadding, mTextPaint);
} else if (i % mFontWidth == 0) {
canvas.drawLine(startX + i, mFontHeight + mPadding + 10, stopX + i, stopY, mPaint);
}
}
private int calcValue() {
return ( getCenter() + getScrollX() - mPadding) ; //minus startX
}
private int getCenter() {
return (getRight() - getLeft()) / 2 ;
}
@Override
public void scrollBy(int x, int y) {
super.scrollBy(x, y);
if (x < 0) { // drag to left
if (getScrollX() < -getCenter() + mPadding) {
scrollTo(-getCenter() + mPadding, 0);
}
} else
if (x >0) { // drag to right
if (mContentWidth - getScrollX() + x < getCenter()) {
scrollTo(mContentWidth - getCenter() + mFontWidth, 0);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = (int) event.getX();
break;
case MotionEvent.ACTION_MOVE:
int x = (int) event.getX();
int deltaX = x - mLastX;
scrollBy(-deltaX, 0);
mLastX = x;
if (mValueChangedListener != null)
mValueChangedListener.onValueChanged(calcValue());
break;
}
return true;
}
}

总结

整体上还是比较粗糙,原形虽然有了,但是还需要优化。

参考