一 天产品需求说需要实现一个量角器的功能:通过打开后摄,然后手动调节两条指针可以显示指针间的夹角。
一接到这个需求,脑海里面抛出几个问题:夹角怎么计算?还有那个两条指针滑动时跟随手指怎么变化怎么实现?
哎,不管了,先实现从简单到开始实现吧,先拆分任务:
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);
}