安卓自定义弧形刻度选择器

Posted yhongm

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了安卓自定义弧形刻度选择器相关的知识,希望对你有一定的参考价值。

android开发过程中实现通过自定义View实现的弧形刻度选择器,效果如下。

技术分享

演示效果

一,测量:

      首先在onMeasure方法中通过测量获取当前View的宽高,中心点,半径

mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
mCenterX = mWidth / 2;
mCenterY = 0;
radius = Math.min(mWidth / 2 - padding, mHeight / 2 - padding);


二,画弧形:

绘制弧形可以通过canvas.drawPath或通过canvas.drawArc()方法绘制弧形,本文中选择通过canvas.drawPath方法绘制,下文会讲解为什么使用drawPath方法绘制弧形

int top = -radius;
int bottom = radius;
int left = mCenterX - radius;
int right = mCenterX + radius;
mArcPath.reset();
mArcPath.addArc(new RectF(left, top, right, bottom), 0, 180);
canvas.drawPath(mArcPath, mArcPaint);


三,绘制弧形刻度线以及刻度:

怎么样绘制刻度线呢??通过观察发现刻度线是垂直于弧形切线的,所以绘制刻度线我们首先要知道这些刻度点当前的切线。PathMeasure.getPosTan()可以获取到Path上某个点的xy坐标与tan值,PathMeasure.getLength可以获取Path的长度,getLength与getPosTan方法配合使用获取Path线上点的pos与tan值,获取到坐标值与tan值之后我们就可以绘制刻度线了。PathMeasure办法只能作用在Path上,这也就解释了为什么选择drawPath的方法绘制弧形。

PathMeasure mPathMeasure = new PathMeasure();
mPathMeasure.setPath(mArcPath, false);
float[] pos = new float[2];
float[] tan = new float[2];
for (int i = 1; i <= mScaleNumber; i++) {  //mScaleNumber为刻度点的数量,for循环遍历刻度点
float percentage = i / (float) mScaleNumber;//计算当前刻度所占总刻度的百分比,
mPathMeasure.getPosTan(mPathMeasure.getLength() * percentage, pos, tan);//通过上一步计算出来的百分比可以计算出当前刻度在Path上的位置
    double atan2 = Math.atan2(tan[1], tan[0]);
    double angle = calcArcAngle(atan2) + 90;//刻度线垂直切线,所以旋转90°
int scale = Math.round(currentAngle + mScaleMin + i * mScaleSpace);
    if (scale >= mScaleMin && scale % mDrawLineSpace == 0) {
float startX = pos[0];
        float startY = pos[1];
        float endX = 0;
        float endY = pos[1];
        if (scale % mDrawTextSpace == 0) {
endX = pos[0] + 80;
mScaleLinePaint.setStrokeWidth(15);
mScaleLinePaint.setColor(mScaleLineColor);
            if (currentAngle >= (-(mScaleNumber / 2) * mScaleSpace)) {
canvas.save();
canvas.rotate((float) (angle + 90), pos[0], pos[1]);
String mScaleText = scale + mScaleUnit;
                float scaleTextLength = mScaleTextPaint.measureText(mScaleText, 0, mScaleText.length());
canvas.drawText(mScaleText, pos[0] - scaleTextLength / 2, pos[1] - 130, mScaleTextPaint);
canvas.restore();
}
} else if (scale % mDrawLineSpace == 0) {
mScaleLinePaint.setColor(mScaleTextColor);
mScaleLinePaint.setStrokeWidth(10);
endX = pos[0] + 50;
}
canvas.save();
canvas.rotate((float) angle, pos[0], pos[1]);
canvas.drawLine(startX, startY, endX, endY, mScaleLinePaint);
canvas.restore();
}
}


四,绘制当前选中的刻度值

String selectedScaleText = selectedScale + mScaleUnit;
float selectedScaleTextLength = mSelectedTextPaint.measureText(selectedScaleText, 0, selectedScaleText.length());
canvas.drawText(selectedScaleText, mCenterX - selectedScaleTextLength / 2, mCenterY + 100, mSelectedTextPaint);


五,绘制中心指示器:

中心指示器位于弧形中心,所以首先获取要弧形中心点坐标,tan值。弧形中心点也就是弧形长度的一半也就是mPathMeasure.getLength() * (0.5f)。通过坐标以及tan值使用drawLine方法绘制直线drawPath方法绘制剪头

PathMeasure mPathMeasure = new PathMeasure();
mPathMeasure.setPath(mArcPath, false);
float[] tan = new float[2];
float[] pos = new float[2];
mPathMeasure.getPosTan(mPathMeasure.getLength() * (0.5f), pos, tan);
canvas.save();
double angle = calcArcAngle(Math.atan2(tan[1], tan[0])) + 90;
canvas.rotate((float) angle, pos[0], pos[1]);
//画直线
canvas.drawLine(pos[0], pos[1], pos[0] + 80, pos[1], mIndicatorPaint);
Path linePath = new Path();
//画箭头
linePath.moveTo(pos[0] + 80, pos[1] - 20);
linePath.lineTo(pos[0] + 80 + 20, pos[1]);
linePath.lineTo(pos[0] + 80, pos[1] + 20);
canvas.drawPath(linePath, mIndicatorPaint);
canvas.restore();


六,为了美观画弧形两侧的遮罩层

弧形的两侧有一个有白色到透明的线性渐变,LinearGradient类可以实现画笔的线性渐变。

//画左侧遮罩
LinearGradient mLeftLinearGradient = new LinearGradient(0, 0, mCenterX - 100, 150, Color.WHITE, Color.TRANSPARENT, Shader.TileMode.CLAMP);
mMaskPaint.setShader(mLeftLinearGradient);
canvas.drawPath(mArcPath, mMaskPaint);
//画右侧遮罩
LinearGradient rightLinearGradient = new LinearGradient(mWidth, 0, mCenterX + 100, 150, Color.WHITE, Color.TRANSPARENT, Shader.TileMode.CLAMP);
mMaskPaint.setShader(rightLinearGradient);
canvas.drawPath(mArcPath, mMaskPaint);
canvas.drawPath(mArcPath, mArcPaint);


七,计算滑动旋转角度:

当触摸屏幕的时候会有横向的坐标x与纵向的坐标值y,x与y与中点会产生一个三角形,通过三角函数定理以及当前坐标我们可以获取到sin值,通过反三角函数Math.asin方法通过sin值我们可以计算出滑动角度。滑动过程中会角度会不断变化,角度变化的差值就决定了选择器的增加或减少。

触摸屏幕对角度的处理

switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
float mDownX = event.getX();
        float mDownY = event.getY();
initAngle = computeAngle(mDownX, mDownY);
        if (mDownX > mCenterX) {
initAngle = 180 - initAngle;
}
break;
    case MotionEvent.ACTION_MOVE:
float mTouchX = event.getX();
        float mTouchY = event.getY();
        if (isTouch(mTouchX, mTouchY)) {
double moveAngle = computeAngle(mTouchX, mTouchY);
            if (mTouchX > mCenterX) {
moveAngle = 180 - moveAngle;
}
double tempAngle = moveAngle - initAngle;
            long addValue = Math.round(tempAngle * mEvenyScaleValue);
currentAngle += addValue;
            if (currentAngle >= -(mScaleNumber / 2) * mScaleSpace) {
invalidate();
} else {
currentAngle = currentAngle - addValue;
}
initAngle = moveAngle;
            return true;
}
break;
}


计算角度

private double computeAngle(float touchX, float touchY) {
double atan2 = Math.atan2(touchY, touchX);
    double taperedEdge = Math.sqrt(Math.pow(touchX - mCenterX, 2) + Math.pow(touchY - mCenterY, 2));//计算斜边
double sin = (touchY - mCenterY) / taperedEdge;
    double asin = Math.asin(sin);
    double calcArcAngle = calcArcAngle(asin);
    return calcArcAngle;
}


本文讲解了如何实现一个弧形刻度选择器,由于本人能力有限,必然存在很多不足,忘大家见解

?大家可以到https://github.com/yhongm/ArcScaleView下载完整的代码,在代码中有详细的注释。欢迎大家fork,star




















以上是关于安卓自定义弧形刻度选择器的主要内容,如果未能解决你的问题,请参考以下文章

安卓自定义弧形刻度选择器

安卓自定义半圆弧形菜单

安卓自定义半圆弧形菜单

VSCode自定义代码片段——CSS选择器

VSCode自定义代码片段6——CSS选择器

Android自定义View之区块选择器