Android实现自定义相机系列(1)—自定义view裁剪控件

版权声明:本博客主要记录学习笔记和遇到的一些问题解决方案,转载请注明出处! https://blog.csdn.net/u010982507/article/details/81197076

目标

本系列文章主要记录自定义相机拍照系列,项目源码还在编写中,后续会传到github,内容包括:
1、使用Android的Camera API自定义拍照模式,例如人脸拍照,OCR拍照等;
2、对拍完的照片进行裁剪;
3、自定义图片伸缩view,使用手势对图片进行放大缩小操作;

本人写博客也是为了自己学习和分享,也会参考网络上许多文章和开源项目,希望大家一块学习进步。
学习这一系列课程,需要拥有Android自定义view的基本知识,这一篇文章主要介绍自定义裁剪view控件。

自定义view裁剪控件

Android自带的系统中有图片裁剪的功能,后面介绍系统自带的拍照功能时再进行介绍,此篇文章介绍自定义裁剪控件的原理和实现,主要实现以下几个功能:

  • 继承View,实现自定义View功能
  • 绘制裁剪矩形框
  • 实现矩形框的拖动功能
  • 实现矩形框拉伸四个角缩放功能
  • 提供返回获取矩形坐标方法,方便进行裁剪

演示效果图

直接看代码会枯燥,这里先看下演示效果图
效果图
这里写图片描述

代码实现

1、自定义view类FrameOverlayView,实现带灰色遮罩的矩形选取框控件。

public class FrameOverlayView extends View {

    // 获取矩形的四个点坐标,方便截取矩形中的图片
    public Rect getFrameRect() {
        Rect rect = new Rect();
        rect.left = (int) frameRect.left;
        rect.top = (int) frameRect.top;
        rect.right = (int) frameRect.right;
        rect.bottom = (int) frameRect.bottom;
        return rect;
    }

    public FrameOverlayView(Context context) {
        super(context);
        init();
    }

    public FrameOverlayView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public FrameOverlayView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    // 创建画笔,Paint.ANTI_ALIAS_FLAG : 用于绘制时抗锯齿
    private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    // 创建橡皮擦画笔
    private Paint eraser = new Paint(Paint.ANTI_ALIAS_FLAG);

    // 代码块,初始化一些属性
    {
        // 关闭硬件加速,注:不能在view级别开启硬件加速
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        // 设置画笔颜色为白色
        paint.setColor(Color.WHITE);
        // 设置只绘制图形轮廓(描边)
        paint.setStyle(Paint.Style.STROKE);
        // 设置线宽
        paint.setStrokeWidth(6);
        // 清除模式[0,0],即最终所有点的像素的alpha 和color 都为 0,所以画出来的效果只有白色背景
        eraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
    }

    private int currentCorner = -1;
    private static final int CORNER_LEFT_TOP = 1;
    private static final int CORNER_RIGHT_TOP = 2;
    private static final int CORNER_RIGHT_BOTTOM = 3;
    private static final int CORNER_LEFT_BOTTOM = 4;
    // 设置边距
    private int margin = 20;
    // 设置角的线宽
    private int cornerLineWidth = 6;
    private int cornerLength = 20;
    private RectF touchRect = new RectF();
    private RectF frameRect = new RectF();
    // 声明手势识别类
    private GestureDetector gestureDetector;

    // 初始化工作
    private void init() {
        Log.e("init", "init");
        gestureDetector = new GestureDetector(getContext(), simpleOnGestureListener);
        // 设置角的长度
        cornerLength = DimensionUtil.dpToPx(18);
        // 设置角线的宽度
        cornerLineWidth = DimensionUtil.dpToPx(3);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        Log.e("onSizeChanged", w + ":" + h + ":" + oldw + ":" + oldh);
        // 当前控件初始化的时候,会调用一次这个函数,此时初始化边框矩形
        initFrameRect(w, h);
    }

    // 初始化边框矩形
    private void initFrameRect(int w, int h) {
        frameRect.left = (int) (w * 0.05);
        frameRect.top = (int) (h * 0.25);
        frameRect.right = w - frameRect.left;
        frameRect.bottom = h - frameRect.top;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.e("onDraw>>>>>>>>>>>>> ", "onDraw");
        // 设置当前view的背景色,灰色背景
        int maskColor = Color.argb(180, 0, 0, 0);
        canvas.drawColor(maskColor);
        // 设置线的粗细
        paint.setStrokeWidth(DimensionUtil.dpToPx(1));
        // 绘制矩形
        canvas.drawRect(frameRect, paint);
        // 设置橡皮擦
        canvas.drawRect(frameRect, eraser);
        // 绘制四个角
        drawCorners(canvas);
    }

    // 绘制四个角
    private void drawCorners(Canvas canvas) {
        paint.setStrokeWidth(cornerLineWidth);
        // left top 左上角
        drawLine(canvas, frameRect.left - cornerLineWidth / 2, frameRect.top, cornerLength, 0);
        drawLine(canvas, frameRect.left, frameRect.top, 0, cornerLength);
        // right top 右上角
        drawLine(canvas, frameRect.right + cornerLineWidth / 2, frameRect.top, -cornerLength, 0);
        drawLine(canvas, frameRect.right, frameRect.top, 0, cornerLength);
        // right bottom 右下角
        drawLine(canvas, frameRect.right, frameRect.bottom, 0, -cornerLength);
        drawLine(canvas, frameRect.right + cornerLineWidth / 2, frameRect.bottom, -cornerLength, 0);
        // left bottom 左下角
        drawLine(canvas, frameRect.left - cornerLineWidth / 2, frameRect.bottom, cornerLength, 0);
        drawLine(canvas, frameRect.left, frameRect.bottom, 0, -cornerLength);
    }

    // 画线
    private void drawLine(Canvas canvas, float x, float y, int dx, int dy) {
        canvas.drawLine(x, y, x + dx, y + dy, paint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 先处理是否是矩形框伸缩
        boolean result = handleDown(event);
        // 如果不是伸缩操作,则是拖动操作
        if (!result) {
            float ex = 60;
            // 兼容性处理,兼容偏移值为60px
            RectF rectExtend = new RectF(frameRect.left - ex, frameRect.top - ex,
                    frameRect.right + ex, frameRect.bottom + ex);
            if (rectExtend.contains(event.getX(), event.getY())) {
                // 将touch事件与手势识别绑定
                gestureDetector.onTouchEvent(event);
                return true;
            }
        }
        return result;
    }

    //*************************处理矩形框缩放start*****************
    private boolean handleDown(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                currentCorner = -1;
                break;
            case MotionEvent.ACTION_DOWN: { // 按下的时候,判断是按的哪个角
                float radius = cornerLength;
                // 按下的时候设置矩形的四角坐标
                touchRect.set(event.getX() - radius, event.getY() - radius, event.getX() + radius,
                        event.getY() + radius);
                // 缩小哪个角
                if (touchRect.contains(frameRect.left, frameRect.top)) {
                    currentCorner = CORNER_LEFT_TOP;
                    return true;
                }

                if (touchRect.contains(frameRect.right, frameRect.top)) {
                    currentCorner = CORNER_RIGHT_TOP;
                    return true;
                }

                if (touchRect.contains(frameRect.right, frameRect.bottom)) {
                    currentCorner = CORNER_RIGHT_BOTTOM;
                    return true;
                }

                if (touchRect.contains(frameRect.left, frameRect.bottom)) {
                    currentCorner = CORNER_LEFT_BOTTOM;
                    return true;
                }
                return false;
            }
            case MotionEvent.ACTION_MOVE:
                // 移动的时候处理缩放
                return handleScale(event);
            default:

        }
        return false;
    }

    // 处理伸缩事件
    private boolean handleScale(MotionEvent event) {
        // 判断缩放的是哪个角
        switch (currentCorner) {
            case CORNER_LEFT_TOP:
                // 当拖动左上角的时候,右下角的坐标是不变的
                scaleTo(event.getX(), event.getY(), frameRect.right, frameRect.bottom);
                return true;
            case CORNER_RIGHT_TOP:
                // 当拖动右上角的时候,左下角是不变的
                scaleTo(frameRect.left, event.getY(), event.getX(), frameRect.bottom);
                return true;
            case CORNER_RIGHT_BOTTOM:
                // 当拖动右下角的时候,左上角是不变的
                scaleTo(frameRect.left, frameRect.top, event.getX(), event.getY());
                return true;
            case CORNER_LEFT_BOTTOM:
                // 当拖动左下角的时候,右上角时不变的
                scaleTo(event.getX(), frameRect.top, frameRect.right, event.getY());
                return true;
            default:
                return false;
        }
    }

    // 移动的时候缩放矩形
    private void scaleTo(float left, float top, float right, float bottom) {
        // 当高度缩放到最大时
        if (bottom - top < getMinimumFrameHeight()) {
            top = frameRect.top;
            bottom = frameRect.bottom;
        }
        //当宽度缩放到最大时
        if (right - left < getMinimumFrameWidth()) {
            left = frameRect.left;
            right = frameRect.right;
        }
        left = Math.max(margin, left);
        top = Math.max(margin, top);
        right = Math.min(getWidth() - margin, right);
        bottom = Math.min(getHeight() - margin, bottom);
        // 重绘矩形
        frameRect.set(left, top, right, bottom);
        invalidate();
    }
    //*************************处理矩形框缩放end*****************

    //*************处理拖动事件 start***************
    // 监听拖动事件,onDown--》onScroll--》onScroll--》onFiling
    private GestureDetector.SimpleOnGestureListener simpleOnGestureListener = new GestureDetector.SimpleOnGestureListener() {
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            // 处理拖动事件,这个地方需要在onTouchEvent事件中将View的Event传递给GestureDetector的onTouchEvent,然后再调用移动方法
            translate(distanceX, distanceY);
            return true;
        }
    };

    // 在屏幕上move的时候,处理矩形边框的拖动事件
    private void translate(float x, float y) {
        if (x > 0) {
            // moving left;
            if (frameRect.left - x < margin) {
                x = frameRect.left - margin;
            }
        } else {
            if (frameRect.right - x > getWidth() - margin) {
                x = frameRect.right - getWidth() + margin;
            }
        }
        if (frameRect.top - y < margin) {
            y = frameRect.top - margin;
        } else {
            if (frameRect.bottom - y > getHeight() - margin) {
                y = frameRect.bottom - getHeight() + margin;
            }
        }
        frameRect.offset(-x, -y);
        invalidate();
    }

    //*************处理拖动事件 end***************

    // 获取矩形最小宽度
    private float getMinimumFrameWidth() {
        return 2.4f * cornerLength;
    }

    // 获取矩形最小高度
    private float getMinimumFrameHeight() {
        return 2.4f * cornerLength;
    }
}

2、XML布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3">

        <ImageView
            android:id="@+id/iv_sourceImage"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/crop" />

        <com.rzr.capture.view.FrameOverlayView
            android:id="@+id/overlayView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </FrameLayout>

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="2"
        android:orientation="vertical">

        <Button
            android:id="@+id/btn_crop"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:text="裁剪" />

        <ImageView
            android:id="@+id/iv_result"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="50dp" />
    </LinearLayout>

</LinearLayout>

3、在MainActivity中调用

public class MainActivity extends AppCompatActivity {
    private FrameOverlayView overlayView;
    private ImageView iv_sourceImage, iv_result;
    private Button btn_crop;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        overlayView = findViewById(R.id.overlayView);
        iv_sourceImage = findViewById(R.id.iv_sourceImage);
        final Bitmap image = ((BitmapDrawable) iv_sourceImage.getDrawable()).getBitmap();
        iv_result = findViewById(R.id.iv_result);
        btn_crop = findViewById(R.id.btn_crop);
        btn_crop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Rect frameRect = overlayView.getFrameRect();
                Bitmap crop = crop(frameRect, image);
                iv_result.setImageBitmap(crop);
            }
        });
    }

    public Bitmap crop(Rect frame, Bitmap image) {
        float[] src = new float[]{frame.right, frame.top};
        Matrix matrix = new Matrix();
        int width = frame.width();
        int height = frame.height();
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
        Canvas canvas = new Canvas(bitmap);
        Bitmap originalBitmap = image;
        matrix.postTranslate(-src[0], -src[1]);
        canvas.drawBitmap(originalBitmap, matrix, null);
        return bitmap;
    }
}

4、DimensionUtil 工具类

public class DimensionUtil {

    public static int dpToPx(int dp) {
        return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
    }

}

猜你喜欢

转载自blog.csdn.net/u010982507/article/details/81197076