上篇介绍了组合自定义控件,那么今天来实现一个表钟效果。
目前还没有让其动起来。下篇咱们将对其进行完善。咱们完整的效果,既然是钟,那他就得实现秒进分,分进时的功能,还得在刻度上面实现数字显示。
最终的效果是这样的。
其实,实现一个自定义控件其实并不难。在看本篇之前首先推荐大家先看上篇Android实现自定义View之组合控件(一)。
实现一个自定义控件,咱们需要重写的方法有onMeasure()尺寸测量方法,draw()绘画方法,还有构造方法。构造方法我们传入2个参数的就可以了。
首先,我们的思路是这样的。先在draw方法中画置2个空心圆,一个实心圆,还有一条线条。然后再绘画刻度。绘画刻度我是从最左端开始绘画的,学过三角函数的大家应该都有知道,正弦,余弦,正切。
其中的难点就在于计算,其实这些计算也是很简单的,下面我画个丑图给大家解释下如何实现坐标点的计算。
很方便我们就可以拿到刻度点的坐标。我们只需要知道圆的半径和角度,利用正余弦就可以解决。
我们知道一个表钟是360度。那么一个表钟有60个刻度。每个刻度占6度。
首先,我们新建议atttrs.xml文件。这个里面我们暂时只让它有一个改变圆的颜色的属性。
attrs.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name = "Custom"> <attr name="color" format = "color"></attr> </declare-styleable> </resources>
下面我直接上代码,就不一一解释了。看过上一篇的童鞋相信大家能看得懂。
/** * */ package com.mero.customclock.widget; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; import android.view.View; import android.view.WindowManager; import com.mero.customclock.R; /** *@项目名称: CustomClock *@文件名称: CustomClockView.java *@Date: 2016-9-25 *@Copyright: 2016 Technology Mero Inc. All rights reserved. *注意:由Mero开发,禁止外泄以及使用本程序于其他的商业目的 。 */ public class CustomClockView extends View{ private float mScreenWidth; private float graduateStrokeShortLenth = 10;//每一短刻度的值 private float graduateStrokeLongLenth = 20;//每一长刻度的值 private Paint p; private String TAG; private Rect mBounds; private int color; /** * @param context * @param attrs * @param defStyleAttr */ public CustomClockView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public CustomClockView(Context context) { super(context); } /** * @param context * @param attrs */ public CustomClockView(Context context, AttributeSet attrs) { super(context, attrs); //得到我们的属性集对象 TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.Custom, 0 ,0); color = typedArray.getColor(R.styleable.Custom_color, 0xFF339933); //得到屏幕的宽度 WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); DisplayMetrics metrics = new DisplayMetrics(); display.getMetrics(metrics); mScreenWidth = metrics.widthPixels; } /** * */ private void init(int color) { p = new Paint(); p.setAntiAlias(true);//设置为抗锯齿 p.setStyle(Paint.Style.STROKE);//设置为线条模式 p.setStrokeWidth(5);//设置线条宽度 } @Override public void draw(Canvas canvas) { super.draw(canvas); init(color); mBounds = new Rect(0, 0, (int)mScreenWidth/3, (int)mScreenWidth/3); //画大小外圈 p.setColor(0xffff3344); canvas.drawCircle(mBounds.width()/2, mBounds.height()/2, 30, p); canvas.drawCircle(mBounds.width()/2, mBounds.width()/2, 100, p); //画中心圈 p.setColor(0xFF33ff33); canvas.drawCircle(mBounds.width()/2, mBounds.height()/2, 2, p); p.setColor(0xff229933); //画分针 p.setColor(0xffffff44); p.setStrokeWidth(2); canvas.drawLine(mBounds.width()/2, mBounds.height()/2, mBounds.width()/2+90, mBounds.height()/2, p); //画刻度 drawGraduate(canvas); } /** * */ private void drawGraduate(Canvas canvas) { //计算出初始刻度的坐标位置 float w0 = mBounds.width()/2; float h0 = mBounds.height()/2; //创建2只新画笔 Paint p1 = new Paint(Paint.ANTI_ALIAS_FLAG);//用来画长刻度 Paint p2 = new Paint(Paint.ANTI_ALIAS_FLAG);//用来画短刻度 p1.setColor(0xff229944); p1.setStrokeWidth(2); p2.setColor(0xff33ff44); p2.setStrokeWidth(2); /*画短刻度*/ for(int i = 0;i<60;i++){ float[] a0 = getNewDirect(i, w0, h0,graduateStrokeShortLenth); float[] a1 = getNewDirect(i, w0, h0,graduateStrokeLongLenth); if(i%5==0){ canvas.drawLine(w0+(float)(100*(Math.cos(Math.PI/180*i*6))), h0-(float)(100*Math.sin(Math.PI/180*i*6)), a1[0], a1[1], p1); }else{ canvas.drawLine(w0+(float)(100*(Math.cos(Math.PI/180*i*6))), h0-(float)(100*Math.sin(Math.PI/180*i*6)), a0[0], a0[1], p2); } } } //计算得到新的尺寸 private float[] getNewDirect(int i,float w0,float h0,float graduateStrokeLenth){ float w1 = 0,h1 = 0; w1 = (float) (w0+(100+graduateStrokeLenth)*Math.cos(Math.PI/180*i*6)); h1 = (float) (h0-(100+graduateStrokeLenth)*Math.sin(Math.PI/180*i*6)); return new float[]{w1,h1}; } /* (non-Javadoc) * @see android.view.View#onMeasure(int, int) */ @SuppressLint("DrawAllocation") @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width = 0; int height = 0; Rect mBounds1 = new Rect(0, 0, (int)mScreenWidth/3, (int)mScreenWidth/3); Log.e(TAG, mBounds1+""); if(widthMode==MeasureSpec.EXACTLY){ width = widthSize; Log.e(TAG,"width in exactly: "+width); }else{ width = mBounds1.width(); Log.e(TAG,"width in other: "+width); } if(heightMode==MeasureSpec.EXACTLY){ height = heightSize; Log.e(TAG,"height in exactly: "+height); }else{ height = mBounds1.height(); Log.e(TAG,"height in other: "+height); } setMeasuredDimension(width, height); } }
在这里咱们需要注意的地方是在测量方法里,由于咱们的自定义不是全屏的控件,所以咱们在使用的时候实际上不希望它自身就是一个match_parent的现象发生。所以在为wrap_content的时候,系统会自动帮我们设置为match_parent。所以此时咱们需要进行测量。也就是测量整个自定义控件的宽高。
在计算完了宽度和高度后,咱们需要使用setMeasuredDimension(width,height)方法将实际测量值设置进去。
实现了之后,咱们在布局文件中就可以使用了。
在布局文件中声明加入控件就可以了。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:mero="http://schemas.android.com/com.mero.customclock" android:layout_width="match_parent" android:layout_height="match_parent" > <com.mero.customclock.widget.CustomClockView android:layout_width="wrap_content" android:layout_height="wrap_content" mero:color="#FF0000" android:layout_centerInParent="true" /> </RelativeLayout>
好了,到这里咱们也就实现了本篇自定义的效果。