Android實現自定義相機系列(1)—自定義view裁剪控制元件
阿新 • • 發佈:2019-01-23
目標
本系列文章主要記錄自定義相機拍照系列,專案原始碼還在編寫中,後續會傳到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);
}
}