圖片裁剪開源框架cropper原始碼解析
這段時間工作內容不是很多,偶然間看到了圖片裁剪框架cropper,便對其產生了興趣,經過幾天的分析,由最初的丈二和尚到現在的深入瞭解也算是付出有所收穫吧,故在此進行學習記錄,不喜勿噴哈。
一、cropper說明文件部分翻譯
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框架使用
<?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==true
public 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框架的分析過程基本就完成了。歡迎踴躍拍磚。。。