├── .gitignore
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── ishow
│ │ └── cutout
│ │ ├── CutoutActivity.java
│ │ ├── CutoutRecord.java
│ │ └── CutoutView.java
│ └── res
│ ├── color
│ └── normal_grey_selected_white.xml
│ ├── drawable-xhdpi
│ ├── imitate_transparent_bg_piece.png
│ └── splash.png
│ ├── layout
│ └── activity_main.xml
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | ### Android template
3 | # Built application files
4 | *.apk
5 | *.ap_
6 |
7 | # Files for the ART/Dalvik VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # Generated files
14 | bin/
15 | gen/
16 | out/
17 |
18 | # Gradle files
19 | .gradle/
20 | build/
21 |
22 | # Local configuration file (sdk imagePath, etc)
23 | local.properties
24 |
25 | # Proguard folder generated by Eclipse
26 | proguard/
27 |
28 | # Log Files
29 | *.log
30 |
31 | # Android Studio Navigation editor temp files
32 | .navigation/
33 |
34 | # Android Studio captures folder
35 | captures/
36 |
37 | # Intellij
38 | *.iml
39 | .idea/
40 |
41 | # Keystore files
42 | *.jks
43 |
44 | # External native build folder generated in Android Studio 2.2 and later
45 | .externalNativeBuild
46 |
47 | # Google Services (e.g. APIs or Firebase)
48 | google-services.json
49 |
50 | # Freeline
51 | freeline.py
52 | freeline/
53 | freeline_project_description.json
54 |
55 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.3"
6 | defaultConfig {
7 | applicationId "com.ishow.demo"
8 | minSdkVersion 17
9 | targetSdkVersion 25
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 |
25 |
26 | compile 'com.ishow:common:1.1.2'
27 | }
28 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\Environment\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include imagePath and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
17 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ishow/cutout/CutoutActivity.java:
--------------------------------------------------------------------------------
1 | package com.ishow.cutout;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.view.View;
6 |
7 | import com.ishow.common.app.activity.BaseActivity;
8 | import com.ishow.common.utils.image.loader.ImageLoader;
9 | import com.ishow.common.utils.image.select.OnSelectPhotoListener;
10 | import com.ishow.common.utils.image.select.SelectPhotoUtils;
11 | import com.ishow.common.widget.loading.LoadingDialog;
12 |
13 | import java.util.List;
14 |
15 | import static com.ishow.cutout.R.id.cutout;
16 |
17 | public class CutoutActivity extends BaseActivity implements OnSelectPhotoListener, View.OnClickListener {
18 | private SelectPhotoUtils mSelectPhotoUtils;
19 | private CutoutView mCutoutView;
20 | private LoadingDialog mLoadingDialog;
21 |
22 | @Override
23 | protected void onCreate(Bundle savedInstanceState) {
24 | super.onCreate(savedInstanceState);
25 | setContentView(R.layout.activity_main);
26 | ImageLoader.init(this);
27 | mCutoutView = (CutoutView) findViewById(cutout);
28 | mCutoutView.setMode(CutoutView.Mode.CUT_OUT);
29 | mCutoutView.setOnCutoutListener(new CutoutView.OnCutoutListener() {
30 | @Override
31 | public void canBack(int count) {
32 |
33 | }
34 |
35 | @Override
36 | public void showLoading() {
37 | mLoadingDialog = LoadingDialog.show(CutoutActivity.this, mLoadingDialog);
38 | }
39 |
40 | @Override
41 | public void dismissLoading() {
42 | LoadingDialog.dismiss(mLoadingDialog);
43 | }
44 | });
45 | mCutoutView.setPhoto("/storage/emulated/0/Android/data/com.ishow.demo/cache/5EC29A97-A8E4-4DAA-AFFC-B654F0B1436C.jpg");
46 |
47 | mSelectPhotoUtils = new SelectPhotoUtils(this, SelectPhotoUtils.SelectMode.SINGLE);
48 | mSelectPhotoUtils.setOnSelectPhotoListener(this);
49 |
50 |
51 | View button = findViewById(R.id.button);
52 | button.setOnClickListener(new View.OnClickListener() {
53 | @Override
54 | public void onClick(View v) {
55 | mSelectPhotoUtils.select();
56 | }
57 | });
58 |
59 | View cut = findViewById(R.id.cut);
60 | cut.setOnClickListener(this);
61 | View back = findViewById(R.id.back);
62 | back.setOnClickListener(this);
63 | View eraser = findViewById(R.id.eraser);
64 | eraser.setOnClickListener(this);
65 | }
66 |
67 | @Override
68 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
69 | super.onActivityResult(requestCode, resultCode, data);
70 | mSelectPhotoUtils.onActivityResult(requestCode, resultCode, data);
71 | }
72 |
73 | @Override
74 | public void onSelectedPhoto(List multiPath, String singlePath) {
75 | mCutoutView.setPhoto(singlePath);
76 | }
77 |
78 | @Override
79 | public void onClick(View v) {
80 | switch (v.getId()) {
81 | case R.id.cut:
82 | mCutoutView.setMode(CutoutView.Mode.CUT_OUT);
83 | break;
84 | case R.id.back:
85 | mCutoutView.back();
86 | break;
87 | case R.id.eraser:
88 | mCutoutView.setMode(CutoutView.Mode.ERASER);
89 | break;
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ishow/cutout/CutoutRecord.java:
--------------------------------------------------------------------------------
1 | package com.ishow.cutout;
2 |
3 | import android.graphics.Matrix;
4 | import android.graphics.Path;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | /**
10 | * Created by yuhaiyang on 2017/9/28.
11 | * 历史记录
12 | */
13 |
14 | @SuppressWarnings("ALL")
15 | public class CutoutRecord {
16 | /**
17 | * 图片路径
18 | */
19 | private String imagePath;
20 | /**
21 | * 擦图
22 | */
23 | private List eraserPathList;
24 | private List eraserMatrixList;
25 |
26 |
27 | /**
28 | * 抠图的路径
29 | */
30 | private List cutoutPathList;
31 | private List cutoutMatrixList;
32 | /**
33 | * 抠图的轨迹
34 | */
35 | private List cutoutTrackList;
36 |
37 | public CutoutRecord() {
38 | }
39 |
40 | public String getImagePath() {
41 | return imagePath;
42 | }
43 |
44 | public void setImagePath(String imagePath) {
45 | this.imagePath = imagePath;
46 | }
47 |
48 |
49 | public void addEraserMatrix(Matrix matrix) {
50 | if (matrix == null) {
51 | return;
52 | }
53 | if (eraserMatrixList == null) {
54 | eraserMatrixList = new ArrayList<>();
55 | }
56 | eraserMatrixList.add(matrix);
57 | }
58 |
59 | public void addEraserPath(Path path) {
60 | if (path == null) {
61 | return;
62 | }
63 | if (eraserPathList == null) {
64 | eraserPathList = new ArrayList<>();
65 | }
66 | eraserPathList.add(path);
67 | }
68 |
69 |
70 | public void addCutoutMatrix(Matrix matrix) {
71 | if (matrix == null) {
72 | return;
73 | }
74 | if (cutoutMatrixList == null) {
75 | cutoutMatrixList = new ArrayList<>();
76 | }
77 | cutoutMatrixList.add(matrix);
78 | }
79 |
80 | public void addCutoutPath(Path path) {
81 | if (path == null) {
82 | return;
83 | }
84 | if (cutoutPathList == null) {
85 | cutoutPathList = new ArrayList<>();
86 | }
87 | cutoutPathList.add(path);
88 | }
89 |
90 | public List getCutoutMatrixList() {
91 | if (cutoutMatrixList == null) {
92 | cutoutMatrixList = new ArrayList<>();
93 | }
94 | return cutoutMatrixList;
95 | }
96 |
97 | public List getCutoutPathList() {
98 | if (cutoutPathList == null) {
99 | cutoutPathList = new ArrayList<>();
100 | }
101 | return cutoutPathList;
102 | }
103 |
104 | public void addCutoutTrack(float length) {
105 | if (cutoutTrackList == null) {
106 | cutoutTrackList = new ArrayList<>();
107 | }
108 | cutoutTrackList.add(length);
109 | }
110 |
111 |
112 | public List getEraserPathList() {
113 | if (eraserPathList == null) {
114 | eraserPathList = new ArrayList<>();
115 | }
116 | return eraserPathList;
117 | }
118 |
119 | public List getEraserMatrixList() {
120 | if (eraserMatrixList == null) {
121 | eraserMatrixList = new ArrayList<>();
122 | }
123 | return eraserMatrixList;
124 | }
125 |
126 | public List getCutoutTrackList() {
127 | if (cutoutTrackList == null) {
128 | cutoutTrackList = new ArrayList<>();
129 | }
130 | return cutoutTrackList;
131 | }
132 |
133 | public void clearPointList() {
134 | if (cutoutTrackList == null) {
135 | return;
136 | }
137 | cutoutTrackList.clear();
138 | }
139 |
140 | public void clearEraserInfo() {
141 | clearEraserMatrixList();
142 | clearEraserPathList();
143 | }
144 |
145 | public void clearEraserMatrixList() {
146 | getEraserMatrixList().clear();
147 | }
148 |
149 | public void clearEraserPathList() {
150 | getEraserPathList().clear();
151 | }
152 |
153 | public void clearCutout() {
154 | getCutoutTrackList().clear();
155 | getCutoutMatrixList().clear();
156 | getCutoutPathList().clear();
157 | }
158 |
159 | /**
160 | * 是否有 记录
161 | */
162 | public boolean hasRecord() {
163 | return !getEraserPathList().isEmpty() || !getCutoutTrackList().isEmpty();
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ishow/cutout/CutoutView.java:
--------------------------------------------------------------------------------
1 | package com.ishow.cutout;
2 |
3 | import android.animation.PropertyValuesHolder;
4 | import android.animation.ValueAnimator;
5 | import android.content.Context;
6 | import android.graphics.Bitmap;
7 | import android.graphics.BitmapFactory;
8 | import android.graphics.BitmapShader;
9 | import android.graphics.BlurMaskFilter;
10 | import android.graphics.Canvas;
11 | import android.graphics.Color;
12 | import android.graphics.Matrix;
13 | import android.graphics.Paint;
14 | import android.graphics.Path;
15 | import android.graphics.PathMeasure;
16 | import android.graphics.PorterDuff;
17 | import android.graphics.PorterDuffXfermode;
18 | import android.graphics.RectF;
19 | import android.graphics.Shader;
20 | import android.os.Environment;
21 | import android.support.annotation.IntDef;
22 | import android.support.annotation.Nullable;
23 | import android.util.AttributeSet;
24 | import android.util.Log;
25 | import android.view.MotionEvent;
26 | import android.view.View;
27 | import android.view.ViewConfiguration;
28 | import android.widget.Toast;
29 |
30 | import com.ishow.common.utils.StringUtils;
31 | import com.ishow.common.utils.image.ImageUtils;
32 |
33 | import java.io.File;
34 | import java.io.FileOutputStream;
35 | import java.lang.annotation.Retention;
36 | import java.lang.annotation.RetentionPolicy;
37 | import java.util.ArrayList;
38 | import java.util.List;
39 | import java.util.UUID;
40 |
41 |
42 | /**
43 | * Created by yuhaiyang on 2017/9/25.
44 | * 抠图
45 | */
46 | public class CutoutView extends View {
47 |
48 | private static final String TAG = "CutoutView";
49 |
50 | /**
51 | * Donw和up之间的有效距离
52 | * 单位px
53 | */
54 | private static final int EFFECTIVE_DISTANCE = 200;
55 | /**
56 | * 移动的有效距离
57 | */
58 | private static final int EFFECTIVE_MOVE_DISTANCE = 500;
59 | /**
60 | * 抠图画笔的透明度
61 | */
62 | private static final int TRACK_ALPHA = 100;
63 |
64 | /**
65 | * 最大放大倍数
66 | */
67 | private static final float MAX_ZOOM_SCALE = 2.5F;
68 | /**
69 | * 缩放的最小倍数
70 | */
71 | private static final float MIN_ZOOM_SCALE = 0.3F;
72 |
73 | /**
74 | * 动作路径
75 | */
76 | private Path mCurrentPath;
77 | private Path mRealPath;
78 | /**
79 | * 用来计算动作路径
80 | */
81 | private PathMeasure mPathMeasure;
82 |
83 | private Paint mPhotoPaint;
84 | private Paint mActionPaint;
85 | private Paint mEnlargePaint;
86 | private Paint mTransparentPaint;
87 |
88 | private PorterDuffXfermode mCutoutPorterMode;
89 | private PorterDuffXfermode mEnlargePorterMode;
90 | private PorterDuffXfermode mEraserPorterMode;
91 | private Bitmap mPhotoBitmap;
92 | private Bitmap mCutoutBitmap;
93 | private Bitmap mEnlargeBgBitmap;
94 |
95 | private boolean isActionTrackVisible;
96 | private boolean isEnlargeVisible;
97 | private boolean isCutoutNewPath;
98 | /**
99 | * 是否手势操作过
100 | */
101 | private boolean isGestured;
102 | private boolean isMoved;
103 | /**
104 | * 模式
105 | * 抠图模式 {@link Mode#CUT_OUT}
106 | * 擦图模式 {@link Mode#ERASER}
107 | */
108 | private int mMode;
109 |
110 | private int mPhotoTop;
111 | private int mPhotoLeft;
112 | private int mPhotoWidth;
113 | private int mPhotoHeight;
114 | private int mViewWidth;
115 | private int mViewHeight;
116 | /**
117 | * 放大视图
118 | */
119 | private int mEnlargeSize;
120 | private int mMinDistance;
121 | /**
122 | * 多点触控处理
123 | */
124 | private int mTouchPoint;
125 | private float[] mDownPoint;
126 | private float[] mMovePoint;
127 | private float[] mUpPoint;
128 | private float[] mTouchTwoPointCenter;
129 | private float[] mLastPointOne;
130 | private float[] mLastPointTwo;
131 | private float[] mStartCutoutPoint;
132 | private float[] mLastCutoutPoint;
133 |
134 | /**
135 | * 上一次2点之间的距离
136 | */
137 | private double mLastTwoPointDistance;
138 |
139 |
140 | private List mCutoutRecordList;
141 | private CutoutRecord mCurrentRecord;
142 | private ValueAnimator mZoomAnimator;
143 |
144 | private Matrix mMatrix;
145 | private Matrix mExchangedMatrix;
146 | private RectF mPhotoRectF;
147 |
148 | public CutoutView(Context context) {
149 | super(context);
150 | init(context);
151 | }
152 |
153 | public CutoutView(Context context, @Nullable AttributeSet attrs) {
154 | super(context, attrs);
155 | init(context);
156 | }
157 |
158 | public CutoutView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
159 | super(context, attrs, defStyleAttr);
160 | init(context);
161 | }
162 |
163 | private void init(Context context) {
164 | setLayerType(View.LAYER_TYPE_SOFTWARE, null);
165 | mMinDistance = ViewConfiguration.get(context).getScaledTouchSlop();
166 | mMatrix = new Matrix();
167 | mExchangedMatrix = new Matrix();
168 | mPhotoRectF = new RectF();
169 |
170 | mMode = Mode.CUT_OUT;
171 |
172 | mCutoutRecordList = new ArrayList<>();
173 | mDownPoint = new float[2];
174 | mMovePoint = new float[2];
175 | mUpPoint = new float[2];
176 | mTouchTwoPointCenter = new float[2];
177 | mLastPointOne = new float[2];
178 | mLastPointTwo = new float[2];
179 | mStartCutoutPoint = new float[2];
180 | mLastCutoutPoint = new float[2];
181 |
182 | mPhotoPaint = new Paint();
183 | mPhotoPaint.setDither(true);
184 | mPhotoPaint.setAntiAlias(true);
185 | mPhotoPaint.setStyle(Paint.Style.FILL);
186 |
187 | mZoomAnimator = ValueAnimator.ofFloat();
188 | mZoomAnimator.setDuration(500);
189 | mZoomAnimator.addUpdateListener(mAniListener);
190 |
191 |
192 | // 透明背景
193 | Bitmap transparentBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.imitate_transparent_bg_piece);
194 | BitmapShader transparentShader = new BitmapShader(transparentBitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
195 | mTransparentPaint = new Paint();
196 | mTransparentPaint.setDither(true);
197 | mTransparentPaint.setAntiAlias(true);
198 | mTransparentPaint.setShader(transparentShader);
199 |
200 | mCurrentPath = new Path();
201 | mRealPath = new Path();
202 |
203 |
204 | mActionPaint = new Paint();
205 | mActionPaint.setDither(true);
206 | mActionPaint.setAntiAlias(true);
207 | mActionPaint.setStyle(Paint.Style.STROKE);
208 | mActionPaint.setColor(Color.RED);
209 | mActionPaint.setStrokeWidth(30);
210 | mActionPaint.setStrokeCap(Paint.Cap.ROUND);
211 | mActionPaint.setStrokeJoin(Paint.Join.ROUND);
212 |
213 | mEnlargePaint = new Paint();
214 | mEnlargePaint.setDither(true);
215 | mEnlargePaint.setAntiAlias(true);
216 | mEnlargePaint.setStyle(Paint.Style.STROKE);
217 | mEnlargePaint.setColor(Color.WHITE);
218 | mEnlargePaint.setStrokeWidth(5);
219 | mEnlargePaint.setStrokeCap(Paint.Cap.ROUND);
220 | mEnlargePaint.setStrokeJoin(Paint.Join.ROUND);
221 |
222 | mPathMeasure = new PathMeasure(mCurrentPath, false);
223 | mCutoutPorterMode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
224 | mEnlargePorterMode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
225 | mEraserPorterMode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
226 | }
227 |
228 |
229 | @Override
230 | public boolean onTouchEvent(MotionEvent event) {
231 | switch (event.getAction() & MotionEvent.ACTION_MASK) {
232 | case MotionEvent.ACTION_DOWN:
233 | mTouchPoint = 1;
234 | isEnlargeVisible = true;
235 | isGestured = false;
236 | isMoved = false;
237 | mDownPoint[0] = event.getX();
238 | mDownPoint[1] = event.getY();
239 | break;
240 | case MotionEvent.ACTION_POINTER_DOWN:
241 | mTouchPoint += 1;
242 | isEnlargeVisible = false;
243 | isGestured = true;
244 |
245 | mLastPointOne[0] = event.getX(0);
246 | mLastPointOne[1] = event.getY(0);
247 | mLastPointTwo[0] = event.getX(1);
248 | mLastPointTwo[1] = event.getY(1);
249 | mLastTwoPointDistance = getTouchPointDistance(mLastPointOne, mLastPointTwo);
250 | break;
251 | case MotionEvent.ACTION_MOVE:
252 | mMovePoint[0] = event.getX();
253 | mMovePoint[1] = event.getY();
254 |
255 | final int moveX = (int) Math.abs(mMovePoint[0] - mDownPoint[0]);
256 | final int moveY = (int) Math.abs(mMovePoint[1] - mDownPoint[1]);
257 |
258 | if (moveX < mMinDistance && moveY < mMinDistance) {
259 | return true;
260 | }
261 |
262 |
263 | if (mTouchPoint >= 2) {
264 | onGestureMove(event);
265 | } else if (!isGestured) { // 手势操作后不能进行其他操作
266 | if (!isMoved) {
267 | if (mMode == Mode.CUT_OUT) {
268 | onCutoutDown(event);
269 | } else {
270 | onEraserDown(event);
271 | }
272 | isMoved = true;
273 | } else {
274 | if (mMode == Mode.CUT_OUT) {
275 | onCutoutMove(event);
276 | } else {
277 | onEraserMove(event);
278 | }
279 | }
280 |
281 |
282 | }
283 |
284 | break;
285 | case MotionEvent.ACTION_POINTER_UP:
286 | mTouchPoint -= 1;
287 | if (mTouchPoint <= 1) {
288 | onGestureUp(event);
289 | }
290 | break;
291 |
292 | case MotionEvent.ACTION_UP:
293 | mTouchPoint = 0;
294 | isEnlargeVisible = false;
295 | mUpPoint[0] = event.getX();
296 | mUpPoint[1] = event.getY();
297 | // 手势操作后不能进行其他操作
298 | if (!isGestured) {
299 | if (mMode == Mode.CUT_OUT) {
300 | onCutoutUp(event);
301 | } else {
302 | onEraserUp(event);
303 | }
304 | }
305 | break;
306 |
307 | }
308 | postInvalidate();
309 | return true;
310 | }
311 |
312 | @Override
313 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
314 | super.onSizeChanged(w, h, oldw, oldh);
315 |
316 |
317 | mViewWidth = w;
318 | mViewHeight = h;
319 |
320 | mEnlargeSize = w / 4;
321 | createEnlargeBgBitmap();
322 | computePhotoInfo();
323 | }
324 |
325 |
326 | @Override
327 | protected void onDraw(Canvas canvas) {
328 | super.onDraw(canvas);
329 | if (mPhotoBitmap == null || mPhotoBitmap.isRecycled()) {
330 | return;
331 | }
332 | canvas.save();
333 | canvas.concat(mMatrix);
334 | /*
335 | * 画仿透明的背景
336 | */
337 | canvas.drawRect(
338 | mPhotoLeft,
339 | mPhotoTop,
340 | mPhotoLeft + mPhotoBitmap.getWidth(),
341 | mPhotoTop + mPhotoBitmap.getHeight(),
342 | mTransparentPaint);
343 |
344 | canvas.drawBitmap(mPhotoBitmap, mPhotoLeft, mPhotoTop, mPhotoPaint);
345 |
346 | if (isActionTrackVisible) {
347 | canvas.drawBitmap(mCutoutBitmap, mPhotoLeft, mPhotoTop, mPhotoPaint);
348 | }
349 | canvas.restore();
350 |
351 | if (isActionTrackVisible && !isCutoutNewPath) {
352 | canvas.drawPath(mCurrentPath, mActionPaint);
353 | }
354 |
355 | drawEnlarge(canvas);
356 | }
357 |
358 | private void drawEnlarge(Canvas canvas) {
359 | if (!isEnlargeVisible || mEnlargeBgBitmap == null) {
360 | return;
361 | }
362 | final int left;
363 | final float effect = mEnlargeSize * 1.2f;
364 | if (mMovePoint[0] < effect && mMovePoint[1] < effect) {
365 | left = getMeasuredWidth() - mEnlargeSize;
366 | } else {
367 | left = 0;
368 | }
369 |
370 | float[] movePoint = new float[2];
371 | mExchangedMatrix.reset();
372 | mMatrix.invert(mExchangedMatrix);
373 | mExchangedMatrix.mapPoints(movePoint, mMovePoint);
374 |
375 | final int x = left + mEnlargeSize / 2;
376 | final int y = mEnlargeSize / 2;
377 | final float moveX = x - movePoint[0] + mPhotoLeft;
378 | final float moveY = y - movePoint[1] + mPhotoTop;
379 |
380 | canvas.drawRect(
381 | left,
382 | 0,
383 | left + mEnlargeSize,
384 | mEnlargeSize,
385 | mTransparentPaint);
386 |
387 |
388 | Bitmap bitmap = getEnlargeResultBitmap();
389 | int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
390 | mPhotoPaint.setXfermode(null);
391 | canvas.drawBitmap(mEnlargeBgBitmap, left, 0, mPhotoPaint);
392 | mPhotoPaint.setXfermode(mEnlargePorterMode);
393 | canvas.drawBitmap(bitmap, moveX, moveY, mPhotoPaint);
394 | mPhotoPaint.setXfermode(null);
395 | canvas.restoreToCount(saveCount);
396 |
397 | canvas.drawRect(left, 0, left + mEnlargeSize, mEnlargeSize, mEnlargePaint);
398 | canvas.drawCircle(x, y, 15, mEnlargePaint);
399 |
400 | recycleBitmap(bitmap);
401 |
402 | }
403 |
404 | /**
405 | * 抠图按下操作
406 | */
407 | @SuppressWarnings("UnusedParameters")
408 | private void onCutoutDown(MotionEvent event) {
409 | isActionTrackVisible = true;
410 | isCutoutNewPath = false;
411 | mActionPaint.setAlpha(TRACK_ALPHA);
412 |
413 | float[] real = computeRealPoint(mDownPoint);
414 |
415 | mPathMeasure.setPath(mRealPath, false);
416 | // 如果之前是closed 就重置
417 | if (mPathMeasure.getLength() == 0 || mPathMeasure.isClosed()) {
418 | mRealPath.reset();
419 | mRealPath.moveTo(real[0], real[1]);
420 | mCurrentPath.reset();
421 | mCurrentPath.moveTo(mDownPoint[0], mDownPoint[1]);
422 | mStartCutoutPoint = real;
423 | } else {
424 | mRealPath.lineTo(real[0], real[1]);
425 |
426 | mLastCutoutPoint = computeNowPoint(mLastCutoutPoint);
427 | mCurrentPath.moveTo(mLastCutoutPoint[0], mLastCutoutPoint[1]);
428 | mCurrentPath.lineTo(mDownPoint[0], mDownPoint[1]);
429 | }
430 |
431 | }
432 |
433 | private void onCutoutMove(MotionEvent event) {
434 | float[] real = computeRealPoint(mMovePoint);
435 | mRealPath.lineTo(real[0], real[1]);
436 | mCurrentPath.lineTo(event.getX(), event.getY());
437 | }
438 |
439 | private void onCutoutUp(MotionEvent event) {
440 | isCutoutNewPath = true;
441 | float[] real = computeRealPoint(mUpPoint);
442 | mRealPath.lineTo(real[0], real[1]);
443 | mCurrentPath.lineTo(event.getX(), event.getY());
444 |
445 | mPathMeasure.setPath(mRealPath, false);
446 | final float actionLength = mPathMeasure.getLength();
447 | final double distance = getPointDistance(mStartCutoutPoint, real);
448 | if (actionLength > EFFECTIVE_MOVE_DISTANCE && distance < EFFECTIVE_DISTANCE) {
449 | mRealPath.close();
450 | final float start[] = computeNowPoint(mStartCutoutPoint);
451 | Path path = new Path();
452 | path.moveTo(event.getX(), event.getY());
453 | path.lineTo(start[0], start[1]);
454 | updateCutoutPathBitmap(path);
455 |
456 | new Thread(mResultCutoutRunnable).start();
457 | } else {
458 | mCurrentRecord.addCutoutTrack(mPathMeasure.getLength());
459 | updateCutoutPathBitmap();
460 |
461 | Matrix matrix = new Matrix();
462 | mMatrix.invert(matrix);
463 | mCurrentRecord.addCutoutMatrix(matrix);
464 | mCurrentRecord.addCutoutPath(mCurrentPath);
465 | mCurrentPath = new Path();
466 |
467 | mLastCutoutPoint[0] = event.getX();
468 | mLastCutoutPoint[1] = event.getY();
469 | mLastCutoutPoint = computeRealPoint(mLastCutoutPoint);
470 |
471 | notifyCanBack();
472 | }
473 | }
474 |
475 | /**
476 | * 橡皮擦按下操作
477 | */
478 | private void onEraserDown(MotionEvent event) {
479 | isActionTrackVisible = false;
480 | mActionPaint.setAlpha(255);
481 | mCurrentPath.reset();
482 | mCurrentPath.moveTo(event.getX(), event.getY());
483 | }
484 |
485 | private void onEraserMove(MotionEvent event) {
486 | mCurrentPath.lineTo(event.getX(), event.getY());
487 | mPhotoBitmap = getEraserResultBitmap();
488 | }
489 |
490 | @SuppressWarnings("UnusedParameters")
491 | private void onEraserUp(MotionEvent event) {
492 | Matrix matrix = new Matrix();
493 | mMatrix.invert(matrix);
494 | mCurrentRecord.addEraserMatrix(matrix);
495 | mCurrentRecord.addEraserPath(mCurrentPath);
496 | mCurrentPath = new Path();
497 | notifyCanBack();
498 | }
499 |
500 | private void onGestureMove(MotionEvent event) {
501 | final float pointOneX = event.getX(0);
502 | final float pointOneY = event.getY(0);
503 | final float pointTwoX = event.getX(1);
504 | final float pointTwoY = event.getY(1);
505 |
506 | final float translateX = Math.min(pointOneX - mLastPointOne[0], pointTwoX - mLastPointTwo[0]);
507 | final float translateY = Math.min(pointOneY - mLastPointOne[1], pointTwoY - mLastPointTwo[1]);
508 | mMatrix.postTranslate(translateX, translateY);
509 | mLastPointOne[0] = pointOneX;
510 | mLastPointOne[1] = pointOneY;
511 | mLastPointTwo[0] = pointTwoX;
512 | mLastPointTwo[1] = pointTwoY;
513 |
514 | mTouchTwoPointCenter[0] = pointOneX + (pointTwoX - pointOneX) / 2;
515 | mTouchTwoPointCenter[1] = pointOneY + (pointTwoY - pointOneY) / 2;
516 |
517 | final float currentScale = getValues(Matrix.MSCALE_X);
518 | final double nowDistance = getTouchPointDistance(mLastPointOne, mLastPointTwo);
519 | float nowScale = (float) (nowDistance / mLastTwoPointDistance);
520 | mLastTwoPointDistance = nowDistance;
521 |
522 | mExchangedMatrix.set(mMatrix);
523 | mExchangedMatrix.postScale(nowScale, nowScale, mTouchTwoPointCenter[0], mTouchTwoPointCenter[1]);
524 | final float preScale = getValues(mExchangedMatrix, Matrix.MSCALE_X);
525 | if (preScale < MIN_ZOOM_SCALE) {
526 | nowScale = MIN_ZOOM_SCALE / currentScale;
527 | } else if (preScale > MAX_ZOOM_SCALE) {
528 | nowScale = MAX_ZOOM_SCALE / currentScale;
529 | }
530 |
531 | mMatrix.postScale(nowScale, nowScale, mTouchTwoPointCenter[0], mTouchTwoPointCenter[1]);
532 | }
533 |
534 |
535 | @SuppressWarnings("UnusedParameters")
536 | private void onGestureUp(MotionEvent event) {
537 | isGestured = true;
538 | final float scale = getValues(Matrix.MSCALE_X);
539 |
540 | if (scale > 1) {
541 | onGestureZoomIn(event, scale);
542 | } else {
543 | onGestureZoomOut(event, scale);
544 | }
545 |
546 | }
547 |
548 |
549 | /**
550 | * 放大
551 | */
552 | @SuppressWarnings("UnusedParameters")
553 | private void onGestureZoomIn(MotionEvent event, float scale) {
554 | final float translateX = getValues(Matrix.MTRANS_X);
555 | final float translateY = getValues(Matrix.MTRANS_Y);
556 |
557 | resetPhotoRectF();
558 | PropertyValuesHolder translateXValues = null;
559 | PropertyValuesHolder translateYValues = null;
560 |
561 |
562 | mMatrix.mapRect(mPhotoRectF);
563 |
564 | final float photoWidth = mPhotoRectF.width();
565 | final float photoHeight = mPhotoRectF.height();
566 | // X轴
567 | if (photoWidth > mViewWidth) {
568 | // 左移
569 | if (mPhotoRectF.right < mViewWidth) {
570 | final float result = translateX + (mViewWidth - mPhotoRectF.right);
571 | translateXValues = PropertyValuesHolder.ofFloat("translateX", translateX, result);
572 | } else if (mPhotoRectF.left > 0) {
573 | final float result = translateX - mPhotoRectF.left;
574 | translateXValues = PropertyValuesHolder.ofFloat("translateX", translateX, result);
575 | }
576 | } else {
577 | /*
578 | * 1. (mViewWidth - viewWidth) / 2 单侧移动的了多少距离
579 | * 2. (mPhotoRectF.left - (mViewWidth - viewWidth) / 2 需要移动的距离
580 | * 3. mTranslateX - (mPhotoRectF.left - (mViewWidth - viewWidth) / 2) 最终的距离
581 | */
582 | final float result = translateX - (mPhotoRectF.left - (mViewWidth - photoWidth) / 2);
583 | translateXValues = PropertyValuesHolder.ofFloat("translateX", translateX, result);
584 | }
585 |
586 | // Y轴
587 | if (photoHeight > mViewHeight) {
588 | // 上移
589 | if (mPhotoRectF.top > 0) {
590 | final float result = translateY - mPhotoRectF.top;
591 | translateYValues = PropertyValuesHolder.ofFloat("translateY", translateY, result);
592 | } else if (mPhotoRectF.bottom < mViewHeight) {
593 | final float result = translateY + (mViewHeight - mPhotoRectF.bottom);
594 | translateYValues = PropertyValuesHolder.ofFloat("translateY", translateY, result);
595 | }
596 | } else {
597 | /*
598 | * 同X
599 | */
600 | final float result = translateY - (mPhotoRectF.top - (mViewHeight - photoHeight) / 2);
601 | translateYValues = PropertyValuesHolder.ofFloat("translateY", translateY, result);
602 | }
603 |
604 | startAnimation(null, translateXValues, translateYValues);
605 | }
606 |
607 | /**
608 | * 缩小
609 | */
610 | @SuppressWarnings("UnusedParameters")
611 | private void onGestureZoomOut(MotionEvent event, float scale) {
612 | final float translateX = getValues(Matrix.MTRANS_X);
613 | final float translateY = getValues(Matrix.MTRANS_Y);
614 |
615 | PropertyValuesHolder scaleValues = PropertyValuesHolder.ofFloat("scale", scale, 1);
616 | PropertyValuesHolder translateXValues = PropertyValuesHolder.ofFloat("translateX", translateX, 0);
617 | PropertyValuesHolder translateYValues = PropertyValuesHolder.ofFloat("translateY", translateY, 0);
618 | mTouchTwoPointCenter[0] = 0;
619 | mTouchTwoPointCenter[1] = 0;
620 |
621 | startAnimation(scaleValues, translateXValues, translateYValues);
622 | }
623 |
624 | private void startAnimation(PropertyValuesHolder scale, PropertyValuesHolder translateX, PropertyValuesHolder translateY) {
625 | if (mZoomAnimator != null && mZoomAnimator.isRunning()) {
626 | mZoomAnimator.cancel();
627 | }
628 |
629 | List list = new ArrayList<>();
630 | if (scale != null) {
631 | list.add(scale);
632 | }
633 |
634 | if (translateX != null) {
635 | list.add(translateX);
636 | }
637 |
638 | if (translateY != null) {
639 | list.add(translateY);
640 | }
641 |
642 | if (list.isEmpty()) {
643 | Log.i(TAG, "startAnimation: no animation");
644 | return;
645 | }
646 | mZoomAnimator.setValues((PropertyValuesHolder[]) list.toArray(new PropertyValuesHolder[list.size()]));
647 | mZoomAnimator.start();
648 | }
649 |
650 |
651 | private Bitmap getEnlargeResultBitmap() {
652 | Bitmap bitmap = Bitmap.createBitmap(mPhotoBitmap.getWidth(), mPhotoBitmap.getHeight(), Bitmap.Config.ARGB_8888);
653 | Canvas canvas = new Canvas(bitmap);
654 | canvas.drawBitmap(mPhotoBitmap, 0, 0, mPhotoPaint);
655 | if (mMode == Mode.CUT_OUT) {
656 | mExchangedMatrix.reset();
657 | mMatrix.invert(mExchangedMatrix);
658 | int saveCount = canvas.saveLayer(0, 0, mPhotoBitmap.getWidth(), mPhotoBitmap.getHeight(), null, Canvas.ALL_SAVE_FLAG);
659 | canvas.translate(-mPhotoLeft, -mPhotoTop);
660 | canvas.concat(mExchangedMatrix);
661 | canvas.drawPath(mCurrentPath, mActionPaint);
662 | canvas.restoreToCount(saveCount);
663 | }
664 | return bitmap;
665 | }
666 |
667 |
668 | /**
669 | * 获取路径Bitmap
670 | */
671 | private Bitmap getPathBitmap() {
672 | Bitmap bitmap = Bitmap.createBitmap(mPhotoBitmap.getWidth(), mPhotoBitmap.getHeight(), Bitmap.Config.ARGB_8888);
673 | Canvas canvas = new Canvas(bitmap);
674 | Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
675 | paint.setMaskFilter(new BlurMaskFilter(100, BlurMaskFilter.Blur.NORMAL));
676 | canvas.translate(-mPhotoLeft, -mPhotoTop);
677 | canvas.drawPath(mRealPath, paint);
678 | return bitmap;
679 | }
680 |
681 |
682 | private Bitmap getEraserResultBitmap() {
683 | Bitmap bitmap = Bitmap.createBitmap(mPhotoBitmap.getWidth(), mPhotoBitmap.getHeight(), Bitmap.Config.ARGB_8888);
684 | Canvas canvas = new Canvas(bitmap);
685 | mPhotoPaint.setXfermode(null);
686 | canvas.drawBitmap(mPhotoBitmap, 0, 0, mPhotoPaint);
687 | mPhotoPaint.setXfermode(mEraserPorterMode);
688 | canvas.drawBitmap(getEraserPathBitmap(), -mPhotoLeft, -mPhotoTop, mPhotoPaint);
689 | mPhotoPaint.setXfermode(null);
690 | return bitmap;
691 | }
692 |
693 | private Bitmap getEraserPathBitmap() {
694 | mExchangedMatrix.reset();
695 | mMatrix.invert(mExchangedMatrix);
696 | Bitmap bitmap = Bitmap.createBitmap(mViewWidth, mViewHeight, Bitmap.Config.ARGB_8888);
697 | Canvas canvas = new Canvas(bitmap);
698 | canvas.save();
699 | canvas.concat(mExchangedMatrix);
700 | canvas.drawPath(mCurrentPath, mActionPaint);
701 | canvas.restore();
702 | return bitmap;
703 | }
704 |
705 |
706 | private void updateCutoutPathBitmap() {
707 | mExchangedMatrix.reset();
708 | mMatrix.invert(mExchangedMatrix);
709 | Canvas canvas = new Canvas(mCutoutBitmap);
710 | canvas.save();
711 | canvas.translate(-mPhotoLeft, -mPhotoTop);
712 | canvas.concat(mExchangedMatrix);
713 | canvas.drawPath(mCurrentPath, mActionPaint);
714 | canvas.restore();
715 | }
716 |
717 | private void updateCutoutPathBitmap(Path path) {
718 | mExchangedMatrix.reset();
719 | mMatrix.invert(mExchangedMatrix);
720 | Canvas canvas = new Canvas(mCutoutBitmap);
721 | canvas.save();
722 | canvas.translate(-mPhotoLeft, -mPhotoTop);
723 | canvas.concat(mExchangedMatrix);
724 | canvas.drawPath(mCurrentPath, mActionPaint);
725 | canvas.drawPath(path, mActionPaint);
726 | canvas.restore();
727 | }
728 |
729 | private Bitmap getBackEraserPathBitmap(List pathList, List matrixList) {
730 | Bitmap bitmap = Bitmap.createBitmap(mPhotoBitmap.getWidth(), mPhotoBitmap.getHeight(), Bitmap.Config.ARGB_8888);
731 | Canvas canvas = new Canvas(bitmap);
732 | for (int i = 0; i < pathList.size(); i++) {
733 | Path path = pathList.get(i);
734 | Matrix matrix = matrixList.get(i);
735 | canvas.save();
736 | canvas.translate(-mPhotoLeft, -mPhotoTop);
737 | canvas.concat(matrix);
738 | canvas.drawPath(path, mActionPaint);
739 | canvas.restore();
740 | }
741 | return bitmap;
742 | }
743 |
744 |
745 | private void createEnlargeBgBitmap() {
746 | if (mEnlargeBgBitmap != null) {
747 | mEnlargeBgBitmap.recycle();
748 | mEnlargeBgBitmap = null;
749 | }
750 | Bitmap bitmap = Bitmap.createBitmap(mEnlargeSize, mEnlargeSize, Bitmap.Config.RGB_565);
751 | Canvas canvas = new Canvas(bitmap);
752 | Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
753 | canvas.drawRect(
754 | 0,
755 | 0,
756 | mEnlargeSize,
757 | mEnlargeSize,
758 | paint);
759 |
760 | mEnlargeBgBitmap = bitmap;
761 | }
762 |
763 | /**
764 | * 获取2点距离
765 | */
766 | private double getPointDistance(float[] down, float[] up) {
767 | final double x = Math.pow(up[0] - down[0], 2);
768 | final double y = Math.pow(up[1] - down[1], 2);
769 | return Math.sqrt(x + y);
770 | }
771 |
772 | /**
773 | * 获取 距离
774 | */
775 | private double getTouchPointDistance(float[] one, float[] two) {
776 | float x = one[0] - two[0];
777 | float y = one[1] - two[1];
778 | return Math.sqrt(x * x + y * y);
779 | }
780 |
781 |
782 | /**
783 | * 计算图片相关信息
784 | */
785 | private void computePhotoInfo() {
786 | // 当是第二张的时候不需要处理
787 | if (mCutoutRecordList.size() > 1) {
788 | return;
789 | }
790 |
791 | if (mPhotoBitmap == null || mViewHeight == 0 || mViewWidth == 0) {
792 | return;
793 | }
794 |
795 | final int width = mPhotoBitmap.getWidth();
796 | final int height = mPhotoBitmap.getHeight();
797 | final float widthScale = ((float) mViewWidth / width);
798 | final float heightScale = ((float) mViewHeight / height);
799 |
800 | Matrix matrix = new Matrix();
801 | final float scale = Math.min(widthScale, heightScale);
802 | if (widthScale <= heightScale) {
803 | mPhotoLeft = 0;
804 | mPhotoTop = (int) ((mViewHeight - height * scale) / 2);
805 | } else {
806 | mPhotoLeft = (int) ((mViewWidth - width * scale) / 2);
807 | mPhotoTop = 0;
808 | }
809 | matrix.postScale(scale, scale);
810 |
811 | Bitmap bitmap = mPhotoBitmap;
812 | mPhotoBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
813 | mPhotoWidth = mPhotoBitmap.getWidth();
814 | mPhotoHeight = mPhotoBitmap.getHeight();
815 |
816 | mCutoutBitmap = Bitmap.createBitmap(mPhotoWidth, mPhotoHeight, Bitmap.Config.ARGB_8888);
817 | }
818 |
819 | public void setMode(@Mode int mode) {
820 | if (mMode == mode) {
821 | return;
822 | }
823 |
824 | mMode = mode;
825 | if (mode == Mode.ERASER && mCurrentRecord != null) {
826 | mCurrentRecord.clearPointList();
827 | isActionTrackVisible = false;
828 | notifyCanBack();
829 | postInvalidate();
830 | } else if (mode == Mode.CUT_OUT) {
831 | mCurrentPath = new Path();
832 | mPathMeasure.setPath(mCurrentPath, false);
833 | }
834 | }
835 |
836 |
837 | public void setPhoto(String path) {
838 | File file = new File(path);
839 | if (!file.exists() || !file.isFile()) {
840 | Toast.makeText(getContext(), R.string.file_not_exist, Toast.LENGTH_SHORT).show();
841 | return;
842 | }
843 | CutoutRecord record = new CutoutRecord();
844 | record.setImagePath(path);
845 |
846 | mMatrix.reset();
847 | mCutoutRecordList.clear();
848 | mCutoutRecordList.add(record);
849 | mCurrentRecord = record;
850 | resetPhotoRectF();
851 |
852 | mPhotoBitmap = BitmapFactory.decodeFile(path);
853 | mPhotoBitmap = ImageUtils.rotateBitmap(ImageUtils.getExifOrientation(path), mPhotoBitmap);
854 | computePhotoInfo();
855 | postInvalidate();
856 | }
857 |
858 |
859 | private Runnable mResultCutoutRunnable = new Runnable() {
860 | @Override
861 | public void run() {
862 | notifyShowLoading();
863 |
864 | Bitmap bitmap = Bitmap.createBitmap(mPhotoBitmap.getWidth(), mPhotoBitmap.getHeight(), Bitmap.Config.ARGB_8888);
865 | Bitmap pathBitmap = getPathBitmap();
866 |
867 | Canvas canvas = new Canvas(bitmap);
868 | mPhotoPaint.setXfermode(null);
869 | canvas.drawBitmap(mPhotoBitmap, 0, 0, mPhotoPaint);
870 | mPhotoPaint.setXfermode(mCutoutPorterMode);
871 | canvas.drawBitmap(pathBitmap, 0, 0, mPhotoPaint);
872 | mPhotoPaint.setXfermode(null);
873 |
874 | recycleBitmap(pathBitmap);
875 | recycleBitmap(mPhotoBitmap);
876 | mPhotoBitmap = bitmap;
877 |
878 | isActionTrackVisible = false;
879 |
880 | String path = saveResult(false);
881 | CutoutRecord record = new CutoutRecord();
882 | record.setImagePath(path);
883 | mCutoutRecordList.add(record);
884 | mCurrentRecord = record;
885 |
886 | notifyCanBack();
887 | notifyDismissLoading();
888 | postInvalidate();
889 | }
890 | };
891 |
892 |
893 | /**
894 | * 回退
895 | * 备注:
896 | * 回退分3个步骤分别为
897 | * 1. 回退抠图时候的路径
898 | * 2. 回退擦除时候的路径
899 | * 3. 回退抠图
900 | */
901 | public void back() {
902 | if (mCurrentRecord == null) {
903 | Log.i(TAG, "back: mCurrentRecord is null");
904 | return;
905 | }
906 | List cutoutTrackList = mCurrentRecord.getCutoutTrackList();
907 | List eraserPatList = mCurrentRecord.getEraserPathList();
908 | if (!cutoutTrackList.isEmpty()) {
909 | backCutoutAction(cutoutTrackList);
910 | } else if (!eraserPatList.isEmpty()) {
911 | backEraserPath(eraserPatList, mCurrentRecord.getEraserMatrixList());
912 | } else if (!mCutoutRecordList.isEmpty()) {
913 | backRecord();
914 | }
915 | }
916 |
917 |
918 | private void backRecord() {
919 | if (mCutoutRecordList.size() <= 1) {
920 | return;
921 | }
922 | final int size = mCutoutRecordList.size();
923 | int last = size - 1;
924 | mCutoutRecordList.remove(last);
925 | mCurrentRecord = mCutoutRecordList.get(last - 1);
926 | mPhotoBitmap = BitmapFactory.decodeFile(mCurrentRecord.getImagePath());
927 |
928 | if (size == 2) {
929 | computePhotoInfo();
930 | notifyCanBack();
931 | }
932 | List pathList = mCurrentRecord.getEraserPathList();
933 | if (!pathList.isEmpty()) {
934 | // 方法默认是回退一个的, 但是现在回退到record 的时候不需要回退这么多
935 | mActionPaint.setAlpha(255);
936 | pathList.add(new Path());
937 | backEraserPath(pathList, mCurrentRecord.getEraserMatrixList());
938 | }
939 |
940 | // 擦除的path 给画上去
941 | postInvalidate();
942 | }
943 |
944 | private void backEraserPath(List pathList, List matrixList) {
945 | final int size = pathList.size();
946 | recycleBitmap(mPhotoBitmap);
947 | mPhotoBitmap = BitmapFactory.decodeFile(mCurrentRecord.getImagePath());
948 | computePhotoInfo();
949 | if (size == 1) {
950 | mCurrentRecord.clearEraserInfo();
951 | notifyCanBack();
952 | } else {
953 | pathList.remove(pathList.size() - 1);
954 | matrixList.remove(matrixList.size() - 1);
955 | Bitmap pathBitmap = getBackEraserPathBitmap(pathList, matrixList);
956 | Bitmap bitmap = Bitmap.createBitmap(mPhotoBitmap.getWidth(), mPhotoBitmap.getHeight(), Bitmap.Config.ARGB_8888);
957 | Canvas canvas = new Canvas(bitmap);
958 | mPhotoPaint.setXfermode(null);
959 | canvas.drawBitmap(mPhotoBitmap, 0, 0, mPhotoPaint);
960 | mPhotoPaint.setXfermode(mEraserPorterMode);
961 | canvas.drawBitmap(pathBitmap, 0, 0, mPhotoPaint);
962 | mPhotoPaint.setXfermode(null);
963 |
964 | recycleBitmap(mPhotoBitmap);
965 | recycleBitmap(pathBitmap);
966 | mPhotoBitmap = bitmap;
967 | }
968 |
969 | postInvalidate();
970 | }
971 |
972 | private void backCutoutAction(List cutoutTrackList) {
973 | final int size = cutoutTrackList.size();
974 | if (size == 1) {
975 | cutoutTrackList.clear();
976 | mCurrentPath.reset();
977 | mPathMeasure.setPath(mCurrentPath, false);
978 | notifyCanBack();
979 | } else {
980 | Path path = new Path();
981 | path.moveTo(mDownPoint[0], mDownPoint[1]);
982 | float length = cutoutTrackList.get(size - 2);
983 | mPathMeasure.getSegment(0, length, path, false);
984 | mCurrentPath = path;
985 | mPathMeasure.setPath(mCurrentPath, false);
986 | cutoutTrackList.remove(size - 1);
987 | }
988 | postInvalidate();
989 | }
990 |
991 |
992 | /**
993 | * 保存图片
994 | */
995 | @SuppressWarnings("unused")
996 | public String saveResult() {
997 | return saveResult(true);
998 | }
999 |
1000 |
1001 | private String saveResult(boolean recycle) {
1002 |
1003 | File cache = generateRandomPhotoFile(getContext());
1004 | try {
1005 | FileOutputStream out = new FileOutputStream(cache);
1006 | mPhotoBitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
1007 | out.flush();
1008 | out.close();
1009 | Log.e(TAG, cache.getAbsolutePath());
1010 | return cache.getAbsolutePath();
1011 | } catch (Exception e) {
1012 | e.printStackTrace();
1013 | return "";
1014 | } finally {
1015 | if (recycle) {
1016 |
1017 | if (mPhotoBitmap != null) {
1018 | mPhotoBitmap.recycle();
1019 | mPhotoBitmap = null;
1020 | }
1021 | }
1022 | }
1023 | }
1024 |
1025 | private OnCutoutListener mCutoutListener;
1026 |
1027 | public void setOnCutoutListener(OnCutoutListener listener) {
1028 | mCutoutListener = listener;
1029 | }
1030 |
1031 | private void notifyCanBack() {
1032 | if (mCutoutListener == null) {
1033 | return;
1034 | }
1035 | post(new Runnable() {
1036 | @Override
1037 | public void run() {
1038 | if (mCutoutRecordList.size() > 1 || (mCurrentRecord != null && mCurrentRecord.hasRecord())) {
1039 | mCutoutListener.canBack(1);
1040 | } else {
1041 | mCutoutListener.canBack(0);
1042 | }
1043 | }
1044 | });
1045 |
1046 | }
1047 |
1048 | private void notifyShowLoading() {
1049 | if (mCutoutListener == null) {
1050 | return;
1051 | }
1052 | post(new Runnable() {
1053 | @Override
1054 | public void run() {
1055 | mCutoutListener.showLoading();
1056 |
1057 | }
1058 | });
1059 | }
1060 |
1061 | private void notifyDismissLoading() {
1062 | if (mCutoutListener == null) {
1063 | return;
1064 | }
1065 | post(new Runnable() {
1066 | @Override
1067 | public void run() {
1068 | mCutoutListener.dismissLoading();
1069 | }
1070 | });
1071 | }
1072 |
1073 |
1074 | /**
1075 | * 生成图片名称
1076 | */
1077 | public static File generateRandomPhotoFile(Context context) {
1078 | return new File(generateRandomPhotoName(context));
1079 | }
1080 |
1081 | /**
1082 | * 生成随机的名字
1083 | */
1084 | public static String generateRandomPhotoName(Context context) {
1085 | File cacheFolder = context.getExternalCacheDir();
1086 | if (null == cacheFolder) {
1087 | File target = Environment.getExternalStorageDirectory();
1088 | cacheFolder = new File(target + File.separator + "Pictures");
1089 | }
1090 |
1091 | Log.d(TAG, "cacheFolder path = " + cacheFolder.getAbsolutePath());
1092 | if (!cacheFolder.exists()) {
1093 | try {
1094 | boolean result = cacheFolder.mkdir();
1095 | Log.d(TAG, " result: " + (result ? "succeeded" : "failed"));
1096 | } catch (Exception e) {
1097 | Log.e(TAG, "generateUri failed: " + e.toString());
1098 | }
1099 | }
1100 | String name = StringUtils.plusString(UUID.randomUUID().toString().toUpperCase(), ".png");
1101 | return StringUtils.plusString(cacheFolder.getAbsolutePath(), File.separator, name);
1102 | }
1103 |
1104 |
1105 | private void recycleBitmap(Bitmap bitmap) {
1106 | if (bitmap != null) {
1107 | bitmap.recycle();
1108 | }
1109 |
1110 | }
1111 |
1112 | private void resetPhotoRectF() {
1113 | mPhotoRectF.left = mPhotoLeft;
1114 | mPhotoRectF.top = mPhotoTop;
1115 | mPhotoRectF.right = mPhotoRectF.left + mPhotoWidth;
1116 | mPhotoRectF.bottom = mPhotoRectF.top + mPhotoHeight;
1117 | }
1118 |
1119 | private float getValues(int index) {
1120 | float[] values = new float[9];
1121 | mMatrix.getValues(values);
1122 | return values[index];
1123 | }
1124 |
1125 | private float getValues(Matrix matrix, int index) {
1126 | float[] values = new float[9];
1127 | matrix.getValues(values);
1128 | return values[index];
1129 | }
1130 |
1131 | private ValueAnimator.AnimatorUpdateListener mAniListener = new ValueAnimator.AnimatorUpdateListener() {
1132 | @Override
1133 | public void onAnimationUpdate(ValueAnimator animation) {
1134 | Object scaleOb = animation.getAnimatedValue("scale");
1135 | if (scaleOb != null) {
1136 | float scale = (float) scaleOb;
1137 | float lastScale = getValues(Matrix.MSCALE_X);
1138 | scale = scale / lastScale;
1139 | mMatrix.postScale(scale, scale, mTouchTwoPointCenter[0], mTouchTwoPointCenter[1]);
1140 | }
1141 |
1142 | float lastTranslateX = getValues(Matrix.MTRANS_X);
1143 | float lastTranslateY = getValues(Matrix.MTRANS_Y);
1144 |
1145 | Object translateXOb = animation.getAnimatedValue("translateX");
1146 | float translateX;
1147 | if (translateXOb != null) {
1148 | translateX = (float) translateXOb - lastTranslateX;
1149 | } else {
1150 | translateX = 0;
1151 | }
1152 |
1153 | Object translateYOb = animation.getAnimatedValue("translateY");
1154 | float translateY;
1155 | if (translateYOb != null) {
1156 | translateY = (float) translateYOb - lastTranslateY;
1157 | } else {
1158 | translateY = 0;
1159 | }
1160 |
1161 | if (translateX != 0 || translateY != 0) {
1162 | mMatrix.postTranslate(translateX, translateY);
1163 | }
1164 | postInvalidate();
1165 | }
1166 | };
1167 |
1168 | private float[] computeRealPoint(float[] point) {
1169 | float[] real = new float[2];
1170 | mExchangedMatrix.reset();
1171 | mMatrix.invert(mExchangedMatrix);
1172 | Log.i(TAG, "computeRealPoint: mExchangedMatrix = " + mExchangedMatrix);
1173 | mExchangedMatrix.mapPoints(real, point);
1174 | Log.i(TAG, "computeRealPoint: real x = " + real[0] + " , real y = " + real[1]);
1175 | return real;
1176 | }
1177 |
1178 | private float[] computeNowPoint(float[] point) {
1179 | float[] now = new float[2];
1180 | mMatrix.mapPoints(now, point);
1181 | return now;
1182 | }
1183 |
1184 | /**
1185 | * 定义图片是单选还是多选
1186 | */
1187 | @SuppressWarnings("WeakerAccess")
1188 | @IntDef({Mode.CUT_OUT, Mode.ERASER})
1189 | @Retention(RetentionPolicy.SOURCE)
1190 | public @interface Mode {
1191 | /**
1192 | * 单选
1193 | */
1194 | int CUT_OUT = 1;
1195 | /**
1196 | * 多选
1197 | */
1198 | int ERASER = 2;
1199 | }
1200 |
1201 |
1202 | @SuppressWarnings("WeakerAccess")
1203 | public interface OnCutoutListener {
1204 |
1205 | void canBack(int count);
1206 |
1207 | void showLoading();
1208 |
1209 | void dismissLoading();
1210 | }
1211 |
1212 | }
1213 |
--------------------------------------------------------------------------------
/app/src/main/res/color/normal_grey_selected_white.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/imitate_transparent_bg_piece.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-show/android-CutoutView/f7b41e552f9bdb681e62cfa16832d72b63ec582a/app/src/main/res/drawable-xhdpi/imitate_transparent_bg_piece.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-show/android-CutoutView/f7b41e552f9bdb681e62cfa16832d72b63ec582a/app/src/main/res/drawable-xhdpi/splash.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
19 |
20 |
26 |
27 |
33 |
34 |
35 |
41 |
42 |
43 |
44 |
49 |
50 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-show/android-CutoutView/f7b41e552f9bdb681e62cfa16832d72b63ec582a/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-show/android-CutoutView/f7b41e552f9bdb681e62cfa16832d72b63ec582a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #309f48
6 | #30379f
7 | #FF4081
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Demo
3 | 文件不存在
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
20 |
21 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.3.3'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
23 | }
24 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-show/android-CutoutView/f7b41e552f9bdb681e62cfa16832d72b63ec582a/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Jun 27 09:45:07 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --imagePath --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --imagePath --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --imagePath --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------