本系列文章主要记录自定义相机拍照系列,项目源码还在编写中,后续会传到github,内容包括:
1、使用Android的Camera API自定义拍照模式,例如人脸拍照,OCR拍照等;
2、对拍完的照片进行裁剪;
3、自定义图片伸缩view,使用手势对图片进行放大缩小操作;
本人写博客也是为了自己学习和分享,也会参考网络上许多文章和开源项目,希望大家一块学习进步。
学习这一系列课程,需要拥有Android自定义view的基本知识,这一篇文章主要介绍自定义裁剪view控件。
Android自带的系统中有图片裁剪的功能,后面介绍系统自带的拍照功能时再进行介绍,此篇文章介绍自定义裁剪控件的原理和实现,主要实现以下几个功能:
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布局文件
"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);
}
}