Commit dfa400a3 authored by 姜天宇's avatar 姜天宇

feat(v1.0.2): 增加信息页、注册页、裁剪页

1.增加系统信息页
2.增加设置-注册页
3.对接注册、找回接口
4.增加秤盘裁剪页
parent d1d6a28b
......@@ -12,7 +12,9 @@ import android.util.Size;
import androidx.camera.camera2.interop.Camera2Interop;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraInfo;
import androidx.camera.core.CameraProvider;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.CameraState;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageProxy;
import androidx.camera.core.Preview;
......@@ -50,6 +52,10 @@ public class CameraxController {
* 获取相机服务provider
*/
private ListenableFuture<ProcessCameraProvider> cameraProviderFutureListener;
/**
* 相机
*/
private Camera camera;
/**
* 相机的生命周期
*/
......@@ -66,6 +72,14 @@ public class CameraxController {
* 图片分析回调
*/
private OnImageAnalyzeListener onImageAnalyzeListener;
/**
* 帧间隔
*/
private int frameInterval = 5;
/**
* 相机状态
*/
private CameraState cameraState = null;
// private SurfaceTexture mColorSurfaceTexture = new SurfaceTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
......@@ -110,6 +124,16 @@ public class CameraxController {
return this;
}
/**
* 设置帧间隔
* @param frameInterval
* @return
*/
public CameraxController setFrameInterval(int frameInterval) {
this.frameInterval = frameInterval;
return this;
}
/**
* 相机初始化
*/
......@@ -121,8 +145,10 @@ public class CameraxController {
// 创建图像分析器
ImageAnalysis imageAnalysis = createImageAnalysis();
// 绑定相机
Camera camera = bindCamera(cameraProvider, imageAnalysis);
camera = bindCamera(cameraProvider, imageAnalysis);
if (camera != null) {
// 禁用自动对焦
camera.getCameraControl().cancelFocusAndMetering();
// 观察相机状态
observeCameraLifecycle(camera);
}
......@@ -151,21 +177,10 @@ public class CameraxController {
long startTime = System.currentTimeMillis();
// 处理图片
count ++;
if (count % 5 == 0) {
if (count % frameInterval == 0) {
analyzeImage(imageProxy);
}
imageProxy.close();
// 休眠
/*long costTime = System.currentTimeMillis() - startTime;
// System.out.println("处理时间:" + costTime);
long leftTime = INTERVAL_FRAMES_ANALYZE - costTime;
if (leftTime <= INTERVAL_FRAMES_ANALYZE && leftTime >= 0){
try {
Thread.sleep(leftTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}*/
});
return imageAnalysis;
......@@ -224,9 +239,14 @@ public class CameraxController {
*/
private void observeCameraLifecycle(Camera camera){
camera.getCameraInfo().getCameraState().removeObservers(lifecycleOwner);
camera.getCameraInfo().getCameraState().observe(lifecycleOwner, cameraState -> {
XLog.i("相机状态:" + cameraState.getType());
switch (cameraState.getType()){
camera.getCameraInfo().getCameraState().observe(lifecycleOwner, state -> {
XLog.i("相机状态:" + state.getType());
cameraState = state;
switch (state.getType()){
case OPEN:
// 相机开启
break;
case CLOSED:
// 相机关闭
break;
......@@ -235,9 +255,43 @@ public class CameraxController {
break;
}
if (cameraState.getError() != null){
XLog.i("相机异常:" + cameraState.getType());
if (state.getError() != null){
XLog.i("相机异常:" + state.getType());
}
});
}
/**
* 相机是否开启
* @return
*/
public boolean isOpen(){
if (cameraState == null){
return false;
}
if (cameraState.getType() == CameraState.Type.OPEN || cameraState.getType() == CameraState.Type.OPENING){
return true;
}
return false;
}
public void release(){
ProcessCameraProvider cameraProvider = null;
try {
cameraProvider = cameraProviderFutureListener.get();
} catch (Exception e) {
XLog.e(e.toString());
}
if (camera != null){
// 移除camera得生命周期监听
camera.getCameraInfo().getCameraState().removeObservers(lifecycleOwner);
}
if (cameraProvider != null){
cameraProvider.unbindAll();
}
onImageAnalyzeListener = null;
cameraState = null;
executors.shutdown();
instance = null;
}
}
package com.wmdigit.common.model;
/**
* 裁剪坐标
* @author dizi
*/
public class CropValue {
private int left, top, width, height;
public CropValue(int left, int top, int width, int height) {
this.left = left;
this.top = top;
this.width = width;
this.height = height;
}
public int getLeft() {
return left;
}
public void setLeft(int left) {
this.left = left;
}
public int getTop() {
return top;
}
public void setTop(int top) {
this.top = top;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
package com.wmdigit.common.utils;
import android.os.Parcel;
import android.os.Parcelable;
/**
* @author dizi
*/
public class ParcelHelper {
/**
* 深度拷贝
* @param input
* @return
* @param <T>
*/
public static <T> T copy(Parcelable input) {
Parcel parcel = null;
try {
parcel = Parcel.obtain();
parcel.writeParcelable(input, 0);
parcel.setDataPosition(0);
return parcel.readParcelable(input.getClass().getClassLoader());
} finally {
if (parcel!=null){
parcel.recycle();
}
}
}
}
package com.wmdigit.common.view.imageview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.util.AttributeSet;
import androidx.databinding.BindingAdapter;
import androidx.databinding.InverseBindingAdapter;
import androidx.databinding.InverseBindingListener;
import com.elvishew.xlog.XLog;
import com.wmdigit.common.model.CropValue;
/**
* 支持双向数据绑定的CropView
* @author dizi
*/
public class BidirectionalDataBindingCropView extends CropView{
private static InverseBindingListener inverseBindingListener;
public BidirectionalDataBindingCropView(Context context) {
super(context);
this.setOnActionUpListener(rect -> {
if (inverseBindingListener != null){
inverseBindingListener.onChange();
}
});
}
public BidirectionalDataBindingCropView(Context context, AttributeSet attrs) {
super(context, attrs);
this.setOnActionUpListener(rect -> {
if (inverseBindingListener != null){
inverseBindingListener.onChange();
}
});
}
public BidirectionalDataBindingCropView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.setOnActionUpListener(rect -> {
if (inverseBindingListener != null){
inverseBindingListener.onChange();
}
});
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
inverseBindingListener = null;
this.setOnActionUpListener(null);
}
/**
* 设置图片帧
* @param view
* @param bitmap
*/
@BindingAdapter("frame")
public static void setFrame(CropView view, Bitmap bitmap){
if (bitmap != null && !bitmap.isRecycled()){
view.setImageBitmap(bitmap);
}
}
/**
* 预设裁剪坐标
* @param view
* @param value
*/
@BindingAdapter("preset_crop_value")
public static void presetCropValue(CropView view, CropValue value){
if (value != null){
view.setPresetCropCoordinate(new Rect(value.getLeft(), value.getTop(), value.getWidth() + value.getLeft(), value.getHeight() + value.getTop()));
}
}
@InverseBindingAdapter(attribute = "preset_crop_value", event = "preset_crop_valueAttrChanged")
public static CropValue getCropValue(CropView view){
Rect rect = view.getCropRect();
CropValue cropValue = new CropValue( 0, 0, 0, 0);
if (rect != null){
cropValue.setLeft(rect.left);
cropValue.setTop(rect.top);
cropValue.setWidth(rect.width());
cropValue.setHeight(rect.height());
}
return cropValue;
}
@BindingAdapter(value = {"preset_crop_valueAttrChanged"})
public static void setCropValueChanged(CropView view, InverseBindingListener listener){
if (listener != null) {
inverseBindingListener = listener;
}
}
}
package com.wmdigit.common.view.imageview;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.animation.DecelerateInterpolator;
import androidx.annotation.ColorInt;
import androidx.annotation.UiThread;
import androidx.appcompat.widget.AppCompatImageView;
import com.wmdigit.common.R;
import com.wmdigit.common.utils.SizeUtils;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
public class CropView extends AppCompatImageView {
private final static String TAG = CropView.class.getSimpleName();
private final static int HANDLE_SIZE = 24;
private final static int HANDLE_WIDTH = 2;
private final static int FRAME_MIN_SIZE = 50;
private final static int FRAME_STROKE_WEIGHT = 1;
private final static int GUIDE_STROKE_WEIGHT = 1;
private final static float DEFAULT_INITIAL_SCALE = 1.0f;
// 颜色值,包含透明度
private static final int TRANSPARENT = 0x00000000;
private static final int TRANSLUCENT_WHITE = 0xBBFFFFFF;
private static final int WHITE = 0xFFFFFFFF;
private static final int TRANSLUCENT_BLACK = 0xBB000000;
private static final int ORANGE = 0xFFF19520;
// 默认的动画时长
private static final int DEFAULT_ANIMATION_DURATION_MILLIS = 100;
// view的宽高
private int mViewWidth = 0;
private int mViewHeight = 0;
// 放大比例
private float mCropScale = 1.0f;
// 旋转角度
private float mImgAngle = 0.0f;
// 图片宽度
private float mImgWidth = 0.0f;
// 图片高度
private float mImgHeight = 0.0f;
// 四角线的长度值
private int mHandleSize;
// 四角的线宽度
private int mHandleWidth;
// 触摸可处理的宽度值
private int mTouchPadding = 0;
// 裁剪框最小值
private int mFrameMinSize;
// 裁剪外框线框宽度
private float mFrameStrokeWeight = 2f;
// 裁剪指导线宽度
private float mGuideStrokeWeight = 2f;
// 准星线宽度
private float mFrontSightWeight = 4f;
// 外框画笔
private Paint mFramePaint;
// 半透明区域画笔
private Paint mTranslucentPaint;
// bitmap画笔
private Paint mBitmapPaint;
// 准星画笔
private Paint mFrontSightPaint;
// 背景的颜色
private int mBackgroundColor;
// 半透明框的颜色
private int mOverlayColor;
// 外框的颜色
private int mFrameColor;
// 四边角点的颜色
private int mHandleColor;
// 指导线颜色
private int mGuideColor;
// 准星颜色
private int mFrontSightColor;
// 裁剪模式
private CropModeEnum mCropMode = CropModeEnum.RATIO_2_3;
// 指导线的显示模式
private ShowModeEnum mGuideShowMode = ShowModeEnum.SHOW_ALWAYS;
// 处理四个角的显示模式
private ShowModeEnum mHandleShowMode = ShowModeEnum.SHOW_ALWAYS;
// 触摸的情况
private TouchAreaEnum mTouchArea = TouchAreaEnum.OUT_OF_BOUNDS;
// 居中中点的矩型区域
private PointF mCenter = new PointF();
// 图形的矩阵类
private Matrix mMatrix = null;
// 图片的rectf区域
private RectF mImageRectF;
// 裁剪框的RectF
private RectF mFrameRectF;
// 初始化的比例大小0.01到1.0,默认情况为0.75
private float mInitialFrameScale;
// 是否初始化
private boolean mIsInitialized;
// 是否开始裁剪
private boolean mIsCropEnabled = true;
// 是否显示指导线
private boolean mShowGuide = true;
// 是否显示处理线
private boolean mShowHandle = true;
private float mLastX, mLastY;
// 旋转的动画
private ValueAnimator mValueAnimator = null;
// 裁剪框放大的动画
private ValueAnimator mFrameValueAnimator = null;
private boolean mIsAnimationEnabled = true;
private int mAnimationDurationMillis = DEFAULT_ANIMATION_DURATION_MILLIS;
private boolean mIsRotating = false;
private boolean mIsAnimating = false;
private OnCropListener mOnCropListener;
// 是否已经翻转了
private boolean mIsReverseY = false;
// 是否有旋转操作
private boolean mIsRotated = false;
// 旋转操作的开关
private boolean mRotateSwitch = true;
private Rect cropRect;
private Rect presetCropCoordinate = null;
// 是否允许移动裁剪框
private boolean moveCropRectEnable = true;
public interface OnActionUpListener{
void onActionUp(Rect rect);
}
private OnActionUpListener onActionUpListener;
public void setOnActionUpListener(OnActionUpListener onActionUpListener) {
this.onActionUpListener = onActionUpListener;
}
public CropView(Context context) {
this(context, null);
}
public CropView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CropView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mHandleSize = SizeUtils.dp2px(HANDLE_SIZE);
mFrameMinSize = SizeUtils.dp2px(FRAME_MIN_SIZE);
mFrameStrokeWeight = SizeUtils.dp2px(FRAME_STROKE_WEIGHT);
mGuideStrokeWeight = SizeUtils.dp2px(GUIDE_STROKE_WEIGHT);
mFramePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTranslucentPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mFrontSightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
// 抗锯齿处理
mBitmapPaint.setFilterBitmap(true);
mMatrix = new Matrix();
mCropScale = 1.0f;
mBackgroundColor = TRANSPARENT;
mFrameColor = WHITE;
mOverlayColor = TRANSLUCENT_BLACK;
mHandleColor = WHITE;
mGuideColor = TRANSLUCENT_WHITE;
mFrontSightColor = ORANGE;
loadStyleable(context, attrs, defStyleAttr);
mValueAnimator = ValueAnimator.ofFloat(0, 1f);
mValueAnimator.setInterpolator(new DecelerateInterpolator());
mValueAnimator.setDuration(mAnimationDurationMillis);
mFrameValueAnimator = ValueAnimator.ofFloat(0, 1f);
mFrameValueAnimator.setInterpolator(new DecelerateInterpolator());
mFrameValueAnimator.setDuration(mAnimationDurationMillis);
}
public void setBgColor(@ColorInt int backgroundColor) {
mBackgroundColor = backgroundColor;
invalidate();
}
/**
* 加载Style自定义属性数据
*
* @param context
* @param attrs
* @param defStyleAttr
*/
private void loadStyleable(Context context, AttributeSet attrs, int defStyleAttr) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CropView, defStyleAttr, 0);
Drawable drawable;
mCropMode = CropModeEnum.SQUARE;
try {
drawable = ta.getDrawable(R.styleable.CropView_img_src);
if (drawable != null)
setImageDrawable(drawable);
for (CropModeEnum mode : CropModeEnum.values()) {
if (ta.getInt(R.styleable.CropView_crop_mode, 3) == mode.getID()) {
mCropMode = mode;
break;
}
}
mBackgroundColor = ta.getColor(R.styleable.CropView_background_color, TRANSPARENT);
mOverlayColor = ta.getColor(R.styleable.CropView_overlay_color, TRANSLUCENT_BLACK);
mFrameColor = ta.getColor(R.styleable.CropView_framecolor, WHITE);
mHandleColor = ta.getColor(R.styleable.CropView_handle_color, WHITE);
mGuideColor = ta.getColor(R.styleable.CropView_guide_color, TRANSLUCENT_WHITE);
for (ShowModeEnum mode : ShowModeEnum.values()) {
if (ta.getInt(R.styleable.CropView_guide_show_mode, 1) == mode.getId()) {
mGuideShowMode = mode;
break;
}
}
for (ShowModeEnum mode : ShowModeEnum.values()) {
if (ta.getInt(R.styleable.CropView_handle_show_mode, 1) == mode.getId()) {
mHandleShowMode = mode;
break;
}
}
setGuideShowMode(mGuideShowMode);
setHandleShowMode(mHandleShowMode);
mHandleSize = ta.getDimensionPixelSize(R.styleable.CropView_handle_size, SizeUtils.dp2px(HANDLE_SIZE));
mHandleWidth = ta.getDimensionPixelSize(R.styleable.CropView_handle_width, SizeUtils.dp2px(HANDLE_WIDTH));
mTouchPadding = ta.getDimensionPixelSize(R.styleable.CropView_touch_padding, 0);
mFrameMinSize =
ta.getDimensionPixelSize(R.styleable.CropView_min_frame_size, SizeUtils.dp2px(FRAME_MIN_SIZE));
mFrameStrokeWeight =
ta.getDimensionPixelSize(R.styleable.CropView_frame_stroke_weight, SizeUtils.dp2px(FRAME_STROKE_WEIGHT));
mGuideStrokeWeight =
ta.getDimensionPixelSize(R.styleable.CropView_guide_stroke_weight, SizeUtils.dp2px(GUIDE_STROKE_WEIGHT));
mIsCropEnabled = ta.getBoolean(R.styleable.CropView_crop_enabled, true);
mInitialFrameScale =
constrain(ta.getFloat(R.styleable.CropView_initial_frame_scale, DEFAULT_INITIAL_SCALE), 0.01f, 1.0f,
DEFAULT_INITIAL_SCALE);
} catch (Exception e) {
e.printStackTrace();
} finally {
ta.recycle();
}
}
/**
* 设置是否允许移动裁剪框
* @param mIsAllowMoveCropRect
*/
public void setMoveCropRect(boolean mIsAllowMoveCropRect) {
this.moveCropRectEnable = mIsAllowMoveCropRect;
}
/**
* 设置指导线的显示模式
*
* @param guideShowMode
*/
public void setGuideShowMode(ShowModeEnum guideShowMode) {
mGuideShowMode = guideShowMode;
switch (guideShowMode) {
case SHOW_ALWAYS:
mShowGuide = true;
break;
case NOT_SHOW:
case SHOW_ON_TOUCH:
mShowGuide = false;
break;
}
invalidate();
}
/**
* 设置处理线的显示模式
*
* @param mode
*/
public void setHandleShowMode(ShowModeEnum mode) {
mHandleShowMode = mode;
switch (mode) {
case SHOW_ALWAYS:
mShowHandle = true;
break;
case NOT_SHOW:
case SHOW_ON_TOUCH:
mShowHandle = false;
break;
}
invalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int viewWidth = MeasureSpec.getSize(widthMeasureSpec);
final int viewHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(viewWidth, viewHeight);
mViewWidth = viewWidth - getPaddingLeft() - getPaddingRight();
mViewHeight = viewHeight - getPaddingTop() - getPaddingBottom();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (getDrawable() != null) {
doLayout(mViewWidth, mViewHeight);
}
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(mBackgroundColor);
if (mIsInitialized) {
setMatrix();
Bitmap bitmap = getBitmap();
if (bitmap != null) {
canvas.drawBitmap(bitmap, mMatrix, mBitmapPaint);
// 画裁剪框
drawCropFrame(canvas);
}
}
}
/**
* 画裁剪框
*
* @param canvas
*/
private void drawCropFrame(Canvas canvas) {
if (!mIsCropEnabled) {
// 如果不是裁剪模式,直接就不画裁剪框
return;
}
if (mIsRotating) {
// 如果正在旋转,不画裁剪框
return;
}
drawOverlay(canvas);
drawFrame(canvas);
drawFrontSight(canvas);
if (mShowGuide) {
drawGuidelines(canvas);
}
if (mShowHandle) {
drawHandleLines(canvas);
}
}
/**
* 绘制文字
* @param canvas
* @param text
*/
private void drawText(Canvas canvas, String text){
}
/**
* 画四角的线
*
* @param canvas
*/
private void drawHandleLines(Canvas canvas) {
mFramePaint.setColor(mHandleColor);
mFramePaint.setStyle(Paint.Style.FILL);
// 指导线最边界(最左/最右/最下/最上)的x和y
float handleLineLeftX = mFrameRectF.left - mHandleWidth;
float handleLineRightX = mFrameRectF.right + mHandleWidth;
float handleLineTopY = mFrameRectF.top - mHandleWidth;
float handleLineBottomY = mFrameRectF.bottom + mHandleWidth;
// 左上竖向
RectF ltRectFVertical =
new RectF(handleLineLeftX, handleLineTopY, mFrameRectF.left, handleLineTopY + mHandleSize);
// 左上横向
RectF ltRectFHorizontal =
new RectF(handleLineLeftX, handleLineTopY, handleLineLeftX + mHandleSize, mFrameRectF.top);
RectF rtRectFHorizontal =
new RectF(handleLineRightX - mHandleSize, handleLineTopY, handleLineRightX, mFrameRectF.top);
RectF rtRectFVertical =
new RectF(mFrameRectF.right, handleLineTopY, handleLineRightX, handleLineTopY + mHandleSize);
RectF lbRectFVertical =
new RectF(handleLineLeftX, handleLineBottomY - mHandleSize, mFrameRectF.left, mFrameRectF.bottom);
RectF lbRectFHorizontal =
new RectF(handleLineLeftX, mFrameRectF.bottom, handleLineLeftX + mHandleSize, handleLineBottomY);
RectF rbRectFVertical =
new RectF(mFrameRectF.right, handleLineBottomY - mHandleSize, handleLineRightX, handleLineBottomY);
RectF rbRectFHorizontal =
new RectF(handleLineRightX - mHandleSize, mFrameRectF.bottom, handleLineRightX, handleLineBottomY);
canvas.drawRect(ltRectFVertical, mFramePaint);
canvas.drawRect(ltRectFHorizontal, mFramePaint);
canvas.drawRect(rtRectFVertical, mFramePaint);
canvas.drawRect(rtRectFHorizontal, mFramePaint);
canvas.drawRect(lbRectFVertical, mFramePaint);
canvas.drawRect(lbRectFHorizontal, mFramePaint);
canvas.drawRect(rbRectFVertical, mFramePaint);
canvas.drawRect(rbRectFHorizontal, mFramePaint);
if (mCropMode == CropModeEnum.FREE) {
// 如果当前是自由模式
mFramePaint.setStrokeCap(Paint.Cap.ROUND);
mFramePaint.setStrokeWidth(mHandleWidth + SizeUtils.dp2px(2));
float centerX = mFrameRectF.left + mFrameRectF.width() / 2;
float centerY = mFrameRectF.top + mFrameRectF.height() / 2;
canvas.drawLine(centerX - mHandleSize, mFrameRectF.top, (centerX + mHandleSize), mFrameRectF.top,
mFramePaint);
canvas.drawLine(centerX - mHandleSize, mFrameRectF.bottom, centerX + mHandleSize, mFrameRectF.bottom,
mFramePaint);
canvas.drawLine(mFrameRectF.left, (centerY - mHandleSize), mFrameRectF.left, centerY + mHandleSize,
mFramePaint);
canvas.drawLine(mFrameRectF.right, centerY - mHandleSize, mFrameRectF.right, centerY + mHandleSize,
mFramePaint);
}
}
/**
* 画准星
* @param canvas
*/
private void drawFrontSight(Canvas canvas){
mFrontSightPaint.setColor(mFrontSightColor);
mFrontSightPaint.setStrokeWidth(mFrontSightWeight);
int frontSightWidth = 30;
int frontSightHeight = 30;
// 准星水平线的起始坐标
float x1 = mFrameRectF.left + (mFrameRectF.width() / 2.0f) - frontSightWidth / 2.0f;
float y1 = mFrameRectF.top + (mFrameRectF.height()/ 2.0f);
// 准星水平线的终点坐标
float x2 = mFrameRectF.left + (mFrameRectF.width() / 2.0f) + frontSightWidth / 2.0f;
float y2 = mFrameRectF.top + (mFrameRectF.height()/ 2.0f);
// 准星垂直线的顶部坐标
float x3 = mFrameRectF.left + (mFrameRectF.width() / 2.0f);
float y3 = mFrameRectF.top + (mFrameRectF.height()/ 2.0f) - frontSightHeight / 2.0f;
// 准星垂直线的底部坐标
float x4 = mFrameRectF.left + (mFrameRectF.width() / 2.0f);
float y4 = mFrameRectF.top + (mFrameRectF.height()/ 2.0f) + frontSightHeight / 2.0f;
// 画水平线
canvas.drawLine(x1, y1, x2, y2, mFrontSightPaint);
// 画垂直线
canvas.drawLine(x3, y3, x4, y4, mFrontSightPaint);
}
/**
* 画指导线
*
* @param canvas
*/
private void drawGuidelines(Canvas canvas) {
mFramePaint.setColor(mGuideColor);
mFramePaint.setStrokeWidth(mGuideStrokeWeight);
// 从左往右第一个竖线的横坐标
float x1 = mFrameRectF.left + (mFrameRectF.right - mFrameRectF.left) / 3.0f;
float x2 = mFrameRectF.right - (mFrameRectF.right - mFrameRectF.left) / 3.0f;
// 从上往下第一个横的y坐标
float y1 = mFrameRectF.top + (mFrameRectF.bottom - mFrameRectF.top) / 3.0f;
float y2 = mFrameRectF.bottom - (mFrameRectF.bottom - mFrameRectF.top) / 3.0f;
// 画竖线
canvas.drawLine(x1, mFrameRectF.top, x1, mFrameRectF.bottom, mFramePaint);
canvas.drawLine(x2, mFrameRectF.top, x2, mFrameRectF.bottom, mFramePaint);
// 画横线
canvas.drawLine(mFrameRectF.left, y1, mFrameRectF.right, y1, mFramePaint);
canvas.drawLine(mFrameRectF.left, y2, mFrameRectF.right, y2, mFramePaint);
}
/**
* 画裁剪框边界线
*
* @param canvas
*/
private void drawFrame(Canvas canvas) {
mFramePaint.setStyle(Paint.Style.STROKE);
mFramePaint.setFilterBitmap(true);
mFramePaint.setColor(mFrameColor);
mFramePaint.setStrokeWidth(mFrameStrokeWeight);
canvas.drawRect(mFrameRectF, mFramePaint);
}
/**
* 画裁剪框的半透明覆盖层
*
* @param canvas
*/
private void drawOverlay(Canvas canvas) {
mTranslucentPaint.setStyle(Paint.Style.FILL);
mTranslucentPaint.setFilterBitmap(true);
mTranslucentPaint.setColor(mOverlayColor);
Path path = new Path();
RectF overlayRectF = new RectF();
overlayRectF.set(mImageRectF);
path.addRect(mFrameRectF, Path.Direction.CW);
path.addRect(overlayRectF, Path.Direction.CCW);
canvas.drawPath(path, mTranslucentPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 如果还没初始化,直接跳过触摸事件
if (!mIsInitialized) {
return false;
}
// 如果没有打开裁剪功能,那么也直接跳过触摸事件
if (!mIsCropEnabled) {
return false;
}
// 如果是在旋转,跳过触摸事件
if (mIsRotating) {
return false;
}
// 如果是在切换裁剪模式,跳过触摸事件
if (mIsAnimating) {
return false;
}
// 如果不允许移动裁剪窗口,跳过触摸事件
if (!moveCropRectEnable){
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
onActionDown(event);
return true;
}
case MotionEvent.ACTION_MOVE: {
onActionMove(event);
if (mTouchArea != TouchAreaEnum.OUT_OF_BOUNDS) {
// 阻止父view拦截点击事件
getParent().requestDisallowInterceptTouchEvent(true);
}
return true;
}
case MotionEvent.ACTION_CANCEL: {
getParent().requestDisallowInterceptTouchEvent(true);
onActionCancel();
return true;
}
case MotionEvent.ACTION_UP: {
getParent().requestDisallowInterceptTouchEvent(true);
onActionUp();
return true;
}
default: {
break;
}
}
return false;
}
private void onActionUp() {
if (mGuideShowMode == ShowModeEnum.SHOW_ON_TOUCH) {
mShowGuide = false;
}
if (mHandleShowMode == ShowModeEnum.SHOW_ON_TOUCH){
mShowHandle = false;
}
mTouchArea = TouchAreaEnum.OUT_OF_BOUNDS;
// 回调通知手部抬起
if (onActionUpListener != null){
cropRect = null;
// 计算矩形
Bitmap source = getBitmap();
if (source != null && !source.isRecycled()){
cropRect = calcCropRect(source.getWidth(), source.getHeight());
}
onActionUpListener.onActionUp(cropRect);
}
invalidate();
}
private void onActionCancel() {
mTouchArea = TouchAreaEnum.OUT_OF_BOUNDS;
invalidate();
}
private void onActionMove(MotionEvent event) {
float diffX = event.getX() - mLastX;
float diffY = event.getY() - mLastY;
// 区分点击的区域进行移动
switch (mTouchArea) {
case CENTER: {
moveFrame(diffX, diffY);
break;
}
case LEFT_TOP: {
moveHandleLeftTop(diffX, diffY);
break;
}
case RIGHT_TOP: {
moveHandleRightTop(diffX, diffY);
break;
}
case LEFT_BOTTOM: {
moveHandleLeftBottom(diffX, diffY);
break;
}
case RIGHT_BOTTOM: {
moveHandleRightBottom(diffX, diffY);
break;
}
case CENTER_LEFT: {
moveHandleCenterLeft(diffX);
break;
}
case CENTER_TOP: {
moveHandleCenterTop(diffY);
break;
}
case CENTER_RIGHT: {
moveHandleCenterRight(diffX);
break;
}
case CENTER_BOTTOM: {
moveHandleCenterBottom(diffY);
break;
}
case OUT_OF_BOUNDS: {
break;
}
}
invalidate();
mLastX = event.getX();
mLastY = event.getY();
}
private void moveHandleCenterBottom(float diffY) {
if (mCropMode == CropModeEnum.FREE) {
mFrameRectF.bottom += diffY;
if (isHeightTooSmall()) {
float offsetY = mFrameMinSize - mFrameRectF.height();
mFrameRectF.bottom += offsetY;
}
checkScaleBounds();
}
}
private void moveHandleCenterRight(float diffX) {
if (mCropMode == CropModeEnum.FREE) {
mFrameRectF.right += diffX;
if (isWidthTooSmall()) {
float offsetX = mFrameMinSize - mFrameRectF.width();
mFrameRectF.right += offsetX;
}
checkScaleBounds();
}
}
private void moveHandleCenterTop(float diffY) {
if (mCropMode == CropModeEnum.FREE) {
mFrameRectF.top += diffY;
if (isHeightTooSmall()) {
float offsetY = mFrameMinSize - mFrameRectF.height();
mFrameRectF.top -= offsetY;
}
checkScaleBounds();
}
}
private void moveHandleCenterLeft(float diffX) {
if (mCropMode == CropModeEnum.FREE) {
mFrameRectF.left += diffX;
if (isWidthTooSmall()) {
float offsetX = mFrameMinSize - mFrameRectF.width();
mFrameRectF.left -= offsetX;
}
checkScaleBounds();
}
}
private void moveHandleRightTop(float diffX, float diffY) {
if (mCropMode == CropModeEnum.FREE) {
mFrameRectF.right += diffX;
mFrameRectF.top += diffY;
if (isWidthTooSmall()) {
float offsetX = mFrameMinSize - mFrameRectF.width();
mFrameRectF.right += offsetX;
}
if (isHeightTooSmall()) {
float offsetY = mFrameMinSize - mFrameRectF.height();
mFrameRectF.top -= offsetY;
}
checkScaleBounds();
} else {
float dx = diffX;
float dy = diffX * getRatioY() / getRatioX();
mFrameRectF.right += dx;
mFrameRectF.top -= dy;
if (isWidthTooSmall()) {
float offsetX = mFrameMinSize - mFrameRectF.width();
mFrameRectF.right += offsetX;
float offsetY = offsetX * getRatioY() / getRatioX();
mFrameRectF.top -= offsetY;
}
if (isHeightTooSmall()) {
float offsetY = mFrameMinSize - mFrameRectF.height();
mFrameRectF.top -= offsetY;
float offsetX = offsetY * getRatioX() / getRatioY();
mFrameRectF.right += offsetX;
}
float ox, oy;
if (!isInsideX(mFrameRectF.right)) {
ox = mFrameRectF.right - mImageRectF.right;
mFrameRectF.right -= ox;
oy = ox * getRatioY() / getRatioX();
mFrameRectF.top += oy;
}
if (!isInsideY(mFrameRectF.top)) {
oy = mImageRectF.top - mFrameRectF.top;
mFrameRectF.top += oy;
ox = oy * getRatioX() / getRatioY();
mFrameRectF.right -= ox;
}
}
}
private void moveHandleLeftBottom(float diffX, float diffY) {
if (mCropMode == CropModeEnum.FREE) {
mFrameRectF.left += diffX;
mFrameRectF.bottom += diffY;
if (isWidthTooSmall()) {
float offsetX = mFrameMinSize - mFrameRectF.width();
mFrameRectF.left -= offsetX;
}
if (isHeightTooSmall()) {
float offsetY = mFrameMinSize - mFrameRectF.height();
mFrameRectF.bottom += offsetY;
}
checkScaleBounds();
} else {
float dx = diffX;
float dy = diffX * getRatioY() / getRatioX();
mFrameRectF.left += dx;
mFrameRectF.bottom -= dy;
if (isWidthTooSmall()) {
float offsetX = mFrameMinSize - mFrameRectF.width();
mFrameRectF.left -= offsetX;
float offsetY = offsetX * getRatioY() / getRatioX();
mFrameRectF.bottom += offsetY;
}
if (isHeightTooSmall()) {
float offsetY = mFrameMinSize - mFrameRectF.height();
mFrameRectF.bottom += offsetY;
float offsetX = offsetY * getRatioX() / getRatioY();
mFrameRectF.left -= offsetX;
}
float ox, oy;
if (!isInsideX(mFrameRectF.left)) {
ox = mImageRectF.left - mFrameRectF.left;
mFrameRectF.left += ox;
oy = ox * getRatioY() / getRatioX();
mFrameRectF.bottom -= oy;
}
if (!isInsideY(mFrameRectF.bottom)) {
oy = mFrameRectF.bottom - mImageRectF.bottom;
mFrameRectF.bottom -= oy;
ox = oy * getRatioX() / getRatioY();
mFrameRectF.left += ox;
}
}
}
private void moveHandleRightBottom(float diffX, float diffY) {
if (mCropMode == CropModeEnum.FREE) {
mFrameRectF.right += diffX;
mFrameRectF.bottom += diffY;
if (isWidthTooSmall()) {
float offsetX = mFrameMinSize - mFrameRectF.width();
mFrameRectF.right += offsetX;
}
if (isHeightTooSmall()) {
float offsetY = mFrameMinSize - mFrameRectF.height();
mFrameRectF.bottom += offsetY;
}
checkScaleBounds();
} else {
float dx = diffX;
float dy = diffX * getRatioY() / getRatioX();
mFrameRectF.right += dx;
mFrameRectF.bottom += dy;
if (isWidthTooSmall()) {
float offsetX = mFrameMinSize - mFrameRectF.width();
mFrameRectF.right += offsetX;
float offsetY = offsetX * getRatioY() / getRatioX();
mFrameRectF.bottom += offsetY;
}
if (isHeightTooSmall()) {
float offsetY = mFrameMinSize - mFrameRectF.height();
mFrameRectF.bottom += offsetY;
float offsetX = offsetY * getRatioX() / getRatioY();
mFrameRectF.right += offsetX;
}
float ox, oy;
if (!isInsideX(mFrameRectF.right)) {
ox = mFrameRectF.right - mImageRectF.right;
mFrameRectF.right -= ox;
oy = ox * getRatioY() / getRatioX();
mFrameRectF.bottom -= oy;
}
if (!isInsideY(mFrameRectF.bottom)) {
oy = mFrameRectF.bottom - mImageRectF.bottom;
mFrameRectF.bottom -= oy;
ox = oy * getRatioX() / getRatioY();
mFrameRectF.right -= ox;
}
}
}
private void moveHandleLeftTop(float diffX, float diffY) {
if (mCropMode == CropModeEnum.FREE) {
mFrameRectF.left += diffX;
mFrameRectF.top += diffY;
if (isWidthTooSmall()) {
float offsetX = mFrameMinSize - mFrameRectF.width();
mFrameRectF.left -= offsetX;
}
if (isHeightTooSmall()) {
float offsetY = mFrameMinSize - mFrameRectF.height();
mFrameRectF.top -= offsetY;
}
checkScaleBounds();
} else {
float dx = diffX;
float dy = diffX * getRatioY() / getRatioX();
mFrameRectF.left += dx;
mFrameRectF.top += dy;
// 控制缩放边界
if (isWidthTooSmall()) {
float offsetX = mFrameMinSize - mFrameRectF.width();
mFrameRectF.left -= offsetX;
float offsetY = offsetX * getRatioY() / getRatioX();
mFrameRectF.top -= offsetY;
}
if (isHeightTooSmall()) {
float offsetY = mFrameMinSize - mFrameRectF.height();
mFrameRectF.top -= offsetY;
float offsetX = offsetY * getRatioX() / getRatioY();
mFrameRectF.left -= offsetX;
}
//限制不能让裁剪框超出图片边界
float ox, oy;
if (!isInsideX(mFrameRectF.left)) {
ox = mImageRectF.left - mFrameRectF.left;
mFrameRectF.left += ox;
oy = ox * getRatioY() / getRatioX();
mFrameRectF.top += oy;
}
if (!isInsideY(mFrameRectF.top)) {
oy = mImageRectF.top - mFrameRectF.top;
mFrameRectF.top += oy;
ox = oy * getRatioX() / getRatioY();
mFrameRectF.left += ox;
}
}
}
/**
* 检查缩放边界
*/
private void checkScaleBounds() {
float lDiff = mFrameRectF.left - mImageRectF.left;
float rDiff = mFrameRectF.right - mImageRectF.right;
float tDiff = mFrameRectF.top - mImageRectF.top;
float bDiff = mFrameRectF.bottom - mImageRectF.bottom;
if (lDiff < 0) {
mFrameRectF.left -= lDiff;
}
if (rDiff > 0) {
mFrameRectF.right -= rDiff;
}
if (tDiff < 0) {
mFrameRectF.top -= tDiff;
}
if (bDiff > 0) {
mFrameRectF.bottom -= bDiff;
}
}
/**
* x 是否在图片内
*
* @param x
* @return
*/
private boolean isInsideX(float x) {
return mImageRectF.left <= x && mImageRectF.right >= x;
}
private boolean isInsideY(float y) {
return mImageRectF.top <= y && mImageRectF.bottom >= y;
}
private boolean isHeightTooSmall() {
return mFrameRectF.height() < mFrameMinSize;
}
private boolean isWidthTooSmall() {
return mFrameRectF.width() < mFrameMinSize;
}
private void moveFrame(float x, float y) {
// 1.先平移
mFrameRectF.left += x;
mFrameRectF.right += x;
mFrameRectF.top += y;
mFrameRectF.bottom += y;
// 2.判断有没有超出界外,如果超出则后退
handleMoveBounds();
}
/**
* 控制不超出界外
*/
private void handleMoveBounds() {
float diff = mFrameRectF.left - mImageRectF.left;
if (diff < 0) {
mFrameRectF.left -= diff;
mFrameRectF.right -= diff;
}
diff = mFrameRectF.right - mImageRectF.right;
if (diff > 0) {
mFrameRectF.left -= diff;
mFrameRectF.right -= diff;
}
diff = mFrameRectF.top - mImageRectF.top;
if (diff < 0) {
mFrameRectF.top -= diff;
mFrameRectF.bottom -= diff;
}
diff = mFrameRectF.bottom - mImageRectF.bottom;
if (diff > 0) {
mFrameRectF.top -= diff;
mFrameRectF.bottom -= diff;
}
}
private void onActionDown(MotionEvent event) {
invalidate();
mLastX = event.getX();
mLastY = event.getY();
handleTouchArea(mLastX, mLastY);
}
/**
* <Strong>控制指导线或者边框线的显示</Strong>
* <p></p>
* 处理触摸的边界来控制指导线或者边框线的显示,共5种,四个角和里面的中心部分
*
* @param x
* @param y
*/
private void handleTouchArea(float x, float y) {
if (isInLeftTopCorner(x, y)) {
mTouchArea = TouchAreaEnum.LEFT_TOP;
handleGuideAndHandleMode();
return;
}
if (isInRightTopCorner(x, y)) {
mTouchArea = TouchAreaEnum.RIGHT_TOP;
handleGuideAndHandleMode();
return;
}
if (isInLeftBottomCorner(x, y)) {
mTouchArea = TouchAreaEnum.LEFT_BOTTOM;
handleGuideAndHandleMode();
return;
}
if (isInRightBottomCorner(x, y)) {
mTouchArea = TouchAreaEnum.RIGHT_BOTTOM;
handleGuideAndHandleMode();
return;
}
if (isInCenterLeftCorner(x, y)) {
mTouchArea = TouchAreaEnum.CENTER_LEFT;
handleGuideAndHandleMode();
return;
}
if (isInCenterTopCorner(x, y)) {
mTouchArea = TouchAreaEnum.CENTER_TOP;
handleGuideAndHandleMode();
return;
}
if (isInCenterRightCorner(x, y)) {
mTouchArea = TouchAreaEnum.CENTER_RIGHT;
handleGuideAndHandleMode();
return;
}
if (isInCenterBottomCorner(x, y)) {
mTouchArea = TouchAreaEnum.CENTER_BOTTOM;
handleGuideAndHandleMode();
return;
}
if (isInFrameCenter(x, y)) {
if (mGuideShowMode == ShowModeEnum.SHOW_ON_TOUCH) {
mShowGuide = true;
}
mTouchArea = TouchAreaEnum.CENTER;
return;
}
// 默认情况
mTouchArea = TouchAreaEnum.OUT_OF_BOUNDS;
}
private boolean isInCenterBottomCorner(float x, float y) {
float dx = x - (mFrameRectF.left + mFrameRectF.width() / 2);
float dy = y - mFrameRectF.bottom;
return isInsideBound(dx, dy);
}
private boolean isInCenterRightCorner(float x, float y) {
float dx = x - mFrameRectF.right;
float dy = y - (mFrameRectF.top + mFrameRectF.height() / 2);
return isInsideBound(dx, dy);
}
private boolean isInCenterTopCorner(float x, float y) {
float dx = x - (mFrameRectF.left + mFrameRectF.width() / 2);
float dy = y - mFrameRectF.top;
return isInsideBound(dx, dy);
}
private boolean isInCenterLeftCorner(float x, float y) {
float dx = x - mFrameRectF.left;
float dy = y - (mFrameRectF.top + mFrameRectF.height() / 2);
return isInsideBound(dx, dy);
}
/**
* 处理指导线和四角线的显示模式
*/
private void handleGuideAndHandleMode() {
if (mHandleShowMode == ShowModeEnum.SHOW_ON_TOUCH) {
mShowHandle = true;
}
if (mGuideShowMode == ShowModeEnum.SHOW_ON_TOUCH) {
mShowGuide = true;
}
}
private boolean isInLeftTopCorner(float x, float y) {
float dx = x - mFrameRectF.left;
float dy = y - mFrameRectF.top;
return isInsideBound(dx, dy);
}
private boolean isInRightTopCorner(float x, float y) {
float dx = x - mFrameRectF.right;
float dy = y - mFrameRectF.top;
return isInsideBound(dx, dy);
}
private boolean isInLeftBottomCorner(float x, float y) {
float dx = x - mFrameRectF.left;
float dy = y - mFrameRectF.bottom;
return isInsideBound(dx, dy);
}
private boolean isInRightBottomCorner(float x, float y) {
float dx = x - mFrameRectF.right;
float dy = y - mFrameRectF.bottom;
return isInsideBound(dx, dy);
}
private boolean isInsideBound(float dx, float dy) {
float d = (float) (Math.pow(dx, 2) + Math.pow(dy, 2));
return (Math.pow(mHandleSize + mTouchPadding, 2)) >= d;
}
private boolean isInFrameCenter(float x, float y) {
if (mFrameRectF.left <= x && mFrameRectF.right >= x) {
if (mFrameRectF.top <= y && mFrameRectF.bottom >= y) {
mTouchArea = TouchAreaEnum.CENTER;
return true;
}
}
return false;
}
/**
* 限制范围
*
* @param val
* @param min
* @param max
* @param defaultVal
* @return
*/
private float constrain(float val, float min, float max, float defaultVal) {
if (val < min || val > max)
return defaultVal;
return val;
}
/**
* 获取当前View显示的的bitmap
*
* @return
*/
private Bitmap getBitmap() {
Bitmap bm = null;
Drawable d = getDrawable();
if (d instanceof BitmapDrawable){
bm = ((BitmapDrawable) d).getBitmap();
}
if (bm != null && bm.isRecycled()){
return null;
}
return bm;
}
/**
* 设置预设裁剪坐标
* @param presetCropCoordinate
*/
public void setPresetCropCoordinate(Rect presetCropCoordinate) {
this.presetCropCoordinate = presetCropCoordinate;
initialCropRectCoordinate();
invalidate();
}
public void initialCropRectCoordinate(){
if (presetCropCoordinate == null){
return;
}
int l = presetCropCoordinate.left;
int t = presetCropCoordinate.top;
int w = presetCropCoordinate.right - l;
int h = presetCropCoordinate.bottom - t;
Bitmap source = getBitmap();
if (source == null){
return;
}
// Bitmap rotated = getRotatedBitmap(source);
// cropRect = calcCropRect(source.getWidth(), source.getHeight());
float rotatedWidth = getRotatedWidth(mImgAngle, source.getWidth(), source.getHeight());
float rotatedHeight = getRotatedHeight(mImgAngle, source.getWidth(), source.getHeight());
float scaleForOriginal = rotatedWidth / mImageRectF.width();
float scaleForOriginalHeight = rotatedHeight / mImageRectF.height();
float offsetX = mImageRectF.left * scaleForOriginal;
float offsetY = mImageRectF.top * scaleForOriginalHeight;
int originalLeft = Math.round((l + offsetX) / scaleForOriginal);
int originalTop = Math.round((t + offsetY) / scaleForOriginalHeight);
int originalRight= Math.round((w + offsetX) / scaleForOriginal) + originalLeft;
int originalBottom=Math.round((h + offsetY) / scaleForOriginalHeight) + originalTop;
mFrameRectF.left = Math.max(originalLeft, 0);
mFrameRectF.top = Math.max(originalTop, 0);
mFrameRectF.right = Math.min(originalRight, mImageRectF.right);
mFrameRectF.bottom = Math.min(originalBottom, mImageRectF.bottom);
}
/**
* 对图片进行布局
*
* @param viewWidth
* @param viewHeight
*/
public void doLayout(int viewWidth, int viewHeight) {
if (viewHeight == 0 || viewWidth == 0) {
viewHeight = mViewHeight;
viewWidth = mViewWidth;
}
if (viewHeight == 0 || viewWidth == 0){
return;
}
PointF pointF = new PointF(getPaddingLeft() + viewWidth * 0.5f, getPaddingTop() + viewHeight * 0.5f);
setCenter(pointF);
setScale(calcScale(viewWidth, viewHeight, mImgAngle));
setMatrix();
RectF rectF = new RectF(0, 0, mImgWidth, mImgHeight);
mImageRectF = calcImageRect(rectF, mMatrix);
mFrameRectF = calculateFrameRect(mImageRectF);
// 加载预设的裁剪坐标
if (presetCropCoordinate != null){
initialCropRectCoordinate();
}
mIsInitialized = true;
invalidate();
}
/**
* 重设矩阵,平移缩放旋转操作
*/
private void setMatrix() {
mMatrix.reset();
mMatrix.setTranslate(mCenter.x - mImgWidth * 0.5f, mCenter.y - mImgHeight * 0.5f);
mMatrix.postScale(mCropScale, mCropScale, mCenter.x, mCenter.y);
if (mRotateSwitch) {
mMatrix.postRotate(mImgAngle, mCenter.x, mCenter.y);
}
}
private RectF calculateFrameRect(RectF imageRect) {
Log.d("image", "width:" + imageRect.width() + "height" + imageRect.height());
float frameW = getRatioX(imageRect.width());
float frameH = getRatioY(imageRect.height());
float frameRatio = frameW / frameH;
float imgRatio = imageRect.width() / imageRect.height();
float l = imageRect.left, t = imageRect.top, r = imageRect.right, b = imageRect.bottom;
if (frameRatio >= imgRatio) {
//宽比长比例大于img图宽高比的情况
l = imageRect.left;
r = imageRect.right;
//图的中点
float hy = (imageRect.top + imageRect.bottom) * 0.5f;
//中点到上下顶点坐标的距离
float hh = (imageRect.width() / frameRatio) * 0.5f;
t = hy - hh;
b = hy + hh;
} else if (frameRatio < imgRatio) {
//宽比长比例大于img图宽高比的情况
t = imageRect.top;
b = imageRect.bottom;
float hx = (imageRect.left + imageRect.right) * 0.5f;
float hw = imageRect.height() * frameRatio * 0.5f;
l = hx - hw;
r = hx + hw;
}
//裁剪框宽度
float w = r - l;
//高度
float h = b - t;
//中心点
float cx = l + w / 2;
float cy = t + h / 2;
//放大后的裁剪框的宽高
float sw = w * mInitialFrameScale;
float sh = h * mInitialFrameScale;
return new RectF(cx - sw / 2, cy - sh / 2, cx + sw / 2, cy + sh / 2);
}
private float getRatioX(float w) {
switch (mCropMode) {
case FIT_IMAGE:
return mImageRectF.width();
case FREE:
return w;
case RATIO_2_3:
return 2;
case RATIO_3_2:
return 3;
case RATIO_4_3:
return 4;
case RATIO_3_4:
return 3;
case RATIO_16_9:
return 16;
case RATIO_9_16:
return 9;
case SQUARE:
return 1;
default:
return w;
}
}
private float getRatioY(float h) {
switch (mCropMode) {
case FIT_IMAGE:
return mImageRectF.height();
case FREE:
return h;
case RATIO_2_3:
return 3;
case RATIO_3_2:
return 2;
case RATIO_4_3:
return 3;
case RATIO_3_4:
return 4;
case RATIO_16_9:
return 9;
case RATIO_9_16:
return 16;
case SQUARE:
return 1;
default:
return h;
}
}
private float getRatioX() {
switch (mCropMode) {
case FIT_IMAGE:
return mImageRectF.width();
case RATIO_2_3:
return 2;
case RATIO_3_2:
return 3;
case RATIO_4_3:
return 4;
case RATIO_3_4:
return 3;
case RATIO_16_9:
return 16;
case RATIO_9_16:
return 9;
case SQUARE:
return 1;
default:
return 1;
}
}
private float getRatioY() {
switch (mCropMode) {
case FIT_IMAGE:
return mImageRectF.height();
case RATIO_2_3:
return 3;
case RATIO_3_2:
return 2;
case RATIO_4_3:
return 3;
case RATIO_3_4:
return 4;
case RATIO_16_9:
return 9;
case RATIO_9_16:
return 16;
case SQUARE:
return 1;
default:
return 1;
}
}
/**
* 将Matrix映射到rect
*
* @param rect
* @param matrix
* @return
*/
private RectF calcImageRect(RectF rect, Matrix matrix) {
RectF applied = new RectF();
matrix.mapRect(applied, rect);
return applied;
}
private float calcScale(int viewWidth, int viewHeight, float angle) {
mImgWidth = getDrawable().getIntrinsicWidth();
mImgHeight = getDrawable().getIntrinsicHeight();
if (mImgWidth <= 0) {
mImgWidth = viewWidth;
}
if (mImgHeight <= 0) {
mImgHeight = viewHeight;
}
float viewRatio = (float) viewWidth / (float) viewHeight;
float imgRatio = getRotatedWidth(angle) / getRotatedHeight(angle);
float scale = 1.0f;
if (imgRatio >= viewRatio) {
scale = viewWidth / getRotatedWidth(angle);
} else if (imgRatio < viewRatio) {
scale = viewHeight / getRotatedHeight(angle);
}
return scale;
}
private float getRotatedWidth(float angle) {
return getRotatedWidth(angle, mImgWidth, mImgHeight);
}
private float getRotatedWidth(float angle, float width, float height) {
return angle % 180 == 0 ? width : height;
}
private float getRotatedHeight(float angle) {
return getRotatedHeight(angle, mImgWidth, mImgHeight);
}
private float getRotatedHeight(float angle, float width, float height) {
return angle % 180 == 0 ? height : width;
}
/**
* 保存中心矩阵
*
* @param center
*/
private void setCenter(PointF center) {
mCenter = center;
}
/**
* 保存比例
*
* @param scale
*/
private void setScale(float scale) {
mCropScale = scale;
}
/**
* 旋转图片
*
* @param degrees
*/
public void rotateImage(RotateDegreesEnum degrees) {
// 标记有旋转操作
mIsRotated = true;
if (mIsReverseY) {
degrees = reverseDegrees(degrees);
}
rotateImage(degrees, mAnimationDurationMillis);
}
/**
* 将翻转方向取反
*
* @param degrees
* @return
*/
private RotateDegreesEnum reverseDegrees(RotateDegreesEnum degrees) {
// 处理翻转后的旋转方向改变问题
switch (degrees) {
case ROTATE_90D: {
degrees = RotateDegreesEnum.ROTATE_M90D;
break;
}
case ROTATE_M90D: {
degrees = RotateDegreesEnum.ROTATE_90D;
break;
}
case ROTATE_180D: {
degrees = RotateDegreesEnum.ROTATE_M180D;
break;
}
case ROTATE_M180D: {
degrees = RotateDegreesEnum.ROTATE_180D;
break;
}
case ROTATE_270D: {
degrees = RotateDegreesEnum.ROTATE_M270D;
break;
}
case ROTATE_M270D: {
degrees = RotateDegreesEnum.ROTATE_270D;
break;
}
}
return degrees;
}
/**
* 旋转图片
*
* @param degrees
* @param durationMillis 需要调整的动画时间
*/
private void rotateImage(RotateDegreesEnum degrees, int durationMillis) {
if (mIsRotating) {
mValueAnimator.cancel();
}
final float currentAngle = mImgAngle;
final float newAngle = (mImgAngle + degrees.getValue());
final float angleDiff = newAngle - currentAngle;
final float currentScale = mCropScale;
final float newScale = calcScale(mViewWidth, mViewHeight, newAngle);
if (mIsAnimationEnabled) {
final float scaleDiff = newScale - currentScale;
mValueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mImgAngle = newAngle % 360;
mCropScale = newScale;
doLayout(mViewWidth, mViewHeight);
mIsRotating = false;
}
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mIsRotating = true;
}
});
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float scale = (float) animation.getAnimatedValue();
mImgAngle = currentAngle + angleDiff * scale;
mCropScale = currentScale + scaleDiff * scale;
setMatrix();
invalidate();
}
});
mValueAnimator.setDuration(durationMillis);
mValueAnimator.start();
} else {
mImgAngle = newAngle % 360;
mCropScale = newScale;
doLayout(mViewWidth, mViewHeight);
}
}
/**
* 左右翻转
*/
public void reverseY() {
if (!mIsReverseY) {
mIsReverseY = true;
} else {
mIsReverseY = false;
}
super.setScaleX(getScaleX() * -1f);
}
public boolean isFirstSetImageBitmap = true;
private Bitmap lastBitmap = null;
@Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
if (isFirstSetImageBitmap){
isFirstSetImageBitmap = false;
invalidate();
}
if (lastBitmap != null && !lastBitmap.isRecycled()){
// 回收上一帧
lastBitmap.recycle();
}
lastBitmap = bm;
}
public Rect getCropRect() {
return cropRect;
}
/**
* 裁剪图像返回裁剪后的bitmap
* <p>
* <Strong>NOTICE:工作在异步线程</Strong>
*
* @return
*/
public void cropImage(OnCropListener onCropListener) {
addOnCropListener(onCropListener);
Observable.create(new ObservableOnSubscribe<Object>() {
@Override
public void subscribe(ObservableEmitter<Object> emitter) throws Exception {
Bitmap cropped = getCroppedBitmap();
if (mOnCropListener != null) {
mOnCropListener.onCropFinished(cropped, cropRect.left, cropRect.top, cropRect.width(), cropRect.height());
}
}})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Object>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Object o) {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
}
/**
* @return
*/
public Bitmap getCroppedBitmap() {
Bitmap source = getBitmap();
if (source == null) {
return null;
}
Bitmap rotated = getRotatedBitmap(source);
cropRect = calcCropRect(source.getWidth(), source.getHeight());
Bitmap cropped =
Bitmap.createBitmap(rotated, cropRect.left, cropRect.top, cropRect.width(), cropRect.height(), null, false);
if (rotated != cropped && rotated != source) {
rotated.recycle();
}
if (mIsReverseY) {
// 如果翻转了照片,那么对照片进行翻转处理
cropped = reverseImg(cropped, -1, 1);
}
return cropped;
}
private Rect calcCropRect(int originalImageWidth, int originalImageHeight) {
float rotatedWidth = getRotatedWidth(mImgAngle, originalImageWidth, originalImageHeight);
float rotatedHeight = getRotatedHeight(mImgAngle, originalImageWidth, originalImageHeight);
float scaleForOriginal = rotatedWidth / mImageRectF.width();
float offsetX = mImageRectF.left * scaleForOriginal;
float offsetY = mImageRectF.top * scaleForOriginal;
int left = Math.round(mFrameRectF.left * scaleForOriginal - offsetX);
int top = Math.round(mFrameRectF.top * scaleForOriginal - offsetY);
int right = Math.round(mFrameRectF.right * scaleForOriginal - offsetX);
int bottom = Math.round(mFrameRectF.bottom * scaleForOriginal - offsetY);
int imageW = Math.round(rotatedWidth);
int imageH = Math.round(rotatedHeight);
return new Rect(Math.max(left, 0), Math.max(top, 0), Math.min(right, imageW), Math.min(bottom, imageH));
}
/**
* 得到旋转后的Bitmap
*
* @param bitmap
* @return
*/
private Bitmap getRotatedBitmap(Bitmap bitmap) {
Matrix rotateMatrix = new Matrix();
rotateMatrix.postRotate(mImgAngle, bitmap.getWidth() / 2, bitmap.getHeight() / 2);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), rotateMatrix, true);
}
private void addOnCropListener(OnCropListener onCropListener) {
mOnCropListener = onCropListener;
}
@UiThread
public void setCropMode(CropModeEnum mode) {
setCropMode(mode, mAnimationDurationMillis);
}
/**
* 设置裁剪层的开关
*
* @param enabled
*/
@UiThread
public void setCropEnabled(boolean enabled) {
mIsCropEnabled = enabled;
invalidate();
}
private void setCropMode(CropModeEnum mode, int durationMillis) {
this.mCropMode = mode;
recalculateFrameRect(durationMillis);
}
private void recalculateFrameRect(int durationMillis) {
if (mImageRectF == null) {
return;
}
if (mIsAnimating) {
mFrameValueAnimator.cancel();
}
final RectF currentRect = new RectF(mFrameRectF);
final RectF newRect = calculateFrameRect(mImageRectF);
final float diffL = newRect.left - currentRect.left;
final float diffT = newRect.top - currentRect.top;
final float diffR = newRect.right - currentRect.right;
final float diffB = newRect.bottom - currentRect.bottom;
if (mIsAnimationEnabled) {
mFrameValueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mFrameRectF = newRect;
invalidate();
mIsAnimating = false;
}
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mIsAnimating = true;
}
});
mFrameValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float scale = (float) animation.getAnimatedValue();
// 裁剪框放大的动画RectF
mFrameRectF =
new RectF(currentRect.left + diffL * scale, currentRect.top + diffT * scale, currentRect.right
+ diffR * scale, currentRect.bottom + diffB * scale);
invalidate();
}
});
mFrameValueAnimator.setDuration(durationMillis);
mFrameValueAnimator.start();
} else {
mFrameRectF = calculateFrameRect(mImageRectF);
invalidate();
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mValueAnimator != null) {
mValueAnimator.cancel();
mValueAnimator = null;
}
if (mFrameValueAnimator != null) {
mFrameValueAnimator.cancel();
mFrameValueAnimator = null;
}
if (mOnCropListener != null) {
mOnCropListener = null;
}
}
/**
* 用于确认后显示预览图
*
* @param bitmap
*/
public void showBitmap(Bitmap bitmap) {
if (mIsRotated) {
mRotateSwitch = false;
}
this.setImageBitmap(bitmap);
// 排除翻转的左右翻转问题
if (mIsReverseY) {
// 恢复CropView的ScaleX值
this.setScaleX(this.getScaleX() * -1f);
}
}
/**
* 翻转照片到相应的尺寸
*
* @param bm
* @param x -1时为水平翻转
* @param y -1时为垂直翻转
* @return
*/
public Bitmap reverseImg(Bitmap bm, int x, int y) {
int width = bm.getWidth();
int height = bm.getHeight();
Matrix matrix = new Matrix();
matrix.postScale(x, 1);
matrix.postScale(1, y);
return Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true);
}
/**
* 手指点击的区域枚举类
*
* @author LiGuangMin
* @email lgm@meitu.com
* @time Created by 2018/8/22 19:00
*/
public enum TouchAreaEnum {
CENTER, LEFT_TOP, RIGHT_TOP, LEFT_BOTTOM, RIGHT_BOTTOM, OUT_OF_BOUNDS, CENTER_LEFT, CENTER_TOP, CENTER_RIGHT, CENTER_BOTTOM
}
/**
* 裁剪框的比例模式
*
* @author LiGuangMin
* @email lgm@meitu.com
* @time Created by 2018/8/22 19:01
*/
public enum CropModeEnum {
FIT_IMAGE(0), RATIO_2_3(1), RATIO_3_2(2), RATIO_4_3(3), RATIO_3_4(4), SQUARE(5), RATIO_16_9(6), RATIO_9_16(7), FREE(
8);
private final int ID;
CropModeEnum(int id) {
ID = id;
}
public int getID() {
return ID;
}
}
/**
* 旋转角度的枚举类
*
* @author LiGuangMin
* @email lgm@meitu.com
* @time Created by 2018/8/22 19:07
*/
public enum RotateDegreesEnum {
ROTATE_90D(90), ROTATE_180D(180), ROTATE_270D(270), ROTATE_M90D(-90), ROTATE_M180D(-180), ROTATE_M270D(-270), ROTATE_0D(
0);
private final int VALUE;
RotateDegreesEnum(final int value) {
this.VALUE = value;
}
public int getValue() {
return VALUE;
}
}
/**
* 展示模式,控制裁剪框显示的时机
*
* @author LiGuangMin
* @email lgm@meitu.com
* @time Created by 2018/8/22 20:44
*/
public enum ShowModeEnum {
SHOW_ALWAYS(1), SHOW_ON_TOUCH(2), NOT_SHOW(3);
private final int ID;
ShowModeEnum(final int id) {
this.ID = id;
}
public int getId() {
return ID;
}
}
public interface OnCropListener {
void onCropFinished(Bitmap bitmap, int left, int top, int width, int height);
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(state);
SavedState ss = (SavedState) state;
this.mCropMode = ss.mCropMode;
this.mImgAngle = ss.mImgAngle;
this.mImgWidth = ss.mImgWidth;
this.mImgHeight = ss.mImgHeight;
this.mHandleSize = ss.mHandleSize;
this.mHandleWidth = ss.mHandleWidth;
this.mTouchPadding = ss.mTouchPadding;
this.mFrameMinSize = ss.mFrameMinSize;
this.mFrameStrokeWeight = ss.mFrameStrokeWeight;
this.mGuideStrokeWeight = ss.mGuideStrokeWeight;
this.mBackgroundColor = ss.mBackgroundColor;
this.mOverlayColor = ss.mOverlayColor;
this.mFrameColor = ss.mFrameColor;
this.mHandleColor = ss.mHandleColor;
this.mGuideColor = ss.mGuideColor;
this.mCropMode = ss.mCropMode;
this.mGuideShowMode = ss.mGuideShowMode;
this.mHandleShowMode = ss.mHandleShowMode;
this.mInitialFrameScale = ss.mInitialFrameScale;
this.mIsInitialized = ss.mIsInitialized;
this.mIsCropEnabled = ss.mIsCropEnabled;
this.mShowGuide = ss.mShowGuide;
this.mShowHandle = ss.mShowHandle;
this.mIsRotating = ss.mIsRotating;
this.mIsAnimating = ss.mIsAnimating;
this.mIsReverseY = ss.mIsReverseY;
this.mIsRotated = ss.mIsRotated;
this.mRotateSwitch = ss.mRotateSwitch;
}
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superParcelable = super.onSaveInstanceState();
SavedState ss = new SavedState(superParcelable);
ss.mCropMode = this.mCropMode;
ss.mImgAngle = this.mImgAngle;
ss.mImgWidth = this.mImgWidth;
ss.mImgHeight = this.mImgHeight;
ss.mHandleSize = this.mHandleSize;
ss.mHandleWidth = this.mHandleWidth;
ss.mTouchPadding = this.mTouchPadding;
ss.mFrameMinSize = this.mFrameMinSize;
ss.mFrameStrokeWeight = this.mFrameStrokeWeight;
ss.mGuideStrokeWeight = this.mGuideStrokeWeight;
ss.mBackgroundColor = this.mBackgroundColor;
ss.mOverlayColor = this.mOverlayColor;
ss.mFrameColor = this.mFrameColor;
ss.mHandleColor = this.mHandleColor;
ss.mGuideColor = this.mGuideColor;
ss.mCropMode = this.mCropMode;
ss.mGuideShowMode = this.mGuideShowMode;
ss.mHandleShowMode = this.mHandleShowMode;
ss.mInitialFrameScale = this.mInitialFrameScale;
ss.mIsInitialized = this.mIsInitialized;
ss.mIsCropEnabled = this.mIsCropEnabled;
ss.mShowGuide = this.mShowGuide;
ss.mShowHandle = this.mShowHandle;
ss.mIsRotating = this.mIsRotating;
ss.mIsAnimating = this.mIsAnimating;
ss.mIsReverseY = this.mIsReverseY;
ss.mIsRotated = this.mIsRotated;
ss.mRotateSwitch = this.mRotateSwitch;
return ss;
}
public static class SavedState extends BaseSavedState {
// 放大比例
private float mCropScale = 1.0f;
// 旋转角度
private float mImgAngle = 0.0f;
// 图片宽度
private float mImgWidth = 0.0f;
// 图片高度
private float mImgHeight = 0.0f;
// 四角线的长度值
private int mHandleSize;
// 四角的线宽度
private int mHandleWidth;
// 触摸可处理的宽度值
private int mTouchPadding = 0;
// 裁剪框最小值
private int mFrameMinSize;
// 裁剪外框线框宽度
private float mFrameStrokeWeight = 2f;
// 裁剪指导线宽度
private float mGuideStrokeWeight = 2f;
// 背景的颜色
private int mBackgroundColor;
// 半透明框的颜色
private int mOverlayColor;
// 外框的颜色
private int mFrameColor;
// 四边角点的颜色
private int mHandleColor;
// 指导线颜色
private int mGuideColor;
// 裁剪模式
private CropModeEnum mCropMode;
// 指导线的显示模式
private ShowModeEnum mGuideShowMode;
// 处理四个角的显示模式
private ShowModeEnum mHandleShowMode;
// 初始化的比例大小0.01到1.0,默认情况为0.75
private float mInitialFrameScale;
// 是否初始化
private boolean mIsInitialized;
// 是否开始裁剪
private boolean mIsCropEnabled = true;
// 是否显示指导线
private boolean mShowGuide = true;
// 是否显示处理线
private boolean mShowHandle = true;
private boolean mIsRotating = false;
private boolean mIsAnimating = false;
// 是否已经翻转了
private boolean mIsReverseY = false;
// 是否有旋转操作
private boolean mIsRotated = false;
// 旋转操作的开关
private boolean mRotateSwitch = true;
SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel source) {
super(source);
mGuideColor = source.readInt();
mHandleColor = source.readInt();
mFrameColor = source.readInt();
mOverlayColor = source.readInt();
mBackgroundColor = source.readInt();
mFrameMinSize = source.readInt();
mTouchPadding = source.readInt();
mHandleWidth = source.readInt();
mHandleSize = source.readInt();
mImgHeight = source.readFloat();
mImgWidth = source.readFloat();
mImgAngle = source.readFloat();
mCropScale = source.readFloat();
mFrameStrokeWeight = source.readFloat();
mGuideStrokeWeight = source.readFloat();
mHandleShowMode = (ShowModeEnum) source.readSerializable();
mGuideShowMode = (ShowModeEnum) source.readSerializable();
mCropMode = (CropModeEnum) source.readSerializable();
mInitialFrameScale = source.readFloat();
mRotateSwitch = (source.readInt() != 0);
mIsRotated = (source.readInt() != 0);
mIsReverseY = (source.readInt() != 0);
mIsAnimating = (source.readInt() != 0);
mIsRotating = (source.readInt() != 0);
mShowHandle = (source.readInt() != 0);
mShowGuide = (source.readInt() != 0);
mIsCropEnabled = (source.readInt() != 0);
mIsInitialized = (source.readInt() != 0);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(mGuideColor);
out.writeInt(mHandleColor);
out.writeInt(mFrameColor);
out.writeInt(mOverlayColor);
out.writeInt(mBackgroundColor);
out.writeInt(mFrameMinSize);
out.writeInt(mTouchPadding);
out.writeInt(mHandleWidth);
out.writeInt(mHandleSize);
out.writeFloat(mImgHeight);
out.writeFloat(mImgWidth);
out.writeFloat(mImgAngle);
out.writeFloat(mCropScale);
out.writeFloat(mFrameStrokeWeight);
out.writeFloat(mGuideStrokeWeight);
out.writeSerializable(mHandleShowMode);
out.writeSerializable(mGuideShowMode);
out.writeSerializable(mCropMode);
out.writeFloat(mInitialFrameScale);
out.writeInt(mRotateSwitch ? 1 : 0);
out.writeInt(mIsRotated ? 1 : 0);
out.writeInt(mIsReverseY ? 1 : 0);
out.writeInt(mIsAnimating ? 1 : 0);
out.writeInt(mIsRotating ? 1 : 0);
out.writeInt(mShowHandle ? 1 : 0);
out.writeInt(mShowGuide ? 1 : 0);
out.writeInt(mIsCropEnabled ? 1 : 0);
out.writeInt(mIsInitialized ? 1 : 0);
}
public static final Creator CREATOR = new Creator() {
@Override
public SavedState createFromParcel(Parcel parcel) {
return new SavedState(parcel);
}
@Override
public SavedState[] newArray(int i) {
return new SavedState[i];
}
};
}
}
......@@ -33,4 +33,43 @@
<item name="android:textColor">@color/white</item>
</style>
<!--自定义裁剪View-->
<declare-styleable name="CropView">
<attr name="img_src" format="reference" />
<attr name="crop_mode">
<enum name="fit_image" value="0" />
<enum name="ratio_2_3" value="1" />
<enum name="ratio_3_2" value="2" />
<enum name="ratio_4_3" value="3" />
<enum name="ratio_3_4" value="4" />
<enum name="square" value="5" />
<enum name="ratio_16_9" value="6" />
<enum name="ratio_9_16" value="7" />
<enum name="free" value="8" />
</attr>
<attr name="background_color" format="reference|color" />
<attr name="overlay_color" format="reference|color" />
<attr name="framecolor" format="reference|color" />
<attr name="handle_color" format="reference|color" />
<attr name="handle_width" format="dimension" />
<attr name="handle_size" format="dimension" />
<attr name="guide_color" format="reference|color" />
<attr name="guide_show_mode">
<enum name="show_always" value="1" />
<enum name="show_on_touch" value="2" />
<enum name="not_show" value="3" />
</attr>
<attr name="handle_show_mode">
<enum name="show_always" value="1" />
<enum name="show_on_touch" value="2" />
<enum name="not_show" value="3" />
</attr>
<attr name="touch_padding" format="dimension" />
<attr name="min_frame_size" format="dimension" />
<attr name="frame_stroke_weight" format="dimension" />
<attr name="guide_stroke_weight" format="dimension" />
<attr name="crop_enabled" format="boolean" />
<attr name="initial_frame_scale" format="float" />
</declare-styleable>
</resources>
\ No newline at end of file
package com.wmdigit.data.mmkv.repository;
import com.elvishew.xlog.XLog;
import com.tencent.mmkv.MMKV;
import com.wmdigit.common.model.CropValue;
import com.wmdigit.data.mmkv.constant.MmkvCons;
/**
......@@ -26,8 +28,33 @@ public class CropLocalRepository {
* 获取是否裁剪
* @return
*/
public boolean getIsCrop(){
public boolean getHasCropped(){
return MMKV.defaultMMKV().getInt(MmkvCons.MMKV_KEY_CROP_WIDTH, 0) > 0;
}
/**
* 保存裁剪坐标
* @param value
*/
public void saveCropValue(CropValue value){
MMKV.defaultMMKV().putInt(MmkvCons.MMKV_KEY_CROP_WIDTH, value.getWidth());
MMKV.defaultMMKV().putInt(MmkvCons.MMKV_KEY_CROP_HEIGHT, value.getHeight());
MMKV.defaultMMKV().putInt(MmkvCons.MMKV_KEY_CROP_X, value.getLeft());
MMKV.defaultMMKV().putInt(MmkvCons.MMKV_KEY_CROP_Y, value.getTop());
}
/**
* 获取裁剪坐标
* @return
*/
public CropValue getCropValue(){
CropValue value = new CropValue(
MMKV.defaultMMKV().getInt(MmkvCons.MMKV_KEY_CROP_X, 0),
MMKV.defaultMMKV().getInt(MmkvCons.MMKV_KEY_CROP_Y, 0),
MMKV.defaultMMKV().getInt(MmkvCons.MMKV_KEY_CROP_WIDTH, 0),
MMKV.defaultMMKV().getInt(MmkvCons.MMKV_KEY_CROP_HEIGHT, 0)
);
return value;
}
}
......@@ -4,5 +4,5 @@ v1.0.1 2024/08/01 1.增加camerax
v1.0.2 2024/08/06 1.增加系统信息页
2.增加设置-注册页
3.对接注册、找回接口
4.todo 增加秤盘裁剪页
4.增加秤盘裁剪页
5.todo 增加AIDL服务
\ No newline at end of file
package com.wmdigit.setting.fragment;
import com.elvishew.xlog.XLog;
import com.wmdigit.camera.CameraxController;
import com.wmdigit.common.base.mvvm.BaseMvvmFragment;
import com.wmdigit.setting.R;
import com.wmdigit.setting.databinding.FragmentCameraCropBinding;
......@@ -12,6 +14,7 @@ import com.wmdigit.setting.viewmodel.CameraCropViewModel;
*/
public class CameraCropFragment extends BaseMvvmFragment<CameraCropViewModel, FragmentCameraCropBinding> {
private boolean isCameraNeedRelease = false;
@Override
protected int getLayoutId() {
......@@ -26,11 +29,48 @@ public class CameraCropFragment extends BaseMvvmFragment<CameraCropViewModel, Fr
@Override
protected void initData() {
mDataBinding.setViewModel(mViewModel);
// 检查Camera是否开启
if(!CameraxController.getInstance(requireContext()).isOpen()){
isCameraNeedRelease = true;
// 将相机生命周期和页面绑定,开启相机
CameraxController.getInstance()
.setLifecycleOwner(this)
.setOnImageAnalyzeListener(mViewModel.getOnImageAnalyzeListener())
.setFrameInterval(2)
.apply();
}
else{
// 注册相机图片监听
CameraxController.getInstance()
.setFrameInterval(2)
.setOnImageAnalyzeListener(mViewModel.getOnImageAnalyzeListener());
}
// 设置预设坐标
mViewModel.setPresetCropValue();
}
@Override
protected void initObserve() {
super.initObserve();
// 观察裁剪坐标变化
mViewModel.croppedValue.observe(this, value -> {
mViewModel.saveCropValue(value);
});
}
@Override
protected void initListener() {
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (isCameraNeedRelease) {
CameraxController.getInstance().release();
}
else{
CameraxController.getInstance().setOnImageAnalyzeListener(null);
}
}
@Override
......
package com.wmdigit.setting.fragment;
import android.graphics.Bitmap;
import com.elvishew.xlog.XLog;
import com.wmdigit.common.base.mvvm.BaseMvvmFragment;
import com.wmdigit.setting.R;
import com.wmdigit.setting.SettingActivity;
import com.wmdigit.setting.databinding.FragmentSystemInfoBinding;
import com.wmdigit.setting.viewmodel.SystemInfoViewModel;
......@@ -33,6 +29,7 @@ public class SystemInfoFragment extends BaseMvvmFragment<SystemInfoViewModel, Fr
protected void initData() {
// 绑定ViewModel和DataBinding
mDataBinding.setViewModel(mViewModel);
}
@Override
......
package com.wmdigit.setting.viewmodel;
import android.app.Application;
import android.graphics.Bitmap;
import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData;
import com.wmdigit.camera.listener.OnImageAnalyzeListener;
import com.wmdigit.common.base.mvvm.BaseViewModel;
import com.wmdigit.common.base.mvvm.SingleLiveEvent;
import com.wmdigit.common.model.CropValue;
import com.wmdigit.common.utils.ParcelHelper;
import com.wmdigit.data.mmkv.repository.CropLocalRepository;
/**
* 相机裁剪ViewModel
* @author dizi
*/
public class CameraCropViewModel extends BaseViewModel {
/**
* 相机当前帧
*/
public SingleLiveEvent<Bitmap> currentFrame = new SingleLiveEvent<>();
/**
* 裁剪坐标
*/
public MutableLiveData<CropValue> croppedValue = new MutableLiveData<>();
/**
* 相机图片回调
*/
private final OnImageAnalyzeListener onImageAnalyzeListener = bitmap -> {
Bitmap cloneBitmap = ParcelHelper.copy(bitmap);
currentFrame.postValue(cloneBitmap);
};
public CameraCropViewModel(@NonNull Application application) {
super(application);
}
public OnImageAnalyzeListener getOnImageAnalyzeListener() {
return onImageAnalyzeListener;
}
/**
* 设置预设裁剪坐标
*/
public void setPresetCropValue(){
if (CropLocalRepository.getInstance().getHasCropped()) {
croppedValue.postValue(CropLocalRepository.getInstance().getCropValue());
}
}
/**
* 保存裁剪坐标
* @param value
*/
public void saveCropValue(CropValue value){
CropLocalRepository.getInstance().saveCropValue(value);
}
}
......@@ -9,7 +9,6 @@ import androidx.databinding.ObservableField;
import com.elvishew.xlog.XLog;
import com.wmdigit.common.base.mvvm.BaseViewModel;
import com.wmdigit.common.utils.DeviceUtils;
import com.wmdigit.data.mmkv.constant.MmkvCons;
import com.wmdigit.data.mmkv.repository.CropLocalRepository;
import com.wmdigit.data.mmkv.repository.UserLocalRepository;
......@@ -87,7 +86,7 @@ public class SystemInfoViewModel extends BaseViewModel {
tenant.set(array[3]);
}
// 获取裁剪状态
isCrop.set(CropLocalRepository.getInstance().getIsCrop());
isCrop.set(CropLocalRepository.getInstance().getHasCropped());
}
/**
......
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
......@@ -9,10 +10,89 @@
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
style="@style/fragment_root"
tools:context=".fragment.CameraCropFragment">
<androidx.constraintlayout.widget.ConstraintLayout
style="@style/setting_module"
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/dp_20">
<!--绿色图标-->
<View
android:id="@+id/icon_camera_crop"
style="@style/icon_title_green"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/tv_camera_crop"
app:layout_constraintBottom_toBottomOf="@+id/tv_camera_crop"/>
<!--标题-->
<TextView
android:id="@+id/tv_camera_crop"
style="@style/text_base.title.module_title"
android:text="@string/module_setting_camera_area"
app:layout_constraintStart_toEndOf="@+id/icon_camera_crop"
app:layout_constraintTop_toTopOf="parent" />
<!--占位符1-->
<View
android:id="@+id/v_placeholder_1"
android:layout_width="@dimen/dp_0"
android:layout_height="@dimen/dp_0"
android:layout_marginStart="@dimen/dp_200"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<!--占位符2-->
<TextView
android:id="@+id/v_placeholder_2"
style="@style/text_base.content"
android:text="@string/module_setting_store_code2"
android:layout_marginEnd="@dimen/dp_10"
android:visibility="invisible"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@+id/v_placeholder_1"/>
<!--裁剪View-->
<com.wmdigit.common.view.imageview.BidirectionalDataBindingCropView
android:id="@+id/crop_view"
android:layout_width="640px"
android:layout_height="480px"
android:background="@color/black"
app:background_color="#66FFFFFF"
app:crop_enabled="true"
app:crop_mode="free"
app:frame_stroke_weight="@dimen/dp_2"
app:guide_color="#66FFFFFF"
app:guide_show_mode="show_always"
app:guide_stroke_weight="@dimen/dp_2"
app:handle_color="@android:color/white"
app:handle_show_mode="show_always"
app:handle_size="@dimen/dp_24"
app:handle_width="@dimen/dp_3"
app:initial_frame_scale="0.75"
app:min_frame_size="@dimen/dp_100"
app:overlay_color="#AA1C1C1C"
app:touch_padding="@dimen/dp_8"
android:layout_marginTop="@dimen/dp_40"
app:frame="@{viewModel.currentFrame}"
app:preset_crop_value="@={viewModel.croppedValue}"
app:layout_constraintStart_toStartOf="@+id/v_placeholder_2"
app:layout_constraintTop_toBottomOf="@+id/tv_camera_crop"
/>
<!--提示-->
<TextView
style="@style/text_base.content"
android:layout_marginTop="@dimen/dp_20"
android:text="@string/module_setting_tip_crop_camera"
app:layout_constraintStart_toStartOf="@+id/crop_view"
app:layout_constraintEnd_toEndOf="@+id/crop_view"
app:layout_constraintTop_toBottomOf="@+id/crop_view"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
</layout>
\ No newline at end of file
......@@ -247,7 +247,7 @@
<EditText
android:id="@+id/edt_camera_crop"
style="@style/edittext_base.w492"
android:textColor="@{viewModel.isCrop? @color/green : @color/red }"
android:textColor="@{viewModel.isCrop? @color/green_008e75 : @color/red }"
android:text="@{viewModel.isCrop? @string/cropped : @string/not_crop}"
android:layout_marginEnd="@dimen/dp_100"
app:layout_constraintTop_toTopOf="@+id/edt_active_status"
......
......@@ -26,6 +26,7 @@
<string name="module_setting_please_input_tenant">输入租户号</string>
<string name="module_setting_forget_register_info">忘记注册信息</string>
<string name="module_setting_forget_register">注册</string>
<string name="module_setting_tip_crop_camera">调整识别区域,使其完全包裹置物台</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
</resources>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment