图片裁剪开源框架cropper源码解析

来源:互联网 发布:代码高亮 源码 编辑:程序博客网 时间:2024/06/05 01:14

这段时间工作内容不是很多,偶然间看到了图片裁剪框架cropper,便对其产生了兴趣,经过几天的分析,由最初的丈二和尚到现在的深入了解也算是付出有所收获吧,故在此进行学习记录,不喜勿喷哈。

一、cropper说明文档部分翻译

github地址:https://github.com/edmodo/cropper/wiki
这里写图片描述

Class Overview
The Cropper is an image cropping tool. It provides a way to set an image in XML or programmatically, and displays a resizable crop window on top of the image. Calling the method getCroppedImage() will then return the Bitmap marked by the crop window.

译1:cropper框架是一个图片裁剪工具,它提供了一种在xml文件或程序中对image图片进行设置,同时在image表层显示一个尺寸可动态变化的裁剪框。我们可以通过调用getCroppedImage()来获取被裁剪框所标志的Bitmap。

Developers can customize the following attributes (both via XML and programmatically):
1、appearance of guidelines in the crop window
2、whether the aspect ratio is fixed or not
3、aspect ratio (if the aspect ratio is fixed)
4、image resource

译2:开发者可以自定义以下属性(通过xml和代码均可)
1、控制裁剪框参考线的动态显示
2、设置是否锁定纵横比
3、设置指定纵横比(锁定纵横比的情况下)
4、设置image资源文件

二、cropper框架使用

参考对源码工程(https://github.com/edmodo/cropper)中CropperSimple示例代码的分析,介绍该工具的使用:

<?xml version="1.0" encoding="utf-8"?><ScrollView    android:id="@+id/scrollview"    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"    tools:context=".MainActivity"    xmlns:app="http://schemas.android.com/apk/res-auto">    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:orientation="vertical"        android:padding="@dimen/content_padding">        <TextView            android:id="@+id/title"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_gravity="center"            android:text="@string/title"            android:textSize="24sp"            android:textStyle="bold"/>        <com.edmodo.cropper.CropImageView            android:id="@+id/CropImageView"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:layout_marginTop="@dimen/content_padding"            android:adjustViewBounds="true"            android:scaleType="centerInside"            android:src="@drawable/butterfly"/>           ...</ScrollView>

这个布局xml文件内容有点长,但内容非常简单,我们只需要关注com.edmodo.cropper.CropImageView(这就是自定义的可供裁剪的View,继承自ImageView)标签即可,该标签中指定了CropImageView的适配方式为centerInside(即将图片的内容完整居中显示),src图片为@drawable/butterfly。
接下来再来分析MainActivity.java代码:

package com.example.croppersample;import android.app.Activity;import android.graphics.Bitmap;import android.os.Bundle;import android.view.View;import android.view.Window;import android.widget.AdapterView;import android.widget.Button;import android.widget.CompoundButton;import android.widget.CompoundButton.OnCheckedChangeListener;import android.widget.ImageView;import android.widget.SeekBar;import android.widget.SeekBar.OnSeekBarChangeListener;import android.widget.Spinner;import android.widget.TextView;import android.widget.ToggleButton;import com.edmodo.cropper.CropImageView;public class MainActivity extends Activity {    // Private Constants    /**    * 指定初始裁剪框的参考线的显示模式:    * 0:Off模式,参考线始终不显示    * 1:On Touch模式,裁剪框被触摸时显示参考线,默认方式    * 2:On模式,参考线一直显示    */    private static final int GUIDELINES_ON_TOUCH = 1;    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        requestWindowFeature(Window.FEATURE_NO_TITLE);        setContentView(R.layout.activity_main);        //ToggleButton,用于设置裁剪框是否锁定纵横比        final ToggleButton fixedAspectRatioToggleButton = (ToggleButton) findViewById(R.id.fixedAspectRatioToggle);        final TextView aspectRatioXTextView = (TextView) findViewById(R.id.aspectRatioX);        //滑动条设置X轴的比例参数        final SeekBar aspectRatioXSeekBar = (SeekBar) findViewById(R.id.aspectRatioXSeek);        final TextView aspectRatioYTextView = (TextView) findViewById(R.id.aspectRatioY);        //滑动条设置Y轴的比例参数        final SeekBar aspectRatioYSeekBar = (SeekBar) findViewById(R.id.aspectRatioYSeek);        final Spinner guidelinesSpinner = (Spinner) findViewById(R.id.showGuidelinesSpin);        final CropImageView cropImageView = (CropImageView) findViewById(R.id.CropImageView);        //获取自定义裁剪View对象        final ImageView croppedImageView = (ImageView) findViewById(R.id.croppedImageView);        //图片裁剪按钮        final Button cropButton = (Button) findViewById(R.id.Button_crop);        fixedAspectRatioToggleButton.setOnCheckedChangeListener(new OnCheckedChangeListener() {            @Override            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {                cropImageView.setFixedAspectRatio(isChecked);                cropImageView.setAspectRatio(aspectRatioXSeekBar.getProgress(), aspectRatioYSeekBar.getProgress());                aspectRatioXSeekBar.setEnabled(isChecked);                aspectRatioYSeekBar.setEnabled(isChecked);            }        });        // 初始设置X/Y轴的进度条均不可用        aspectRatioXSeekBar.setEnabled(false);        aspectRatioYSeekBar.setEnabled(false);        aspectRatioXTextView.setText(String.valueOf(aspectRatioXSeekBar.getProgress()));        aspectRatioYTextView.setText(String.valueOf(aspectRatioXSeekBar.getProgress()));aspectRatioXSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {            @Override            public void onProgressChanged(SeekBar aspectRatioXSeekBar, int progress, boolean fromUser) {                if (progress < 1) {                    aspectRatioXSeekBar.setProgress(1);                }//设置裁剪框的X/Y轴的比例大小         cropImageView.setAspectRatio(aspectRatioXSeekBar.getProgress(), aspectRatioYSeekBar.getProgress());                aspectRatioXTextView.setText(String.valueOf(aspectRatioXSeekBar.getProgress()));            }            @Override            public void onStartTrackingTouch(SeekBar seekBar) {                // Do nothing.            }            @Override            public void onStopTrackingTouch(SeekBar seekBar) {                // Do nothing.            }        });        // Initialize aspect ratio Y SeekBar.        aspectRatioYSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {            @Override            public void onProgressChanged(SeekBar aspectRatioYSeekBar, int progress, boolean fromUser) {                if (progress < 1) {                    aspectRatioYSeekBar.setProgress(1);                }                cropImageView.setAspectRatio(aspectRatioXSeekBar.getProgress(), aspectRatioYSeekBar.getProgress());                aspectRatioYTextView.setText(String.valueOf(aspectRatioYSeekBar.getProgress()));            }            @Override            public void onStartTrackingTouch(SeekBar seekBar) {                // Do nothing.            }            @Override            public void onStopTrackingTouch(SeekBar seekBar) {                // Do nothing.            }        });        // Set up the Guidelines Spinner.        guidelinesSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {            public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {                cropImageView.setGuidelines(i);            }            public void onNothingSelected(AdapterView<?> adapterView) {                // Do nothing.            }        });        //设置初始的参考线显示模式        guidelinesSpinner.setSelection(GUIDELINES_ON_TOUCH);        // Initialize the Crop button.        cropButton.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {            //通过裁剪按钮获得裁剪到的bitmap,并进行显示                final Bitmap croppedImage = cropImageView.getCroppedImage();                croppedImageView.setImageBitmap(croppedImage);            }        });    }}

通过以上代码我们可知,croppedImageView的裁剪分为两种方式:第一种为锁定纵横比方式,分别通过指定的在X/Y轴的比例大小设置纵横比,后续对裁剪框的大小调整便会参照此纵横比;第二种为自由方式,即用户可自由地进行裁剪框大小的设定,然后进行裁剪。
croppedImageView的主要方法如下:

//设置裁剪框是否保持纵横比public void setFixedAspectRatio(boolean fixAspectRatio) //设置指定的X/Y轴比例大小,纵横比=X/Y,此时需要fixAspectRatio==truepublic void setAspectRatio(int aspectRatioX, int aspectRatioY)//设置参考线的显示模式,模式说明参照前面介绍public void setGuidelines(int guidelinesMode)//获得裁剪得到的Bitmap对象public Bitmap getCroppedImage()

可以看到,CroppedImageView具有良好的封装性,基本上我们只需通过以上几个方法,便可实现对图片的裁剪功能,是不是非常简单和方便呢?总结为三步:
1、xml中引入CropImageView标签;
2、获得cropImageView对象,完成初始设置(锁定纵横比,参考线等);
3、调用getCroppedImage()获取裁剪的bitmap对象;

三、cropper框架结构

这里写图片描述
通过查看源码,发现了一个设计很巧妙的地方——枚举(enum),对,枚举的使用,也在此膜拜一下作者大神,源码中在两个地方用到了枚举:
1、edge包中的Edge,字面上可以猜测它和边界有关,是的,该枚举中对裁剪框的四个边界进行了总结,如下:

package com.edmodo.cropper.cropwindow.edge;import android.graphics.RectF;import android.support.annotation.NonNull;import com.edmodo.cropper.util.AspectRatioUtil;/** * Enum representing an edge in the crop window. */public enum Edge {    LEFT,  //裁剪框左边界    TOP,   //裁剪框上边界    RIGHT, //裁剪框右边界    BOTTOM;//裁剪框下边界    private float mCoordinate;  //边界坐标    ...

可以看到每个Edge对象中都维持了一个mCoordinate局部变量,这个变量非常重要,而且规定了当为Edge.LEFT或Edge.RIGHT时mCoordinate代表X方向横坐标,当为Edge.TOP或Edge.BOTTOM时代表Y方向纵坐标,可以思考一下为什么要这样规定呢?之所以要定义出四个边界的枚举,是为了确定出裁剪框的大小和坐标位置,通过上面的的规定,即知道了左右边界的X坐标和上下边界Y坐标,似乎是能够确定出裁剪框的大小和坐标位置的。答案是肯定的,因为四个边框的交接点的坐标确定了下来,故我们只要知道了左上(left-top)坐标和右下(right-bottom)坐标便能确定出裁剪框的尺寸大小和位置坐标了。

另外再看一下edge包中的另一个类EdgePair,其代码很短,也很简单:

package com.edmodo.cropper.cropwindow.edge;/** * Simple class to hold a pair of Edges. */public class EdgePair {    public Edge primary;     //X轴边界    public Edge secondary;   //Y轴边界    // Constructor     public EdgePair(Edge edge1, Edge edge2) {        primary = edge1;        secondary = edge2;    }}

可以看到,EdgePair就是包含两个Edge的简单集合,关于它的作用,将在后面进行说明。

2、handle包中的Handle,也可以从字面上猜测它和处理有关,这个枚举中定义了对裁剪框的所有有效触摸类型,如触摸内部、触摸四个边界、触摸四个边角共9中方式,先来看看它的源码:

package com.edmodo.cropper.cropwindow.handle;import android.graphics.RectF;import android.support.annotation.NonNull;import com.edmodo.cropper.cropwindow.edge.Edge;public enum Handle {    //触摸左上角    TOP_LEFT(new CornerHandleHelper(Edge.TOP, Edge.LEFT)),    //触摸右上角    TOP_RIGHT(new CornerHandleHelper(Edge.TOP, Edge.RIGHT)),    //触摸左下角    BOTTOM_LEFT(new CornerHandleHelper(Edge.BOTTOM, Edge.LEFT)),    //触摸右下角    BOTTOM_RIGHT(new CornerHandleHelper(Edge.BOTTOM, Edge.RIGHT)),    //触摸左边界    LEFT(new VerticalHandleHelper(Edge.LEFT)),    //触摸上边界    TOP(new HorizontalHandleHelper(Edge.TOP)),    //触摸右边界    RIGHT(new VerticalHandleHelper(Edge.RIGHT)),    //触摸下边界    BOTTOM(new HorizontalHandleHelper(Edge.BOTTOM)),    //触摸裁剪框内部    CENTER(new CenterHandleHelper());    //HandleHelper为抽象类,定义了对不同触摸方式的处理    private HandleHelper mHelper;    //构造函数必须传入一个触摸方式的处理类HandleHelper    Handle(HandleHelper helper) {        mHelper = helper;    }    //非锁定纵横比下,对触摸方式的响应,刷新裁剪框显示    public void updateCropWindow(float x,                                 float y,                                 @NonNull RectF imageRect,                                 float snapRadius) {        mHelper.updateCropWindow(x, y, imageRect, snapRadius);    }    //锁定纵横比下,对触摸方式的响应,刷新裁剪框显示    public void updateCropWindow(float x,                                 float y,                                 float targetAspectRatio,                                 @NonNull RectF imageRect,                                 float snapRadius) {        mHelper.updateCropWindow(x, y, targetAspectRatio, imageRect, snapRadius);    }}

前面把handle类理解为和处理有关其实是不太准确的,在这里更正一下,通过源码我们可以发现handle应该是理解为 待处理的触摸类型 对象,共有9种,真正的触摸处理是由HandleHelper对象完成的,该类为抽象类,这样是为了保证不同 待处理的触摸方式 有不同的触摸处理动作。举个栗子:当我们在触摸裁剪框内部时,触摸处理动作是裁剪框随着手指的移动而移动,裁剪框本身大小不会变化;当我们触摸裁剪框左边界时,触摸处理动作是裁剪框的左边界随着手指的移动而移动,裁剪框的大小会发生变化;当我们触摸裁剪框左上角时,触摸处理动作是裁剪框的左边界和上边界随着手指的移动而移动,裁剪框的大小也会发生变化。
所以,我们可以看到抽象类HandleHelper有四个继承子类,分别是CenterHandleHelper、CornerHandleHelper、HorizontalHandleHelper、VerticalHandleHelper,对应着不同的触摸处理动作。
这里写图片描述

接下来就是util包了,主要包含四个工具类:AspectRadioUtil/HandleUtil/MathUtil/PaintUtil,通过字面上就能够知道它们的作用了吧,下面在分析CropImageView源码时会逐一使用到。

四、cropper源码分析

cropper源码的主要体现为CropImageView类,它是整个框架的核心类,该类继承自ImageView,在ImageView的基础上增加了裁剪框的显示、拖拽和裁剪功能,下面就结合其源码进行分析:
首先看看Constructor

public CropImageView(Context context) {        super(context);        init(context, null);    }    public CropImageView(Context context, AttributeSet attrs) {        super(context, attrs);        init(context, attrs);    }    public CropImageView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init(context, attrs);    }

三个构造方法都调用了init(context, attrs)方法,主要完成一些初始化的设置

private void init(@NonNull Context context, @Nullable AttributeSet attrs) {        //获取自定义属性        final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CropImageView, 0, 0);        //分割线显示模式        mGuidelinesMode = typedArray.getInteger(R.styleable.CropImageView_guidelines, 1);        //是否锁定纵横比        mFixAspectRatio = typedArray.getBoolean(R.styleable.CropImageView_fixAspectRatio, false);        //纵横比X轴比例大小        mAspectRatioX = typedArray.getInteger(R.styleable.CropImageView_aspectRatioX, 1);        //纵横比Y轴比例大小        mAspectRatioY = typedArray.getInteger(R.styleable.CropImageView_aspectRatioY, 1);        typedArray.recycle();        final Resources resources = context.getResources();        //描绘边界的画笔        mBorderPaint = PaintUtil.newBorderPaint(resources);        //描绘参考线的画笔        mGuidelinePaint = PaintUtil.newGuidelinePaint(resources);        //描绘半透明蒙版(CropImageView之内裁剪框之外)的画笔        mSurroundingAreaOverlayPaint = PaintUtil.newSurroundingAreaOverlayPaint(resources);        //描绘倒角的画笔        mCornerPaint = PaintUtil.newCornerPaint(resources);        //手指触点距离裁剪框范围偏差        mHandleRadius = resources.getDimension(R.dimen.target_radius);           //手指触点距离CropImageView边界偏差        mSnapRadius = resources.getDimension(R.dimen.snap_radius);        //描边宽度        mBorderThickness = resources.getDimension(R.dimen.border_thickness);        //倒角宽度        mCornerThickness = resources.getDimension(R.dimen.corner_thickness);        //倒角长度        mCornerLength = resources.getDimension(R.dimen.corner_length);    }

相关注释在源码中都已标注,其中比较难以理解的是mHandleRadius和mSnapRadius,这两个变量代表偏差的意思,我们知道在用手指触摸手机屏幕时,由于手指和屏幕是大面积接触,在计算接触点的时候是存在一定误差的,所以在处理时需要一定的方法来抵消掉这种误差。mHandleRadius就是用于消除手指触摸裁剪框(包括边界、倒角和内部)时的误差,mSnapRadius是用于当手指拖拽裁剪框到接近(未到达)CropImageView边界时,使得裁剪框的边界和CropImageView的边界重合。

接下来就是onLayout()方法了,CropImageView对该方法进行了复写:

@Override    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {        super.onLayout(changed, left, top, right, bottom);        //获取CropImageView的坐标信息(left,top,right,bottom),保存于mBitmapRect中        mBitmapRect = getBitmapRect();        //初始化裁剪框        initCropWindow(mBitmapRect);    }

再来看看initCropWindow(mBitmapRect)方法:

private void initCropWindow(@NonNull RectF bitmapRect) {            //锁定纵横比            if (mFixAspectRatio) {                initCropWindowWithFixedAspectRatio(bitmapRect);            } else {  //未锁定纵横比                final float horizontalPadding = 0.1f * bitmapRect.width();                final float verticalPadding = 0.1f * bitmapRect.height();                Edge.LEFT.setCoordinate(bitmapRect.left + horizontalPadding);                Edge.TOP.setCoordinate(bitmapRect.top + verticalPadding);                Edge.RIGHT.setCoordinate(bitmapRect.right - horizontalPadding);                Edge.BOTTOM.setCoordinate(bitmapRect.bottom - verticalPadding);            }        }

可以看到裁剪框的初始化分为两种情况,主要功能是完成对裁剪框的四边界(Edge)坐标进行赋值,例如未锁定纵横比的情况下,设置的边界尺寸是CropImageView的对应边界值减去默认的内边距(padding,左右padding为宽度的1/10,上下padding为高度的1/10),后续的对于裁剪框的绘制使用的都是边界(Edge)坐标。

再接下来就是重要的onDraw()方法了

@Override    protected void onDraw(Canvas canvas) {        //调用父类绘制方法        super.onDraw(canvas);        /**        * 下面四步完成裁剪框的绘制;        * 1、绘制半透明蒙版效果        * 2、绘制参考线        * 3、绘制边界        * 4、绘制倒角        */        drawDarkenedSurroundingArea(canvas);        drawGuidelines(canvas);        drawBorder(canvas);        drawCorners(canvas);    }

关于每一步的绘制过程不再过多分析,只是强调一点,每一步用到的坐标数据都来自于onLayout()中保存至Edge的坐标值。~~太唠叨了…

最后分析一下最最重要的onTouchEvent(MotionEvent event)方法

 @Override    public boolean onTouchEvent(MotionEvent event) {        if (!isEnabled()) {            return false;        }        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                onActionDown(event.getX(), event.getY());                return true;            case MotionEvent.ACTION_UP:            case MotionEvent.ACTION_CANCEL:                getParent().requestDisallowInterceptTouchEvent(false);                onActionUp();                return true;            case MotionEvent.ACTION_MOVE:                onActionMove(event.getX(), event.getY());                getParent().requestDisallowInterceptTouchEvent(true);                return true;            default:                return false;        }    }

首先在MotionEvent.ACTION_DOWN中调用了onActionDown(event.getX(), event.getY())方法,看看里面做了哪些处理

private void onActionDown(float x, float y) {        //获取裁剪框的左上角和右下角坐标        final float left = Edge.LEFT.getCoordinate();        final float top = Edge.TOP.getCoordinate();        final float right = Edge.RIGHT.getCoordinate();        final float bottom = Edge.BOTTOM.getCoordinate();        //根据手指触点坐标和裁剪框坐标以及可允许误差mHandleRadius判断是哪种触摸种类(前面总结的9种中的一种)并返回        mPressedHandle = HandleUtil.getPressedHandle(x, y, left, top, right, bottom, mHandleRadius);        //如果获取的触摸种类不为空,获取其偏移量(x,y方向)        if (mPressedHandle != null) {            HandleUtil.getOffset(mPressedHandle, x, y, left, top, right, bottom, mTouchOffset);            invalidate();        }    }

这里解释一下为什么要获取一个偏移量mTouchOffset(PointF类型),这个偏移量用于后续对裁剪框进行拖拽或大小改变时的坐标补偿,因为在获取触摸种类时使用了mHandleRadius作为允许的偏差,所以在这个偏差范围内的触点误差是需要补偿回来的,不然会导致拖拽裁剪框的时候会有“一跳”的现象,影响界面友好性。到这里你也许会问为什么要引入mHandleRadius这样一个偏差参数,如果不引入的话就不需要进行误差补偿了,对的,理论上就应该是这样的。但是这样的话,对用户的要求就非常高了,如果没有可允许偏差mHandleRadius,只有用户非常精确地按下裁剪框的特殊位置(如边界和边角处),程序才会返回特定的触摸类型,这样用户在使用的时候是会被逼疯的…

接下来就是MotionEvent.ACTION_MOVE中的onActionMove(event.getX(), event.getY())方法了

private void onActionMove(float x, float y) {        if (mPressedHandle == null) {            return;        }        //x,y坐标分别通过mTouchOffset进行误差补偿        x += mTouchOffset.x;        y += mTouchOffset.y;        //锁定纵横比        if (mFixAspectRatio) {            mPressedHandle.updateCropWindow(x, y, getTargetAspectRatio(), mBitmapRect, mSnapRadius);        } else {  //非锁定纵横比            mPressedHandle.updateCropWindow(x, y, mBitmapRect, mSnapRadius);        }        invalidate();    }

再次以非锁定纵横比的情况进行分析,源码中可以看到在完成坐标补偿后,便对特定触摸类型进行了更新坐标的操作,这里以相对复杂的触摸左上倒角为例(mPressedHandle==Handle.TOP_LEFT),分析其中的处理过程:
mPressedHandle.updateCropWindow(x, y, mBitmapRect, mSnapRadius)会调用至mHelper.updateCropWindow(x, y, imageRect, snapRadius)方法,翻看其源码

void updateCropWindow(float x,                          float y,                          @NonNull RectF imageRect,                          float snapRadius) {        //获取EdgePair(边界对)对象        final EdgePair activeEdges = getActiveEdges();        //返回第一个边界        final Edge primaryEdge = activeEdges.primary;        //返回第二个边界        final Edge secondaryEdge = activeEdges.secondary;        if (primaryEdge != null)            primaryEdge.adjustCoordinate(x, y, imageRect, snapRadius, UNFIXED_ASPECT_RATIO_CONSTANT);        if (secondaryEdge != null)            secondaryEdge.adjustCoordinate(x, y, imageRect, snapRadius, UNFIXED_ASPECT_RATIO_CONSTANT);    }

可以看到前面提到的EdgePair在这里使用到了,其作用就是保存了两条边界,即在构造TOP_LEFT(new CornerHandleHelper(Edge.TOP, Edge.LEFT))时传入的两条边界,当用户拖拽倒角的时候分别调用两条边的adjustCoordinate(…)方法进行坐标更新。

待坐标更新完成后再进行重绘。requestDisallowInterceptTouchEvent(true)方法是保证父类的touch事件能够传递下来。

然后就是MotionEvent.ACTION_UP和MotionEvent.ACTION_CANCEL的onActionUp()方法了

private void onActionUp() {        if (mPressedHandle != null) {            mPressedHandle = null;            invalidate();        }    }

很简单吧,就是进行触摸完成后的清理工作。

最后便是裁剪操作了,再分析一下其源码

public Bitmap getCroppedImage() {        final Drawable drawable = getDrawable();        if (drawable == null || !(drawable instanceof BitmapDrawable)) {            return null;        }        final float[] matrixValues = new float[9];        getImageMatrix().getValues(matrixValues);        final float scaleX = matrixValues[Matrix.MSCALE_X];        final float scaleY = matrixValues[Matrix.MSCALE_Y];        final float transX = matrixValues[Matrix.MTRANS_X];        final float transY = matrixValues[Matrix.MTRANS_Y];        final float bitmapLeft = (transX < 0) ? Math.abs(transX) : 0;        final float bitmapTop = (transY < 0) ? Math.abs(transY) : 0;        //获取原始的bitmap        final Bitmap originalBitmap = ((BitmapDrawable) drawable).getBitmap();        //获取X轴裁剪的起始坐标        final float cropX = (bitmapLeft + Edge.LEFT.getCoordinate()) / scaleX;        //获取Y轴裁剪的起始坐标        final float cropY = (bitmapTop + Edge.TOP.getCoordinate()) / scaleY;        //获取裁剪宽度        final float cropWidth = Math.min(Edge.getWidth() / scaleX, originalBitmap.getWidth() - cropX);        //获取裁剪高度        final float cropHeight = Math.min(Edge.getHeight() / scaleY, originalBitmap.getHeight() - cropY);        //返回裁剪后的bitmap        return Bitmap.createBitmap(originalBitmap,                                   (int) cropX,                                   (int) cropY,                                   (int) cropWidth,                                   (int) cropHeight);    }

由于裁剪的对象是原始的bitmap(即未经缩放处理),而裁剪边界是经过缩放处理后的值,所以需要对裁剪边界的坐标或宽高进行等比例的放大,最后形成的起始坐标和宽高才是对原始bitmap进行裁剪操作。

至此,关于cropper框架的分析过程基本就完成了。欢迎踊跃拍砖。。。

3 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 4岁宝宝不爱学习怎么办 4岁宝宝不爱写字怎么办 孩子调皮老师不让上学了怎么办 小学生写字握笔握出剪子来怎么办 儿子6岁不会写字怎么办 宝宝在幼儿园不说话怎么办 孩子上幼儿园不说话怎么办 幼儿写数字不写怎么办 小孩不吃饭怎么办 十个月 十个月小孩不爱吃饭怎么办 十个月的小孩不吃饭怎么办 家长要调幼儿园监控怎么办 自己带孩子婆婆生气怎么办 婆婆老是觉得我奶水不够怎么办 1岁小儿特别懒怎么办 17的孩子很懒怎么办 上大班的孩子不愿写字怎么办 快上中班的小朋友不爱学习怎么办 小学生两边肩膀不平应该怎么办 写字右肩膀疼是怎么办 开车久了肩膀疼怎么办 3岁宝宝撕书怎么办 孩子上幼儿园不爱写字怎么办 一年级小孩不爱做作业怎么办 小孩不愿多做作业怎么办 小孩一年级不自觉做作业怎么办 小孩会读不会写怎么办 好多字都不会写怎么办 写作业怎么办才能写快 五周宝宝爱玩不写字怎么办 爱玩手机的小孩怎么办? 一年级学生记不住生字怎么办 一年级小孩记不住生字怎么办 配镜度数高了怎么办 宝宝两岁半不肯坐马桶拉臭臭怎么办 儿子字写得不好 怎么办 小孩不听话不爱读书和写字怎么办 两岁宝宝不愿意穿衣服怎么办 做题粗心不认真怎么办 5岁宝宝不会写字怎么办 四岁宝宝不会写字怎么办