Android自定义View--ClockView
前一篇博客中,简要介绍了关于自定义View的流程,以及一些重要的函数,接下来我会编写一个自己的CliokView(继承自View类)。
实现效果图如下:
第一步编写构造函数
通常使用的构造函数有三个,分别如下
public ClockView(Context context){
this(context,null);
}
public ClockView(Context context, AttributeSet attrs){
super(context,attrs);
}
public ClockView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
第二步编写onMeasure函数
onMeasure有两个参数,分别是宽高与对应方向上的测量模式合成的一个值,不能直接使用此参数作为宽高。我们首先使用getMode方法获取测量模式,如果测量模式为AT_MOST(表示子View具体大小没有尺寸限制,但是存在上限,上限一般为父View大小),那么我们手动设置View的宽高为MATCH_PARENT。其余情况不做修改。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
//没有指定宽高,使用了wrap_content,则手动指定宽高为MATCH_PARENT
if (modeWidth == AT_MOST && modeHeight == AT_MOST){
ViewGroup.LayoutParams layoutParams = getLayoutParams();
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
setLayoutParams(layoutParams);
}
}
第三步编写onSizeChanged方法
onSizeChanged方法在View的尺寸发生变化时调用,方法有四个参数,分别代表变化之前的宽高与变化之后的宽高。我们定义两个内部变量mWidth与mHeight,并将比那花后的宽高赋给他们,方便使用。
之后定义三个变量CenterX,CenterY,mClockLength,分别代表Clock的圆心坐标以及半径。
protected void onSizeChanged(int w,int h,int oldw,int oldh){
super.onSizeChanged(w,h,oldw,oldh);
mWidth = w;
mHeight = h;
CenterX = mWidth / 2f;
CenterY = mHeight / 2f;
mClockLength = Math.min(mWidth,mHeight) / 2.4f;
}
第四步onDraw方法
由于我们继承的时View类,定义的View也就没有子View。因此也就没有onLayout方法。
在Android绘图时我们要用到Canvas(画布),Paint(画笔)。
首先定义一个init函数,用来初始化画笔,并在构造函数中调用它。
private void init(){
mClockPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//抗锯齿
mClockPaint.setStyle(Paint.Style.FILL);//填充
mClockPaint.setColor(Color.parseColor("#ededec"));//设置颜色
mScalePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mScalePaint.setStyle(Paint.Style.STROKE);
mScalePaint.setStrokeCap(Paint.Cap.ROUND);#线条末端圆角,也即线条长度=线的长度+线的宽度
mScalePaint.setColor(Color.BLACK);
mPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPointPaint.setStyle(Paint.Style.FILL);
mPointPaint.setColor(Color.BLACK);
mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mLinePaint.setStyle(Paint.Style.STROKE);
mLinePaint.setStrokeCap(Paint.Cap.ROUND);
mLinePaint.setColor(Color.BLACK);
mLinePaint.setStrokeWidth(5);
mSecondPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mSecondPaint.setStyle(Paint.Style.STROKE);
mSecondPaint.setColor(Color.RED);
mSecondPaint.setStrokeWidth(5);//笔刷宽度
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.RED);
}
下一步定义drawClock函数,此函数真正负责绘制图形。
private void drawclock(Canvas canvas){
canvas.drawCircle(CenterX,CenterY,mClockLength,mClockPaint);//画出整个表盘
//将坐标中心移动到时钟中心
canvas.translate(CenterX, CenterY);
canvas.save();//保存画布状态,压栈
for (int i=0; i<=360; i+=30) {//画出clock的刻度,并旋转30度,最终转一圈画出12个刻度
canvas.drawLine(0,mClockLength * 0.82f * -1,0,mClockLength * 0.9f * -1, mScalePaint);
canvas.rotate(30);
}
canvas.drawCircle(0,0,20,mPointPaint);//clock的中心
canvas.restore();//取出初始的画布状态
drawHour(canvas);
drawMinute(canvas);
drawSecond(canvas);
}
android手机屏幕的初始坐标系以屏幕左上角为原点,向右向下分别为x方向与y方向的正方向。canvas.translate方法有两个参数,此方法将坐标原点移动到所给定的坐标。上边代码将坐标原点移动到clock中点,方便之后操作。关于刻度的绘制,我是用的方法是旋转,先绘制出表盘上方12点钟方向的刻度,之后旋转画布,绘制出其他的刻度。当然如果不想这样旋转画布,也可以使用Math.sin和Math.cos方法计算出刻度的坐标,然后绘制。
但是画布操作是不可逆的,也就是说前边的画布操作会影响到后边的操作,因此我们需要对画布装台进行保存和回滚。而save方法就是将当前画布的状态保存,然后放入一个栈中。restore方法从栈顶将画布状态取出,并恢复。
绘制完刻度之后就需要绘制指针
private void drawHour(Canvas canvas){
double i = mHour % 12d * 30d + mMinute / 2d;
float y = (float)(Math.cos(Math.PI * i / 180) * mClockLength * -0.5f);
float x = (float)(Math.sin(Math.PI * i / 180) * mClockLength * 0.5f);
canvas.drawLine(0,0,x,y,mLinePaint);
}
mHour,mMinute是定义来保存时间值的变量。
第五步提供外部接口
作为一个自定义的clock,可以设置自动获取时间,也可以在外部设置时间。在这里我定义了一个外部设置时间的方法setTime,当然在设置时间之前也需要检查时间格式是否标准。
public void setTime(int hour, int minute, int second){
this.mHour = hour;
this.mMinute = minute;
this.mSecond = second;
checkTime();
postInvalidate();//刷新UI,关于postInvalidate与Invalidate区别,自行百度
}