android 自定义View:量角器的实现

一 天产品需求说需要实现一个量角器的功能:通过打开后摄,然后手动调节两条指针可以显示指针间的夹角。

一接到这个需求,脑海里面抛出几个问题:夹角怎么计算?还有那个两条指针滑动时跟随手指怎么变化怎么实现?

哎,不管了,先实现从简单到开始实现吧,先拆分任务:

                                                 1、实现相机的预览功能。

                                                 2、指针跟随手指的变化来移动。

                                                 3、实现指针间的夹角。

相机预览功能

 //先打开相机设备
 public void openCamera(final SurfaceTexture surfaceTexture, String cameraId) {
        CameraManager cameraManager = (CameraManager) 
                      mContext.getSystemService(Context.CAMERA_SERVICE);
        mCameraId = cameraId;
        mHandlerThread = new HandlerThread("camera thread");
        mHandlerThread.start();
        mBgHandler = new Handler(mHandlerThread.getLooper());
     
        try {
            cameraManager.openCamera(cameraId, new CameraDevice.StateCallback() {
                @Override
                public void onOpened(@NonNull CameraDevice camera) {
                    mCameraDevice = camera;
                    Log.d(TAG, "onOpened");
                    if (mPause) {
                        camera.close();
                        Log.d(TAG, " onOpened pause = " + mPause);
                        return;
                    }

                    startPreview(surfaceTexture);
                    callBackCameraState(mCameraStateCallBack::openCameraCallBack, true);
                }

                @Override
                public void onDisconnected(@NonNull CameraDevice camera) {
                    Log.d(TAG, "onDisconnected");

                }

                @Override
                public void onError(@NonNull CameraDevice camera, int error) {
                    Log.e(TAG, "onError error = " + error);
                    callBackCameraState(mCameraStateCallBack::openCameraCallBack, false);

                }
            }, mBgHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
  
//开始启动预览
 private void startPreview(final SurfaceTexture surfaceTexture) {
        Log.d(TAG, "startPreview mIsPreview = " + mIsPreview);
        if (mIsPreview) {
            return;
        }
        List<Surface> surfaceList = new ArrayList<>();
        final Surface surface = new Surface(surfaceTexture);
        surfaceList.add(surface);
        try {
            mCameraDevice.createCaptureSession(surfaceList, new 
                    CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                   startConfigured(session, surface);
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                    Log.d(TAG, "onConfigureFailed ");
                    callBackCameraState(mCameraStateCallBack::startPreviewCallBack, false);
                }
            }, mBgHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

    }

   //预览成功之后开始配置参数
    private void startConfigured(CameraCaptureSession session, Surface surface) {
        Log.d(TAG, "onConfigured");
        mSession = session;
        try {
            if (mPause) {
                Log.d(TAG, "onConfigured mpause = " + mPause);
                mSession.close();
                mCameraDevice.close();
                return;
            }

            mBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mBuilder.addTarget(surface);
            mBuilder.set(CaptureRequest.JPEG_ORIENTATION, 270);
            mBuilder.set(CaptureRequest.CONTROL_SCENE_MODE, 
                        CaptureRequest.CONTROL_SCENE_MODE_FACE_PRIORITY);
            mBuilder.set(CaptureRequest.CONTROL_AF_MODE,  
                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO);
            mSession.setRepeatingRequest(mBuilder.build(), new 
            CameraCaptureSession.CaptureCallback() {
                @Override
                public void onCaptureStarted(@NonNull CameraCaptureSession session,
                                             @NonNull CaptureRequest request, long 
                                                     timestamp,
                                             long frameNumber) {
                    super.onCaptureStarted(session, request, timestamp, frameNumber);

                }
            }, mBgHandler);

            mIsPreview = true;
            callBackCameraState(mCameraStateCallBack::startPreviewCallBack, true);

        } catch (CameraAccessException e) {
            e.printStackTrace();
            callBackCameraState(mCameraStateCallBack::startPreviewCallBack, false);
        }

    }

一段操作之后相机预览的框架是实现了。

接下来实现指针功能,先思考一下问题,指针应该是绕着一个中心点摆来摆去的,如果点击位置离指针位置太远无法滑动。先实现个自定义view,继承普通的View好了,重先 onTouch 方法以及 onDraw 方法

@Override
public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //down 时判断是否重新绘制,绘制时绘制那一条指针
                computerMoveDirection(new PointF(event.getX(), event.getY()));
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            default:break;
        }

        return true;
 }

    private void computerMoveDirection(PointF downPoint) {
        moveType = 0;
        //mEndPoint1 表示指针1末端的坐标, mEndPoint2 表示指针2末端的坐标
         //  mCenterPoint 表示两条指针饶折中心转的坐标
        double distanceToLine1 = pointToLine(mCenterPoint, mEndPoint1, downPoint);
        double distanceToLine2 = pointToLine(mCenterPoint, mEndPoint2, downPoint);
        if (distanceToLine1 < MOVE_DISTANCE) {
            moveType = 1;
        }
        if (distanceToLine1 > distanceToLine2 && distanceToLine2 < MOVE_DISTANCE) {
            moveType = 2;
        }
    }


// 点到直线的最短距离的判断 点(x0,y0) 到由两点组成的线段(x1,y1) ,( x2,y2 )
//设计上个世纪学的数学知识?
private double pointToLine(PointF centerPoint, PointF endPoint, PointF downPoint) {
        double space = 0;
        double a, b, c;
        a = lineSpace(centerPoint, endPoint);// 线段的长度
        b = lineSpace(centerPoint, downPoint);// (x1,y1)到点的距离
        c = lineSpace(endPoint, downPoint);// (x2,y2)到点的距离
        if (c <= 0.000001 || b <= 0.000001) {
            space = 0;
            return space;
        }
        if (a <= 0.000001) {
            space = b;
            return space;
        }
        if (c * c >= a * a + b * b) {
            space = b;
            return space;
        }
        if (b * b >= a * a + c * c) {
            space = c;
            return space;
        }
        double p = (a + b + c) / 2;// 半周长
        double s = Math.sqrt(p * (p - a) * (p - b) * (p - c));// 海伦公式求面积
        space = 2 * s / a;// 返回点到线的距离(利用三角形面积公式求高)
        return space;
  }


// 计算两点之间的距离,这貌似是初中还是高中的知识?
 private double lineSpace(PointF point1, PointF point2 ) {
        double lineLength = 0;
        lineLength = Math.sqrt((point1.x - point2.x) *(point1.x - point2.x) + (point1.y - 
                    point2.y)* (point1.y - point2.y));
        return lineLength;
    }

这么一大段操作下来就是了计算down事件是否相应:down坐标距离两条指针的距离是否小于 MOVE_DISTANCE,小于该值就相应。那个手指在滑动时指针应该是跟随着转动的,确定一个指针需要两个坐标点,中心点是已经知道而且不变的,现在需要确定应该是在 move 时间中计算:

@Override
 public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                computerMoveDirection(new PointF(event.getX(), event.getY()));
                break;
            case MotionEvent.ACTION_MOVE:
                PointF pointF = new PointF(event.getX(), event.getY());
                computerAngle(pointF);
                break;
            default:break;
        }

        return true;
    }
 
private void computerAngle(PointF pointF) {
        if (moveType == 1) {
            mEndPoint1 = computerEndPoint(pointF);
            mDegree = computerAngle(mCenterPoint.x, mCenterPoint.y, mEndPoint1.x,  
                                   mEndPoint1.y,  mEndPoint2.x, mEndPoint2.y);
            mDegreeL = computerAngle(mCenterPoint.x, mCenterPoint.y, mEndPoint1.x, 
                                    mEndPoint1.y, mPointLeft.x, mPointLeft.y);
            invalidate();
            mMoveAngleCallBack.angleCallBack(mDegree);
        } else if (moveType == 2){
            mEndPoint2 = computerEndPoint(pointF);
            mDegree = computerAngle(mCenterPoint.x, mCenterPoint.y, mEndPoint1.x,  
                      mEndPoint1.y,  mEndPoint2.x, mEndPoint2.y);
            mDegreeR = computerAngle(mCenterPoint.x, mCenterPoint.y, mPointLeft.x, 
                       mPointLeft.y,  mEndPoint2.x, mEndPoint2.y);
            invalidate();
            mMoveAngleCallBack.angleCallBack(mDegree);
        }

    }

这里对各个角、线进行说明吧

 

o 点的坐标是 mCenterPoint, mPointLeft 是 C 点的坐标,mEndPoint1是点 A 的坐标, mEndPoint2 是点 B 的坐标,

mDegreeL 是 ∠COA 的角度,mDegreeR 是 ∠COB 的角度, mDegree 是 ∠AOB 的角度。

现在知道了角度已经坐标,只剩下怎么去绘制了。

 @Override
    public void draw(Canvas canvas) {
        if (mPaint == null) {
            mPaint = new Paint();
            mPaint.setStrokeWidth(5);
            mPaint.setColor(Color.RED);
            mPaint.setAntiAlias(true);
            //计算中心点
            mCenterPoint = new PointF(getWidth() / 2f, getHeight() * 1.0f - 
                                      mPaddingBottom);
            mPointLeft = new PointF(0,getHeight() * 1.0f - mPaddingBottom);
            mEndPoint1 = new PointF(mCenterPoint.x,  mCenterPoint.y - distance);
            mEndPoint2 = new PointF(mCenterPoint.x,  mCenterPoint.y - distance);
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inSampleSize = 2;
            bitmap = BitmapFactory.decodeResource(getResources(), 
                                    R.drawable.pointer_icon, options);
            Matrix matrix = new Matrix();
            //逆时针旋转 90°
            matrix.postRotate(-90, bitmap.getWidth(), bitmap.getHeight());
            Bitmap rotateBitmap = Bitmap.createBitmap(bitmap, 0, 0,bitmap.getWidth(), 
                                  bitmap.getHeight(),matrix, true);
            bitmap.recycle();
            bitmap = rotateBitmap;

            mCenterDotBitmap = BitmapFactory.decodeResource(getResources(), 
                                R.drawable.center_dot_icon, options);
        }


        if (mDebug) {
            canvas.drawLine(mCenterPoint.x, mCenterPoint.y, mEndPoint1.x, mEndPoint1.y, 
                            mPaint);
            canvas.drawLine(mCenterPoint.x, mCenterPoint.y, mEndPoint2.x, mEndPoint2.y, 
                            mPaint);
        }


        //将bitmap 绘制在指针 1 的位置(平移、旋转)
        Matrix matrix = new Matrix();
        int offsetX = bitmap.getWidth() ;
        int offsetY = bitmap.getHeight() ;
        matrix.preTranslate(mCenterPoint.x - offsetX, mCenterPoint.y - offsetY / 2f);
        matrix.postRotate(mDegreeL, mCenterPoint.x, mCenterPoint.y);
        canvas.drawBitmap(bitmap, matrix, mPaint);

        //将bitmap 绘制在指针 3 的位置(平移、旋转)
        Matrix matrixR = new Matrix();
        matrixR.preTranslate(mCenterPoint.x - offsetX,mCenterPoint.y  - offsetY / 2f);
        matrixR.postRotate(mDegreeR, mCenterPoint.x, mCenterPoint.y);
        canvas.drawBitmap(bitmap, matrixR, mPaint);


        int  width = mCenterDotBitmap.getWidth();
        int height = mCenterDotBitmap.getHeight();
        canvas.drawBitmap(mCenterDotBitmap, mCenterPoint.x - width / 2, mCenterPoint.y - 
                          height / 2, mPaint);

        super.draw(canvas);
    }

github地址:https://github.com/dengjiangdun/Protractor

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值