如图:

思路:在一个自定义View上绘制一张图片(参照前面提到的另一篇文章),在该自定义View上绘制一个自定义的FloatDrawable,也就是图中的浮层。绘制图片和FloatDrawable的交集的补集部分灰色阴影(这个其实很简单,就一句话)。在自定义View的touch中去处理具体的拖动事件和FloatDrawable的变换。图片的绘制和FloatDrawable的绘制以及变换最终其实就是在操作各自的Rect而已,Rect就是一个有矩形,有四个坐标,图片和FloatDrawable就是按照坐标去绘制的。

CropImageView.java

该类继承View

功能:在onDraw方法中画图片、浮层,处理touch事件,最后根据坐标对图片进行剪裁。

package com.play.playgame.cropimg;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View; /**
* Created by Administrator on 2017/9/22.
*/ public class CropImageView extends View {
// 在touch重要用到的点,
private float mX_1 = ;
private float mY_1 = ;
// 触摸事件判断
private final int STATUS_SINGLE = ;
private final int STATUS_MULTI_START = ;
private final int STATUS_MULTI_TOUCHING = ;
// 当前状态
private int mStatus = STATUS_SINGLE;
// 默认裁剪的宽高
private int cropWidth;
private int cropHeight;
// 浮层Drawable的四个点
private final int EDGE_LT = ;
private final int EDGE_RT = ;
private final int EDGE_LB = ;
private final int EDGE_RB = ;
private final int EDGE_MOVE_IN = ;
private final int EDGE_MOVE_OUT = ;
private final int EDGE_NONE = ; public int currentEdge = EDGE_NONE; protected float oriRationWH = ;
protected final float maxZoomOut = 5.0f;
protected final float minZoomIn = 0.333333f; protected Drawable mDrawable;
protected FloatDrawable mFloatDrawable; protected Rect mDrawableSrc = new Rect();// 图片Rect变换时的Rect
protected Rect mDrawableDst = new Rect();// 图片Rect
protected Rect mDrawableFloat = new Rect();// 浮层的Rect
protected boolean isFrist = true;
private boolean isTouchInSquare = true; protected Context mContext; public CropImageView(Context context) {
super(context);
init(context);
} public CropImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
} public CropImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context); } @SuppressLint("NewApi")
private void init(Context context) {
this.mContext = context;
try {
if (android.os.Build.VERSION.SDK_INT >= ) {
this.setLayerType(LAYER_TYPE_SOFTWARE, null);
}
} catch (Exception e) {
e.printStackTrace();
}
mFloatDrawable = new FloatDrawable(context);
} public void setDrawable(Drawable mDrawable, int cropWidth, int cropHeight) {
this.mDrawable = mDrawable;
this.cropWidth = cropWidth;
this.cropHeight = cropHeight;
this.isFrist = true;
invalidate();
} @SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) { if (event.getPointerCount() > ) {
if (mStatus == STATUS_SINGLE) {
mStatus = STATUS_MULTI_START;
} else if (mStatus == STATUS_MULTI_START) {
mStatus = STATUS_MULTI_TOUCHING;
}
} else {
if (mStatus == STATUS_MULTI_START
|| mStatus == STATUS_MULTI_TOUCHING) {
mX_1 = event.getX();
mY_1 = event.getY();
} mStatus = STATUS_SINGLE;
} switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mX_1 = event.getX();
mY_1 = event.getY();
currentEdge = getTouch((int) mX_1, (int) mY_1);
isTouchInSquare = mDrawableFloat.contains((int) event.getX(),
(int) event.getY()); break; case MotionEvent.ACTION_UP:
checkBounds();
break; case MotionEvent.ACTION_POINTER_UP:
currentEdge = EDGE_NONE;
break; case MotionEvent.ACTION_MOVE:
if (mStatus == STATUS_MULTI_TOUCHING) { } else if (mStatus == STATUS_SINGLE) {
int dx = (int) (event.getX() - mX_1);
int dy = (int) (event.getY() - mY_1); mX_1 = event.getX();
mY_1 = event.getY();
// 根據得到的那一个角,并且变换Rect
if (!(dx == && dy == )) {
switch (currentEdge) {
case EDGE_LT:
mDrawableFloat.set(mDrawableFloat.left + dx,
mDrawableFloat.top + dy, mDrawableFloat.right,
mDrawableFloat.bottom);
break; case EDGE_RT:
mDrawableFloat.set(mDrawableFloat.left,
mDrawableFloat.top + dy, mDrawableFloat.right
+ dx, mDrawableFloat.bottom);
break; case EDGE_LB:
mDrawableFloat.set(mDrawableFloat.left + dx,
mDrawableFloat.top, mDrawableFloat.right,
mDrawableFloat.bottom + dy);
break; case EDGE_RB:
mDrawableFloat.set(mDrawableFloat.left,
mDrawableFloat.top, mDrawableFloat.right + dx,
mDrawableFloat.bottom + dy);
break; case EDGE_MOVE_IN:
if (isTouchInSquare) {
mDrawableFloat.offset((int) dx, (int) dy);
}
break; case EDGE_MOVE_OUT:
break;
}
mDrawableFloat.sort();
invalidate();
}
}
break;
} return true;
} // 根据初触摸点判断是触摸的Rect哪一个角
public int getTouch(int eventX, int eventY) {
if (mFloatDrawable.getBounds().left <= eventX
&& eventX < (mFloatDrawable.getBounds().left + mFloatDrawable
.getBorderWidth())
&& mFloatDrawable.getBounds().top <= eventY
&& eventY < (mFloatDrawable.getBounds().top + mFloatDrawable
.getBorderHeight())) {
return EDGE_LT;
} else if ((mFloatDrawable.getBounds().right - mFloatDrawable
.getBorderWidth()) <= eventX
&& eventX < mFloatDrawable.getBounds().right
&& mFloatDrawable.getBounds().top <= eventY
&& eventY < (mFloatDrawable.getBounds().top + mFloatDrawable
.getBorderHeight())) {
return EDGE_RT;
} else if (mFloatDrawable.getBounds().left <= eventX
&& eventX < (mFloatDrawable.getBounds().left + mFloatDrawable
.getBorderWidth())
&& (mFloatDrawable.getBounds().bottom - mFloatDrawable
.getBorderHeight()) <= eventY
&& eventY < mFloatDrawable.getBounds().bottom) {
return EDGE_LB;
} else if ((mFloatDrawable.getBounds().right - mFloatDrawable
.getBorderWidth()) <= eventX
&& eventX < mFloatDrawable.getBounds().right
&& (mFloatDrawable.getBounds().bottom - mFloatDrawable
.getBorderHeight()) <= eventY
&& eventY < mFloatDrawable.getBounds().bottom) {
return EDGE_RB;
} else if (mFloatDrawable.getBounds().contains(eventX, eventY)) {
return EDGE_MOVE_IN;
}
return EDGE_MOVE_OUT;
} @Override
protected void onDraw(Canvas canvas) { if (mDrawable == null) {
return;
} if (mDrawable.getIntrinsicWidth() ==
|| mDrawable.getIntrinsicHeight() == ) {
return;
} configureBounds();
// 在画布上花图片
mDrawable.draw(canvas);
canvas.save();
// 在画布上画浮层FloatDrawable,Region.Op.DIFFERENCE是表示Rect交集的补集
canvas.clipRect(mDrawableFloat, Region.Op.DIFFERENCE);
// 在交集的补集上画上灰色用来区分
canvas.drawColor(Color.parseColor("#a0000000"));
canvas.restore();
// 画浮层
mFloatDrawable.draw(canvas);
} protected void configureBounds() {
// configureBounds在onDraw方法中调用
// isFirst的目的是下面对mDrawableSrc和mDrawableFloat只初始化一次,
// 之后的变化是根据touch事件来变化的,而不是每次执行重新对mDrawableSrc和mDrawableFloat进行设置
if (isFrist) {
oriRationWH = ((float) mDrawable.getIntrinsicWidth())
/ ((float) mDrawable.getIntrinsicHeight()); final float scale = mContext.getResources().getDisplayMetrics().density;
int w = Math.min(getWidth(), (int) (mDrawable.getIntrinsicWidth()
* scale + 0.5f));
int h = (int) (w / oriRationWH); int left = (getWidth() - w) / ;
int top = (getHeight() - h) / ;
int right = left + w;
int bottom = top + h; mDrawableSrc.set(left, top, right, bottom);
mDrawableDst.set(mDrawableSrc); int floatWidth = dipTopx(mContext, cropWidth);
int floatHeight = dipTopx(mContext, cropHeight); if (floatWidth > getWidth()) {
floatWidth = getWidth();
floatHeight = cropHeight * floatWidth / cropWidth;
} if (floatHeight > getHeight()) {
floatHeight = getHeight();
floatWidth = cropWidth * floatHeight / cropHeight;
} int floatLeft = (getWidth() - floatWidth) / ;
int floatTop = (getHeight() - floatHeight) / ;
mDrawableFloat.set(floatLeft, floatTop, floatLeft + floatWidth,
floatTop + floatHeight); isFrist = false;
} mDrawable.setBounds(mDrawableDst);
mFloatDrawable.setBounds(mDrawableFloat);
} // 在up事件中调用了该方法,目的是检查是否把浮层拖出了屏幕
protected void checkBounds() {
int newLeft = mDrawableFloat.left;
int newTop = mDrawableFloat.top; boolean isChange = false;
if (mDrawableFloat.left < getLeft()) {
newLeft = getLeft();
isChange = true;
} if (mDrawableFloat.top < getTop()) {
newTop = getTop();
isChange = true;
} if (mDrawableFloat.right > getRight()) {
newLeft = getRight() - mDrawableFloat.width();
isChange = true;
} if (mDrawableFloat.bottom > getBottom()) {
newTop = getBottom() - mDrawableFloat.height();
isChange = true;
} mDrawableFloat.offsetTo(newLeft, newTop);
if (isChange) {
invalidate();
}
} // 进行图片的裁剪,所谓的裁剪就是根据Drawable的新的坐标在画布上创建一张新的图片
public Bitmap getCropImage() {
Bitmap tmpBitmap = Bitmap.createBitmap(getWidth(), getHeight(),
Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(tmpBitmap);
mDrawable.draw(canvas); Matrix matrix = new Matrix();
float scale = (float) (mDrawableSrc.width())
/ (float) (mDrawableDst.width());
matrix.postScale(scale, scale); Bitmap ret = Bitmap.createBitmap(tmpBitmap, mDrawableFloat.left,
mDrawableFloat.top, mDrawableFloat.width(),
mDrawableFloat.height(), matrix, true);
tmpBitmap.recycle();
tmpBitmap = null; return ret;
} public int dipTopx(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}

FloatDrawable.java

继承自Drawable

功能:图片上面的浮动框,通过拖动确定位置

package com.play.playgame.cropimg;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable; public class FloatDrawable extends Drawable { private Context mContext;
private int offset = ;
private Paint mLinePaint = new Paint();
private Paint mLinePaint2 = new Paint();
{
mLinePaint.setARGB(, , , );
mLinePaint.setStrokeWidth(1F);
mLinePaint.setStyle(Paint.Style.STROKE);
mLinePaint.setAntiAlias(true);
mLinePaint.setColor(Color.WHITE);
//
mLinePaint2.setARGB(, , , );
mLinePaint2.setStrokeWidth(7F);
mLinePaint2.setStyle(Paint.Style.STROKE);
mLinePaint2.setAntiAlias(true);
mLinePaint2.setColor(Color.WHITE);
} public FloatDrawable(Context context) {
super();
this.mContext = context; } public int getBorderWidth() {
return dipTopx(mContext, offset);//根据dip计算的像素值,做适配用的
} public int getBorderHeight() {
return dipTopx(mContext, offset);
} @Override
public void draw(Canvas canvas) { int left = getBounds().left;
int top = getBounds().top;
int right = getBounds().right;
int bottom = getBounds().bottom; Rect mRect = new Rect(left + dipTopx(mContext, offset) / , top
+ dipTopx(mContext, offset) / , right
- dipTopx(mContext, offset) / , bottom
- dipTopx(mContext, offset) / );
//画默认的选择框
canvas.drawRect(mRect, mLinePaint);
//画四个角的四个粗拐角、也就是八条粗线
canvas.drawLine((left + dipTopx(mContext, offset) / - 3.5f), top
+ dipTopx(mContext, offset) / ,
left + dipTopx(mContext, offset) - 8f,
top + dipTopx(mContext, offset) / , mLinePaint2);
canvas.drawLine(left + dipTopx(mContext, offset) / ,
top + dipTopx(mContext, offset) / ,
left + dipTopx(mContext, offset) / ,
top + dipTopx(mContext, offset) / + , mLinePaint2);
canvas.drawLine(right - dipTopx(mContext, offset) + 8f,
top + dipTopx(mContext, offset) / ,
right - dipTopx(mContext, offset) / ,
top + dipTopx(mContext, offset) / , mLinePaint2);
canvas.drawLine(right - dipTopx(mContext, offset) / ,
top + dipTopx(mContext, offset) / - 3.5f,
right - dipTopx(mContext, offset) / ,
top + dipTopx(mContext, offset) / + , mLinePaint2);
canvas.drawLine((left + dipTopx(mContext, offset) / - 3.5f), bottom
- dipTopx(mContext, offset) / ,
left + dipTopx(mContext, offset) - 8f,
bottom - dipTopx(mContext, offset) / , mLinePaint2);
canvas.drawLine((left + dipTopx(mContext, offset) / ), bottom
- dipTopx(mContext, offset) / ,
(left + dipTopx(mContext, offset) / ),
bottom - dipTopx(mContext, offset) / - 30f, mLinePaint2);
canvas.drawLine((right - dipTopx(mContext, offset) + 8f), bottom
- dipTopx(mContext, offset) / ,
right - dipTopx(mContext, offset) / ,
bottom - dipTopx(mContext, offset) / , mLinePaint2);
canvas.drawLine((right - dipTopx(mContext, offset) / ), bottom
- dipTopx(mContext, offset) / - 30f,
right - dipTopx(mContext, offset) / ,
bottom - dipTopx(mContext, offset) / + 3.5f, mLinePaint2); } @Override
public void setBounds(Rect bounds) {
super.setBounds(new Rect(bounds.left - dipTopx(mContext, offset) / ,
bounds.top - dipTopx(mContext, offset) / , bounds.right
+ dipTopx(mContext, offset) / , bounds.bottom
+ dipTopx(mContext, offset) / ));
} @Override
public void setAlpha(int alpha) { } @Override
public void setColorFilter(ColorFilter cf) { } @Override
public int getOpacity() {
return ;
} public int dipTopx(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
} }

使用

布局中:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" > <com.onehead.cropimage.CropImageView
android:id="@+id/cropimage"
android:layout_width="match_parent"
android:layout_height="match_parent" /> </RelativeLayout>

Activity中:

public class MainActivity extends ActionBarActivity {
private CropImageView mView; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mView = (CropImageView) findViewById(R.id.cropimage);
//设置资源和默认长宽
mView.setDrawable(getResources().getDrawable(R.drawable.test2), ,
);
//调用该方法得到剪裁好的图片
Bitmap mBitmap= mView.getCropImage();
}
}

Android 自定义控件——图片剪裁的更多相关文章

  1. [Android实例教程] 教你如何拍照+相册选择图片+剪裁图片完整实现

    [Android实例教程] 教你如何拍照+相册选择图片+剪裁图片完整实现 今天做Android项目的时候要用到图片选择,要实现拍照获取图片和从相册获取图片,并且要求在获取完之后可以裁剪,试了很多方法之 ...

  2. Android图片剪裁库

    最近利用一周左右的业余时间,终于完成了一个Android图片剪裁库,核心功能是根据自己的理解实现的,部分代码参考了Android源码的图片剪裁应用.现在将该代码开源在Github上以供大家学习和使用, ...

  3. Android 拍照图片选取与图片剪裁

    最近从以前的项目中扒下来一个常用的模块,在这里有必要记录一下的,就是android上获取图片以及裁剪图片,怎么样?这个功能是不是很常用啊,你随便打开一个App,只要它有注册功能都会有设置人物头像的功能 ...

  4. Android圆形图片自定义控件

    Android圆形图片控件效果图如下: 代码如下: RoundImageView.java package com.dxd.roundimageview; import android.content ...

  5. Android自定义控件:进度条的四种实现方式(Progress Wheel的解析)

    最近一直在学习自定义控件,搜了许多大牛们Blog里分享的小教程,也上GitHub找了一些类似的控件进行学习.发现读起来都不太好懂,就想写这么一篇东西作为学习笔记吧. 一.控件介绍: 进度条在App中非 ...

  6. [转]Android自定义控件:进度条的四种实现方式(Progress Wheel的解析)

    最近一直在学习自定义控件,搜了许多大牛们Blog里分享的小教程,也上GitHub找了一些类似的控件进行学习.发现读起来都不太好懂,就想写这么一篇东西作为学习笔记吧. 一.控件介绍: 进度条在App中非 ...

  7. Android自定义控件之自定义组合控件

    前言: 前两篇介绍了自定义控件的基础原理Android自定义控件之基本原理(一).自定义属性Android自定义控件之自定义属性(二).今天重点介绍一下如何通过自定义组合控件来提高布局的复用,降低开发 ...

  8. Android自定义控件之基本原理

    前言: 在日常的Android开发中会经常和控件打交道,有时Android提供的控件未必能满足业务的需求,这个时候就需要我们实现自定义一些控件,今天先大致了解一下自定义控件的要求和实现的基本原理. 自 ...

  9. Android自定义控件1

    概述 Android已经为我们提供了大量的View供我们使用,但是可能有时候这些组件不能满足我们的需求,这时候就需要自定义控件了.自定义控件对于初学者总是感觉是一种复杂的技术.因为里面涉及到的知识点会 ...

随机推荐

  1. ApplicationLoader登录失败

    报错:Please sign in with an app-specific password. You can create one at appleid.apple.com 是因为帐号开启了双重认 ...

  2. json简介及josn数组中取字符

    1.json字符串就是字符串,只不过格式是Json格式的,以键值对的形式存在,键和值可以是字符串,数字,空值,数组等. json对象在花括号中书写,一个json对象包含多个键值对,json对象以花括号 ...

  3. DOCKER - J2EE中容器:WEB容器、EJB容器

    转自:http://www.voidcn.com/article/p-yizkqdxp-zg.html

  4. 【JavaScript游戏开发】使用HTML5 canvas开发的网页版中国象棋项目

    //V1.0 : 实现棋子的布局,画布及游戏场景的初始化 //V2.0 : 实现棋子的颜色改变 //V3.0 :实现所有象棋的走棋规则 //V4.0 : 实现所有棋子的吃子功能 完整的项目源码已经开源 ...

  5. [Ynoi2015]纵使日薄西山

    题目大意: 给定一个序列,每次单点修改,然后进行询问. 定义一次操作为,选择一个位置$x$,将这个位置的数和左边.右边两个位置的数(不存在则忽略)各减去1,然后和0取max. 对序列中最大的位置进行一 ...

  6. luogu P4238 多项式求逆 (模板题、FFT)

    手动博客搬家: 本文发表于20181125 13:21:46, 原地址https://blog.csdn.net/suncongbo/article/details/84485718 题目链接: ht ...

  7. UVA Jin Ge Jin Qu hao 12563

    Jin Ge Jin Qu hao (If you smiled when you see the title, this problem is for you ^_^) For those who ...

  8. 配置db账号和密码时一定注意空格问题、空行问题否则连接报错

    #postgresql dbpg.datasource.type=com.alibaba.druid.pool.DruidDataSourcepg.datasource.driverClassName ...

  9. HDU 2295

    二分答案+重复覆盖.注意返回的条件哦,不能光套模板. #include <iostream> #include <cstdio> #include <cstring> ...

  10. 屏幕測试亮点,新买了一个显示器,使用web简单的測试下了亮点

    1,购买了一个新的显示器 趁着双11的时候价格廉价.入手了一个显示器. http://serve.netsh.org/pub/dead_pixel.bin 滚动下就能够换颜色了.把chrome最大化, ...