自定义view,类似ps中的曲线调节功能,忘了截图,以下源码。
public class GridDragView extends View {
private int dp_4;//4dp
private int width;//view宽度,设置width = height
private int line_length;//绘制区域大小
private int line_weight_light;//细线
private int line_weight_bold;//粗线
private Paint mPaint;//画主要部分
private PointF left_top, right_top, left_bottom, right_bottom;//view顶点
private int radius;//小圆圈半径
private float line_gap;//网格距离
private DashPathEffect dashPathEffect;//绘制虚线
private PointF controlPoint;//控制点,即手指按下部位
private Paint beislPaint;//绘制曲线
private Path mPath;
private int textSize;
private String text_coordinate = "";
public GridDragView(Context context) {
this(context, null);
}
public GridDragView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public GridDragView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
DensityUtils densityUtils = new DensityUtils();//工具类,获取屏幕参数,dp2px等
// screenWidth = mDensityUtils.getScreenWidth(context);
line_weight_bold = densityUtils.dip2px(context, 2);
line_weight_light = densityUtils.dip2px(context, 1);
radius = densityUtils.dip2px(context, 7);
dp_4 = densityUtils.dip2px(context, 4);
textSize = densityUtils.dip2px(context, 12);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(Color.WHITE);
mPaint.setStrokeWidth(line_weight_bold);
beislPaint = new Paint();
beislPaint.setAntiAlias(true);
beislPaint.setDither(true);
beislPaint.setColor(Color.WHITE);
beislPaint.setStrokeWidth(line_weight_bold);
beislPaint.setStyle(Paint.Style.STROKE);
left_top = new PointF();
left_bottom = new PointF();
right_bottom = new PointF();
right_top = new PointF();
dashPathEffect = new DashPathEffect(new float[]{16, 8}, 0);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
controlPoint = new PointF();
mPath = new Path();
}
private static final String TAG = "GridDragView";
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = MeasureSpec.getSize(widthMeasureSpec);
if (BuildConfig.DEBUG) {
Log.e(TAG, "onMeasure: width" + width);
}
line_length = (int) (width * 0.95);//设置绘制区域是view大小的95%,不充满
//初始化坐标
line_gap = line_length / 4f;
left_top.set((width - line_length) / 2f, (width - line_length) / 2f);
right_top.set((width + line_length) / 2f, (width - line_length) / 2f);
left_bottom.set((width - line_length) / 2f, (width + line_length) / 2f);
right_bottom.set((width + line_length) / 2f, (width + line_length) / 2f);
if (BuildConfig.DEBUG){
Log.e(TAG, "onMeasure: ltx:"+left_top.x+" lty:"+left_top.y+" rbx:"+right_bottom.x+" rby:"+right_bottom.y);
}
//初始化控制点
controlPoint.set(width / 2f, width / 2f);
setMeasuredDimension(width, width);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画外轮廓
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawLine(left_top.x, left_top.y, right_top.x, right_top.y, mPaint);
canvas.drawLine(right_top.x, right_top.y, right_bottom.x, right_bottom.y, mPaint);
canvas.drawLine(right_bottom.x, right_bottom.y, left_bottom.x, left_bottom.y, mPaint);
canvas.drawLine(left_bottom.x, left_bottom.y, left_top.x, left_top.y, mPaint);
canvas.drawCircle(right_top.x, right_top.y, radius, mPaint);
canvas.drawCircle(left_bottom.x, left_bottom.y, radius, mPaint);
//画网格
mPaint.setStrokeWidth(line_weight_light);
float startX = left_top.x;
float startY = left_top.y;
for (int i = 0; i < 3; i++) {
startX = startX + line_gap;
canvas.drawLine(startX, left_top.y, startX, left_bottom.y, mPaint);
}
for (int i = 0; i < 3; i++) {
startY = startY + line_gap;
canvas.drawLine(left_top.x, startY, right_top.x, startY, mPaint);
}
// canvas.drawLine(left_bottom.x,left_bottom.y,right_top.x,right_top.y,mPaint);
//画虚线
mPaint.setPathEffect(dashPathEffect);
canvas.drawLine(left_bottom.x, left_bottom.y, right_top.x, right_top.y, mPaint);
mPaint.setPathEffect(null);
mPaint.setStrokeWidth(line_weight_bold);
// mPath.reset();
mPath.moveTo(left_bottom.x, left_bottom.y);
// mPath.quadTo(controlPoint.x, controlPoint.y, right_top.x, right_top.y);
canvas.drawPath(mPath, beislPaint);
mPaint.setStyle(Paint.Style.FILL);
//画触摸点
canvas.drawCircle(controlPoint.x, controlPoint.y, radius, mPaint);
//画坐标值text
if (null != text_coordinate)
drawText(canvas, controlPoint, radius, mPaint);
}
private void drawText(Canvas canvas, PointF controlPoint, int radius, Paint paint) {
paint.setTextSize(textSize);
float textWidth = mPaint.measureText(text_coordinate);
// 文字baseline在y轴方向的位置
float baseLineY = Math.abs(mPaint.ascent() + mPaint.descent()) / 2;
float sx ;
float sy = controlPoint.y + baseLineY;
if (sy >= (width + line_length - dp_4) / 2f) {
sy = (width + line_length - dp_4) / 2f;
}
if (sy <= (width - line_length) / 2f + baseLineY * 3) {
sy = (width - line_length) / 2f + baseLineY * 3;
}
if (controlPoint.x + controlPoint.y <= line_length) {
sx = controlPoint.x + radius * 2;
} else {
sx = controlPoint.x - radius * 2 - textWidth;
}
canvas.drawText(text_coordinate, sx, sy, paint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
mPath.reset();//reset方法应该放在此处,因为path初始化是在下面的initSuffix方法中,如果在onDraw或initSuffix中调用,则会将已经计算好的path复位,导致绘制无效
mPath.moveTo(left_bottom.x, left_bottom.y);//应在此处调用,否则会画一条(0,0)过来的线
controlPoint.x = (int) event.getX();
controlPoint.y = (int) event.getY();
//约束x
if (controlPoint.x > (width + line_length) / 2f)
controlPoint.x = (width + line_length) / 2f;
if (controlPoint.x < (width - line_length) / 2f)
controlPoint.x = (width - line_length) / 2f;
if (controlPoint.y > (width + line_length) / 2f)
controlPoint.y = (width + line_length) / 2f;
if (controlPoint.y < (width - line_length) / 2f)
controlPoint.y = (width + line_length) / 2f;
//计算y
initSuffix();
if (mOnCurveChangeListener != null) {
text_coordinate = mOnCurveChangeListener.getFixedCoordinate(controlPoint,left_bottom,right_top);
mOnCurveChangeListener.onCurveDragged(text_coordinate);
}
invalidate();
break;
}
return true;
}
//求方程系数,假设函数解析式 y = a*x*x + b * x + c,此处使用二次函数曲线,没有使用二阶贝塞尔曲线,因为贝塞尔曲线控制点不在曲线上,导致曲线上的坐标获取困难,没想到好的方案
private void initSuffix() {
double temp1 = (left_bottom.y - right_top.y) * (left_bottom.x - controlPoint.x) -
(left_bottom.y - controlPoint.y) * (left_bottom.x - right_top.x);
double temp2 = (left_bottom.x * left_bottom.x - right_top.x * right_top.x) * (left_bottom.x - controlPoint.x) -
(left_bottom.x * left_bottom.x - controlPoint.x * controlPoint.x) * (left_bottom.x - right_top.x);
if (temp2 != 0) {
//系数a
double a = temp1 / temp2;
double tmp3 = left_bottom.y - right_top.y - a * (left_bottom.x * left_bottom.x - right_top.x * right_top.x);
double tmp4 = left_bottom.x - right_top.x;
if (tmp4 != 0) {
//系数b
double b = tmp3 / tmp4;
//系数c
double c = left_bottom.y - left_bottom.x * left_bottom.x * a - left_bottom.x * b;
for (float i = left_bottom.x; i <= right_top.x; i += 3) {
double y = a * i * i + b * i + c;
//约束y的值,其中加或减去dp_4是一个经验值,避免与边框重合
if (y <= (width - line_length + dp_4) / 2f) {
y = (width - line_length + dp_4) / 2f;
}
if (y >= (width + line_length - dp_4) / 2f) {
y = (width + line_length - dp_4) / 2f;
}
mPath.lineTo(i, (float) y);//key
}
}
}
}
private OnCurveChangeListener mOnCurveChangeListener;
public void setOnCurveChangeListener(OnCurveChangeListener onCurveChangeListener) {
mOnCurveChangeListener = onCurveChangeListener;
}
public interface OnCurveChangeListener {
//触摸点改变
void onCurveDragged(String coordinate);
//将触摸点坐标转换成对应的值(根据需要)
String getFixedCoordinate(PointF contrlPoint,PointF leftBottom,PointF rightTop);
}
}