大多图片裁剪大多两种操作:改变裁剪区图片不能缩放、裁剪区固定图片缩放,两种方法都可以裁剪到不同图片,本次介绍的是可变裁剪区同时能缩放图片,同时记录自己的开发项目过程。
裁剪视图一共三个view,最底层的缩放CilpImageView ,中间是可变裁剪区CilpBorderView,还有最顶层的CilpTouchView。监听CilpTouchView的OnTouch事件,通过判断down手势是否落在拉伸裁剪区的按钮内分发给CilpBorderView或者CilpImageView 。
Options类是默认裁剪配置。
CilpBorderView类:
public class CilpBorderView extends View {
private int borderColor = Color.parseColor("#FFFFFF");
private int outSideColor = Color.parseColor("#20000000");
private float borderWidth = 2;
private float lineWidth = 1;
private Rect[] rects=new Rect[2];
private Paint cutPaint;
private Paint outSidePaint;
private RectF cilpRectF;
//图片右坐标
private int iconRight=0;
//图片左坐标
private int iconLeft=0;
private Bitmap bitmap;
private int iconOffset;
private int width=0;
private int height=0;
private int verLine1;
private int verLine2;
private Options options;
private static final int TOP_ICON_ACTION=1;
private static final int BOTTOM_ICON_ACTION=2;
private int action=-1;
private float actionY;
public CilpBorderView(Context context) {
super(context);
initView();
}
public CilpBorderView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public CilpBorderView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
cutPaint = new Paint();
cutPaint.setColor(borderColor);
cutPaint.setStrokeWidth(borderWidth);
cutPaint.setStyle(Paint.Style.STROKE);
outSidePaint=new Paint();
outSidePaint.setAntiAlias(true);
outSidePaint.setColor(outSideColor);
outSidePaint.setStyle(Paint.Style.FILL);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_crop_drag_y);
iconOffset = bitmap.getWidth()/2;
options = new Options();
}
@Override
protected void onDraw(Canvas canvas) {
if (cilpRectF == null) {
cilpRectF = new RectF(options.paddingWidth, (getHeight() - options.cilpHeight) / 2, getWidth() - options.paddingWidth, (getHeight() + options.cilpHeight) / 2);
verLine1 = (int) (cilpRectF.left + cilpRectF.width() / 3);
verLine2 = (int) (cilpRectF.left + cilpRectF.width() * 2 / 3);
}
if (width == 0)
width = getWidth();
if (height == 0)
height = getHeight();
canvas.save();
drawLine(canvas);
drawRound(canvas);
drawIcon(canvas);
canvas.restore();
super.onDraw(canvas);
}
private void drawIcon(Canvas canvas) {
if (iconLeft == 0 && iconRight == 0) {
iconLeft = width / 2 - iconOffset;
iconRight=width / 2 + iconOffset;
}
canvas.drawBitmap(bitmap, iconLeft, cilpRectF.top-iconOffset, null);
canvas.drawBitmap(bitmap, iconLeft, cilpRectF.bottom-iconOffset, null);
Rect rect=new Rect(iconLeft-options.iconClick, (int)(cilpRectF.top-iconOffset)-options.iconClick,iconRight+options.iconClick,
(int)(cilpRectF.top+iconOffset)+options.iconClick);
rects[0]=rect;
rect=new Rect(iconLeft-options.iconClick,(int)(cilpRectF.bottom-iconOffset)-options.iconClick,iconRight+options.iconClick,
(int)(cilpRectF.bottom+iconOffset)+options.iconClick);
rects[1]=rect;
}
private void drawLine(Canvas canvas){
cutPaint.setStrokeWidth(lineWidth);
float p = cilpRectF.top + cilpRectF.height() / 3;
//横线
canvas.drawLine(options.paddingWidth, p, width-options.paddingWidth, p, cutPaint);
p = cilpRectF.top + cilpRectF.height() * 2 / 3;
canvas.drawLine(options.paddingWidth, p, width-options.paddingWidth, p, cutPaint);
//竖线
canvas.drawLine(verLine1, cilpRectF.top, verLine1, cilpRectF.bottom, cutPaint);
canvas.drawLine(verLine2, cilpRectF.top, verLine2, cilpRectF.bottom, cutPaint);
}
private void drawRound(Canvas canvas) {
//绘制边框
cutPaint.setStrokeWidth(borderWidth);
canvas.drawRect(cilpRectF, cutPaint);
//绘制外区域
//左中框
canvas.drawRect(0, cilpRectF.top, options.paddingWidth, cilpRectF.bottom, outSidePaint);
//上框
canvas.drawRect(0, 0, width, cilpRectF.top, outSidePaint);
//右中框
canvas.drawRect(cilpRectF.right, cilpRectF.top, width, cilpRectF.bottom, outSidePaint);
//下框
canvas.drawRect(0, cilpRectF.bottom, width, height, outSidePaint);
}
public void setOptions(Options options) {
this.options = options;
}
/**
* 根据手势做拉伸
*/
public boolean iconOntouch(MotionEvent event,RectF imgRect){
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
if (rects[0].contains((int)event.getX(),(int)event.getY())) {
action=TOP_ICON_ACTION;
}
if (rects[1].contains((int)event.getX(),(int)event.getY())) {
action=BOTTOM_ICON_ACTION;
}
actionY=event.getY();
break;
case MotionEvent.ACTION_MOVE:
float y=actionY-event.getY();
switch (action){
case TOP_ICON_ACTION:
cilpRectF.top=cilpRectF.top-y;
break;
case BOTTOM_ICON_ACTION:
cilpRectF.bottom=cilpRectF.bottom-y;
break;
}
checkBroad(imgRect);
actionY=event.getY();
invalidate();
break;
case MotionEvent.ACTION_UP:
action=-1;
break;
}
return true;
}
/**
* @description 边界校验
* @param imgRect
*/
private void checkBroad(RectF imgRect) {
if ((cilpRectF.bottom-cilpRectF.top) < options.min_height){//高度少于最小高度
switch (action) {
case TOP_ICON_ACTION:
cilpRectF.top=cilpRectF.bottom - options.min_height;
break;
case BOTTOM_ICON_ACTION:
cilpRectF.bottom=cilpRectF.top + options.min_height;
break;
}
} else if ((cilpRectF.bottom-cilpRectF.top) > options.max_height){//高度大于最大高度
switch (action) {
case TOP_ICON_ACTION:
cilpRectF.top=cilpRectF.bottom - options.max_height;
break;
case BOTTOM_ICON_ACTION:
cilpRectF.bottom=cilpRectF.top + options.max_height;
break;
}
}
if (cilpRectF.top < options.paddingHeight) {
cilpRectF.top = options.paddingHeight;
}
if (cilpRectF.bottom > height-options.paddingHeight){
cilpRectF.bottom = height-options.paddingHeight;
}
if ( cilpRectF.top < imgRect.top) {
cilpRectF.top = imgRect.top;
}
if ( cilpRectF.bottom > imgRect.bottom) {
cilpRectF.bottom = imgRect.bottom;
}
}
//判断手势down事件是否落在拉伸按钮区域内
public boolean isIconClick(MotionEvent event){
if (rects[0].contains((int)event.getX(), (int)event.getY())) {
System.out.println("点击顶部图标");
action=TOP_ICON_ACTION;
return true;
}
if (rects[1].contains((int)event.getX(), (int)event.getY())) {
System.out.println("点击底部图标");
action=BOTTOM_ICON_ACTION;
return true;
}
actionY=event.getY();
return false;
}
public RectF getCilpRectF() {
return cilpRectF;
}
public void setCilpRectF(RectF cilpRectF) {
this.cilpRectF = cilpRectF;
invalidate();
}
CilpImageView 类:
public class CilpImageView extends ImageView implements ScaleGestureDetector.OnScaleGestureListener,
ViewTreeObserver.OnGlobalLayoutListener{
private static float SCALE_MID = 0.1f;
/**
* 初始化时的缩放比例,如果图片宽或高大于屏幕,此值将小0
*/
private float initScale = 1.0f;
/**
* 缩放的手势检测
*/
private ScaleGestureDetector scaleGestureDetector = null;
private final Matrix scaleMatrix = new Matrix();
private final float[] matrixValues = new float[9];
/**
* 用于双击缩放
*/
private GestureDetector gestureDetector;
//是否自动缩放任务
private boolean isAutoScale;
private float mLastX;
private float mLastY;
private float centerX;
private float centerY;
//图片原始宽高
private int drawableW;
private int drawableH;
private boolean isCanDrag;
private int lastPointerCount;
private RectF borderRectF;
private CilpRectFChangeListener cilpRectFChangeListener;
private Options options;
public CilpImageView(Context context) {
super(context);
initView(context);
}
public CilpImageView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public CilpImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context) {
options = new Options();
setScaleType(ScaleType.MATRIX);
setBackgroundColor(Color.BLACK);
gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {//双击
if (isAutoScale)
return true;
float x=e.getX();
float y=e.getY();
if (getScale() != initScale) {
CilpImageView.this.postDelayed(new AutoScaleRunnable(initScale, x, y),16);
} else {
CilpImageView.this.postDelayed(new AutoScaleRunnable(initScale * 2, x, y),16);
}
isAutoScale=true;
return true;
}
});
scaleGestureDetector=new ScaleGestureDetector(context,this);
}
/**
* 获得当前的缩放比例
*
* @return
*/
public final float getScale() {
scaleMatrix.getValues(matrixValues);
return matrixValues[Matrix.MSCALE_X];
}
/**
* 边界校验
*/
private void checkBorder(){
RectF rectF=getMatrixRectF();
float deltaX = 0;
float deltaY = 0;
int width=getWidth();
// 如果宽或高大于屏幕,则控制范围,这里的0.001是因为精度丢失会产生问题,但是误差一般很小,所以直接加了一个0.01
if (rectF.width() + 0.01 >= width - 2 * options.paddingWidth) {
if (rectF.left > options.paddingWidth) {
deltaX = -rectF.left + options.paddingWidth;
}
if (rectF.right < width - options.paddingWidth){
deltaX = width - options.paddingWidth - rectF.right;
}
}
if (rectF.height() +0.01 >= borderRectF.height()){
if (rectF.top > borderRectF.top){
deltaY = -rectF.top + borderRectF.top;
}
if (rectF.bottom < borderRectF.bottom){
deltaY = borderRectF.bottom-rectF.bottom;
}
}
scaleMatrix.postTranslate(deltaX,deltaY);
}
/**
* 根据当前图片的Matrix获得图片的范围
*/
public RectF getMatrixRectF() {
Matrix matrix = scaleMatrix;
RectF rect = new RectF();
Drawable d = getDrawable();
if (null != d) {
rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
matrix.mapRect(rect);
}
return rect;
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
if (getDrawable() == null) {
return true;
}
float scaleFactor=detector.getScaleFactor();
scaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
checkBorder();
setImageMatrix(scaleMatrix);
setCilpRectFIfNeed();
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) { }
/**
* 根据手势做缩放
*/
public boolean onImageTouch(MotionEvent event, RectF borderRect) {
this.borderRectF=borderRect;
if (gestureDetector.onTouchEvent(event)){
return true;
}
scaleGestureDetector.onTouchEvent(event);
float x = 0, y = 0;
// 拿到触摸点的个数
final int pointerCount = event.getPointerCount();
// 得到多个触摸点的x与y
for (int i = 0; i < pointerCount; i++) {
x += event.getX(i);
y += event.getY(i);
}
x = x / pointerCount;
y = y / pointerCount;
/**
* 每当触摸点发生变化时,重置mLasX , mLastY
*/
if (pointerCount != lastPointerCount) {
isCanDrag = false;
mLastX = x;
mLastY = y;
}
lastPointerCount = pointerCount;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
resetMidScale();
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
float dx = x - mLastX;
float dy = y - mLastY;
if (!isCanDrag) {
isCanDrag = isCanDrag(dx, dy);
}
if (isCanDrag) {
if (getDrawable() != null) {
RectF rectF = getMatrixRectF();
// 如果宽度小于屏幕宽度,则禁止左右移动
if (rectF.width() <= borderRectF.width()) {
dx = 0;
}
// 如果高度小于屏幕高度,则禁止上下移动
if (rectF.height() <= borderRectF.height()) {
dy = 0;
}
scaleMatrix.postTranslate(dx, dy);
checkBorder();
setImageMatrix(scaleMatrix);
}
}
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_UP:
if (SCALE_MID>getScale()) {
postDelayed(
new AutoScaleRunnable(SCALE_MID, getWidth()/2, event.getY()), 1);
}
break;
case MotionEvent.ACTION_CANCEL:
lastPointerCount = 0;
break;
}
return true;
}
/**
* 是否是拖动行为
*/
private boolean isCanDrag(float dx, float dy) {
return Math.sqrt((dx * dx) + (dy * dy)) >= 0;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnGlobalLayoutListener(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
@Override
public void onGlobalLayout() {
if (0!=getHeight()) {
if (borderRectF==null)
borderRectF=new RectF(options.paddingWidth,(getHeight() - options.cilpHeight)/2,
getWidth()-options.paddingWidth,(getHeight() + options.cilpHeight)/2);
int height=getHeight();
Drawable d = getDrawable();
if (d == null)
return;
int width = getWidth();
// 拿到图片的宽和高
drawableW = d.getIntrinsicWidth();
drawableH = d.getIntrinsicHeight();
float scale ;
scale= borderRectF.width() /drawableW;
scaleMatrix.reset();
initScale = scale;
SCALE_MID=initScale;
centerX=(width - (borderRectF.width())) / 2;
centerY=(height - drawableH*scale) / 2;
scaleMatrix.postTranslate(centerX, centerY);
scaleMatrix.postScale(scale, scale,centerX,centerY);
// 图片移动至屏幕中心
setImageMatrix(scaleMatrix);
setCilpRectFIfNeed();
}
}
//校验裁剪边界
private void setCilpRectFIfNeed(){
RectF rectF=getMatrixRectF();
if (cilpRectFChangeListener!=null && (rectF.height() < borderRectF.height())){
borderRectF.top = rectF.top;
borderRectF.bottom = rectF.bottom;
rectF.right = borderRectF.right;
rectF.left = borderRectF.left;
cilpRectFChangeListener.onChange(rectF);
}
}
/**
* 计算最小缩放值
*/
private void resetMidScale(){
int distanceW = (int) (drawableW - borderRectF.width());
int distanceH = (int) (drawableH - borderRectF.height());
if (distanceH < distanceW) {//按高度算最小缩放比例
SCALE_MID= borderRectF.height() /drawableH;
} else {
SCALE_MID= borderRectF.width() /drawableW;
}
}
/**
* 剪切图片,返回剪切后的bitmap对象
*
* @return
*/
public Bitmap clip(RectF rectF) {
Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
draw(canvas);
bitmap=Bitmap.createBitmap(bitmap, (int)rectF.left,
(int)rectF.top,(int)rectF.width(),(int)rectF.height());
return bitmap;
}
/**
* 自动缩放任务
*/
private class AutoScaleRunnable implements Runnable{
static final float BIGGER=1.07f;
static final float SMALLER=0.93f;
private float tarScale;
private float tmpScale;
/**
* 缩放的中中心
*/
private float x;
private float y;
AutoScaleRunnable(float tarScale, float x, float y) {
this.tarScale=tarScale;
this.x=x;
this.y=y;
if (getScale() < tarScale){
tmpScale=BIGGER;
} else {
tmpScale=SMALLER;
}
}
@Override
public void run() {
scaleMatrix.postScale(tmpScale, tmpScale, x, y);
checkBorder();
setImageMatrix(scaleMatrix);
final float currentScale = getScale();
// 如果值在合法范围内,继续缩放
if ( ((tmpScale > 1f) && (currentScale < tarScale)) ||
((tmpScale < 1f) && (tarScale final float nextScale = tmpScale * currentScale;//下次的缩放比例
if (((tmpScale > 1f) && (currentScale //放大
if (nextScale > tarScale){
tmpScale = tarScale / currentScale;
}
}
if (((tmpScale < 1f) && (currentScale > tarScale))){//缩小
if (nextScale < tarScale){
tmpScale = tarScale / currentScale;
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
CilpImageView.this.postOnAnimation(this);
}else{
CilpImageView.this.postDelayed(this,1);
}
} else {
setCilpRectFIfNeed();
isAutoScale=false;
}
}
}
public void setCilpRectFChangeListener(CilpRectFChangeListener cilpRectFChangeListener) {
this.cilpRectFChangeListener = cilpRectFChangeListener;
}
public void setOptions(Options options) {
this.options = options;
}
public interface CilpRectFChangeListener{
void onChange(RectF rectF);
}
}
CilpTouchView类:
public class CilpTouchView extends View implements View.OnTouchListener{
private CilpImageView imageView;
private CilpBorderView borderView;
private boolean iconClick;
private RectF changeRect;
public CilpTouchView(Context context, CilpBorderView borderView, final CilpImageView imageView) {
super(context);
if (borderView == null || imageView == null) {
throw new NullPointerException("view is null");
}
this.borderView=borderView;
this.imageView=imageView;
imageView.setCilpRectFChangeListener(new CilpImageView.CilpRectFChangeListener() {
@Override
public void onChange(RectF rectF) {
CilpTouchView.this.borderView.setCilpRectF(rectF);
}
});
setOnTouchListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (borderView.isIconClick(event)){
iconClick = true;
changeRect= imageView.getMatrixRectF();
} else {
iconClick = false;
changeRect=borderView.getCilpRectF();
}
}
if (iconClick){
borderView.iconOntouch(event, changeRect);
} else {
imageView.onImageTouch(event, changeRect);
}
if (event.getAction() == MotionEvent.ACTION_UP) {
iconClick=false;
}
return true;
}
}
例子下载:https://github.com/gdflk/VariableCilpImageView