Android 自定义控件——图片剪裁

如图:

思路:在一个自定义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 = 0;
    private float mY_1 = 0;
    // 触摸事件判断
    private final int STATUS_SINGLE = 1;
    private final int STATUS_MULTI_START = 2;
    private final int STATUS_MULTI_TOUCHING = 3;
    // 当前状态
    private int mStatus = STATUS_SINGLE;
    // 默认裁剪的宽高
    private int cropWidth;
    private int cropHeight;
    // 浮层Drawable的四个点
    private final int EDGE_LT = 1;
    private final int EDGE_RT = 2;
    private final int EDGE_LB = 3;
    private final int EDGE_RB = 4;
    private final int EDGE_MOVE_IN = 5;
    private final int EDGE_MOVE_OUT = 6;
    private final int EDGE_NONE = 7;

    public int currentEdge = EDGE_NONE;

    protected float oriRationWH = 0;
    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 >= 11) {
                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() > 1) {
            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 == 0 && dy == 0)) {
                        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() == 0
                || mDrawable.getIntrinsicHeight() == 0) {
            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) / 2;
            int top = (getHeight() - h) / 2;
            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) / 2;
            int floatTop = (getHeight() - floatHeight) / 2;
            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 = 50;
    private Paint mLinePaint = new Paint();
    private Paint mLinePaint2 = new Paint();
    {
        mLinePaint.setARGB(200, 50, 50, 50);
        mLinePaint.setStrokeWidth(1F);
        mLinePaint.setStyle(Paint.Style.STROKE);
        mLinePaint.setAntiAlias(true);
        mLinePaint.setColor(Color.WHITE);
        //
        mLinePaint2.setARGB(200, 50, 50, 50);
        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) / 2, top
                + dipTopx(mContext, offset) / 2, right
                - dipTopx(mContext, offset) / 2, bottom
                - dipTopx(mContext, offset) / 2);
        //画默认的选择框
        canvas.drawRect(mRect, mLinePaint);
        //画四个角的四个粗拐角、也就是八条粗线
        canvas.drawLine((left + dipTopx(mContext, offset) / 2 - 3.5f), top
                        + dipTopx(mContext, offset) / 2,
                left + dipTopx(mContext, offset) - 8f,
                top + dipTopx(mContext, offset) / 2, mLinePaint2);
        canvas.drawLine(left + dipTopx(mContext, offset) / 2,
                top + dipTopx(mContext, offset) / 2,
                left + dipTopx(mContext, offset) / 2,
                top + dipTopx(mContext, offset) / 2 + 30, mLinePaint2);
        canvas.drawLine(right - dipTopx(mContext, offset) + 8f,
                top + dipTopx(mContext, offset) / 2,
                right - dipTopx(mContext, offset) / 2,
                top + dipTopx(mContext, offset) / 2, mLinePaint2);
        canvas.drawLine(right - dipTopx(mContext, offset) / 2,
                top + dipTopx(mContext, offset) / 2 - 3.5f,
                right - dipTopx(mContext, offset) / 2,
                top + dipTopx(mContext, offset) / 2 + 30, mLinePaint2);
        canvas.drawLine((left + dipTopx(mContext, offset) / 2 - 3.5f), bottom
                        - dipTopx(mContext, offset) / 2,
                left + dipTopx(mContext, offset) - 8f,
                bottom - dipTopx(mContext, offset) / 2, mLinePaint2);
        canvas.drawLine((left + dipTopx(mContext, offset) / 2), bottom
                        - dipTopx(mContext, offset) / 2,
                (left + dipTopx(mContext, offset) / 2),
                bottom - dipTopx(mContext, offset) / 2 - 30f, mLinePaint2);
        canvas.drawLine((right - dipTopx(mContext, offset) + 8f), bottom
                        - dipTopx(mContext, offset) / 2,
                right - dipTopx(mContext, offset) / 2,
                bottom - dipTopx(mContext, offset) / 2, mLinePaint2);
        canvas.drawLine((right - dipTopx(mContext, offset) / 2), bottom
                        - dipTopx(mContext, offset) / 2 - 30f,
                right - dipTopx(mContext, offset) / 2,
                bottom - dipTopx(mContext, offset) / 2 + 3.5f, mLinePaint2);

    }

    @Override
    public void setBounds(Rect bounds) {
        super.setBounds(new Rect(bounds.left - dipTopx(mContext, offset) / 2,
                bounds.top - dipTopx(mContext, offset) / 2, bounds.right
                + dipTopx(mContext, offset) / 2, bounds.bottom
                + dipTopx(mContext, offset) / 2));
    }

    @Override
    public void setAlpha(int alpha) {

    }

    @Override
    public void setColorFilter(ColorFilter cf) {

    }

    @Override
    public int getOpacity() {
        return 0;
    }

    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), 300,
            300);
            //调用该方法得到剪裁好的图片
            Bitmap mBitmap= mView.getCropImage();
   }
}
时间: 2024-10-12 03:20:35

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

android 选择图片 剪裁 拍照 兼容所有版本的代码

前言,版本兼容问题主要是由于4.4以前和4.4以后的Uri的格式不同所造成的错误 资料 Android 4.4从图库选择图片,获取图片路径并裁剪 1.拍照 和选择图片 ①选择图片 1 intent = new Intent(Intent.ACTION_GET_CONTENT); 2 intent.setType("image/*"); 3 startActivityForResult(intent, GALLERY_REQUEST_CODE);   ②拍照 1 intent = new

Android开发实践:自己动手编写图片剪裁应用(2)

上篇文章主要介绍了我开源在Github上的图片剪裁库(ImageCropper)的基本特性和用法,从本文开始,慢慢介绍一些开发图片剪裁应用中涉及的知识点和技术. 其实Android系统本身也提供了图片剪裁的模块,我们可以直接通过Intent来调用系统的图片剪裁功能,本文我们就先了解一下系统自带的图片剪裁功能是如何调用的吧. 得到被剪裁图片的URL地址 既然是图片剪裁,就一定要有被剪裁的图片,由于图片数据一般很大,为了防止内存溢出,普通APP与Android系统图片剪裁应用之间是通过URL来传递图

Android开发实践:自己动手编写图片剪裁应用(1)

最近利用一周左右的业余时间,终于完成了一个Android图片剪裁库,核心功能是根据自己的理解实现的,部分代码参考了Android源码的图片剪裁应用.现在将该代码开源在Github上以供大家学习和使用,地址:https://github.com/Jhuster/ImageCropper,效果如下所示: 我的大致计划是首先介绍一下这个库的用法,然后再写几篇文章介绍一下其中的一些原理和关键技术,希望对Android开发新手有所帮助. [特性] 支持通过手势移动和缩放剪裁窗口 支持固定剪裁窗口大小.固定

Android开发实践:自己动手编写图片剪裁应用(3)

前面两篇文章分别介绍了我编写的开源项目ImageCropper库,以及如何调用系统的图片剪裁模块,本文则继续分析一下开发Android图片剪裁应用中需要用到的Bitmap操作. 在Android系统中,对图片的操作主要是通过Bitmap类和Matrix类来完成,本文就介绍一下图片剪裁应用中对Bitmap的一些操作,包括:打开.保存.剪裁.旋转等,我已经将这些操作都封装到了一个BitmapHelper.java类中,放到GitHub上了(点击这里),大家可以方便地集成到自己的项目中. 打开图片 图

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

最近从以前的项目中扒下来一个常用的模块,在这里有必要记录一下的,就是android上获取图片以及裁剪图片,怎么样?这个功能是不是很常用啊,你随便打开一个App,只要它有注册功能都会有设置人物头像的功能,尤其在内容型的app中更为常见,那么这些功能是怎么实现的呢?今天,在这里就记录一下好了,防止以后的项目中也会用到,就直接拿来用好了. 1.通过拍照或者图册获取图片(不需要剪裁) 这种获取图片的方式就比较次了,因为不设置图片的剪裁功能,有可能因为图片过大,导致OOM,但是这种方式也是有必要讲一下的,

Android图片剪裁库

最近利用一周左右的业余时间,终于完成了一个Android图片剪裁库,核心功能是根据自己的理解实现的,部分代码参考了Android源码的图片剪裁应用.现在将该代码开源在Github上以供大家学习和使用,地址:https://github.com/Jhuster/ImageCropper,效果如下所示: 我的大致计划是首先介绍一下这个库的用法,然后再写几篇文章介绍一下其中的一些原理和关键技术,希望对Android开发新手有所帮助. [特性] 支持通过手势移动和缩放剪裁窗口 支持固定剪裁窗口大小.固定

android拍照图片选取与图片剪裁

转载请注明出处:http://blog.csdn.net/allen315410/article/details/39994913 最近从以前的项目中扒下来一个常用的模块,在这里有必要记录一下的,就是android上获取图片以及裁剪图片,怎么样?这个功能是不是很常用啊,你随便打开一个App,只要它有注册功能都会有设置人物头像的功能,尤其在内容型的app中更为常见,那么这些功能是怎么实现的呢?今天,在这里就记录一下好了,防止以后的项目中也会用到,就直接拿来用好了. 1.通过拍照或者图册获取图片(不

Android自定义控件系列八:详解onMeasure()(二)--利用onMeasure测量来实现图片拉伸永不变形,解决屏幕适配问题

上一篇文章详细讲解了一下onMeasure/measure方法在Android自定义控件时的原理和作用,参看博文:Android自定义控件系列七:详解onMeasure()方法中如何测量一个控件尺寸(一),今天就来真正实践一下,让这两个方法大显神威来帮我们搞定图片的屏幕适配问题. 请尊重原创劳动成果,转载请注明出处:http://blog.csdn.net/cyp331203/article/details/45038329,非允许请勿用于商业或盈利用途,违者必究. 使用ImageView会遇到

Android圆形图片--自定义控件

Android圆形图片控件效果图如下: 代码如下: RoundImageView.java package com.dxd.roundimageview; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Canvas