话不多说,先上图看个明了
在提取知识点之前,先对这个控件做一下简要说明:
这个控件主要是可以通过滑动射线区域和圆弧区域来修改中间的值大小,可以指定偏低/正常/偏高三个范围的值大小以及对应范围的颜色.
首先申明几点,免得耽误大家时间:
1.这篇文章不详解这个控件自定义的详细流程,想要看源码的朋友可以到敝人的githug库ScrollSetValueCirclePb里去拉,源码里的注释也很详细的,如果能帮助到你的话,还劳烦各位给个星星鼓励一下下.
2.也不是从整体上讲Android怎么自定义一个控件,如果对Android自定义控件还不是很熟悉的朋友可以看看这篇文章AndroidNote,写的很棒,当初敝人就是从那里深入自定义控件的啊,在此感谢作者的无私分享.
3.本文就是纯粹提取敝人在写这个控件时所涉及到的技术点,可以看看下面的知识点有没有朋友你需要的:
1.使用DrawFilter
去除绘制锯齿
//从canvas层面去除绘制锯齿
DrawFilter mDrawFilter= new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
canvas.setDrawFilter(mDrawFilter);
//去除画笔锯齿
Paint mLinePaint= new Paint();
mLinePaint.setAntiAlias(true);
2.使用Xfermode
指定重叠绘制时的显示效果
Paint mLinePaint= new Paint();
mLinePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
关于Xfermode的使用,大家可以看这篇文章http://blog.youkuaiyun.com/t12x3456/article/details/10432935
3.画外围射线
这里的难点是如何计算射线的始/终点坐标,这个控件是每过10度画一条射线,总共360度,那就有36条射线.这里是根据射线所在的角度计算对应的弧度,然后再根据弧度在这条射线上截取对应的起始点.需要注意的是我这里是翻转了Y坐标了的哈,因为下面用到的计算触摸点所在的坐标象限的函数只支持Y轴向上的条件.
//画默认射线
for (float i = 0; i < 360; i += 10) {
double rad = i * PI / 180;
//射线内侧起点
float startX = (float) (((mRayOutRadius - 35) - circleWidth) *Math.sin(rad));
float startY = -(float) (((mRayOutRadius - 35) - circleWidth) * Math.cos(rad));
//射线外侧终点,所以射线长度为 35px
float stopX = (float) (mRayOutRadius * Math.sin(rad) + 1);
float stopY = -(float) (mRayOutRadius * Math.cos(rad) + 1);
canvas.drawLine(startX, startY, stopX, stopY, mLinePaint);
}
4.通过Region
和Path
限制控件的可滑动范围
上面说了,这个控件只可以滑动射线末端到圆弧之间的区域,滑动其他区域是不会响应这个触摸事件的.所以需要我们在前面画外侧射线的时候就得用Region记录下射线扫过的路径(Path)的所有点的坐标,在onTouchEvent方法里判断我们的触摸点的坐标是否包含在这个Region里面,如果包含我们才做对应的逻辑,所以在前面的代码中加入这部分逻辑的代码就是:
Path rayInnerPath = new Path();
Path rayOuterPath = new Path();
//射线区域内侧的点坐标集合
mRayInnerRegion = new Region();
//射线区域外侧的点坐标集合
mRayOuterRegion = new Region();
//整个控件区域内的所有点坐标集合
Region viewRegion = new Region(-mRayOutRadius, -mRayOutRadius, mRayOutRadius,mRayOutRadius);
//画默认射线
for (float i = 0; i < 360; i += 10) {
double rad = i * PI / 180;
//射线内侧起点
float startX = (float) (((mRayOutRadius - 35) - circleWidth) *Math.sin(rad));
float startY = -(float) (((mRayOutRadius - 35) - circleWidth) *Math.cos(rad));
//射线外侧终点,所以射线长度为 35px
float stopX = (float) (mRayOutRadius * Math.sin(rad) + 1);
float stopY = -(float) (mRayOutRadius * Math.cos(rad) + 1);
//取的是射线区域内侧150px的区域的所有点坐标
rayInnerPath.addCircle(0, 0, mRayOutRadius - 150, Path.Direction.CW);
mRayInnerRegion.setPath(rayInnerPath, viewRegion);
//取的是射线区域外侧50px的区域的所有点坐标
rayOuterPath.addCircle(0, 0, mRayOutRadius + 50, Path.Direction.CW);
mRayOuterRegion.setPath(rayOuterPath, viewRegion);
canvas.drawLine(startX, startY, stopX, stopY, mLinePaint);
}
处理触摸事件:
@Override
public boolean onTouchEvent(MotionEvent event) {
//由于画布原点移到了控件中心,所以要矫正触点坐标
int eventX = (int) (event.getX() - mCentreX);
int eventY = (int) (mCentreY - event.getY());
if (mRayOuterRegion != null && mRayInnerRegion != null) {
//只有当触摸在限制的射线区域内才处理滑动事件
if (mRayOuterRegion.contains(eventX, eventY) && !mRayInnerRegion.contains(eventX,eventY)) {
Point point = new Point(eventX, eventY);
//获取这个触摸点在控件上的角度,根据这个角度绘制值射线和值圆弧
mRadianByPos = GetRadianByPos(point);
invalidate();
if (mValueChangeListener != null) {
//将当前值传递出去 mValueChangeListener.currentValue(mRadianByPos / mUnitScale);
}
}
return true;
}
return false;
}
画值射线和值圆弧我就不多说, 原理跟画外侧射线一样的.
某些地方还有待优化的请各位多多指正,可以评论也可以发邮件给我:xwi_it@163.com
谢谢!