如图:

思路:在一个自定义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. python tips:作用域与名字空间

    Python具有静态作用域,变量的作用域由它定义的位置决定,而与调用的位置无关. a = 2 def f(): a = 2 第一行的a的作用域是全局作用域,作用于定义位置后面的所有位置. 第四行的a的 ...

  2. tomcat8版本实现虚拟主机

    vim /etc/hosts192.168.30.21   www.crushlinux.com192.168.30.21   www.cloud.com [root@localhost ~]# cd ...

  3. Python基础(二)数据类型

    (一)数字 Python3中的数字类型分为3种,分别是整型,浮点型以及复数. Python2种的数字类型分为4种,分别是整型,长整型,浮点型以及复数. 其中长整型时Python2为应对位数较大的而设置 ...

  4. Day 20 re模块(正则表达式)

    re模块 作用:取文本或者字符串内找你所需要的东西 import re re.findall(参数一,参数二,参数三) #暂时用到前两个,第一个为正则表达式,第二个为字符串,也就是被搜索的文本 ^元字 ...

  5. 分享接口管理平台 eoLinker AMS 线上专业版V3.0,只为更好的体验,了解一下?

    不知不觉中,eoLinker AMS从2016年上线至今已经三个年头,按照一年一个大版本的迭代计划,我们终于迎来了eoLinker AMS 专业版3.0. AMS产品也从最初专注于API文档管理,成长 ...

  6. 继承(day09)

    二十一 继承(Inheritance) ... 子类的构造函数和析构函数 5.1 子类的构造函数 )如果子类构造函数没有显式指明基类子对象的初始化方式,那么该子对象将以无参方式被初始化. )如果希望基 ...

  7. 《奋斗吧!菜鸟》 第八次作业:Alpha冲刺 Scrum meeting 1

    项目 内容 这个作业属于哪个课程 任课教师链接 作业要求 https://www.cnblogs.com/nwnu-daizh/p/11012922.html 团队名称 奋斗吧!菜鸟 作业学习目标 A ...

  8. Ningx初学

    原文地址 1. 到官网下载Ningx 2. 启动 2.1双击nginx.exe图标,可见黑窗口一闪而过,启动完毕. 2.2 命令行到nginx目录,输入nginx启动.(注,此方式命令行窗口无任何提示 ...

  9. [bzoj3505][CQOI2014]数三角形_组合数学

    数三角形 bzoj-3505 CQOI-2014 题目大意:给你一个n*m的网格图,问你从中选取三个点,能构成三角形的个数. 注释:$1\le n,m\le 1000$. 想法:本来是想着等中考完了之 ...

  10. [GraphQL] Apollo React Mutation Component

    In this lesson I refactor a React component that utilizes a higher-order component for mutations to ...