Android自定义View实战---圆盘温度计
来源:互联网 发布:cube escape 知乎 编辑:程序博客网 时间:2024/04/30 05:35
了解了基本的自定义view基础后,现在我们就来实践下自定义view,也是看到我华为手机上自带的天气预报软件后,想着模仿做一个,于是,我自己尝试了下,虽然不算太像,但是还算能看,期待后期的改进。
通过本文你可以用到以下技术:
1)view的测量
2)canvas绘图技巧
3)接口回调
4)触摸事件的处理
最终效果如下所示
可以通过输入框自己设定最低、最高温度和当前温度(这是为我天气预报软件做的方法)
1.绘制流程
1)其中最主要的是中间的圆环的绘制,采用的是
canvas的drawArc(RectF oval,float startAngle, float sweepAngle, boolean useCenter, Paint paint)方法。
其中参数意义为
oval:圆外接的矩形
startAngle:开始角度
sweepAngle:扫描角度
useCenter:是否和圆心连线
paint:画笔
角度大小,以此图为标准
当然中间的圆环可能看起来是两个圆之间的区域,开始我以为是要画2个园,没想到只要把画笔设置一下就好了
circlePaint.setStrokeWidth(60.0f);上面就是两个圆与圆之间(圆环)的区域。
值得一提的是canvas的这个画圆的方法设计的很强大,还可以画椭圆的,就看你圆外接的矩形的宽高了。
2)而中间的那个刻度线,采用的是
Canvas.drawLine(float startX, float startY, float stopX, float stopY, Paint paint)方法其中参数的意义为
startX:线起始点的x轴位置
startY:线起始点的y轴位置
stopX:线结束点的x轴位置
stopY:线结束点的y轴位置
paint:画线的画笔
画了一根刻度线后,采用旋转的是
Canvas.rotate(float degrees, float px, float py)方法
其中参数意义为
degrees: 每次要旋转的角度
px: 旋转的圆心x轴坐标
py: 旋转的圆心y轴坐标
3)圆盘中间的文字的采用的是
canvas的Canvas.drawText(String text, float x, float y, Paint paint)方法
参数意义这里就不说了,应该猜的出的。
4)中间的指示针采用的是一个小实心圆,采用的是
drawCircle(float cx, float cy, float radius,Paint paint)方法
参数意义
cx: 中心点的x轴
cy: 中心点的y轴
radius: 半径
paint: Paint画笔对象
5)中间的颜色渐变,采用的是SweepGradient
SweepGradient(float cx, float cy, int[] colors, float[] positions)
参数意义
cx:渐变圆心的x轴坐标
cy:渐变圆心的y轴坐标
colors[]: 颜色数组
positions: 颜色分隔的位置数组,可以为null,系统自己分
2. 绘制的计算过程
好了,基本的绘制内容就是这么多了,其他的就是计算过程了,这些都是在onDraw()内完成的。
先说圆环的绘制的计算过程吧。
0)在绘制之前,你首先得准备好笔
工欲善其事必先利其器。初始化画笔的工作是在两个参数的构造方法里的,因为只需初始化一次,而onDraw()方法会随着绘制过程很可能会不断的调用,所以初始化的工作放在构造方法里
public MyCircleView(Context context, AttributeSet attrs) {super(context, attrs);screenWidth=MeasureUtil.getScreenWidth(context);screenHeight=MeasureUtil.getScreenHeight(context);Log.e("My----->", "2 "+screenWidth+" "+screenHeight);initPaint();}private void initPaint() {linePaint = new Paint();linePaint.setColor(Color.CYAN);linePaint.setStyle(Style.FILL);linePaint.setAntiAlias(true);linePaint.setStrokeWidth(1.0f);textPaint = new Paint();textPaint.setColor(Color.BLACK);textPaint.setTextAlign(Paint.Align.CENTER);textPaint.setAntiAlias(true);textPaint.setTextSize(30);centerTextPaint = new Paint();centerTextPaint.setColor(Color.BLUE);centerTextPaint.setTextAlign(Paint.Align.CENTER);centerTextPaint.setAntiAlias(true);centerTextPaint.setTextSize(80);circlePaint = new Paint();circlePaint.setColor(Color.WHITE);circlePaint.setAntiAlias(true);circlePaint.setStyle(Paint.Style.STROKE);circlePaint.setStrokeCap(Cap.ROUND);//实现末端圆弧circlePaint.setStrokeWidth(60.0f);indicatorPaint=new Paint();indicatorPaint.setColor(0xFFF7F709);indicatorPaint.setAntiAlias(true);indicatorPaint.setStyle(Paint.Style.FILL);// 着色的共有270度,这里设置了12个颜色均分360度sint[] colors = { 0xFFD52B2B, 0xFFf70101, 0xFFFFFFFF, 0xFFFFFFFF,0xFF6AE2FD, 0xFF8CD0E5, 0xFFA3CBCB, 0xFFD1C299, 0xFFE5BD7D,0xFFAA5555, 0xFFBB4444, 0xFFC43C3C };mCenter = screenWidth / 2;mRadius = screenWidth/ 2 - 100;// 渐变色mSweepGradient = new SweepGradient(mCenter, mCenter, colors, null);// 构建圆的外切矩形mRectF = new RectF(mCenter - mRadius, mCenter - mRadius, mCenter+ mRadius, mCenter + mRadius);}
1)圆环设计的宽度为wrap_content,故需要重写onMeasure方法,告诉画笔它的外接圆矩形的宽度
// 因为自定义的空间的高度设置的是wrap_content,所以我们必须要重写onMeasure方法去测量高度,否则布局界面看不到// 其他控件(被覆盖)@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));}测量方法
/** * 测量宽度 * * @param widthMeasureSpec * @return */private int measureWidth(int widthMeasureSpec) {int mode = MeasureSpec.getMode(widthMeasureSpec);int size = MeasureSpec.getSize(widthMeasureSpec);// 默认宽高;defaultValue=screenWidth;switch (mode) {case MeasureSpec.AT_MOST:// 最大值模式 当控件的layout_Width或layout_height属性指定为wrap_content时Log.e("cmos---->", "size " + size + " screenWidth " + screenWidth);size = Math.min(defaultValue, size);break;case MeasureSpec.EXACTLY:// 精确值模式// 当控件的android:layout_width=”100dp”或android:layout_height=”match_parent”时break;default:size = defaultValue;break;}defaultValue = size;return size;}
圆环的外接矩形
mCenter = screenWidth / 2;//圆心坐标mRadius = screenWidth/ 2 - 100;//圆的半径 留了100dp,是为了给绘制文字留空间// 构建圆的外切矩形mRectF = new RectF(mCenter - mRadius, mCenter - mRadius, mCenter+ mRadius, mCenter + mRadius); canvas.drawArc(mRectF, 135, 270, false, circlePaint);这样,圆环就绘制计算完成。
2)刻度值的计算绘制
因为整个圆盘是270度,设置每3度画一刻度线,故要绘制90根,这里选择的是以圆盘正上方的刻度线为基准刻度,将其绘制好后,然后进行旋转绘制,这样绘制过程就Ok了
for (int i = 0; i < 120; i++) {if (i <= 45 || i >= 75) {//空白部分不用绘制刻度canvas.drawLine(mCenter, mCenter - mRadius - 30, mCenter,mCenter - mRadius + 30, linePaint);//30是因为设置的填充圆环的宽度为60的原因}canvas.rotate(3, mCenter, mCenter);}
3)圆盘刻度值计算绘制
这个就稍微复杂一点了,不过也还好,计算过程无非就是高中三角值公式的使用过程,在此之前,你先要了解角度的起始值和象限。
其中黄线的长度即是圆环的半径
// x代表文字的x轴距离圆心x轴的距离 因为刚好是45度,所以文字x轴值和y值相等int x = 0;// 三角形的斜边int c = mRadius + 60 / 2 + 40;// 40代表这个字距离圆外边的距离</span>// 因为是每45度写一次文字,故根据到圆心的位置,利用三角形的公式,可以算出文字的坐标值x = (int) Math.sqrt((c * c / 2));canvas.drawText("10", mCenter - x, mCenter + x, textPaint);canvas.drawText("15", mCenter - c, mCenter,textPaint);canvas.drawText("20", mCenter - x, mCenter - x,textPaint);canvas.drawText("25", mCenter, mCenter - c,textPaint);canvas.drawText( "30", mCenter + x, mCenter - x,textPaint);canvas.drawText( "35", mCenter + c, mCenter,textPaint);canvas.drawText( "40", mCenter + x, mCenter + x,textPaint);
可以看出,这里刻度值我把它写死了,当然,你也可以暴露一个方法,让调用者去设置起始值和结束值。
4)中间的小圆点指示器
了解了文字的计算过程后,再来算这个的画,就相对容易了很多。
currentScanDegree=(getCurrentDegree()-10)*3;int insideIndicator=mRadius-60;//离圆环的距离 if (currentScanDegree<=45) {//第三象限 canvas.drawCircle((float)(mCenter-insideIndicator*Math.sin(Math.PI*(currentScanDegree+45)/180)),(float)(mCenter+insideIndicator*Math.cos(Math.PI*(currentScanDegree+45)/180)), 10, indicatorPaint);}else if(45<currentScanDegree&¤tScanDegree<=135) {//第二象限canvas.drawCircle((float)(mCenter-insideIndicator*Math.cos(Math.PI*(currentScanDegree-45)/180)),(float)(mCenter-insideIndicator*Math.sin(Math.PI*(currentScanDegree-45)/180)), 10, indicatorPaint);}else if (135<currentScanDegree&¤tScanDegree<=225) {//第一象限canvas.drawCircle((float)(mCenter+insideIndicator*Math.sin(Math.PI*(currentScanDegree-135)/180)),(float)(mCenter-insideIndicator*Math.cos(Math.PI*(currentScanDegree-135)/180)), 10, indicatorPaint);}else if(225<currentScanDegree&¤tScanDegree<=270){//第四象限canvas.drawCircle((float)(mCenter+insideIndicator*Math.cos(Math.PI*(currentScanDegree-225)/180)),(float)(mCenter+insideIndicator*Math.sin(Math.PI*(currentScanDegree-225)/180)), 10, indicatorPaint);}过程也就不解释了,同3的过程一致
5)中间文字的具体温度值
这个是根据扫描的角度值进行换算的
canvas.drawText((currentScanDegree/9+10)+"℃", mCenter, mCenter, centerTextPaint);
相信了解了上面的过程,这个就很简单了吧。
那么,怎么让我们的view动起来呢,有点动态感,对此,我们只要不断的改变扫描的度数就好,对此我设定了3个值,最低温度值(起始)、最高温度值(结束)、当前温度。
点击按钮让其重绘。具体代码如下
private void showDegree(final int minDegree, final int maxDegree, final int currentDegree) { myCircleView.setMinDegree(minDegree); new Thread(new Runnable() {@Overridepublic void run() {for (int i = minDegree; i < (maxDegree-minDegree)*3+minDegree; i++) {try {Thread.sleep(30);} catch (InterruptedException e) {// TODO 自动生成的 catch 块e.printStackTrace();}myCircleView.setMaxDegree(i);myCircleView.postInvalidate();}for (int j = 10; j<=(currentDegree-10)*3+10; j++) {if (j<=(currentDegree-10)*3+10) {//23*3+10=79myCircleView.setCurrentDegree(j);}myCircleView.postInvalidate();}}}).start();}
上面要注意的地方是要注意使用postInvalidata()方法,因为我们是在子线程操控UI线程,所以不能单纯的用invalidata()方法。
3. view添加滑动触摸事件
最后作为扩展,我为此控件添加了滑动触摸事件
代码如下:
@Overridepublic boolean onTouchEvent(MotionEvent event) {// TODO 自动生成的方法存根switch (event.getAction()) {case MotionEvent.ACTION_DOWN:isCanMove = true;break;case MotionEvent.ACTION_MOVE:float x = event.getX();float y = event.getY();float StartX = event.getX();float StartY = event.getY();// 判断当前手指距离圆心的距离 如果大于mCenter代表在圆心的右侧if (x > mCenter) {x = x - mCenter;} else {x = mCenter - x;}if (y > mCenter) {y = y - mCenter;} else {y = mCenter - y;}// 判断当前手指是否在圆环上的(30+10多加了10个像素)if ((mRadius + 40) < Math.sqrt(x * x + y * y)|| Math.sqrt(x * x + y * y) < (mRadius - 40)) {Log.e("cmos---->", "终止滑动");isCanMove = false;return false;}float cosValue = x / (float) Math.sqrt(x * x + y * y);// 根据cosValue求角度值double acos = Math.acos(cosValue);// 弧度值acos = Math.toDegrees(acos);// 角度值if (StartX > mCenter && StartY < mCenter) {acos = 360 - acos;// 第一象限Log.e("象限---->", "第一象限");} else if (StartX < mCenter && StartY < mCenter) {acos = 180 + acos;// 第二象限Log.e("象限---->", "第二象限");} else if (StartX < mCenter && StartY > mCenter) {acos = 180 - acos;// 第三象限Log.e("象限---->", "第三象限");} else {// acos=acos;Log.e("象限---->", "第四象限");}Log.e("旋转的角度---->", acos + "");scanDegree = (int) acos;if (scanDegree >= 135 && scanDegree < 360) {scanDegree = scanDegree - 135;int actualDegree = (int) (scanDegree / 9);if (mGetDegreeInterface != null) {mGetDegreeInterface.getActualDegree(actualDegree+ minDegrees);}} else if (scanDegree <= 45) {scanDegree = (int) (180 + 45 + acos);int actualDegree = (int) (scanDegree / 9);if (mGetDegreeInterface != null) {mGetDegreeInterface.getActualDegree(actualDegree+ minDegrees);}} else {return false;}postInvalidate();return true;}return true;}其中上面用到了接口回调事件
if (mGetDegreeInterface != null) {mGetDegreeInterface.getActualDegree(actualDegree+ minDegrees);}假如我们主界面需要当前滑动到的温度值,可以利用此方法
/** * 获取当前温度值接口 * */public interface GetDegreeInterface {void getActualDegree(int degree);}public void setGetDegreeInterface(GetDegreeInterface arg) {this.mGetDegreeInterface = arg;}
例子源码点我下载
好的,全文到此结束,对文章内容有疑问的、或者有错的地方、或者代码可以有优化的方法的,欢迎留言探讨,共同进步!!!
- Android自定义View实战---圆盘温度计
- android:自定义温度计View
- android自定义view--温度计
- Android自定义组件view 温度计
- android自定义view——温度计
- 自定义View之温度计
- 自定义圆盘式view
- 自定义View圆盘遥控器
- Android自定义View圆盘滑动控件(已适配多种分辨率)
- android 自定义view实现类似圆盘抽奖的效果
- android自定义圆盘时钟
- Android自定义view的实战
- Android 自定义view综合实战
- android 自定义View开发实战(一) CustomTitleView
- android 自定义View开发实战(二) CustomCircleView
- android 自定义view实战之switchButton
- Android 自定义View实战系列 :时间轴
- Android 自定义View实战系列 :时间轴
- 数据结构实验之栈三:后缀式求值
- 欢迎使用CSDN-markdown编辑器
- C#控制台 输出hello world之 在main函数下使用console.writeline
- C语言文件操作函数
- XML解析之pull解析
- Android自定义View实战---圆盘温度计
- 多线程编程入门(7):线程范围内的共享变量
- 排序算法 之 希尔排序ShellSort
- 匈牙利算法(膜拜大神orz)
- XZ_iOS之View的生命周期
- 排序
- POJ Wormholes 3259(最短路)
- Android Cursor类的了解和使用
- Codeforces 14D 求树的直径(网上大多数是DFS于是自己写了个BFS)