android-crop源码解析

  1. 1. android-crop
  2. 2. 使用
  3. 3. 源码解析
    1. 3.1. 选择图片
    2. 3.2. 裁图前的准备
    3. 3.3. 真正裁剪
    4. 3.4. RotateBitmap
    5. 3.5. HighlightView
    6. 3.6. CropImageView
    7. 3.7. ImageViewTouchBase
  4. 4. 总结

Github: android-crop 分析版本:adb9b6e

android-crop 是一个基于 AOSP 的图片裁剪库。

android-crop

android-crop

整个界面分为上面的两个 Button,显示的 CropImageView,裁剪显示区域的 HighlightView。通过拖拉已经放大缩小 HighlightView 来确定裁剪区域。同时当 HighlightView 缩小的一定值的时候, CropImageView 也会缩小。

使用

引入包:

1
compile 'com.soundcloud.android:android-crop:1.0.1@aar'

AndroidManifest.xml 中申明 CropImageActivity :

1
<activity android:name="com.soundcloud.android.crop.CropImageActivity" />

选择想要裁剪的图片(选择图片的这个过程也可以自己完成):

1
Crop.pickImage(activity)

之后会返回到 onActivityResult 中:

1
2
3
4
5
6
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent result) {
if (requestCode == Crop.REQUEST_PICK && resultCode == RESULT_OK) {
beginCrop(result.getData());
}
}

执行裁剪:

1
2
3
4
private void beginCrop(Uri source) {
Uri destination = Uri.fromFile(new File(getCacheDir(), "cropped"));//temp url
Crop.of(source, destination).asSquare().start(activity);
}

裁剪完成:

1
2
3
4
5
6
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent result) {
if (requestCode == Crop.REQUEST_CROPK) {
handleCrop(resultCode, result);
}
}

源码解析

选择图片

先从选择图片开始看,这个地方也就是通过 Intent 的 type 为 image/* 去做操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Crop {
/**
* Pick image from an Activity
* 通过Activity的方式启动
*
* @param activity Activity to receive result
*/

public static void pickImage(Activity activity) {
pickImage(activity, REQUEST_PICK);
}

/**
* Pick image from an Activity with a custom request code
*
* @param activity Activity to receive result
* @param requestCode requestCode for result
*/

public static void pickImage(Activity activity, int requestCode) {
try {
activity.startActivityForResult(getImagePicker(), requestCode);
} catch (ActivityNotFoundException e) {
showImagePickerError(activity);
}
}

private static Intent getImagePicker() {
return new Intent(Intent.ACTION_GET_CONTENT).setType("image/*");
}

private static void showImagePickerError(Context context) {
Toast.makeText(context, R.string.crop__pick_error, Toast.LENGTH_SHORT).show();
}

}

Crop 中不仅仅提供了 Activity 的方式,同时还提供了 android.app.Fragmentandroid.support.v4.app.Fragment 的方式。

裁图前的准备

先从入口开始看:

1
2
Uri destination = Uri.fromFile(new File(getCacheDir(), "cropped"));
Crop.of(source, destination).asSquare().start(this);

传入的 destination 是裁剪完之后保存的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public class Crop {
interface Extra {
String ASPECT_X = "aspect_x";
String ASPECT_Y = "aspect_y";
String MAX_X = "max_x";
String MAX_Y = "max_y";
String ERROR = "error";
}

private Intent cropIntent;

/**
* Create a crop Intent builder with source and destination image Uris
*
* @param source Uri for image to crop
* @param destination Uri for saving the cropped image
*/

public static Crop of(Uri source, Uri destination) {
return new Crop(source, destination);
}

private Crop(Uri source, Uri destination) {
cropIntent = new Intent();
cropIntent.setData(source);
cropIntent.putExtra(MediaStore.EXTRA_OUTPUT, destination);
}

/**
* Crop area with fixed 1:1 aspect ratio
* X:Y 的比例为 1:1
*/

public Crop asSquare() {
cropIntent.putExtra(Extra.ASPECT_X, 1);
cropIntent.putExtra(Extra.ASPECT_Y, 1);
return this;
}

/**
* Send the crop Intent from an Activity
*
* @param activity Activity to receive result
*/

public void start(Activity activity) {
start(activity, REQUEST_CROP);
}

/**
* Send the crop Intent from an Activity with a custom request code
*
* @param activity Activity to receive result
* @param requestCode requestCode for result
*/

public void start(Activity activity, int requestCode) {
activity.startActivityForResult(getIntent(activity), requestCode);
}

/**
* Get Intent to start crop Activity
*
* @param context Context
* @return Intent for CropImageActivity
*/

public Intent getIntent(Context context) {
cropIntent.setClass(context, CropImageActivity.class);
return cropIntent;
}
}

通过调用 Crop.of(source, destination).asSquare().start(this) 进入到 CropImageActivity 这个界面中。其中还经历了许多的配置,比如初始化的时候裁图显示的X/Y比例是多少等。

那么进入到 CropImageActivity 中看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
public class CropImageActivity extends MonitoredActivity {
private static final int SIZE_DEFAULT = 2048;
private static final int SIZE_LIMIT = 4096;

private int aspectX;
private int aspectY;

// Output image
private int maxX;
private int maxY;
private int exifRotation;

private Uri sourceUri;//要裁图的图片的uri
private Uri saveUri;//裁完图的图片要保存的地址

private final Handler handler = new Handler();

private CropImageView imageView;//显示bitmap的View
private HighlightView cropView;//显示裁剪的View

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setupWindowFlags();//初始化界面
setupViews();//初始化界面里的控件

loadInput();//加载传入的数据
if (rotateBitmap == null) {
finish();
return;
}
startCrop();//开始裁剪
}

/**
* 加载数据
*
* @return
*/

private void loadInput() {
Intent intent = getIntent();
Bundle extras = intent.getExtras();

if (extras != null) {
aspectX = extras.getInt(Crop.Extra.ASPECT_X);
aspectY = extras.getInt(Crop.Extra.ASPECT_Y);
maxX = extras.getInt(Crop.Extra.MAX_X);
maxY = extras.getInt(Crop.Extra.MAX_Y);
saveUri = extras.getParcelable(MediaStore.EXTRA_OUTPUT);
}

sourceUri = intent.getData();
if (sourceUri != null) {
exifRotation = CropUtil.getExifRotation(CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri));//通过图片的Exif信息得到要选择多少度

InputStream is = null;
try {
sampleSize = calculateBitmapSampleSize(sourceUri);//计算图片在decode的时候的sampleSize
is = getContentResolver().openInputStream(sourceUri);
BitmapFactory.Options option = new BitmapFactory.Options();
option.inSampleSize = sampleSize;
//decode出通过exif信息旋转之后,通过option的simpleSize采样缩放之后的bitmap,赋值给成员变量rotateBitmap
rotateBitmap = new RotateBitmap(BitmapFactory.decodeStream(is, null, option), exifRotation);
} catch (IOException e) {
Log.e("Error reading image: " + e.getMessage(), e);
setResultException(e);
} catch (OutOfMemoryError e) {
Log.e("OOM reading image: " + e.getMessage(), e);
setResultException(e);
} finally {
CropUtil.closeSilently(is);
}
}
}

/**
* 计算simpleSize
*
* @return
*/

private int calculateBitmapSampleSize(Uri bitmapUri) throws IOException {
InputStream is = null;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
try {
is = getContentResolver().openInputStream(bitmapUri);
BitmapFactory.decodeStream(is, null, options); //bitmap的高宽都存到了options中
} finally {
CropUtil.closeSilently(is);//关流
}

int maxSize = getMaxImageSize();//得到最大的显示size
int sampleSize = 1;
//通过bitmap的高或者宽除以simpleSize得到的值与maxSize对比,如果还大的话,simpleSize向右移动一位,就是乘以2,如果都不大于maxSize的话,就选定当前这个值
while (options.outHeight / sampleSize > maxSize || options.outWidth / sampleSize > maxSize) {
sampleSize = sampleSize << 1;
}
return sampleSize;
}

/**
* 得到最大的Size
*
* @return
*/

private int getMaxImageSize() {
int textureLimit = getMaxTextureSize();
if (textureLimit == 0) {
return SIZE_DEFAULT;//2048
} else {
return Math.min(textureLimit, SIZE_LIMIT);//4096
}
}

/**
* OpenGL的texture的大小是ImageView最大绘制的大小
*
* @return
*/

private int getMaxTextureSize() {
// The OpenGL texture size is the maximum size that can be drawn in an ImageView
int[] maxSize = new int[1];
GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, maxSize, 0);
return maxSize[0];
}

private void startCrop() {
if (isFinishing()) {
return;
}
//设置bitmap
imageView.setImageRotateBitmapResetBase(rotateBitmap, true);
CropUtil.startBackgroundJob(this, null, getResources().getString(R.string.crop__wait),
new Runnable() {
public void run() {
//线程计数器
final CountDownLatch latch = new CountDownLatch(1);
//主线程进行UI操作
handler.post(new Runnable() {
public void run() {
if (imageView.getScale() == 1F) {
imageView.center();
}
latch.countDown();
}
});
//子线程阻塞住,直到latch.countDown();执行
try {
latch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//设置裁剪View
new Cropper().crop();
}
}, handler
);
}

private class Cropper {
public void crop() {
handler.post(new Runnable() {
public void run() {
makeDefault();//设置默认值
imageView.invalidate();//更新绘制
if (imageView.highlightViews.size() == 1) {
cropView = imageView.highlightViews.get(0);
cropView.setFocus(true);
}
}
});
}

private void makeDefault() {
if (rotateBitmap == null) {
return;
}

HighlightView hv = new HighlightView(imageView);//这个imageview就是CropImageView
final int width = rotateBitmap.getWidth();
final int height = rotateBitmap.getHeight();

Rect imageRect = new Rect(0, 0, width, height);

// Make the default size about 4/5 of the width or height
int cropWidth = Math.min(width, height) * 4 / 5;//要显示裁剪的view的宽度是bitmap的宽度或高度中的最小值的4/5
@SuppressWarnings("SuspiciousNameCombination")
int cropHeight = cropWidth;

if (aspectX != 0 && aspectY != 0) {
if (aspectX > aspectY) {//根据传入的X:Y的比例进行设置
cropHeight = cropWidth * aspectY / aspectX;
} else {
cropWidth = cropHeight * aspectX / aspectY;
}
}

int x = (width - cropWidth) / 2;//裁剪view的x坐标
int y = (height - cropHeight) / 2;//裁剪view的y坐标

RectF cropRect = new RectF(x, y, x + cropWidth, y + cropHeight);
hv.setup(imageView.getUnrotatedMatrix(), imageRect, cropRect, aspectX != 0 && aspectY != 0);//初始化HighlightView
imageView.add(hv);
}
}
}

startCrop 中做的 job 是以下操作,主要是通过 Activity 生命周期来显示或者隐藏 Dialog 和在子线程中执行 job 操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
class CropUtil {
/**
* 该方法主要操作就是进行耗时操作前后显示dialog,同时监听Activity的生命去显示和隐藏dialog
*
* @param activity
* @param title
* @param message
* @param job
* @param handler
*/

public static void startBackgroundJob(MonitoredActivity activity, String title, String message, Runnable job, Handler handler) {
// Make the progress dialog uncancelable, so that we can guarantee
// the thread will be done before the activity getting destroyed
ProgressDialog dialog = ProgressDialog.show(
activity, title, message, true, false);
new Thread(new BackgroundJob(activity, job, dialog, handler)).start();
}

//MonitoredActivity.LifeCycleAdapter是Activity的生命周期监听的一个Adapter
private static class BackgroundJob extends MonitoredActivity.LifeCycleAdapter implements Runnable {

private final MonitoredActivity activity;
private final ProgressDialog dialog;
private final Runnable job;
private final Handler handler;//主线程的handler
private final Runnable cleanupRunner = new Runnable() {
public void run() {
//从activity中remove掉,防止泄露
activity.removeLifeCycleListener(BackgroundJob.this);
if (dialog.getWindow() != null) dialog.dismiss();
}
};

public BackgroundJob(MonitoredActivity activity, Runnable job,
ProgressDialog dialog, Handler handler)
{

this.activity = activity;
this.dialog = dialog;
this.job = job;
this.activity.addLifeCycleListener(this);
this.handler = handler;
}

public void run() {
try {
//执行job
job.run();
} finally {
handler.post(cleanupRunner);
}
}

@Override
public void onActivityDestroyed(MonitoredActivity activity) {
// We get here only when the onDestroyed being called before
// the cleanupRunner. So, run it now and remove it from the queue
cleanupRunner.run();
handler.removeCallbacks(cleanupRunner);
}

@Override
public void onActivityStopped(MonitoredActivity activity) {
//当Activity的状态为stop的时候,将dialog隐藏掉
dialog.hide();
}

@Override
public void onActivityStarted(MonitoredActivity activity) {
//当Activity的状态为started的时候,将dialog显示出来
dialog.show();
}
}
}

真正裁剪

当点击『完成』时候:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
public class CropImageActivity extends MonitoredActivity {
private void onSaveClicked() {
if (cropView == null || isSaving) {
return;
}
isSaving = true;

Bitmap croppedImage;
//得到HighlightView的Rect大小
Rect r = cropView.getScaledCropRect(sampleSize);
int width = r.width();
int height = r.height();

int outWidth = width;
int outHeight = height;
//如果intent传参传的是maxX和maxY的话,这里就需要进行判断
//如果裁剪出的width大于maxX或者裁剪出的height大于maxY,则进行比例缩放
if (maxX > 0 && maxY > 0 && (width > maxX || height > maxY)) {
float ratio = (float) width / (float) height;
if ((float) maxX / (float) maxY > ratio) {
outHeight = maxY;
outWidth = (int) ((float) maxY * ratio + .5f);
} else {
outWidth = maxX;
outHeight = (int) ((float) maxX / ratio + .5f);
}
}

try {
//得到裁剪之后的Bitmap
croppedImage = decodeRegionCrop(r, outWidth, outHeight);
} catch (IllegalArgumentException e) {
setResultException(e);
finish();
return;
}

if (croppedImage != null) {
//在CropImageView中显示裁剪了的bitmap
imageView.setImageRotateBitmapResetBase(new RotateBitmap(croppedImage, exifRotation), true);
imageView.center();
imageView.highlightViews.clear();
}
//保存裁剪之后的bitmap
saveImage(croppedImage);
}

private Bitmap decodeRegionCrop(Rect rect, int outWidth, int outHeight) {
// Release memory now
//释放CropImageView中的bitmap
clearImageView();

InputStream is = null;
Bitmap croppedImage = null;
try {
is = getContentResolver().openInputStream(sourceUri);
//得到BitmapRegionDecoder
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
final int width = decoder.getWidth();
final int height = decoder.getHeight();
//如果exif信息中要旋转
if (exifRotation != 0) {
// Adjust crop area to account for image rotation
Matrix matrix = new Matrix();
matrix.setRotate(-exifRotation);
//adjusted为旋转后的rect
RectF adjusted = new RectF();
matrix.mapRect(adjusted, new RectF(rect));

// Adjust to account for origin at 0,0
adjusted.offset(adjusted.left < 0 ? width : 0, adjusted.top < 0 ? height : 0);
rect = new Rect((int) adjusted.left, (int) adjusted.top, (int) adjusted.right, (int) adjusted.bottom);
}

try {
//裁剪rect区域的图像
croppedImage = decoder.decodeRegion(rect, new BitmapFactory.Options());
//裁剪的宽度或高度大于想要的宽度或高度的话,通过矩阵进行缩放,然后再创建出一个想要高宽的bitmap
if (croppedImage != null && (rect.width() > outWidth || rect.height() > outHeight)) {
Matrix matrix = new Matrix();
matrix.postScale((float) outWidth / rect.width(), (float) outHeight / rect.height());
croppedImage = Bitmap.createBitmap(croppedImage, 0, 0, croppedImage.getWidth(), croppedImage.getHeight(), matrix, true);
}
} catch (IllegalArgumentException e) {
// Rethrow with some extra information
throw new IllegalArgumentException("Rectangle " + rect + " is outside of the image ("
+ width + "," + height + "," + exifRotation + ")", e);
}

} catch (IOException e) {
Log.e("Error cropping image: " + e.getMessage(), e);
setResultException(e);
} catch (OutOfMemoryError e) {
Log.e("OOM cropping image: " + e.getMessage(), e);
setResultException(e);
} finally {
CropUtil.closeSilently(is);
}
return croppedImage;
}

/**
* 回收之前没有裁剪的bitmap
*/

private void clearImageView() {
imageView.clear();
if (rotateBitmap != null) {
rotateBitmap.recycle();
}
System.gc();
}

private void saveImage(Bitmap croppedImage) {
if (croppedImage != null) {
final Bitmap b = croppedImage;
CropUtil.startBackgroundJob(this, null, getResources().getString(R.string.crop__saving),
new Runnable() {
public void run() {
saveOutput(b);
}
}, handler
);
} else {
finish();
}
}

/**
* 保存bitmap
*
* @param croppedImage
*/

private void saveOutput(Bitmap croppedImage) {
if (saveUri != null) {
OutputStream outputStream = null;
try {
outputStream = getContentResolver().openOutputStream(saveUri);
if (outputStream != null) {
croppedImage.compress(Bitmap.CompressFormat.JPEG, 90, outputStream);
}
} catch (IOException e) {
setResultException(e);
Log.e("Cannot open file: " + saveUri, e);
} finally {
CropUtil.closeSilently(outputStream);
}

CropUtil.copyExifRotation(
CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri),
CropUtil.getFromMediaUri(this, getContentResolver(), saveUri)
);

setResultUri(saveUri);
}

final Bitmap b = croppedImage;
handler.post(new Runnable() {
public void run() {
imageView.clear();
b.recycle();
}
});

finish();
}
}

裁剪是通过 BitmapRegionDecoder 来实现的,BitmapRegionDecoder.newInstance(InputStream is, boolean isShareable) ,传入的参数是一个 InputStream ,这样做的好处是可以减少创建出原图大小的 Bitmap ,达到节省内存,有效的防止 OOM

RotateBitmap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
class RotateBitmap {

private Bitmap bitmap;
private int rotation;

public RotateBitmap(Bitmap bitmap, int rotation) {
this.bitmap = bitmap;
this.rotation = rotation % 360;
}

public void setRotation(int rotation) {
this.rotation = rotation;
}

public int getRotation() {
return rotation;
}

public Bitmap getBitmap() {
return bitmap;
}

public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}

//得到旋转的矩阵
public Matrix getRotateMatrix() {
// By default this is an identity matrix
Matrix matrix = new Matrix();
if (bitmap != null && rotation != 0) {
// We want to do the rotation at origin, but since the bounding
// rectangle will be changed after rotation, so the delta values
// are based on old & new width/height respectively.
int cx = bitmap.getWidth() / 2;
int cy = bitmap.getHeight() / 2;
matrix.preTranslate(-cx, -cy);
matrix.postRotate(rotation);
matrix.postTranslate(getWidth() / 2, getHeight() / 2);
}
return matrix;
}

public boolean isOrientationChanged() {
return (rotation / 90) % 2 != 0;
}

//得到高度,要判断exif中是否要旋转
public int getHeight() {
if (bitmap == null) return 0;
if (isOrientationChanged()) {
return bitmap.getWidth();
} else {
return bitmap.getHeight();
}
}

//得到宽度,要判断exif中是否要旋转
public int getWidth() {
if (bitmap == null) return 0;
if (isOrientationChanged()) {
return bitmap.getHeight();
} else {
return bitmap.getWidth();
}
}

//回收bitmap
public void recycle() {
if (bitmap != null) {
bitmap.recycle();
bitmap = null;
}
}
}

HighlightView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
class HighlightView {

public static final int GROW_NONE = (1 << 0);//没有状态
public static final int GROW_LEFT_EDGE = (1 << 1);//触摸到左边边界状态
public static final int GROW_RIGHT_EDGE = (1 << 2);//触摸到右边边界状态
public static final int GROW_TOP_EDGE = (1 << 3);//触摸到上面边界状态
public static final int GROW_BOTTOM_EDGE = (1 << 4);//触摸到下边边界状态
public static final int MOVE = (1 << 5);//移动状态

private static final int DEFAULT_HIGHLIGHT_COLOR = 0xFF33B5E5;//默认颜色
private static final float HANDLE_RADIUS_DP = 12f;//默认handle的半径
private static final float OUTLINE_DP = 2f;//线的宽度

enum ModifyMode {None, Move, Grow}//当前触摸状态

enum HandleMode {Changing, Always, Never}

RectF cropRect; // Image space
Rect drawRect; // Screen space
Matrix matrix;
private RectF imageRect; // Image space

private final Paint outsidePaint = new Paint();
private final Paint outlinePaint = new Paint();
private final Paint handlePaint = new Paint();

private View viewContext; // View displaying image
private boolean showThirds;//是否绘制裁剪View里面的四条线
private boolean showCircle;//是否绘制圆
private int highlightColor;//裁剪View绘制的颜色

private ModifyMode modifyMode = ModifyMode.None;
private HandleMode handleMode = HandleMode.Changing;
private boolean maintainAspectRatio;//是否希望变化的时候保存X/Y的比例
private float initialAspectRatio;//初始化的时候比例
private float handleRadius;
private float outlineWidth;
private boolean isFocused;

public HighlightView(View context) {
viewContext = context;
initStyles(context.getContext());
}

private void initStyles(Context context) {
//读取attr的值
TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.cropImageStyle, outValue, true);
TypedArray attributes = context.obtainStyledAttributes(outValue.resourceId, R.styleable.CropImageView);
try {
showThirds = attributes.getBoolean(R.styleable.CropImageView_showThirds, false);
showCircle = attributes.getBoolean(R.styleable.CropImageView_showCircle, false);
highlightColor = attributes.getColor(R.styleable.CropImageView_highlightColor,
DEFAULT_HIGHLIGHT_COLOR);
handleMode = HandleMode.values()[attributes.getInt(R.styleable.CropImageView_showHandles, 0)];
} finally {
attributes.recycle();
}
}

public void setup(Matrix m, Rect imageRect, RectF cropRect, boolean maintainAspectRatio) {
//通过传进来的Matrix生成一个新的Matrix赋值给成员变量
matrix = new Matrix(m);
//裁剪的区域
this.cropRect = cropRect;
//整个图的区域
this.imageRect = new RectF(imageRect);
//希望保持比例
this.maintainAspectRatio = maintainAspectRatio;
//得到初始化比例
initialAspectRatio = this.cropRect.width() / this.cropRect.height();
//匹配裁剪区域对应到屏幕坐标系上
drawRect = computeLayout();
//初始化画笔
outsidePaint.setARGB(125, 50, 50, 50);
outlinePaint.setStyle(Paint.Style.STROKE);
outlinePaint.setAntiAlias(true);
outlineWidth = dpToPx(OUTLINE_DP);

handlePaint.setColor(highlightColor);
handlePaint.setStyle(Paint.Style.FILL);
handlePaint.setAntiAlias(true);
handleRadius = dpToPx(HANDLE_RADIUS_DP);
//当前ModifyMode为None
modifyMode = ModifyMode.None;
}

private float dpToPx(float dp) {
return dp * viewContext.getResources().getDisplayMetrics().density;
}

protected void draw(Canvas canvas) {
//保存
canvas.save();
Path path = new Path();
outlinePaint.setStrokeWidth(outlineWidth);
//如果没有获得焦点,那么绘制黑色
if (!hasFocus()) {
outlinePaint.setColor(Color.BLACK);
canvas.drawRect(drawRect, outlinePaint);
} else {//获得了焦点的情况下
//得到CropImageView的Rect区域
Rect viewDrawingRect = new Rect();
viewContext.getDrawingRect(viewDrawingRect);

path.addRect(new RectF(drawRect), Path.Direction.CW);
outlinePaint.setColor(highlightColor);
//是否支持硬件加速
if (isClipPathSupported(canvas)) {
//clipPath取不属于RectF(drawRect)和viewDrawingRect的集合
canvas.clipPath(path, Region.Op.DIFFERENCE);
canvas.drawRect(viewDrawingRect, outsidePaint);
} else {
drawOutsideFallback(canvas);
}
//回到保存之前的样子
canvas.restore();
//画path
canvas.drawPath(path, outlinePaint);
//画中间的四条线
if (showThirds) {
drawThirds(canvas);
}
//画圆
if (showCircle) {
drawCircle(canvas);
}

if (handleMode == HandleMode.Always || (handleMode == HandleMode.Changing && modifyMode == ModifyMode.Grow)) {
//画handle的圆
drawHandles(canvas);
}
}
}

/*
* Fall back to naive method for darkening outside crop area
*/

private void drawOutsideFallback(Canvas canvas) {
canvas.drawRect(0, 0, canvas.getWidth(), drawRect.top, outsidePaint);
canvas.drawRect(0, drawRect.bottom, canvas.getWidth(), canvas.getHeight(), outsidePaint);
canvas.drawRect(0, drawRect.top, drawRect.left, drawRect.bottom, outsidePaint);
canvas.drawRect(drawRect.right, drawRect.top, canvas.getWidth(), drawRect.bottom, outsidePaint);
}

/*
* Clip path is broken, unreliable or not supported on:
* - JellyBean MR1
* - ICS & ICS MR1 with hardware acceleration turned on
*/

@SuppressLint("NewApi")
private boolean isClipPathSupported(Canvas canvas) {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
return false;
} else if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|| Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
return true;
} else {
return !canvas.isHardwareAccelerated();
}
}

/**
* 画handle的圆
*
* @param canvas
*/

private void drawHandles(Canvas canvas) {
int xMiddle = drawRect.left + ((drawRect.right - drawRect.left) / 2);
int yMiddle = drawRect.top + ((drawRect.bottom - drawRect.top) / 2);

canvas.drawCircle(drawRect.left, yMiddle, handleRadius, handlePaint);
canvas.drawCircle(xMiddle, drawRect.top, handleRadius, handlePaint);
canvas.drawCircle(drawRect.right, yMiddle, handleRadius, handlePaint);
canvas.drawCircle(xMiddle, drawRect.bottom, handleRadius, handlePaint);
}

/**
* 画里面的四条线
*
* @param canvas
*/

private void drawThirds(Canvas canvas) {
outlinePaint.setStrokeWidth(1);
float xThird = (drawRect.right - drawRect.left) / 3;
float yThird = (drawRect.bottom - drawRect.top) / 3;

canvas.drawLine(drawRect.left + xThird, drawRect.top,
drawRect.left + xThird, drawRect.bottom, outlinePaint);
canvas.drawLine(drawRect.left + xThird * 2, drawRect.top,
drawRect.left + xThird * 2, drawRect.bottom, outlinePaint);
canvas.drawLine(drawRect.left, drawRect.top + yThird,
drawRect.right, drawRect.top + yThird, outlinePaint);
canvas.drawLine(drawRect.left, drawRect.top + yThird * 2,
drawRect.right, drawRect.top + yThird * 2, outlinePaint);
}

private void drawCircle(Canvas canvas) {
outlinePaint.setStrokeWidth(1);
canvas.drawOval(new RectF(drawRect), outlinePaint);
}

public void setMode(ModifyMode mode) {
if (mode != modifyMode) {
modifyMode = mode;
viewContext.invalidate();
}
}

// Determines which edges are hit by touching at (x, y)
//在CropImageView的OnTouchEvent中,通过x,y判断是否有碰触到HighlightView
public int getHit(float x, float y) {
Rect r = computeLayout();
final float hysteresis = 20F;//误差值,误差在这个范围内就算hit到了
int retval = GROW_NONE;

// verticalCheck makes sure the position is between the top and
// the bottom edge (with some tolerance). Similar for horizCheck.
boolean verticalCheck = (y >= r.top - hysteresis)
&& (y < r.bottom + hysteresis);
boolean horizCheck = (x >= r.left - hysteresis)
&& (x < r.right + hysteresis);

// Check whether the position is near some edge(s)
if ((Math.abs(r.left - x) < hysteresis) && verticalCheck) {
retval |= GROW_LEFT_EDGE;
}
if ((Math.abs(r.right - x) < hysteresis) && verticalCheck) {
retval |= GROW_RIGHT_EDGE;
}
if ((Math.abs(r.top - y) < hysteresis) && horizCheck) {
retval |= GROW_TOP_EDGE;
}
if ((Math.abs(r.bottom - y) < hysteresis) && horizCheck) {
retval |= GROW_BOTTOM_EDGE;
}

// Not near any edge but inside the rectangle: move
if (retval == GROW_NONE && r.contains((int) x, (int) y)) {
retval = MOVE;
}
return retval;
}

// Handles motion (dx, dy) in screen space.
// The "edge" parameter specifies which edges the user is dragging.
//处理手势事件
void handleMotion(int edge, float dx, float dy) {
Rect r = computeLayout();
//如果触摸状态是MOVE的话,移动
if (edge == MOVE) {
// Convert to image space before sending to moveBy()
//将坐标转换成相对于CropImageView的坐标
moveBy(dx * (cropRect.width() / r.width()),
dy * (cropRect.height() / r.height()));
} else {
//不是触摸到左右边界,那么移动x方向为0
if (((GROW_LEFT_EDGE | GROW_RIGHT_EDGE) & edge) == 0) {
dx = 0;
}
//不是触摸到上下边界,那么移动y方向为0
if (((GROW_TOP_EDGE | GROW_BOTTOM_EDGE) & edge) == 0) {
dy = 0;
}

// Convert to image space before sending to growBy()
float xDelta = dx * (cropRect.width() / r.width());
float yDelta = dy * (cropRect.height() / r.height());
//放大缩小
growBy((((edge & GROW_LEFT_EDGE) != 0) ? -1 : 1) * xDelta,
(((edge & GROW_TOP_EDGE) != 0) ? -1 : 1) * yDelta);
}
}

// Grows the cropping rectangle by (dx, dy) in image space
//cropRect移动dx,dy
void moveBy(float dx, float dy) {
Rect invalRect = new Rect(drawRect);

cropRect.offset(dx, dy);

// Put the cropping rectangle inside image rectangle
cropRect.offset(
Math.max(0, imageRect.left - cropRect.left),
Math.max(0, imageRect.top - cropRect.top));

cropRect.offset(
Math.min(0, imageRect.right - cropRect.right),
Math.min(0, imageRect.bottom - cropRect.bottom));

drawRect = computeLayout();
//取并集
invalRect.union(drawRect);
//invalRect再缩小-handleRadius那么大
invalRect.inset(-(int) handleRadius, -(int) handleRadius);
//绘制
viewContext.invalidate(invalRect);
}

// Grows the cropping rectangle by (dx, dy) in image space.
void growBy(float dx, float dy) {
//如果要保持比例的话,重新计算dx或dy
if (maintainAspectRatio) {
if (dx != 0) {
dy = dx / initialAspectRatio;
} else if (dy != 0) {
dx = dy * initialAspectRatio;
}
}

// Don't let the cropping rectangle grow too fast.
// Grow at most half of the difference between the image rectangle and
// the cropping rectangle.
RectF r = new RectF(cropRect);
if (dx > 0F && r.width() + 2 * dx > imageRect.width()) {
dx = (imageRect.width() - r.width()) / 2F;
if (maintainAspectRatio) {
dy = dx / initialAspectRatio;
}
}
if (dy > 0F && r.height() + 2 * dy > imageRect.height()) {
dy = (imageRect.height() - r.height()) / 2F;
if (maintainAspectRatio) {
dx = dy * initialAspectRatio;
}
}
//左右缩小-dx,上下缩小-dy
r.inset(-dx, -dy);

// Don't let the cropping rectangle shrink too fast
//不要收缩的太快
final float widthCap = 25F;
if (r.width() < widthCap) {
r.inset(-(widthCap - r.width()) / 2F, 0F);
}
float heightCap = maintainAspectRatio
? (widthCap / initialAspectRatio)
: widthCap;
if (r.height() < heightCap) {
r.inset(0F, -(heightCap - r.height()) / 2F);
}

// Put the cropping rectangle inside the image rectangle
if (r.left < imageRect.left) {
r.offset(imageRect.left - r.left, 0F);
} else if (r.right > imageRect.right) {
r.offset(-(r.right - imageRect.right), 0F);
}
if (r.top < imageRect.top) {
r.offset(0F, imageRect.top - r.top);
} else if (r.bottom > imageRect.bottom) {
r.offset(0F, -(r.bottom - imageRect.bottom));
}

cropRect.set(r);
drawRect = computeLayout();
viewContext.invalidate();
}

// Returns the cropping rectangle in image space with specified scale
public Rect getScaledCropRect(float scale) {
//得到传入参数放大后的的裁剪区域,scale一般传入的就是simpleSize
return new Rect((int) (cropRect.left * scale), (int) (cropRect.top * scale),
(int) (cropRect.right * scale), (int) (cropRect.bottom * scale));
}

// Maps the cropping rectangle from image space to screen space
private Rect computeLayout() {
RectF r = new RectF(cropRect.left, cropRect.top,
cropRect.right, cropRect.bottom);
matrix.mapRect(r);
return new Rect(Math.round(r.left), Math.round(r.top),
Math.round(r.right), Math.round(r.bottom));
}

public void invalidate() {
drawRect = computeLayout();
}

public boolean hasFocus() {
return isFocused;
}

public void setFocus(boolean isFocused) {
this.isFocused = isFocused;
}

}

CropImageView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
public class CropImageView extends ImageViewTouchBase {

ArrayList<HighlightView> highlightViews = new ArrayList<HighlightView>();
HighlightView motionHighlightView;
Context context;

private float lastX;
private float lastY;
private int motionEdge;
private int validPointerId;

public CropImageView(Context context) {
super(context);
}

public CropImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}

public CropImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (bitmapDisplayed.getBitmap() != null) {
for (HighlightView hv : highlightViews) {
//设置矩阵
hv.matrix.set(getUnrotatedMatrix());
hv.invalidate();
if (hv.hasFocus()) {
//让HighlightView全部显示在屏幕上
centerBasedOnHighlightView(hv);
}
}
}
}

@Override
protected void zoomTo(float scale, float centerX, float centerY) {
//固定位置zoom
super.zoomTo(scale, centerX, centerY);
for (HighlightView hv : highlightViews) {
hv.matrix.set(getUnrotatedMatrix());
hv.invalidate();
}
}

@Override
protected void zoomIn() {
//放大
super.zoomIn();
for (HighlightView hv : highlightViews) {
hv.matrix.set(getUnrotatedMatrix());
hv.invalidate();
}
}

@Override
protected void zoomOut() {
//缩小
super.zoomOut();
for (HighlightView hv : highlightViews) {
hv.matrix.set(getUnrotatedMatrix());
hv.invalidate();
}
}

@Override
protected void postTranslate(float deltaX, float deltaY) {
//平移
super.postTranslate(deltaX, deltaY);
for (HighlightView hv : highlightViews) {
hv.matrix.postTranslate(deltaX, deltaY);
hv.invalidate();
}
}

@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
CropImageActivity cropImageActivity = (CropImageActivity) context;
//如果正在保存,则跳过
if (cropImageActivity.isSaving()) {
return false;
}

switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
for (HighlightView hv : highlightViews) {
//判断触摸到了什么
int edge = hv.getHit(event.getX(), event.getY());
if (edge != HighlightView.GROW_NONE) {
motionEdge = edge;
motionHighlightView = hv;
lastX = event.getX();
lastY = event.getY();
// Prevent multiple touches from interfering with crop area re-sizing
//设置PointerId防止多指操作
validPointerId = event.getPointerId(event.getActionIndex());
//设置触摸状态
motionHighlightView.setMode((edge == HighlightView.MOVE)
? HighlightView.ModifyMode.Move
: HighlightView.ModifyMode.Grow);
break;
}
}
break;
case MotionEvent.ACTION_UP:
if (motionHighlightView != null) {
//让HighlightView全部显示在屏幕上
centerBasedOnHighlightView(motionHighlightView);
motionHighlightView.setMode(HighlightView.ModifyMode.None);
}
motionHighlightView = null;
center();
break;
case MotionEvent.ACTION_MOVE:
//让HiglightView去处理手势
if (motionHighlightView != null && event.getPointerId(event.getActionIndex()) == validPointerId) {
motionHighlightView.handleMotion(motionEdge, event.getX()
- lastX, event.getY() - lastY);
lastX = event.getX();
lastY = event.getY();
}

// If we're not zoomed then there's no point in even allowing the user to move the image around.
// This call to center puts it back to the normalized location.
if (getScale() == 1F) {
center();
}
break;
}

return true;
}

// Pan the displayed image to make sure the cropping rectangle is visible.
private void ensureVisible(HighlightView hv) {
Rect r = hv.drawRect;

int panDeltaX1 = Math.max(0, getLeft() - r.left);
int panDeltaX2 = Math.min(0, getRight() - r.right);

int panDeltaY1 = Math.max(0, getTop() - r.top);
int panDeltaY2 = Math.min(0, getBottom() - r.bottom);

int panDeltaX = panDeltaX1 != 0 ? panDeltaX1 : panDeltaX2;
int panDeltaY = panDeltaY1 != 0 ? panDeltaY1 : panDeltaY2;

if (panDeltaX != 0 || panDeltaY != 0) {
panBy(panDeltaX, panDeltaY);
}
}

// If the cropping rectangle's size changed significantly, change the
// view's center and scale according to the cropping rectangle.
private void centerBasedOnHighlightView(HighlightView hv) {
Rect drawRect = hv.drawRect;

float width = drawRect.width();
float height = drawRect.height();

float thisWidth = getWidth();
float thisHeight = getHeight();

float z1 = thisWidth / width * .6F;
float z2 = thisHeight / height * .6F;

float zoom = Math.min(z1, z2);
zoom = zoom * this.getScale();
zoom = Math.max(1F, zoom);

if ((Math.abs(zoom - getScale()) / zoom) > .1) {
float[] coordinates = new float[] { hv.cropRect.centerX(), hv.cropRect.centerY() };
getUnrotatedMatrix().mapPoints(coordinates);
zoomTo(zoom, coordinates[0], coordinates[1], 300F);
}

ensureVisible(hv);
}

@Override
protected void onDraw(@NonNull Canvas canvas) {
super.onDraw(canvas);
for (HighlightView highlightView : highlightViews) {
highlightView.draw(canvas);
}
}

public void add(HighlightView hv) {
highlightViews.add(hv);
invalidate();
}
}

ImageViewTouchBase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
abstract class ImageViewTouchBase extends ImageView {

private static final float SCALE_RATE = 1.25F;

// This is the base transformation which is used to show the image
// initially. The current computation for this shows the image in
// it's entirety, letterboxing as needed. One could choose to
// show the image as cropped instead.
//
// This matrix is recomputed when we go from the thumbnail image to
// the full size image.
protected Matrix baseMatrix = new Matrix();

// This is the supplementary transformation which reflects what
// the user has done in terms of zooming and panning.
//
// This matrix remains the same when we go from the thumbnail image
// to the full size image.
protected Matrix suppMatrix = new Matrix();

// This is the final matrix which is computed as the concatentation
// of the base matrix and the supplementary matrix.
private final Matrix displayMatrix = new Matrix();

// Temporary buffer used for getting the values out of a matrix.
private final float[] matrixValues = new float[9];

// The current bitmap being displayed.
protected final RotateBitmap bitmapDisplayed = new RotateBitmap(null, 0);

int thisWidth = -1;
int thisHeight = -1;

float maxZoom;

private Runnable onLayoutRunnable;

protected Handler handler = new Handler();

// ImageViewTouchBase will pass a Bitmap to the Recycler if it has finished
// its use of that Bitmap
public interface Recycler {
public void recycle(Bitmap b);
}

private Recycler recycler;

public ImageViewTouchBase(Context context) {
super(context);
init();
}

public ImageViewTouchBase(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

public ImageViewTouchBase(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}

public void setRecycler(Recycler recycler) {
this.recycler = recycler;
}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
thisWidth = right - left;
thisHeight = bottom - top;
Runnable r = onLayoutRunnable;
if (r != null) {
onLayoutRunnable = null;
r.run();
}
if (bitmapDisplayed.getBitmap() != null) {
getProperBaseMatrix(bitmapDisplayed, baseMatrix, true);
setImageMatrix(getImageViewMatrix());
}
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
event.startTracking();
return true;
}
return super.onKeyDown(keyCode, event);
}

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && !event.isCanceled()) {
if (getScale() > 1.0f) {
// If we're zoomed in, pressing Back jumps out to show the
// entire image, otherwise Back returns the user to the gallery
zoomTo(1.0f);
return true;
}
}
return super.onKeyUp(keyCode, event);
}

@Override
public void setImageBitmap(Bitmap bitmap) {
setImageBitmap(bitmap, 0);
}

//设置旋转的Bitmap
private void setImageBitmap(Bitmap bitmap, int rotation) {
super.setImageBitmap(bitmap);
Drawable d = getDrawable();
if (d != null) {
d.setDither(true);
}

Bitmap old = bitmapDisplayed.getBitmap();
bitmapDisplayed.setBitmap(bitmap);
bitmapDisplayed.setRotation(rotation);

if (old != null && old != bitmap && recycler != null) {
recycler.recycle(old);
}
}

//释放
public void clear() {
setImageBitmapResetBase(null, true);
}


// This function changes bitmap, reset base matrix according to the size
// of the bitmap, and optionally reset the supplementary matrix
public void setImageBitmapResetBase(final Bitmap bitmap, final boolean resetSupp) {
setImageRotateBitmapResetBase(new RotateBitmap(bitmap, 0), resetSupp);
}

public void setImageRotateBitmapResetBase(final RotateBitmap bitmap, final boolean resetSupp) {
final int viewWidth = getWidth();

if (viewWidth <= 0) {
onLayoutRunnable = new Runnable() {
public void run() {
//反复调用自己,知道得到view的宽度为止
setImageRotateBitmapResetBase(bitmap, resetSupp);
}
};
return;
}

if (bitmap.getBitmap() != null) {
getProperBaseMatrix(bitmap, baseMatrix, true);
setImageBitmap(bitmap.getBitmap(), bitmap.getRotation());
} else {
baseMatrix.reset();
setImageBitmap(null);
}

if (resetSupp) {
suppMatrix.reset();
}
setImageMatrix(getImageViewMatrix());
maxZoom = calculateMaxZoom();
}

// Center as much as possible in one or both axis. Centering is defined as follows:
// * If the image is scaled down below the view's dimensions then center it.
// * If the image is scaled larger than the view and is translated out of view then translate it back into view.
protected void center() {
final Bitmap bitmap = bitmapDisplayed.getBitmap();
if (bitmap == null) {
return;
}
Matrix m = getImageViewMatrix();

RectF rect = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
m.mapRect(rect);

float height = rect.height();
float width = rect.width();

float deltaX = 0, deltaY = 0;

deltaY = centerVertical(rect, height, deltaY);
deltaX = centerHorizontal(rect, width, deltaX);

postTranslate(deltaX, deltaY);
setImageMatrix(getImageViewMatrix());
}

private float centerVertical(RectF rect, float height, float deltaY) {
int viewHeight = getHeight();
if (height < viewHeight) {
deltaY = (viewHeight - height) / 2 - rect.top;
} else if (rect.top > 0) {
deltaY = -rect.top;
} else if (rect.bottom < viewHeight) {
deltaY = getHeight() - rect.bottom;
}
return deltaY;
}

private float centerHorizontal(RectF rect, float width, float deltaX) {
int viewWidth = getWidth();
if (width < viewWidth) {
deltaX = (viewWidth - width) / 2 - rect.left;
} else if (rect.left > 0) {
deltaX = -rect.left;
} else if (rect.right < viewWidth) {
deltaX = viewWidth - rect.right;
}
return deltaX;
}

//初始化,设置ImageView的ScaleType
private void init() {
setScaleType(ImageView.ScaleType.MATRIX);
}

protected float getValue(Matrix matrix, int whichValue) {
matrix.getValues(matrixValues);
return matrixValues[whichValue];
}

// Get the scale factor out of the matrix.
protected float getScale(Matrix matrix) {
return getValue(matrix, Matrix.MSCALE_X);
}

protected float getScale() {
return getScale(suppMatrix);
}

// Setup the base matrix so that the image is centered and scaled properly.
private void getProperBaseMatrix(RotateBitmap bitmap, Matrix matrix, boolean includeRotation) {
float viewWidth = getWidth();
float viewHeight = getHeight();

float w = bitmap.getWidth();
float h = bitmap.getHeight();
matrix.reset();

// We limit up-scaling to 3x otherwise the result may look bad if it's a small icon
float widthScale = Math.min(viewWidth / w, 3.0f);
float heightScale = Math.min(viewHeight / h, 3.0f);
float scale = Math.min(widthScale, heightScale);

if (includeRotation) {
matrix.postConcat(bitmap.getRotateMatrix());
}
matrix.postScale(scale, scale);
matrix.postTranslate((viewWidth - w * scale) / 2F, (viewHeight - h * scale) / 2F);
}

// Combine the base matrix and the supp matrix to make the final matrix
protected Matrix getImageViewMatrix() {
// The final matrix is computed as the concatentation of the base matrix
// and the supplementary matrix
displayMatrix.set(baseMatrix);
displayMatrix.postConcat(suppMatrix);
return displayMatrix;
}

public Matrix getUnrotatedMatrix(){
Matrix unrotated = new Matrix();
getProperBaseMatrix(bitmapDisplayed, unrotated, false);
unrotated.postConcat(suppMatrix);
return unrotated;
}

protected float calculateMaxZoom() {
if (bitmapDisplayed.getBitmap() == null) {
return 1F;
}

float fw = (float) bitmapDisplayed.getWidth() / (float) thisWidth;
float fh = (float) bitmapDisplayed.getHeight() / (float) thisHeight;
return Math.max(fw, fh) * 4; // 400%
}

protected void zoomTo(float scale, float centerX, float centerY) {
if (scale > maxZoom) {
scale = maxZoom;
}

float oldScale = getScale();
float deltaScale = scale / oldScale;

suppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);
setImageMatrix(getImageViewMatrix());
center();
}

protected void zoomTo(final float scale, final float centerX,
final float centerY, final float durationMs)
{

final float incrementPerMs = (scale - getScale()) / durationMs;
final float oldScale = getScale();
final long startTime = System.currentTimeMillis();

handler.post(new Runnable() {
public void run() {
long now = System.currentTimeMillis();
float currentMs = Math.min(durationMs, now - startTime);
float target = oldScale + (incrementPerMs * currentMs);
zoomTo(target, centerX, centerY);

if (currentMs < durationMs) {
handler.post(this);
}
}
});
}

protected void zoomTo(float scale) {
float cx = getWidth() / 2F;
float cy = getHeight() / 2F;
zoomTo(scale, cx, cy);
}

protected void zoomIn() {
zoomIn(SCALE_RATE);
}

protected void zoomOut() {
zoomOut(SCALE_RATE);
}

protected void zoomIn(float rate) {
if (getScale() >= maxZoom) {
return; // Don't let the user zoom into the molecular level
}
if (bitmapDisplayed.getBitmap() == null) {
return;
}

float cx = getWidth() / 2F;
float cy = getHeight() / 2F;

suppMatrix.postScale(rate, rate, cx, cy);
setImageMatrix(getImageViewMatrix());
}

protected void zoomOut(float rate) {
if (bitmapDisplayed.getBitmap() == null) {
return;
}

float cx = getWidth() / 2F;
float cy = getHeight() / 2F;

// Zoom out to at most 1x
Matrix tmp = new Matrix(suppMatrix);
tmp.postScale(1F / rate, 1F / rate, cx, cy);

if (getScale(tmp) < 1F) {
suppMatrix.setScale(1F, 1F, cx, cy);
} else {
suppMatrix.postScale(1F / rate, 1F / rate, cx, cy);
}
setImageMatrix(getImageViewMatrix());
center();
}

protected void postTranslate(float dx, float dy) {
suppMatrix.postTranslate(dx, dy);
}

protected void panBy(float dx, float dy) {
postTranslate(dx, dy);
setImageMatrix(getImageViewMatrix());
}
}

总结

项目中裁剪的核心是 BitmapRegionDecoder ,还要许多可取之处,比如计算 simpleSize 的方法、得到 View 的宽度的方法等。

BitmapRegionDecoder 类用来编译(解码)在图片内不同的方形区域,BitmapRegionDecoder 类在使用较大图片只需要取得图片中的一小部分的内容是特别有效益的。我们创建一个 BitmapRegionDecoder 类,并调用 newInstance() 方法,就可以得到 BitmapRegionDecoder 的对象之后,我们就能调用 decodeRegion() 方法去多次获得位图的特定地区的小图片。