自定义View:用Canvas实现转盘View
来源:互联网 发布:美萍收银软件 编辑:程序博客网 时间:2024/05/17 07:13
前两天道长的同学让道长帮忙做个控件,道长看到效果图和需求感觉挺有意思,然后就答应下来了。下面和小伙伴们分享一下制作过程。
效果图和需求就不给小伙伴们看了,需求大概就是转盘可以来回拖动,点击有标志显示。然后给小伙伴们看一下完成后的效果图(道长终于会弄动态图了,哈哈哈…):
一、绘制
1.测量空间宽高
在onMeasure()中测量宽高并且设置宽高为宽高的最小值,代码如下:
/** * 设置控件为正方形 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = Math.min(getMeasuredWidth(), getMeasuredHeight()); // 获取圆形的直径 mRadius = width - mPadding - mBackgroundPadding; // 中心点 mCenter = width / 2; setMeasuredDimension(width, width); }
2.绘制控件背景以及圆盘背景
小伙伴们应该都知道使用Canvas绘制View的话要从底层开始,不然的话会遮挡底层的展示。
/** * 绘制控件背景以及圆盘背景 */ private void drawBackground() { mCanvas.drawBitmap(mBgBitmap, null, new Rect(0, 0, getMeasuredWidth(), getMeasuredWidth()), null); // 圆盘背景颜色设置 mPaint.setColor(0x50000000); mCanvas.drawCircle(mCenter, mCenter, mCenter - mPadding, mPaint); }
3.绘制扇形以及扇形上的文字、背景
运用for循环绘制每一个扇形上的文字以及背景,代码如下:
float tmpAngle = mStartAngle; float sweepAngle = (float) (360 / mItemCount); // 绘制扇形以及扇形上的文字、背景 for (int i = 0; i < mItemCount; i++) { // 绘制扇形 drawFanShaped(tmpAngle, sweepAngle, i); // 绘制扇形上的文本 drawFanText(tmpAngle, sweepAngle, mStrs[i]);// drawFanText(tmpAngle, sweepAngle, "i:" + i + ":::" + (i * 60 + offset)); // 移动到下一区域 tmpAngle += sweepAngle; }
1)绘制扇形区域
- 计算偏置量
首先计算出来每次拖动圆盘的偏置量,即转动角度,代码如下:
// 计算偏置量 float turnAngle = tmpAngle % 360; if (i == 0) { offset = turnAngle; }
我们看一下效果图:
- 矫正偏置量
计算出来每次拖动圆盘的偏置量后进行矫正,矫正完成后我们就可以知道扇形转动到的角度,代码如下:
// 矫正偏置量 setRight = i * sweepAngle + offset; if (i * sweepAngle + offset > 360) { setRight = i * sweepAngle + offset - 360; } else if (i * sweepAngle + offset < 0) { setRight = i * sweepAngle + offset + 360; }
我们再看一下矫正后的效果图:
- 设置扇形的绘制区域
我们这里要考虑到当扇形转动到0度边界的点击情况,代码如下:
// 设置扇形区域边界 if (setRight > 300) { if ((finalClickAngle >= setRight && finalClickAngle < 360) || (finalClickAngle >= 0 && finalClickAngle < setRight - 300)) { setClickZone(); clickZone = i; drawSingle(setRight, sweepAngle); } else { setDefaultZone(); } } else { if (finalClickAngle >= setRight && finalClickAngle < setRight + sweepAngle) { setClickZone(); clickZone = i; drawSingle(setRight, sweepAngle); } else { setDefaultZone(); } }
/** * 默认扇形区域边界 */ private void setDefaultZone() { mRange = new RectF(mPadding + mBackgroundPadding, mPadding + mBackgroundPadding, mRadius, mRadius); } /** * 设置点击扇形区域边界 */ private void setClickZone() { mRange = new RectF(mPadding + mScalePadding, mPadding + mScalePadding, mRadius - mScalePadding + mBackgroundPadding, mRadius - mScalePadding + mBackgroundPadding); }
- 图片渲染绘制扇形图片
由于扇形的背景是由图片绘制,所以我们这里要用到图片渲染,代码如下:
setFanPaintShader(i); mCanvas.drawArc(mRange, tmpAngle, sweepAngle, true, mBitmapPaint);
/** * 设置扇形渲染对象 * * @param i */ private void setFanPaintShader(int i) { // 创建Bitmap渲染对象 mBitmapShader = new BitmapShader(mImgsBitmap[i], Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); float scale = 1.0f; // 比较bitmap宽和高,获得较小值 int bSize = Math.min(mImgsBitmap[i].getWidth(), mImgsBitmap[i].getHeight()); scale = mRadius * 1.0f / bSize; // shader的变换矩阵,用于放大或者缩小 mMatrix.setScale(scale, scale); // 设置变换矩阵 mBitmapShader.setLocalMatrix(mMatrix); // 设置shader mBitmapPaint.setShader(mBitmapShader); }
- 绘制扇形上的文字
我们看到扇形上的文字居中而且成弧形,通过计算偏移量,以及矫正让文字居中,代码如下:
/** * 绘制扇形上的文本 * * @param startAngle * @param sweepAngle * @param string */ private void drawFanText(float startAngle, float sweepAngle, String string) { Path path = new Path(); path.addArc(mRange, startAngle, sweepAngle); float textWidth = mTextPaint.measureText(string); // 利用水平偏移让文字居中 float hOffset = (float) (mRadius * Math.PI / mItemCount / 2 - textWidth / 2);// 水平偏移 float vOffset = mRadius / 2 / 6;// 垂直偏移 mCanvas.drawTextOnPath(string, path, hOffset, vOffset, mTextPaint); }
1)绘制中心遮挡区域
- 绘制中心遮挡区域的背景
中心遮挡区域的背景可以看到是有透视效果,其实也是通过和控件背景同一个图片渲染,代码如下:
/** * 绘制中心遮挡圆背景 */ private void drawCenterBg() { setCirclePaintShader(); mCanvas.drawCircle(mCenter, mCenter, mCenter / 2, mCircleBitmapPaint); }
/** * 设置中心遮挡圆渲染对象 */ private void setCirclePaintShader() { // 创建Bitmap渲染对象 mCircleBitmapShader = new BitmapShader(mBgBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); float scale = 1.0f; // 比较bitmap宽和高,获得较小值 int bSize = Math.min(mBgBitmap.getWidth(), mBgBitmap.getHeight()); scale = mRadius * 1.0f / bSize; // shader的变换矩阵,用于放大或者缩小 mCircleMatrix.setScale(scale, scale); // 设置变换矩阵 mCircleBitmapShader.setLocalMatrix(mCircleMatrix); // 设置shader mCircleBitmapPaint.setShader(mCircleBitmapShader); }
- 绘制中心遮挡的文字
中心遮挡的文字的设置比较简单,但是要计算文字的偏置量,通过矫正让文字居中,代码如下:
/** * 绘制中心遮挡的文字 * * @param str */ private void drawCenterText(String str) { float textWidth = mTextPaint.measureText(str); // 利用偏移让文字居中 float hOffset = textWidth / 2;// 水平偏移 float vOffset = mTextSize / 4;// 垂直偏移 mCanvas.drawText(str, mCenter - hOffset, mCenter + vOffset, mTextPaint); }
到了这里,整个空间的绘制已经完成,下面看一下转盘的拖动以及点击。效果图如下:
二、动作
1.转盘的拖动以及点击
这里我们先看一下onTouchEvent()中关于点击事件的处理,代码如下:
@Override public boolean onTouchEvent(MotionEvent event) { start(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downX = event.getX(); downY = event.getY(); // 每次转动圆盘都要去掉点中区域 finalClickAngle = -1; isClick = false; break; case MotionEvent.ACTION_MOVE: // 圆心的下方 if (downY - mCenter >= 0) { distanceX = -(event.getX() - downX); } else {// 圆心的上方 distanceX = event.getX() - downX; } // 圆心的右方 if (downX - mCenter >= 0) { distanceY = event.getY() - downY; } else {// 圆心的左方 distanceY = -(event.getY() - downY); } // 圆盘转动的距离 if (Math.abs(distanceY) - Math.abs(distanceX) >= 0) { distance = distanceY; } else { distance = distanceX; } // 每隔30px采集一次定位点 if (Math.abs(distance) >= 30) { downX = event.getX(); downY = event.getY(); } // 圆盘移动误差矫正 float moveDistance = disTemp - Math.abs(distance); if (moveDistance < 5 && moveDistance >= 0) { distance = 0; } else { disTemp = Math.abs(distance); } // 圆盘转动状态设置 if (Math.abs(distance) < 5) { distance = 0; turnState = true; } else { turnState = false; } // 点击误差矫正 if (Math.abs(distance) > 5) { isClick = true; } break; case MotionEvent.ACTION_UP: // 每项角度大小 float angle = (float) (360 / mItemCount); // 角度 = Math.atan((dpPoint.y-dpCenter.y) / (dpPoint.x-dpCenter.x)) / π(3.14) * 180 double clickAngle = Math.atan((downY - mCenter) / (downX - mCenter)) / Math.PI * 180; // 点击区域 int zone = (int) (clickAngle / angle); float overflow = (float) (clickAngle % angle); // 点击角度的矫正 // 圆心的下方 if (downY - mCenter >= 0) { if (overflow >= 0) { finalClickAngle = (float) clickAngle; } else { finalClickAngle = (float) clickAngle + 180; } } else {// 圆心的上方 if (overflow >= 0) { finalClickAngle = (float) clickAngle + 180; } else { finalClickAngle = (float) clickAngle + 360; } } if (isClick == false) { // 调用回调接口 clickZone(); } else { // 每次转动圆盘都要去掉点中区域 finalClickAngle = -1; } turnState = true; break; } return true; }
- 回调接口调用
由于流程控制先点击后绘制,所以在视觉上就会存在延时。所以回调接口调用要延时100ms,代码如下:
/** * 回调点击区域 */ private void clickZone() { new Handler().postDelayed(new Runnable() { @Override public void run() { if (distance == 0) { mViewOnClickListener.onClicked(clickZone); } } }, 100); }
- 点击区域标志的绘制
由于点击区域标志显示在每个扇形区域的中间,所以就要通过角度计算出标志的坐标。代码如下:
/** * 绘制点击区域的标志 * * @param angle * @param sweepAngle */ private void drawSingle(float angle, float sweepAngle) { mPaint.setColor(Color.BLUE); // 计算标志的坐标 // positionX = Math.sin(Math.PI*角度/180) * R positionY = Math.cos(Math.PI*角度/180) * R float positionX = (float) (Math.cos(Math.PI * (angle + sweepAngle / 2) / 180) * mCenter); float positionY = (float) (Math.sin(Math.PI * (angle + sweepAngle / 2) / 180) * mCenter); mCanvas.drawCircle(mCenter + positionX, mCenter + positionY, 20, mPaint); }
2.不断绘制
由于不断绘制View属于耗时操作,所以我们要开启一个线程,在子线程中不断绘制。代码如下:
/** * 开启线程 */ public void start() { threadState = true; stopTime = 0; if (thread != null && thread.isAlive()) { if (DEBUG) { Log.e("yushan", "start: thread is alive"); } } else { thread = new Thread(new Runnable() { @Override public void run() { // 不断的进行绘制 while (threadState) { long start = System.currentTimeMillis(); draw(); long end = System.currentTimeMillis(); long pieTime = end - start; stopTime += pieTime; try { if (pieTime < 50) { Thread.sleep(50 - pieTime); } } catch (InterruptedException e) { e.printStackTrace(); } // 3秒不操作就休眠 if (stopTime >= 3000) { stop(); } } if (DEBUG) { Log.i("yushan", "run: thread stopping"); } } }); thread.start(); } } /** * 关闭线程 */ public void stop() { if (threadState) { threadState = false; } }
三、优化
1.矫正
在刚才的代码中已经贴过了,就不一一贴了,就是一些转盘转动矫正,点击矫正什么的(主要是道长有些懒,不想贴了)。
2.休眠
不停地绘制View非常耗电,而且占用大量手机内存,容易造成内存溢出。所以道长设置每过3s,只要不操作View,View就停止绘制。小伙伴们也可以自己设置。
这篇博客暂时就到这里了,希望这篇博客能够为小伙伴们提供一些帮助。如果有更好的优化或者改进希望小伙伴们可以告知道长。源码贴在下面。
源码下载
CoronaDemo
阅读全文
0 0
- 自定义View:用Canvas实现转盘View
- 自定义View实现抽奖转盘
- 自定义View实现转盘旋转效果
- 利用Android自定义View实现转盘旋转的效果
- Android自定义View实现转盘旋转的效果
- Android-view自定义-Canvas
- 自定义VIEW③Canvas
- 自定义view-canvas练习
- 抽奖转盘(二)属性动画的简单实现和自定义View实现
- 自定义View-Paint和Canvas
- android自定义view onDraw canvas
- Android自定义View使用canvas实现轮播图效果
- 利用canvas实现分隔虚线(自定义view)
- 自定义view—Canvas实现手写板和涂鸦功能
- android 自定义view使用Canvas实现支付宝咻一咻功能
- 用自定义view实现刮刮乐
- 用print在canvas自定义View实现飘雪花,多色雪花功能。
- 如何写出自定义View——Google 转盘
- C语言 设置控制台字体颜色 SetConsoleTextAttribute
- Error:Your project path contains non-ASCII characters.
- 设计模式六大原则-依赖倒置原则
- 数据库配置
- Hadoop-ssh免密码登录原理
- 自定义View:用Canvas实现转盘View
- RxJava介绍(观察者和异步)
- 访问者模式,记者街访
- UVALive3638 UVA12100 POJ3125 HDU1972 Printer Queue【队列+模拟】
- 总结2
- 4G网络相关知识(二)
- JobScheduler工作原理
- vue2.0之多页面的开发
- 什么是贷款服务费?房屋中介收取合理吗?