1. 程式人生 > >Android實現自定義相機系列(1)—自定義view裁剪控制元件

Android實現自定義相機系列(1)—自定義view裁剪控制元件

目標

本系列文章主要記錄自定義相機拍照系列,專案原始碼還在編寫中,後續會傳到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);
    }

}