├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── cymaybe
│ │ └── foucssurfaceview
│ │ └── ExampleInstrumentedTest.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── cymaybe
│ │ └── foucssurfaceview
│ │ ├── activity
│ │ └── MainActivity.java
│ │ └── fragment
│ │ └── PictureFragment.java
│ └── res
│ ├── drawable-xxhdpi
│ ├── ic_take_picture_normal.png
│ └── ic_take_picture_pressed.png
│ ├── drawable
│ └── take_picture_button_bg.xml
│ ├── layout-land
│ ├── activity_main.xml
│ ├── crop_mode_layout.xml
│ └── picture_layout.xml
│ ├── layout
│ ├── activity_main.xml
│ ├── crop_mode_layout.xml
│ └── picture_layout.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── focussurfaceview
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── cymaybe
│ │ └── foucsurfaceview
│ │ └── ExampleInstrumentedTest.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── cymaybe
│ │ └── foucsurfaceview
│ │ ├── FocusSurfaceView.java
│ │ └── animation
│ │ ├── SimpleValueAnimator.java
│ │ ├── SimpleValueAnimatorListener.java
│ │ ├── ValueAnimatorV14.java
│ │ └── ValueAnimatorV8.java
│ └── res
│ └── values
│ ├── attrs.xml
│ └── strings.xml
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screenshots
├── circle.png
├── circle_pre.png
├── demo.gif
├── free.png
├── free_pre.png
├── ratio_3_4.png
├── ratio_3_4_pre.png
├── square.png
└── square_pre.png
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 |
15 | # Gradle files
16 | .gradle/
17 | build/
18 |
19 | # Local configuration file (sdk path, etc)
20 | local.properties
21 |
22 | # Proguard folder generated by Eclipse
23 | proguard/
24 |
25 | # Log Files
26 | *.log
27 |
28 | # Android Studio Navigation editor temp files
29 | .navigation/
30 |
31 | # Android Studio captures folder
32 | captures/
33 |
34 |
35 | # added by moubiao 2016-03-29
36 | fingerprint-recognition.iml
37 |
38 | # generated files
39 | out/
40 |
41 | # Windows thumbnail db
42 | Thumbs.db
43 |
44 | # OSX files
45 | .DS_Store
46 |
47 | # Eclipse project files
48 | .classpath
49 | .project
50 |
51 | # Android Studio
52 | *.iml
53 | .idea
54 |
55 | # Local IDEA workspace
56 | .idea/workspace.xml
57 |
58 | # NDK
59 | obj/
60 |
61 | # added by moubiao 2016-03-30
62 | test/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FocusSurfaceView
2 | 该库大部分参考IsseiAoki的SimpleCropView https://github.com/IsseiAoki/SimpleCropView
3 | 实现了在相机的预览界面指定一个区域的大小,形状和位置,只拍摄该指定区域里的图像
4 | 支持 API Level 10 and above.
5 | 
6 | 
7 | 
8 | 
9 | 
10 | ##使用方法:
11 | 在工程和module里的build.gradle分别添加
12 | ```groovy
13 | allprojects {
14 | repositories {
15 | ...
16 | maven { url 'https://jitpack.io' }
17 | }
18 | }
19 | dependencies {
20 | compile 'com.github.CGmaybe10:FocusSurfaceView:v1.0.1'
21 | }
22 | ```
23 | ```xml
24 |
45 | ```
46 | ```java
47 | private FocusSurfaceView previewSFV = (FocusSurfaceView) findViewById(R.id.preview_sv);
48 | mCamera.autoFocus(new Camera.AutoFocusCallback() {
49 | @Override
50 | public void onAutoFocus(boolean success, Camera camera) {
51 | mCamera.takePicture(new Camera.ShutterCallback() {
52 | @Override
53 | public void onShutter() {
54 | }
55 | }, null, null, new Camera.PictureCallback() {
56 | @Override
57 | public void onPictureTaken(byte[] data, Camera camera) {
58 | Bitmap cropBitmap = previewSFV.getPicture(data);
59 | }
60 | });
61 | }
62 | }
63 | });
64 | ```
65 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.0"
6 | defaultConfig {
7 | applicationId "com.cymaybe.foucssurfaceview"
8 | minSdkVersion 14
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 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
25 | exclude group: 'com.android.support', module: 'support-annotations'
26 | })
27 | compile 'com.android.support:appcompat-v7:25.0.1'
28 | compile 'com.android.support:design:25.0.1'
29 | testCompile 'junit:junit:4.12'
30 | compile project(':focussurfaceview')
31 | }
32 |
--------------------------------------------------------------------------------
/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:\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path 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 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/cymaybe/foucssurfaceview/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.cymaybe.foucssurfaceview;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.cymaybe.foucssurfaceview", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/cymaybe/foucssurfaceview/activity/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.cymaybe.foucssurfaceview.activity;
2 |
3 | import android.content.Context;
4 | import android.content.pm.PackageManager;
5 | import android.graphics.Bitmap;
6 | import android.graphics.BitmapFactory;
7 | import android.hardware.Camera;
8 | import android.os.Bundle;
9 | import android.support.annotation.Nullable;
10 | import android.support.design.widget.Snackbar;
11 | import android.support.v4.app.ActivityCompat;
12 | import android.support.v4.content.ContextCompat;
13 | import android.support.v7.app.AppCompatActivity;
14 | import android.view.OrientationEventListener;
15 | import android.view.Surface;
16 | import android.view.SurfaceHolder;
17 | import android.view.View;
18 | import android.widget.Button;
19 |
20 | import com.cymaybe.foucssurfaceview.R;
21 | import com.cymaybe.foucssurfaceview.fragment.PictureFragment;
22 | import com.cymaybe.foucsurfaceview.FocusSurfaceView;
23 |
24 | import static android.Manifest.permission.CAMERA;
25 | import static com.cymaybe.foucssurfaceview.fragment.PictureFragment.CROP_PICTURE;
26 | import static com.cymaybe.foucssurfaceview.fragment.PictureFragment.ORIGIN_PICTURE;
27 |
28 | public class MainActivity extends AppCompatActivity implements View.OnClickListener, SurfaceHolder.Callback {
29 | private static final String TAG = "moubiao";
30 |
31 | private FocusSurfaceView previewSFV;
32 | private Button mTakeBT, mThreeFourBT, mFourThreeBT, mNineSixteenBT, mSixteenNineBT, mFitImgBT, mCircleBT, mFreeBT, mSquareBT,
33 | mCircleSquareBT, mCustomBT;
34 |
35 | private Camera mCamera;
36 | private SurfaceHolder mHolder;
37 | private boolean focus = false;
38 |
39 | @Override
40 | protected void onCreate(@Nullable Bundle savedInstanceState) {
41 | super.onCreate(savedInstanceState);
42 | setContentView(R.layout.activity_main);
43 |
44 | initData();
45 | initView();
46 | setListener();
47 | }
48 |
49 | private void initData() {
50 | DetectScreenOrientation detectScreenOrientation = new DetectScreenOrientation(this);
51 | detectScreenOrientation.enable();
52 | }
53 |
54 | private void initView() {
55 | previewSFV = (FocusSurfaceView) findViewById(R.id.preview_sv);
56 | mHolder = previewSFV.getHolder();
57 | mHolder.addCallback(MainActivity.this);
58 | mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
59 |
60 | mTakeBT = (Button) findViewById(R.id.take_bt);
61 | mThreeFourBT = (Button) findViewById(R.id.three_four_bt);
62 | mFourThreeBT = (Button) findViewById(R.id.four_three_bt);
63 | mNineSixteenBT = (Button) findViewById(R.id.nine_sixteen_bt);
64 | mSixteenNineBT = (Button) findViewById(R.id.sixteen_nine_bt);
65 | mFitImgBT = (Button) findViewById(R.id.fit_image_bt);
66 | mCircleBT = (Button) findViewById(R.id.circle_bt);
67 | mFreeBT = (Button) findViewById(R.id.free_bt);
68 | mSquareBT = (Button) findViewById(R.id.square_bt);
69 | mCircleSquareBT = (Button) findViewById(R.id.circle_square_bt);
70 | mCustomBT = (Button) findViewById(R.id.custom_bt);
71 | }
72 |
73 | private void setListener() {
74 | mTakeBT.setOnClickListener(this);
75 | mThreeFourBT.setOnClickListener(this);
76 | mFourThreeBT.setOnClickListener(this);
77 | mNineSixteenBT.setOnClickListener(this);
78 | mSixteenNineBT.setOnClickListener(this);
79 | mFitImgBT.setOnClickListener(this);
80 | mCircleBT.setOnClickListener(this);
81 | mFreeBT.setOnClickListener(this);
82 | mSquareBT.setOnClickListener(this);
83 | mCircleSquareBT.setOnClickListener(this);
84 | mCustomBT.setOnClickListener(this);
85 | }
86 |
87 | @Override
88 | public void surfaceCreated(SurfaceHolder surfaceHolder) {
89 | initCamera();
90 | setCameraParams();
91 | }
92 |
93 | private void initCamera() {
94 | if (checkPermission()) {
95 | try {
96 | mCamera = android.hardware.Camera.open(0);//1:采集指纹的摄像头. 0:拍照的摄像头.
97 | mCamera.setPreviewDisplay(mHolder);
98 | } catch (Exception e) {
99 | Snackbar.make(mTakeBT, "camera open failed!", Snackbar.LENGTH_SHORT).show();
100 | finish();
101 | e.printStackTrace();
102 | }
103 | } else {
104 | requestPermission();
105 | }
106 | }
107 |
108 | private boolean checkPermission() {
109 | return ContextCompat.checkSelfPermission(getApplicationContext(), CAMERA) == PackageManager.PERMISSION_GRANTED;
110 | }
111 |
112 | private void requestPermission() {
113 | ActivityCompat.requestPermissions(this, new String[]{CAMERA}, 10000);
114 | }
115 |
116 | @Override
117 | public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
118 | switch (requestCode) {
119 | case 10000:
120 | if (grantResults.length > 0) {
121 | if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
122 | initCamera();
123 | setCameraParams();
124 | }
125 | }
126 |
127 | break;
128 | }
129 | }
130 |
131 | private void setCameraParams() {
132 | if (mCamera == null) {
133 | return;
134 | }
135 | try {
136 | Camera.Parameters parameters = mCamera.getParameters();
137 |
138 | int orientation = judgeScreenOrientation();
139 | if (Surface.ROTATION_0 == orientation) {
140 | mCamera.setDisplayOrientation(90);
141 | parameters.setRotation(90);
142 | } else if (Surface.ROTATION_90 == orientation) {
143 | mCamera.setDisplayOrientation(0);
144 | parameters.setRotation(0);
145 | } else if (Surface.ROTATION_180 == orientation) {
146 | mCamera.setDisplayOrientation(180);
147 | parameters.setRotation(180);
148 | } else if (Surface.ROTATION_270 == orientation) {
149 | mCamera.setDisplayOrientation(180);
150 | parameters.setRotation(180);
151 | }
152 |
153 | parameters.setPictureSize(1280, 720);
154 | parameters.setPreviewSize(1280, 720);
155 | mCamera.setParameters(parameters);
156 | mCamera.startPreview();
157 | } catch (Exception e) {
158 | e.printStackTrace();
159 | }
160 | }
161 |
162 | @Override
163 | public void onWindowFocusChanged(boolean hasFocus) {
164 | View decorView = getWindow().getDecorView();
165 | decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
166 | | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
167 | | View.SYSTEM_UI_FLAG_FULLSCREEN
168 | | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
169 | }
170 |
171 | /**
172 | * 判断屏幕方向
173 | *
174 | * @return 0:竖屏 1:左横屏 2:反向竖屏 3:右横屏
175 | */
176 | private int judgeScreenOrientation() {
177 | return getWindowManager().getDefaultDisplay().getRotation();
178 | }
179 |
180 | @Override
181 | public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
182 |
183 | }
184 |
185 | @Override
186 | public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
187 | releaseCamera();
188 | }
189 |
190 | private void releaseCamera() {
191 | if (mCamera != null) {
192 | mCamera.stopPreview();
193 | mCamera.release();
194 | mCamera = null;
195 | }
196 | }
197 |
198 | @Override
199 | public void onClick(View view) {
200 | switch (view.getId()) {
201 | case R.id.take_bt:
202 | if (!focus) {
203 | takePicture();
204 | }
205 | break;
206 | case R.id.three_four_bt:
207 | previewSFV.setCropMode(FocusSurfaceView.CropMode.RATIO_3_4);
208 | break;
209 | case R.id.four_three_bt:
210 | previewSFV.setCropMode(FocusSurfaceView.CropMode.RATIO_4_3);
211 | break;
212 | case R.id.nine_sixteen_bt:
213 | previewSFV.setCropMode(FocusSurfaceView.CropMode.RATIO_9_16);
214 | break;
215 | case R.id.sixteen_nine_bt:
216 | previewSFV.setCropMode(FocusSurfaceView.CropMode.RATIO_16_9);
217 | break;
218 | case R.id.fit_image_bt:
219 | previewSFV.setCropMode(FocusSurfaceView.CropMode.FIT_IMAGE);
220 | break;
221 | case R.id.circle_bt:
222 | previewSFV.setCropMode(FocusSurfaceView.CropMode.CIRCLE);
223 | break;
224 | case R.id.free_bt:
225 | previewSFV.setCropMode(FocusSurfaceView.CropMode.FREE);
226 | break;
227 | case R.id.square_bt:
228 | previewSFV.setCropMode(FocusSurfaceView.CropMode.SQUARE);
229 | break;
230 | case R.id.circle_square_bt:
231 | previewSFV.setCropMode(FocusSurfaceView.CropMode.CIRCLE_SQUARE);
232 | break;
233 | case R.id.custom_bt:
234 | previewSFV.setCropMode(FocusSurfaceView.CropMode.CUSTOM);
235 | break;
236 | default:
237 | break;
238 | }
239 | }
240 |
241 | /**
242 | * 拍照
243 | */
244 | private void takePicture() {
245 | mCamera.autoFocus(new Camera.AutoFocusCallback() {
246 | @Override
247 | public void onAutoFocus(boolean success, Camera camera) {
248 | focus = success;
249 | if (success) {
250 | mCamera.cancelAutoFocus();
251 | mCamera.takePicture(new Camera.ShutterCallback() {
252 | @Override
253 | public void onShutter() {
254 | }
255 | }, null, null, new Camera.PictureCallback() {
256 | @Override
257 | public void onPictureTaken(byte[] data, Camera camera) {
258 | Bitmap originBitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
259 | Bitmap cropBitmap = previewSFV.getPicture(data);
260 | PictureFragment pictureFragment = new PictureFragment();
261 | Bundle bundle = new Bundle();
262 | bundle.putParcelable(ORIGIN_PICTURE, originBitmap);
263 | bundle.putParcelable(CROP_PICTURE, cropBitmap);
264 | pictureFragment.setArguments(bundle);
265 | pictureFragment.show(getFragmentManager(), null);
266 |
267 | focus = false;
268 | mCamera.startPreview();
269 | }
270 | });
271 | }
272 | }
273 | });
274 | }
275 |
276 | /**
277 | * 用来监测左横屏和右横屏切换时旋转摄像头的角度
278 | */
279 | private class DetectScreenOrientation extends OrientationEventListener {
280 | DetectScreenOrientation(Context context) {
281 | super(context);
282 | }
283 |
284 | @Override
285 | public void onOrientationChanged(int orientation) {
286 | if (260 < orientation && orientation < 290) {
287 | setCameraParams();
288 | } else if (80 < orientation && orientation < 100) {
289 | setCameraParams();
290 | }
291 | }
292 | }
293 | }
294 |
--------------------------------------------------------------------------------
/app/src/main/java/com/cymaybe/foucssurfaceview/fragment/PictureFragment.java:
--------------------------------------------------------------------------------
1 | package com.cymaybe.foucssurfaceview.fragment;
2 |
3 | import android.app.DialogFragment;
4 | import android.graphics.Bitmap;
5 | import android.os.Bundle;
6 | import android.support.annotation.Nullable;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.ImageView;
11 |
12 | import com.cymaybe.foucssurfaceview.R;
13 |
14 | /**
15 | * Created by moubiao on 2016/12/7.
16 | */
17 |
18 | public class PictureFragment extends DialogFragment {
19 | public static final String ORIGIN_PICTURE = "originPic";
20 | public static final String CROP_PICTURE = "cropPic";
21 |
22 | private Bitmap mOriginPicBitmap, mCropPicBitmap;
23 |
24 | @Override
25 | public void onCreate(Bundle savedInstanceState) {
26 | super.onCreate(savedInstanceState);
27 | Bundle data = getArguments();
28 | if (data != null) {
29 | mOriginPicBitmap = data.getParcelable(ORIGIN_PICTURE);
30 | mCropPicBitmap = data.getParcelable(CROP_PICTURE);
31 | }
32 | }
33 |
34 | @Nullable
35 | @Override
36 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
37 | return inflater.inflate(R.layout.picture_layout, container, false);
38 | }
39 |
40 | @Override
41 | public void onViewCreated(View view, Bundle savedInstanceState) {
42 | ImageView originImg = (ImageView) view.findViewById(R.id.origin_picture_img);
43 | originImg.setImageBitmap(mOriginPicBitmap);
44 | ImageView cropImg = (ImageView) view.findViewById(R.id.crop_picture_img);
45 | cropImg.setImageBitmap(mCropPicBitmap);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_take_picture_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CGmaybe10/FocusSurfaceView/26d0babfd5b4044e980adbd42c98ee2ffdd6f6ca/app/src/main/res/drawable-xxhdpi/ic_take_picture_normal.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_take_picture_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CGmaybe10/FocusSurfaceView/26d0babfd5b4044e980adbd42c98ee2ffdd6f6ca/app/src/main/res/drawable-xxhdpi/ic_take_picture_pressed.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/take_picture_button_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-land/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
29 |
30 |
37 |
38 |
39 |
40 |
41 |
42 |
49 |
50 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-land/crop_mode_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
16 |
17 |
21 |
22 |
26 |
27 |
31 |
32 |
36 |
37 |
41 |
42 |
46 |
47 |
51 |
52 |
56 |
57 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-land/picture_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
29 |
30 |
31 |
38 |
39 |
40 |
41 |
42 |
43 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/crop_mode_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
16 |
17 |
21 |
22 |
26 |
27 |
31 |
32 |
36 |
37 |
41 |
42 |
46 |
47 |
51 |
52 |
56 |
57 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/picture_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CGmaybe10/FocusSurfaceView/26d0babfd5b4044e980adbd42c98ee2ffdd6f6ca/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CGmaybe10/FocusSurfaceView/26d0babfd5b4044e980adbd42c98ee2ffdd6f6ca/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CGmaybe10/FocusSurfaceView/26d0babfd5b4044e980adbd42c98ee2ffdd6f6ca/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CGmaybe10/FocusSurfaceView/26d0babfd5b4044e980adbd42c98ee2ffdd6f6ca/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CGmaybe10/FocusSurfaceView/26d0babfd5b4044e980adbd42c98ee2ffdd6f6ca/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | FoucsSurfaceView
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/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.2.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 |
--------------------------------------------------------------------------------
/focussurfaceview/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/focussurfaceview/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.0"
6 |
7 | defaultConfig {
8 | minSdkVersion 10
9 | targetSdkVersion 25
10 | versionCode 1
11 | versionName "1.0"
12 |
13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
14 |
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | }
23 |
24 | dependencies {
25 | compile fileTree(dir: 'libs', include: ['*.jar'])
26 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
27 | exclude group: 'com.android.support', module: 'support-annotations'
28 | })
29 | compile 'com.android.support:appcompat-v7:25.0.1'
30 | testCompile 'junit:junit:4.12'
31 | }
32 |
--------------------------------------------------------------------------------
/focussurfaceview/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:\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path 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 |
--------------------------------------------------------------------------------
/focussurfaceview/src/androidTest/java/com/cymaybe/foucsurfaceview/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.cymaybe.foucsurfaceview;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.cymaybe.foucsurfaceview.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/focussurfaceview/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/focussurfaceview/src/main/java/com/cymaybe/foucsurfaceview/FocusSurfaceView.java:
--------------------------------------------------------------------------------
1 | package com.cymaybe.foucsurfaceview;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Bitmap;
6 | import android.graphics.BitmapFactory;
7 | import android.graphics.Canvas;
8 | import android.graphics.Paint;
9 | import android.graphics.Path;
10 | import android.graphics.PointF;
11 | import android.graphics.PorterDuff;
12 | import android.graphics.PorterDuffXfermode;
13 | import android.graphics.Rect;
14 | import android.graphics.RectF;
15 | import android.graphics.drawable.Drawable;
16 | import android.os.Build;
17 | import android.util.AttributeSet;
18 | import android.util.DisplayMetrics;
19 | import android.view.MotionEvent;
20 | import android.view.SurfaceView;
21 | import android.view.WindowManager;
22 | import android.view.animation.DecelerateInterpolator;
23 | import android.view.animation.Interpolator;
24 |
25 | import com.cymaybe.foucsurfaceview.animation.SimpleValueAnimator;
26 | import com.cymaybe.foucsurfaceview.animation.SimpleValueAnimatorListener;
27 | import com.cymaybe.foucsurfaceview.animation.ValueAnimatorV14;
28 | import com.cymaybe.foucsurfaceview.animation.ValueAnimatorV8;
29 |
30 | /**
31 | * Created by moubiao on 2016/11/2.
32 | * 可以指定拍摄区域的SurfaceView
33 | */
34 |
35 | public class FocusSurfaceView extends SurfaceView {
36 | private static final int HANDLE_SIZE_IN_DP = 14;
37 | private static final int MIN_FRAME_SIZE_IN_DP = 50;
38 | private static final int FRAME_STROKE_WEIGHT_IN_DP = 1;
39 | private static final int GUIDE_STROKE_WEIGHT_IN_DP = 1;
40 | private static final float DEFAULT_INITIAL_FRAME_SCALE = 0.75f;
41 | private static final int DEFAULT_ANIMATION_DURATION_MILLIS = 100;
42 |
43 | private static final int TRANSPARENT = 0x00000000;
44 | private static final int TRANSLUCENT_WHITE = 0xBBFFFFFF;
45 | private static final int WHITE = 0xFFFFFFFF;
46 | private static final int TRANSLUCENT_BLACK = 0xBB000000;
47 |
48 | private boolean mIsInitialized = false;
49 | private float mBoundaryWidth = 0;//裁剪框可移动的范围的宽
50 | private float mBoundaryHeight = 0;//裁剪框可移动的范围的高
51 | private RectF mBoundaryRect;//裁剪框的大小和可移动范围
52 | private Paint mPaintFrame;
53 | private int mCropWidth;
54 | private int mCropHeight;
55 | private RectF mFrameRect;//裁剪框的rect
56 | private Paint mPaintTranslucent;
57 |
58 | private float mLastX, mLastY;
59 | private boolean mIsRotating = false;
60 | private boolean mIsAnimating = false;
61 | private SimpleValueAnimator mAnimator = null;
62 | private final Interpolator DEFAULT_INTERPOLATOR = new DecelerateInterpolator();
63 | private Interpolator mInterpolator = DEFAULT_INTERPOLATOR;
64 |
65 | private TouchArea mTouchArea = TouchArea.OUT_OF_BOUNDS;
66 |
67 | private CropMode mCropMode = CropMode.SQUARE;
68 | private ShowMode mGuideShowMode = ShowMode.SHOW_ALWAYS;
69 | private ShowMode mHandleShowMode = ShowMode.SHOW_ALWAYS;
70 | private float mMinFrameSize;
71 | private int mHandleSize;
72 | private int mTouchPadding = 0;
73 | private boolean mShowGuide = true;
74 | private boolean mShowHandle = true;
75 | private boolean mIsCropEnabled = true;
76 | private boolean mIsEnabled = true;
77 | private boolean mIsChangeEnabled = false;
78 | private PointF mCustomRatio;
79 | private float mFrameStrokeWeight = 2.0f;
80 | private float mGuideStrokeWeight = 2.0f;
81 | private int mOverlayColor;
82 | private int mFrameColor;
83 | private int mHandleColor;
84 | private int mGuideColor;
85 | private Drawable mFrameBackground;
86 | private float mInitialFrameScale; // 0.01 ~ 1.0, 0.75 is default value
87 | private boolean mIsAnimationEnabled = true;
88 | private int mAnimationDurationMillis = DEFAULT_ANIMATION_DURATION_MILLIS;
89 | private boolean mIsHandleShadowEnabled = true;
90 |
91 | public FocusSurfaceView(Context context) {
92 | this(context, null);
93 | }
94 |
95 | public FocusSurfaceView(Context context, AttributeSet attrs) {
96 | this(context, attrs, 0);
97 | }
98 |
99 | public FocusSurfaceView(Context context, AttributeSet attrs, int defStyle) {
100 | super(context, attrs, defStyle);
101 |
102 | float density = getDensity();
103 | mHandleSize = (int) (density * HANDLE_SIZE_IN_DP);
104 | mMinFrameSize = density * MIN_FRAME_SIZE_IN_DP;
105 | mFrameStrokeWeight = density * FRAME_STROKE_WEIGHT_IN_DP;
106 | mGuideStrokeWeight = density * GUIDE_STROKE_WEIGHT_IN_DP;
107 |
108 | mPaintFrame = new Paint();
109 | mPaintTranslucent = new Paint();
110 |
111 | mFrameColor = WHITE;
112 | mOverlayColor = TRANSLUCENT_BLACK;
113 | mHandleColor = WHITE;
114 | mGuideColor = TRANSLUCENT_WHITE;
115 |
116 | handleStyleable(context, attrs, defStyle, density);
117 | }
118 |
119 | private void handleStyleable(Context context, AttributeSet attrs, int defStyle, float mDensity) {
120 | TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.FocusSurfaceView, defStyle, 0);
121 | mCropMode = CropMode.SQUARE;
122 | try {
123 | for (CropMode mode : CropMode.values()) {
124 | if (ta.getInt(R.styleable.FocusSurfaceView_focus_mode, 3) == mode.getId()) {
125 | mCropMode = mode;
126 | break;
127 | }
128 | }
129 | float customRatioX = ta.getFloat(R.styleable.FocusSurfaceView_focus_frame_ratio_x, 1.0f);
130 | float customRatioY = ta.getFloat(R.styleable.FocusSurfaceView_focus_frame_ratio_y, 1.0f);
131 | mCustomRatio = new PointF(customRatioX, customRatioY);
132 | mOverlayColor = ta.getColor(R.styleable.FocusSurfaceView_focus_overlay_color, TRANSLUCENT_BLACK);
133 | mFrameColor = ta.getColor(R.styleable.FocusSurfaceView_focus_frame_color, WHITE);
134 | mHandleColor = ta.getColor(R.styleable.FocusSurfaceView_focus_handle_color, WHITE);
135 | mGuideColor = ta.getColor(R.styleable.FocusSurfaceView_focus_guide_color, TRANSLUCENT_WHITE);
136 | for (ShowMode mode : ShowMode.values()) {
137 | if (ta.getInt(R.styleable.FocusSurfaceView_focus_guide_show_mode, 1) == mode.getId()) {
138 | mGuideShowMode = mode;
139 | break;
140 | }
141 | }
142 |
143 | for (ShowMode mode : ShowMode.values()) {
144 | if (ta.getInt(R.styleable.FocusSurfaceView_focus_handle_show_mode, 1) == mode.getId()) {
145 | mHandleShowMode = mode;
146 | break;
147 | }
148 | }
149 | setGuideShowMode(mGuideShowMode);
150 | setHandleShowMode(mHandleShowMode);
151 | mHandleSize = ta.getDimensionPixelSize(R.styleable.FocusSurfaceView_focus_handle_size, (int) (HANDLE_SIZE_IN_DP * mDensity));
152 | mTouchPadding = ta.getDimensionPixelSize(R.styleable.FocusSurfaceView_focus_touch_padding, 0);
153 |
154 | mCropWidth = ta.getDimensionPixelSize(R.styleable.FocusSurfaceView_focus_crop_width, dip2px(getContext(), 200f));
155 | mCropHeight = ta.getDimensionPixelSize(R.styleable.FocusSurfaceView_focus_crop_height, dip2px(getContext(), 200f));
156 |
157 | mMinFrameSize = ta.getDimensionPixelSize(R.styleable.FocusSurfaceView_focus_min_frame_size, (int) (MIN_FRAME_SIZE_IN_DP * mDensity));
158 | mFrameStrokeWeight = ta.getDimensionPixelSize(R.styleable.FocusSurfaceView_focus_frame_stroke_weight, (int) (FRAME_STROKE_WEIGHT_IN_DP * mDensity));
159 | mGuideStrokeWeight = ta.getDimensionPixelSize(R.styleable.FocusSurfaceView_focus_guide_stroke_weight, (int) (GUIDE_STROKE_WEIGHT_IN_DP * mDensity));
160 | mIsCropEnabled = ta.getBoolean(R.styleable.FocusSurfaceView_focus_crop_enabled, true);
161 | mInitialFrameScale = constrain(ta.getFloat(R.styleable.FocusSurfaceView_focus_initial_frame_scale, DEFAULT_INITIAL_FRAME_SCALE),
162 | 0.01f, 1.0f, DEFAULT_INITIAL_FRAME_SCALE);
163 | mIsAnimationEnabled = ta.getBoolean(R.styleable.FocusSurfaceView_focus_animation_enabled, true);
164 | mAnimationDurationMillis = ta.getInt(R.styleable.FocusSurfaceView_focus_animation_duration, DEFAULT_ANIMATION_DURATION_MILLIS);
165 | mIsHandleShadowEnabled = ta.getBoolean(R.styleable.FocusSurfaceView_focus_handle_shadow_enabled, true);
166 | mIsChangeEnabled = ta.getBoolean(R.styleable.FocusSurfaceView_focus_frame_can_change, false);
167 | mFrameBackground = ta.getDrawable(R.styleable.FocusSurfaceView_focus_frame_background);
168 | } catch (Exception e) {
169 | e.printStackTrace();
170 | } finally {
171 | ta.recycle();
172 | }
173 | }
174 |
175 | @Override
176 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
177 | final int viewWidth = MeasureSpec.getSize(widthMeasureSpec);
178 | final int viewHeight = MeasureSpec.getSize(heightMeasureSpec);
179 |
180 | setMeasuredDimension(viewWidth, viewHeight);
181 |
182 | mBoundaryWidth = viewWidth - getPaddingLeft() - getPaddingRight();
183 | mBoundaryHeight = viewHeight - getPaddingTop() - getPaddingBottom();
184 | }
185 |
186 | @Override
187 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
188 | setupLayout();
189 | }
190 |
191 | /**
192 | * 计算裁剪框的位置和活动区域
193 | */
194 | private void setupLayout() {
195 | if (mBoundaryWidth == 0 || mBoundaryHeight == 0) {
196 | return;
197 | }
198 | mBoundaryRect = new RectF(0f, 0f, mBoundaryWidth, mBoundaryHeight);
199 | float left = (mBoundaryWidth - mCropWidth) / 2;
200 | float top = (mBoundaryHeight - mCropHeight) / 2;
201 | float right = left + mCropWidth;
202 | float bottom = top + mCropHeight;
203 | if (mFrameRect == null) {
204 | mFrameRect = new RectF(left, top, right, bottom);
205 | mIsInitialized = true;
206 | }
207 | }
208 |
209 | @Override
210 | public void onDraw(Canvas canvas) {
211 | if (mIsInitialized) {
212 | if (mFrameBackground != null) {
213 | canvas.save();
214 | canvas.translate(getWidth() / 2, getHeight() / 2);
215 | mFrameBackground.setBounds(-mCropWidth / 2, -mCropHeight / 2, mCropWidth / 2, mCropHeight / 2);
216 | mFrameBackground.draw(canvas);
217 | canvas.restore();
218 | }
219 | drawCropFrame(canvas);
220 | }
221 | }
222 |
223 | /**
224 | * 画裁剪框
225 | *
226 | * @param canvas
227 | */
228 | private void drawCropFrame(Canvas canvas) {
229 | if (!mIsCropEnabled) return;
230 | if (mIsRotating) return;
231 | drawOverlay(canvas);
232 | drawFrame(canvas);
233 | if (mShowGuide) drawGuidelines(canvas);
234 | if (mShowHandle) drawHandles(canvas);
235 | }
236 |
237 | /**
238 | * 画灰色的背景
239 | *
240 | * @param canvas
241 | */
242 | private void drawOverlay(Canvas canvas) {
243 | mPaintTranslucent.setAntiAlias(true);
244 | mPaintTranslucent.setFilterBitmap(true);
245 | mPaintTranslucent.setColor(mOverlayColor);
246 | mPaintTranslucent.setStyle(Paint.Style.FILL);
247 | Path path = new Path();
248 | if (!mIsAnimating
249 | && (mCropMode == CropMode.CIRCLE || mCropMode == CropMode.CIRCLE_SQUARE)) {
250 | path.addRect(mBoundaryRect, Path.Direction.CW);
251 | PointF circleCenter = new PointF((mFrameRect.left + mFrameRect.right) / 2,
252 | (mFrameRect.top + mFrameRect.bottom) / 2);
253 | float circleRadius = (mFrameRect.right - mFrameRect.left) / 2;
254 | path.addCircle(circleCenter.x, circleCenter.y, circleRadius, Path.Direction.CCW);
255 | canvas.drawPath(path, mPaintTranslucent);
256 | } else {
257 | path.addRect(mBoundaryRect, Path.Direction.CW);
258 | path.addRect(mFrameRect, Path.Direction.CCW);
259 | canvas.drawPath(path, mPaintTranslucent);
260 | }
261 | }
262 |
263 | /**
264 | * 画裁剪框的的四条边
265 | *
266 | * @param canvas
267 | */
268 | private void drawFrame(Canvas canvas) {
269 | mPaintFrame.setAntiAlias(true);
270 | mPaintFrame.setFilterBitmap(true);
271 | mPaintFrame.setStyle(Paint.Style.STROKE);
272 | mPaintFrame.setColor(mFrameColor);
273 | mPaintFrame.setStrokeWidth(mFrameStrokeWeight);
274 | canvas.drawRect(mFrameRect, mPaintFrame);
275 | }
276 |
277 | /**
278 | * 画裁剪框里的横竖线
279 | */
280 | private void drawGuidelines(Canvas canvas) {
281 | mPaintFrame.setColor(mGuideColor);
282 | mPaintFrame.setStrokeWidth(mGuideStrokeWeight);
283 | float h1 = mFrameRect.left + (mFrameRect.right - mFrameRect.left) / 3.0f;
284 | float h2 = mFrameRect.right - (mFrameRect.right - mFrameRect.left) / 3.0f;
285 | float v1 = mFrameRect.top + (mFrameRect.bottom - mFrameRect.top) / 3.0f;
286 | float v2 = mFrameRect.bottom - (mFrameRect.bottom - mFrameRect.top) / 3.0f;
287 | canvas.drawLine(h1, mFrameRect.top, h1, mFrameRect.bottom, mPaintFrame);
288 | canvas.drawLine(h2, mFrameRect.top, h2, mFrameRect.bottom, mPaintFrame);
289 | canvas.drawLine(mFrameRect.left, v1, mFrameRect.right, v1, mPaintFrame);
290 | canvas.drawLine(mFrameRect.left, v2, mFrameRect.right, v2, mPaintFrame);
291 | }
292 |
293 | /**
294 | * 画裁剪框四个角上的圆点
295 | */
296 | private void drawHandles(Canvas canvas) {
297 | if (mIsHandleShadowEnabled) drawHandleShadows(canvas);
298 | mPaintFrame.setStyle(Paint.Style.FILL);
299 | mPaintFrame.setColor(mHandleColor);
300 | canvas.drawCircle(mFrameRect.left, mFrameRect.top, mHandleSize, mPaintFrame);
301 | canvas.drawCircle(mFrameRect.right, mFrameRect.top, mHandleSize, mPaintFrame);
302 | canvas.drawCircle(mFrameRect.left, mFrameRect.bottom, mHandleSize, mPaintFrame);
303 | canvas.drawCircle(mFrameRect.right, mFrameRect.bottom, mHandleSize, mPaintFrame);
304 | }
305 |
306 | /**
307 | * 画裁剪框四个角上的圆点的阴影
308 | */
309 | private void drawHandleShadows(Canvas canvas) {
310 | mPaintFrame.setStyle(Paint.Style.FILL);
311 | mPaintFrame.setColor(TRANSLUCENT_BLACK);
312 | RectF rect = new RectF(mFrameRect);
313 | rect.offset(0, 1);
314 | canvas.drawCircle(rect.left, rect.top, mHandleSize, mPaintFrame);
315 | canvas.drawCircle(rect.right, rect.top, mHandleSize, mPaintFrame);
316 | canvas.drawCircle(rect.left, rect.bottom, mHandleSize, mPaintFrame);
317 | canvas.drawCircle(rect.right, rect.bottom, mHandleSize, mPaintFrame);
318 | }
319 |
320 | /**
321 | * 计算裁剪框的区域
322 | *
323 | * @param imageRect 裁剪框的可活动范围
324 | */
325 | private RectF calcFrameRect(RectF imageRect) {
326 | float frameW = getRatioX(imageRect.width());
327 | float frameH = getRatioY(imageRect.height());
328 | float imgRatio = imageRect.width() / imageRect.height();
329 | float frameRatio = frameW / frameH;
330 | float l = imageRect.left, t = imageRect.top, r = imageRect.right, b = imageRect.bottom;
331 | if (frameRatio >= imgRatio) {
332 | l = imageRect.left;
333 | r = imageRect.right;
334 | float hy = (imageRect.top + imageRect.bottom) * 0.5f;
335 | float hh = (imageRect.width() / frameRatio) * 0.5f;
336 | t = hy - hh;
337 | b = hy + hh;
338 | } else if (frameRatio < imgRatio) {
339 | t = imageRect.top;
340 | b = imageRect.bottom;
341 | float hx = (imageRect.left + imageRect.right) * 0.5f;
342 | float hw = imageRect.height() * frameRatio * 0.5f;
343 | l = hx - hw;
344 | r = hx + hw;
345 | }
346 | float w = r - l;
347 | float h = b - t;
348 | float cx = l + w / 2;
349 | float cy = t + h / 2;
350 | float sw = w * mInitialFrameScale;
351 | float sh = h * mInitialFrameScale;
352 | return new RectF(cx - sw / 2, cy - sh / 2, cx + sw / 2, cy + sh / 2);
353 | }
354 |
355 | @Override
356 | public boolean onTouchEvent(MotionEvent event) {
357 | if (!mIsChangeEnabled) return false;
358 | if (!mIsInitialized) return false;
359 | if (!mIsCropEnabled) return false;
360 | if (!mIsEnabled) return false;
361 | if (mIsRotating) return false;
362 | if (mIsAnimating) return false;
363 | switch (event.getAction()) {
364 | case MotionEvent.ACTION_DOWN:
365 | onDown(event);
366 | return true;
367 | case MotionEvent.ACTION_MOVE:
368 | onMove(event);
369 | if (mTouchArea != TouchArea.OUT_OF_BOUNDS) {
370 | getParent().requestDisallowInterceptTouchEvent(true);
371 | }
372 | return true;
373 | case MotionEvent.ACTION_CANCEL:
374 | getParent().requestDisallowInterceptTouchEvent(false);
375 | onCancel();
376 | return true;
377 | case MotionEvent.ACTION_UP:
378 | getParent().requestDisallowInterceptTouchEvent(false);
379 | onUp(event);
380 | return true;
381 | }
382 | return false;
383 | }
384 |
385 |
386 | private void onDown(MotionEvent e) {
387 | invalidate();
388 | mLastX = e.getX();
389 | mLastY = e.getY();
390 | checkTouchArea(e.getX(), e.getY());
391 | }
392 |
393 | private void onMove(MotionEvent e) {
394 | float diffX = e.getX() - mLastX;
395 | float diffY = e.getY() - mLastY;
396 | switch (mTouchArea) {
397 | case CENTER:
398 | moveFrame(diffX, diffY);
399 | break;
400 | case LEFT_TOP:
401 | moveHandleLT(diffX, diffY);
402 | break;
403 | case RIGHT_TOP:
404 | moveHandleRT(diffX, diffY);
405 | break;
406 | case LEFT_BOTTOM:
407 | moveHandleLB(diffX, diffY);
408 | break;
409 | case RIGHT_BOTTOM:
410 | moveHandleRB(diffX, diffY);
411 | break;
412 | case OUT_OF_BOUNDS:
413 | break;
414 | }
415 | invalidate();
416 | mLastX = e.getX();
417 | mLastY = e.getY();
418 | }
419 |
420 | private void onUp(MotionEvent e) {
421 | if (mGuideShowMode == ShowMode.SHOW_ON_TOUCH) mShowGuide = false;
422 | if (mHandleShowMode == ShowMode.SHOW_ON_TOUCH) mShowHandle = false;
423 | mTouchArea = TouchArea.OUT_OF_BOUNDS;
424 | invalidate();
425 | }
426 |
427 | private void onCancel() {
428 | mTouchArea = TouchArea.OUT_OF_BOUNDS;
429 | invalidate();
430 | }
431 |
432 | /**
433 | * 检查手指触摸的区域
434 | *
435 | * @param x X坐标
436 | * @param y Y坐标
437 | */
438 | private void checkTouchArea(float x, float y) {
439 | if (isInsideCornerLeftTop(x, y)) {
440 | mTouchArea = TouchArea.LEFT_TOP;
441 | if (mHandleShowMode == ShowMode.SHOW_ON_TOUCH) mShowHandle = true;
442 | if (mGuideShowMode == ShowMode.SHOW_ON_TOUCH) mShowGuide = true;
443 | return;
444 | }
445 | if (isInsideCornerRightTop(x, y)) {
446 | mTouchArea = TouchArea.RIGHT_TOP;
447 | if (mHandleShowMode == ShowMode.SHOW_ON_TOUCH) mShowHandle = true;
448 | if (mGuideShowMode == ShowMode.SHOW_ON_TOUCH) mShowGuide = true;
449 | return;
450 | }
451 | if (isInsideCornerLeftBottom(x, y)) {
452 | mTouchArea = TouchArea.LEFT_BOTTOM;
453 | if (mHandleShowMode == ShowMode.SHOW_ON_TOUCH) mShowHandle = true;
454 | if (mGuideShowMode == ShowMode.SHOW_ON_TOUCH) mShowGuide = true;
455 | return;
456 | }
457 | if (isInsideCornerRightBottom(x, y)) {
458 | mTouchArea = TouchArea.RIGHT_BOTTOM;
459 | if (mHandleShowMode == ShowMode.SHOW_ON_TOUCH) mShowHandle = true;
460 | if (mGuideShowMode == ShowMode.SHOW_ON_TOUCH) mShowGuide = true;
461 | return;
462 | }
463 | if (isInsideFrame(x, y)) {
464 | if (mGuideShowMode == ShowMode.SHOW_ON_TOUCH) mShowGuide = true;
465 | mTouchArea = TouchArea.CENTER;
466 | return;
467 | }
468 | mTouchArea = TouchArea.OUT_OF_BOUNDS;
469 | }
470 |
471 | private boolean isInsideFrame(float x, float y) {
472 | if (mFrameRect.left <= x && mFrameRect.right >= x) {
473 | if (mFrameRect.top <= y && mFrameRect.bottom >= y) {
474 | mTouchArea = TouchArea.CENTER;
475 | return true;
476 | }
477 | }
478 | return false;
479 | }
480 |
481 | private boolean isInsideCornerLeftTop(float x, float y) {
482 | float dx = x - mFrameRect.left;
483 | float dy = y - mFrameRect.top;
484 | float d = dx * dx + dy * dy;
485 | return sq(mHandleSize + mTouchPadding) >= d;
486 | }
487 |
488 | private boolean isInsideCornerRightTop(float x, float y) {
489 | float dx = x - mFrameRect.right;
490 | float dy = y - mFrameRect.top;
491 | float d = dx * dx + dy * dy;
492 | return sq(mHandleSize + mTouchPadding) >= d;
493 | }
494 |
495 | private boolean isInsideCornerLeftBottom(float x, float y) {
496 | float dx = x - mFrameRect.left;
497 | float dy = y - mFrameRect.bottom;
498 | float d = dx * dx + dy * dy;
499 | return sq(mHandleSize + mTouchPadding) >= d;
500 | }
501 |
502 | private boolean isInsideCornerRightBottom(float x, float y) {
503 | float dx = x - mFrameRect.right;
504 | float dy = y - mFrameRect.bottom;
505 | float d = dx * dx + dy * dy;
506 | return sq(mHandleSize + mTouchPadding) >= d;
507 | }
508 |
509 | private void moveFrame(float x, float y) {
510 | mFrameRect.left += x;
511 | mFrameRect.right += x;
512 | mFrameRect.top += y;
513 | mFrameRect.bottom += y;
514 | checkMoveBounds();
515 | }
516 |
517 | @SuppressWarnings("UnnecessaryLocalVariable")
518 | private void moveHandleLT(float diffX, float diffY) {
519 | if (mCropMode == CropMode.FREE) {
520 | mFrameRect.left += diffX;
521 | mFrameRect.top += diffY;
522 | if (isWidthTooSmall()) {
523 | float offsetX = mMinFrameSize - getFrameWidth();
524 | mFrameRect.left -= offsetX;
525 | }
526 | if (isHeightTooSmall()) {
527 | float offsetY = mMinFrameSize - getFrameHeight();
528 | mFrameRect.top -= offsetY;
529 | }
530 | checkScaleBounds();
531 | } else {
532 | float dx = diffX;
533 | float dy = diffX * getRatioY() / getRatioX();
534 | mFrameRect.left += dx;
535 | mFrameRect.top += dy;
536 | if (isWidthTooSmall()) {
537 | float offsetX = mMinFrameSize - getFrameWidth();
538 | mFrameRect.left -= offsetX;
539 | float offsetY = offsetX * getRatioY() / getRatioX();
540 | mFrameRect.top -= offsetY;
541 | }
542 | if (isHeightTooSmall()) {
543 | float offsetY = mMinFrameSize - getFrameHeight();
544 | mFrameRect.top -= offsetY;
545 | float offsetX = offsetY * getRatioX() / getRatioY();
546 | mFrameRect.left -= offsetX;
547 | }
548 | float ox, oy;
549 | if (!isInsideHorizontal(mFrameRect.left)) {
550 | ox = mBoundaryRect.left - mFrameRect.left;
551 | mFrameRect.left += ox;
552 | oy = ox * getRatioY() / getRatioX();
553 | mFrameRect.top += oy;
554 | }
555 | if (!isInsideVertical(mFrameRect.top)) {
556 | oy = mBoundaryRect.top - mFrameRect.top;
557 | mFrameRect.top += oy;
558 | ox = oy * getRatioX() / getRatioY();
559 | mFrameRect.left += ox;
560 | }
561 | }
562 | }
563 |
564 | @SuppressWarnings("UnnecessaryLocalVariable")
565 | private void moveHandleRT(float diffX, float diffY) {
566 | if (mCropMode == CropMode.FREE) {
567 | mFrameRect.right += diffX;
568 | mFrameRect.top += diffY;
569 | if (isWidthTooSmall()) {
570 | float offsetX = mMinFrameSize - getFrameWidth();
571 | mFrameRect.right += offsetX;
572 | }
573 | if (isHeightTooSmall()) {
574 | float offsetY = mMinFrameSize - getFrameHeight();
575 | mFrameRect.top -= offsetY;
576 | }
577 | checkScaleBounds();
578 | } else {
579 | float dx = diffX;
580 | float dy = diffX * getRatioY() / getRatioX();
581 | mFrameRect.right += dx;
582 | mFrameRect.top -= dy;
583 | if (isWidthTooSmall()) {
584 | float offsetX = mMinFrameSize - getFrameWidth();
585 | mFrameRect.right += offsetX;
586 | float offsetY = offsetX * getRatioY() / getRatioX();
587 | mFrameRect.top -= offsetY;
588 | }
589 | if (isHeightTooSmall()) {
590 | float offsetY = mMinFrameSize - getFrameHeight();
591 | mFrameRect.top -= offsetY;
592 | float offsetX = offsetY * getRatioX() / getRatioY();
593 | mFrameRect.right += offsetX;
594 | }
595 | float ox, oy;
596 | if (!isInsideHorizontal(mFrameRect.right)) {
597 | ox = mFrameRect.right - mBoundaryRect.right;
598 | mFrameRect.right -= ox;
599 | oy = ox * getRatioY() / getRatioX();
600 | mFrameRect.top += oy;
601 | }
602 | if (!isInsideVertical(mFrameRect.top)) {
603 | oy = mBoundaryRect.top - mFrameRect.top;
604 | mFrameRect.top += oy;
605 | ox = oy * getRatioX() / getRatioY();
606 | mFrameRect.right -= ox;
607 | }
608 | }
609 | }
610 |
611 | @SuppressWarnings("UnnecessaryLocalVariable")
612 | private void moveHandleLB(float diffX, float diffY) {
613 | if (mCropMode == CropMode.FREE) {
614 | mFrameRect.left += diffX;
615 | mFrameRect.bottom += diffY;
616 | if (isWidthTooSmall()) {
617 | float offsetX = mMinFrameSize - getFrameWidth();
618 | mFrameRect.left -= offsetX;
619 | }
620 | if (isHeightTooSmall()) {
621 | float offsetY = mMinFrameSize - getFrameHeight();
622 | mFrameRect.bottom += offsetY;
623 | }
624 | checkScaleBounds();
625 | } else {
626 | float dx = diffX;
627 | float dy = diffX * getRatioY() / getRatioX();
628 | mFrameRect.left += dx;
629 | mFrameRect.bottom -= dy;
630 | if (isWidthTooSmall()) {
631 | float offsetX = mMinFrameSize - getFrameWidth();
632 | mFrameRect.left -= offsetX;
633 | float offsetY = offsetX * getRatioY() / getRatioX();
634 | mFrameRect.bottom += offsetY;
635 | }
636 | if (isHeightTooSmall()) {
637 | float offsetY = mMinFrameSize - getFrameHeight();
638 | mFrameRect.bottom += offsetY;
639 | float offsetX = offsetY * getRatioX() / getRatioY();
640 | mFrameRect.left -= offsetX;
641 | }
642 | float ox, oy;
643 | if (!isInsideHorizontal(mFrameRect.left)) {
644 | ox = mBoundaryRect.left - mFrameRect.left;
645 | mFrameRect.left += ox;
646 | oy = ox * getRatioY() / getRatioX();
647 | mFrameRect.bottom -= oy;
648 | }
649 | if (!isInsideVertical(mFrameRect.bottom)) {
650 | oy = mFrameRect.bottom - mBoundaryRect.bottom;
651 | mFrameRect.bottom -= oy;
652 | ox = oy * getRatioX() / getRatioY();
653 | mFrameRect.left += ox;
654 | }
655 | }
656 | }
657 |
658 | @SuppressWarnings("UnnecessaryLocalVariable")
659 | private void moveHandleRB(float diffX, float diffY) {
660 | if (mCropMode == CropMode.FREE) {
661 | mFrameRect.right += diffX;
662 | mFrameRect.bottom += diffY;
663 | if (isWidthTooSmall()) {
664 | float offsetX = mMinFrameSize - getFrameWidth();
665 | mFrameRect.right += offsetX;
666 | }
667 | if (isHeightTooSmall()) {
668 | float offsetY = mMinFrameSize - getFrameHeight();
669 | mFrameRect.bottom += offsetY;
670 | }
671 | checkScaleBounds();
672 | } else {
673 | float dx = diffX;
674 | float dy = diffX * getRatioY() / getRatioX();
675 | mFrameRect.right += dx;
676 | mFrameRect.bottom += dy;
677 | if (isWidthTooSmall()) {
678 | float offsetX = mMinFrameSize - getFrameWidth();
679 | mFrameRect.right += offsetX;
680 | float offsetY = offsetX * getRatioY() / getRatioX();
681 | mFrameRect.bottom += offsetY;
682 | }
683 | if (isHeightTooSmall()) {
684 | float offsetY = mMinFrameSize - getFrameHeight();
685 | mFrameRect.bottom += offsetY;
686 | float offsetX = offsetY * getRatioX() / getRatioY();
687 | mFrameRect.right += offsetX;
688 | }
689 | float ox, oy;
690 | if (!isInsideHorizontal(mFrameRect.right)) {
691 | ox = mFrameRect.right - mBoundaryRect.right;
692 | mFrameRect.right -= ox;
693 | oy = ox * getRatioY() / getRatioX();
694 | mFrameRect.bottom -= oy;
695 | }
696 | if (!isInsideVertical(mFrameRect.bottom)) {
697 | oy = mFrameRect.bottom - mBoundaryRect.bottom;
698 | mFrameRect.bottom -= oy;
699 | ox = oy * getRatioX() / getRatioY();
700 | mFrameRect.right -= ox;
701 | }
702 | }
703 | }
704 |
705 | private void checkScaleBounds() {
706 | float lDiff = mFrameRect.left - mBoundaryRect.left;
707 | float rDiff = mFrameRect.right - mBoundaryRect.right;
708 | float tDiff = mFrameRect.top - mBoundaryRect.top;
709 | float bDiff = mFrameRect.bottom - mBoundaryRect.bottom;
710 |
711 | if (lDiff < 0) {
712 | mFrameRect.left -= lDiff;
713 | }
714 | if (rDiff > 0) {
715 | mFrameRect.right -= rDiff;
716 | }
717 | if (tDiff < 0) {
718 | mFrameRect.top -= tDiff;
719 | }
720 | if (bDiff > 0) {
721 | mFrameRect.bottom -= bDiff;
722 | }
723 | }
724 |
725 | private void checkMoveBounds() {
726 | float diff = mFrameRect.left - mBoundaryRect.left;
727 | if (diff < 0) {
728 | mFrameRect.left -= diff;
729 | mFrameRect.right -= diff;
730 | }
731 | diff = mFrameRect.right - mBoundaryRect.right;
732 | if (diff > 0) {
733 | mFrameRect.left -= diff;
734 | mFrameRect.right -= diff;
735 | }
736 | diff = mFrameRect.top - mBoundaryRect.top;
737 | if (diff < 0) {
738 | mFrameRect.top -= diff;
739 | mFrameRect.bottom -= diff;
740 | }
741 | diff = mFrameRect.bottom - mBoundaryRect.bottom;
742 | if (diff > 0) {
743 | mFrameRect.top -= diff;
744 | mFrameRect.bottom -= diff;
745 | }
746 | }
747 |
748 | private boolean isInsideHorizontal(float x) {
749 | return mBoundaryRect.left <= x && mBoundaryRect.right >= x;
750 | }
751 |
752 | private boolean isInsideVertical(float y) {
753 | return mBoundaryRect.top <= y && mBoundaryRect.bottom >= y;
754 | }
755 |
756 | private boolean isWidthTooSmall() {
757 | return getFrameWidth() < mMinFrameSize;
758 | }
759 |
760 | private boolean isHeightTooSmall() {
761 | return getFrameHeight() < mMinFrameSize;
762 | }
763 |
764 | private void recalculateFrameRect(int durationMillis) {
765 | if (mBoundaryRect == null) return;
766 | if (mIsAnimating) {
767 | getAnimator().cancelAnimation();
768 | }
769 | final RectF currentRect = new RectF(mFrameRect);
770 | final RectF newRect = calcFrameRect(mBoundaryRect);
771 | final float diffL = newRect.left - currentRect.left;
772 | final float diffT = newRect.top - currentRect.top;
773 | final float diffR = newRect.right - currentRect.right;
774 | final float diffB = newRect.bottom - currentRect.bottom;
775 | if (mIsAnimationEnabled) {
776 | SimpleValueAnimator animator = getAnimator();
777 | animator.addAnimatorListener(new SimpleValueAnimatorListener() {
778 | @Override
779 | public void onAnimationStarted() {
780 | mIsAnimating = true;
781 | }
782 |
783 | @Override
784 | public void onAnimationUpdated(float scale) {
785 | mFrameRect = new RectF(currentRect.left + diffL * scale,
786 | currentRect.top + diffT * scale,
787 | currentRect.right + diffR * scale,
788 | currentRect.bottom + diffB * scale);
789 | invalidate();
790 | }
791 |
792 | @Override
793 | public void onAnimationFinished() {
794 | mFrameRect = newRect;
795 | invalidate();
796 | mIsAnimating = false;
797 | }
798 | });
799 | animator.startAnimation(durationMillis);
800 | } else {
801 | mFrameRect = calcFrameRect(mBoundaryRect);
802 | invalidate();
803 | }
804 | }
805 |
806 | private float getRatioX(float w) {
807 | switch (mCropMode) {
808 | case FIT_IMAGE:
809 | return mBoundaryRect.width();
810 | case FREE:
811 | return w;
812 | case RATIO_4_3:
813 | return 4;
814 | case RATIO_3_4:
815 | return 3;
816 | case RATIO_16_9:
817 | return 16;
818 | case RATIO_9_16:
819 | return 9;
820 | case SQUARE:
821 | case CIRCLE:
822 | case CIRCLE_SQUARE:
823 | return 1;
824 | case CUSTOM:
825 | return mCustomRatio.x;
826 | default:
827 | return w;
828 | }
829 | }
830 |
831 | private float getRatioY(float h) {
832 | switch (mCropMode) {
833 | case FIT_IMAGE:
834 | return mBoundaryRect.height();
835 | case FREE:
836 | return h;
837 | case RATIO_4_3:
838 | return 3;
839 | case RATIO_3_4:
840 | return 4;
841 | case RATIO_16_9:
842 | return 9;
843 | case RATIO_9_16:
844 | return 16;
845 | case SQUARE:
846 | case CIRCLE:
847 | case CIRCLE_SQUARE:
848 | return 1;
849 | case CUSTOM:
850 | return mCustomRatio.y;
851 | default:
852 | return h;
853 | }
854 | }
855 |
856 | private float getRatioX() {
857 | switch (mCropMode) {
858 | case FIT_IMAGE:
859 | return mBoundaryRect.width();
860 | case RATIO_4_3:
861 | return 4;
862 | case RATIO_3_4:
863 | return 3;
864 | case RATIO_16_9:
865 | return 16;
866 | case RATIO_9_16:
867 | return 9;
868 | case SQUARE:
869 | case CIRCLE:
870 | case CIRCLE_SQUARE:
871 | return 1;
872 | case CUSTOM:
873 | return mCustomRatio.x;
874 | default:
875 | return 1;
876 | }
877 | }
878 |
879 | private float getRatioY() {
880 | switch (mCropMode) {
881 | case FIT_IMAGE:
882 | return mBoundaryRect.height();
883 | case RATIO_4_3:
884 | return 3;
885 | case RATIO_3_4:
886 | return 4;
887 | case RATIO_16_9:
888 | return 9;
889 | case RATIO_9_16:
890 | return 16;
891 | case SQUARE:
892 | case CIRCLE:
893 | case CIRCLE_SQUARE:
894 | return 1;
895 | case CUSTOM:
896 | return mCustomRatio.y;
897 | default:
898 | return 1;
899 | }
900 | }
901 |
902 | private float getDensity() {
903 | DisplayMetrics displayMetrics = new DisplayMetrics();
904 | ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()
905 | .getMetrics(displayMetrics);
906 | return displayMetrics.density;
907 | }
908 |
909 | private float sq(float value) {
910 | return value * value;
911 | }
912 |
913 | private float constrain(float val, float min, float max, float defaultVal) {
914 | if (val < min || val > max) return defaultVal;
915 | return val;
916 | }
917 |
918 | private SimpleValueAnimator getAnimator() {
919 | setupAnimatorIfNeeded();
920 | return mAnimator;
921 | }
922 |
923 | private void setupAnimatorIfNeeded() {
924 | if (mAnimator == null) {
925 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
926 | mAnimator = new ValueAnimatorV8(mInterpolator);
927 | } else {
928 | mAnimator = new ValueAnimatorV14(mInterpolator);
929 | }
930 | }
931 | }
932 |
933 | /**
934 | * Set crop mode
935 | *
936 | * @param mode crop mode
937 | * @param durationMillis animation duration in milliseconds
938 | */
939 | public void setCropMode(CropMode mode, int durationMillis) {
940 | if (mode == CropMode.CUSTOM) {
941 | setCustomRatio(1, 1);
942 | } else {
943 | mCropMode = mode;
944 | recalculateFrameRect(durationMillis);
945 | }
946 | }
947 |
948 | /**
949 | * 设置裁剪模式
950 | */
951 | public void setCropMode(CropMode mode) {
952 | setCropMode(mode, mAnimationDurationMillis);
953 | }
954 |
955 | /**
956 | * Set custom aspect ratio to crop frame
957 | *
958 | * @param ratioX ratio x
959 | * @param ratioY ratio y
960 | * @param durationMillis animation duration in milliseconds
961 | */
962 | public void setCustomRatio(int ratioX, int ratioY, int durationMillis) {
963 | if (ratioX == 0 || ratioY == 0) return;
964 | mCropMode = CropMode.CUSTOM;
965 | mCustomRatio.set(ratioX, ratioY);
966 | recalculateFrameRect(durationMillis);
967 | }
968 |
969 | /**
970 | * 设置裁剪框的长宽比
971 | *
972 | * @param ratioX ratio x
973 | * @param ratioY ratio y
974 | */
975 | public void setCustomRatio(int ratioX, int ratioY) {
976 | setCustomRatio(ratioX, ratioY, mAnimationDurationMillis);
977 | }
978 |
979 | /**
980 | * 设置裁剪框以外的颜色
981 | *
982 | * @param overlayColor color resId or color int(ex. 0xFFFFFFFF)
983 | */
984 | public void setOverlayColor(int overlayColor) {
985 | this.mOverlayColor = overlayColor;
986 | invalidate();
987 | }
988 |
989 | /**
990 | * 设置裁剪框的颜色
991 | */
992 | public void setFrameColor(int frameColor) {
993 | this.mFrameColor = frameColor;
994 | invalidate();
995 | }
996 |
997 | /**
998 | * 裁剪框四个点的颜色
999 | */
1000 | public void setHandleColor(int handleColor) {
1001 | this.mHandleColor = handleColor;
1002 | invalidate();
1003 | }
1004 |
1005 | /**
1006 | * 裁剪框横竖线的颜色
1007 | */
1008 | public void setGuideColor(int guideColor) {
1009 | this.mGuideColor = guideColor;
1010 | invalidate();
1011 | }
1012 |
1013 | /**
1014 | * 裁剪框的最小宽度,单位dp
1015 | *
1016 | * @param minDp crop frame minimum size in density-independent pixels
1017 | */
1018 | public void setMinFrameSizeInDp(int minDp) {
1019 | mMinFrameSize = minDp * getDensity();
1020 | }
1021 |
1022 | /**
1023 | * 裁剪框的最小宽度,单位px
1024 | *
1025 | * @param minPx crop frame minimum size in pixels
1026 | */
1027 | public void setMinFrameSizeInPx(int minPx) {
1028 | mMinFrameSize = minPx;
1029 | }
1030 |
1031 | /**
1032 | * 设置四个点的大小,单位dp
1033 | *
1034 | * @param handleDp handle radius in density-independent pixels
1035 | */
1036 | public void setHandleSizeInDp(int handleDp) {
1037 | mHandleSize = (int) (handleDp * getDensity());
1038 | }
1039 |
1040 | /**
1041 | * 设置触摸区域的大小
1042 | *
1043 | * @param paddingDp crop frame handle touch padding(touch area) in density-independent pixels
1044 | */
1045 | public void setTouchPaddingInDp(int paddingDp) {
1046 | mTouchPadding = (int) (paddingDp * getDensity());
1047 | }
1048 |
1049 | /**
1050 | * 设置 guideline 的显示模式
1051 | * (SHOW_ALWAYS/NOT_SHOW/SHOW_ON_TOUCH)
1052 | *
1053 | * @param mode guideline show mode
1054 | */
1055 | public void setGuideShowMode(ShowMode mode) {
1056 | mGuideShowMode = mode;
1057 | switch (mode) {
1058 | case SHOW_ALWAYS:
1059 | mShowGuide = true;
1060 | break;
1061 | case NOT_SHOW:
1062 | case SHOW_ON_TOUCH:
1063 | mShowGuide = false;
1064 | break;
1065 | }
1066 | invalidate();
1067 | }
1068 |
1069 | /**
1070 | * 设置 handle 的显示模式
1071 | * (SHOW_ALWAYS/NOT_SHOW/SHOW_ON_TOUCH)
1072 | *
1073 | * @param mode handle show mode
1074 | */
1075 | public void setHandleShowMode(ShowMode mode) {
1076 | mHandleShowMode = mode;
1077 | switch (mode) {
1078 | case SHOW_ALWAYS:
1079 | mShowHandle = true;
1080 | break;
1081 | case NOT_SHOW:
1082 | case SHOW_ON_TOUCH:
1083 | mShowHandle = false;
1084 | break;
1085 | }
1086 | invalidate();
1087 | }
1088 |
1089 | /**
1090 | * 设置裁剪框的宽
1091 | */
1092 | public void setFrameStrokeWeightInDp(int weightDp) {
1093 | mFrameStrokeWeight = weightDp * getDensity();
1094 | invalidate();
1095 | }
1096 |
1097 | /**
1098 | * 设置裁剪框内横竖线的宽
1099 | */
1100 | public void setGuideStrokeWeightInDp(int weightDp) {
1101 | mGuideStrokeWeight = weightDp * getDensity();
1102 | invalidate();
1103 | }
1104 |
1105 | /**
1106 | * 是否显示裁剪框
1107 | *
1108 | * @param enabled should show crop frame?
1109 | */
1110 | public void setCropEnabled(boolean enabled) {
1111 | mIsCropEnabled = enabled;
1112 | invalidate();
1113 | }
1114 |
1115 | /**
1116 | * 是否锁定裁剪框,若锁定怎裁剪框不能移动
1117 | *
1118 | * @param enabled should lock crop frame?
1119 | */
1120 | @Override
1121 | public void setEnabled(boolean enabled) {
1122 | super.setEnabled(enabled);
1123 | mIsEnabled = enabled;
1124 | }
1125 |
1126 | /**
1127 | * Set initial scale of the frame.(0.01 ~ 1.0)
1128 | *
1129 | * @param initialScale initial scale
1130 | */
1131 | public void setInitialFrameScale(float initialScale) {
1132 | mInitialFrameScale = constrain(initialScale, 0.01f, 1.0f, DEFAULT_INITIAL_FRAME_SCALE);
1133 | }
1134 |
1135 | /**
1136 | * 设置是否显示裁剪框上四个点的阴影效果
1137 | */
1138 | public void setHandleShadowEnabled(boolean handleShadowEnabled) {
1139 | mIsHandleShadowEnabled = handleShadowEnabled;
1140 | }
1141 |
1142 | /**
1143 | * 获取裁剪框的宽
1144 | */
1145 | private float getFrameWidth() {
1146 | return (mFrameRect.right - mFrameRect.left);
1147 | }
1148 |
1149 | /**
1150 | * 获取裁剪框的高
1151 | */
1152 | private float getFrameHeight() {
1153 | return (mFrameRect.bottom - mFrameRect.top);
1154 | }
1155 |
1156 | /**
1157 | * 手指触摸区域
1158 | */
1159 | private enum TouchArea {
1160 | OUT_OF_BOUNDS, CENTER, LEFT_TOP, RIGHT_TOP, LEFT_BOTTOM, RIGHT_BOTTOM
1161 | }
1162 |
1163 | /**
1164 | * 裁剪模式
1165 | */
1166 | public enum CropMode {
1167 | FIT_IMAGE(0), RATIO_4_3(1), RATIO_3_4(2), SQUARE(3), RATIO_16_9(4), RATIO_9_16(5), FREE(
1168 | 6), CUSTOM(7), CIRCLE(8), CIRCLE_SQUARE(9);
1169 | private final int ID;
1170 |
1171 | CropMode(final int id) {
1172 | this.ID = id;
1173 | }
1174 |
1175 | public int getId() {
1176 | return ID;
1177 | }
1178 | }
1179 |
1180 | /**
1181 | * 显示模式
1182 | */
1183 | public enum ShowMode {
1184 | SHOW_ALWAYS(1), SHOW_ON_TOUCH(2), NOT_SHOW(3);
1185 | private final int ID;
1186 |
1187 | ShowMode(final int id) {
1188 | this.ID = id;
1189 | }
1190 |
1191 | public int getId() {
1192 | return ID;
1193 | }
1194 | }
1195 |
1196 | /**
1197 | * 获取裁剪框
1198 | */
1199 | public RectF getFrameRect() {
1200 | return mFrameRect;
1201 | }
1202 |
1203 | /**
1204 | * 获取照片
1205 | *
1206 | * @param data 从camera返回的数据
1207 | * @return 裁剪后的bitmap
1208 | */
1209 | public Bitmap getPicture(byte[] data) {
1210 | //原始照片
1211 | Bitmap originBitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
1212 | //原始照片的宽高
1213 | float picWidth = originBitmap.getWidth();
1214 | float picHeight = originBitmap.getHeight();
1215 |
1216 | //预览界面的宽高
1217 | float preWidth = getWidth();
1218 | float preHeight = getHeight();
1219 |
1220 | //预览界面和照片的比例
1221 | float preRW = picWidth / preWidth;
1222 | float preRH = picHeight / preHeight;
1223 |
1224 | //裁剪框的位置和宽高
1225 | RectF frameRect = getFrameRect();
1226 | float frameLeft = frameRect.left;
1227 | float frameTop = frameRect.top;
1228 | float frameWidth = frameRect.width();
1229 | float frameHeight = frameRect.height();
1230 |
1231 | int cropLeft = (int) (frameLeft * preRW);
1232 | int cropTop = (int) (frameTop * preRH);
1233 | int cropWidth = (int) (frameWidth * preRW);
1234 | int cropHeight = (int) (frameHeight * preRH);
1235 |
1236 | Bitmap cropBitmap = Bitmap.createBitmap(originBitmap, cropLeft, cropTop, cropWidth, cropHeight);
1237 |
1238 | if (mCropMode == CropMode.CIRCLE) {
1239 | cropBitmap = getCircularBitmap(cropBitmap);
1240 | }
1241 | return cropBitmap;
1242 | }
1243 |
1244 | /**
1245 | * 获取圆形图片
1246 | */
1247 | public Bitmap getCircularBitmap(Bitmap square) {
1248 | if (square == null) return null;
1249 | Bitmap output = Bitmap.createBitmap(square.getWidth(), square.getHeight(), Bitmap.Config.ARGB_8888);
1250 |
1251 | final Rect rect = new Rect(0, 0, square.getWidth(), square.getHeight());
1252 | Canvas canvas = new Canvas(output);
1253 |
1254 | int halfWidth = square.getWidth() / 2;
1255 | int halfHeight = square.getHeight() / 2;
1256 |
1257 | final Paint paint = new Paint();
1258 | paint.setAntiAlias(true);
1259 | paint.setFilterBitmap(true);
1260 |
1261 | canvas.drawCircle(halfWidth, halfHeight, Math.min(halfWidth, halfHeight), paint);
1262 | paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
1263 | canvas.drawBitmap(square, rect, rect, paint);
1264 | return output;
1265 | }
1266 |
1267 | private int dip2px(Context context, float dipValue) {
1268 | final float scale = context.getResources().getDisplayMetrics().density;
1269 | return (int) (dipValue * scale + 0.5f);
1270 | }
1271 | }
1272 |
--------------------------------------------------------------------------------
/focussurfaceview/src/main/java/com/cymaybe/foucsurfaceview/animation/SimpleValueAnimator.java:
--------------------------------------------------------------------------------
1 | package com.cymaybe.foucsurfaceview.animation;
2 | @SuppressWarnings("unused")
3 | public interface SimpleValueAnimator {
4 | void startAnimation(long duration);
5 | void cancelAnimation();
6 | boolean isAnimationStarted();
7 | void addAnimatorListener(SimpleValueAnimatorListener animatorListener);
8 | }
9 |
--------------------------------------------------------------------------------
/focussurfaceview/src/main/java/com/cymaybe/foucsurfaceview/animation/SimpleValueAnimatorListener.java:
--------------------------------------------------------------------------------
1 | package com.cymaybe.foucsurfaceview.animation;
2 |
3 | public interface SimpleValueAnimatorListener {
4 | void onAnimationStarted();
5 | void onAnimationUpdated(float scale);
6 | void onAnimationFinished();
7 | }
8 |
--------------------------------------------------------------------------------
/focussurfaceview/src/main/java/com/cymaybe/foucsurfaceview/animation/ValueAnimatorV14.java:
--------------------------------------------------------------------------------
1 | package com.cymaybe.foucsurfaceview.animation;
2 |
3 | import android.animation.Animator;
4 | import android.animation.ValueAnimator;
5 | import android.annotation.TargetApi;
6 | import android.os.Build;
7 | import android.view.animation.Interpolator;
8 |
9 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
10 | public class ValueAnimatorV14 implements SimpleValueAnimator,
11 | Animator.AnimatorListener,
12 | ValueAnimator.AnimatorUpdateListener {
13 | private static final int DEFAULT_ANIMATION_DURATION = 150;
14 | private ValueAnimator animator;
15 | private SimpleValueAnimatorListener animatorListener = new SimpleValueAnimatorListener() {
16 | @Override
17 | public void onAnimationStarted() {
18 |
19 | }
20 |
21 | @Override
22 | public void onAnimationUpdated(float scale) {
23 |
24 | }
25 |
26 | @Override
27 | public void onAnimationFinished() {
28 |
29 | }
30 | };
31 |
32 | public ValueAnimatorV14(Interpolator interpolator) {
33 | animator = ValueAnimator.ofFloat(0.0f, 1.0f);
34 | animator.addListener(this);
35 | animator.addUpdateListener(this);
36 | animator.setInterpolator(interpolator);
37 | }
38 |
39 | @Override
40 | public void startAnimation(long duration) {
41 | if (duration >= 0) {
42 | animator.setDuration(duration);
43 | } else {
44 | animator.setDuration(DEFAULT_ANIMATION_DURATION);
45 | }
46 | animator.start();
47 | }
48 |
49 | @Override
50 | public void cancelAnimation() {
51 | animator.cancel();
52 | }
53 |
54 | @Override
55 | public boolean isAnimationStarted() {
56 | return animator.isStarted();
57 | }
58 |
59 | @Override
60 | public void addAnimatorListener(SimpleValueAnimatorListener animatorListener) {
61 | if (animatorListener != null) this.animatorListener = animatorListener;
62 | }
63 |
64 | @Override
65 | public void onAnimationStart(Animator animation) {
66 | animatorListener.onAnimationStarted();
67 | }
68 |
69 | @Override
70 | public void onAnimationEnd(Animator animation) {
71 | animatorListener.onAnimationFinished();
72 | }
73 |
74 | @Override
75 | public void onAnimationCancel(Animator animation) {
76 | animatorListener.onAnimationFinished();
77 | }
78 |
79 | @Override
80 | public void onAnimationRepeat(Animator animation) {
81 |
82 | }
83 |
84 | @Override
85 | public void onAnimationUpdate(ValueAnimator animation) {
86 | animatorListener.onAnimationUpdated(animation.getAnimatedFraction());
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/focussurfaceview/src/main/java/com/cymaybe/foucsurfaceview/animation/ValueAnimatorV8.java:
--------------------------------------------------------------------------------
1 | package com.cymaybe.foucsurfaceview.animation;
2 |
3 | import android.os.SystemClock;
4 | import android.view.animation.Interpolator;
5 |
6 | import java.util.concurrent.Executors;
7 | import java.util.concurrent.ScheduledExecutorService;
8 | import java.util.concurrent.TimeUnit;
9 |
10 | public class ValueAnimatorV8 implements SimpleValueAnimator {
11 | private static final int FRAME_RATE = 30;
12 | private static final int UPDATE_SPAN = Math.round((float) 1000 / (float) FRAME_RATE);
13 | private static final int DEFAULT_ANIMATION_DURATION = 150;
14 |
15 | private Interpolator mInterpolator;
16 | ScheduledExecutorService service;
17 | long start;
18 | boolean isAnimationStarted = false;
19 | long duration;
20 | private SimpleValueAnimatorListener animatorListener = new SimpleValueAnimatorListener() {
21 | @Override
22 | public void onAnimationStarted() {
23 |
24 | }
25 |
26 | @Override
27 | public void onAnimationUpdated(float scale) {
28 |
29 | }
30 |
31 | @Override
32 | public void onAnimationFinished() {
33 |
34 | }
35 |
36 | };
37 |
38 | private final Runnable runnable = new Runnable() {
39 | @Override
40 | public void run() {
41 | long elapsed = SystemClock.uptimeMillis() - start;
42 | if (elapsed > duration) {
43 | isAnimationStarted = false;
44 | animatorListener.onAnimationFinished();
45 | service.shutdown();
46 | return;
47 | }
48 | float scale = Math.min(mInterpolator.getInterpolation((float) elapsed / duration), 1);
49 | animatorListener.onAnimationUpdated(scale);
50 | }
51 | };
52 |
53 | public ValueAnimatorV8(Interpolator interpolator) {
54 | this.mInterpolator = interpolator;
55 | }
56 |
57 | @Override
58 | public void startAnimation(long duration) {
59 | if (duration >= 0) {
60 | this.duration = duration;
61 | } else {
62 | this.duration = DEFAULT_ANIMATION_DURATION;
63 | }
64 | isAnimationStarted = true;
65 | animatorListener.onAnimationStarted();
66 | start = SystemClock.uptimeMillis();
67 | service = Executors.newSingleThreadScheduledExecutor();
68 | service.scheduleAtFixedRate(runnable, 0, UPDATE_SPAN, TimeUnit.MILLISECONDS);
69 | }
70 |
71 | @Override
72 | public void cancelAnimation() {
73 | isAnimationStarted = false;
74 | service.shutdown();
75 | animatorListener.onAnimationFinished();
76 | }
77 |
78 | @Override
79 | public boolean isAnimationStarted() {
80 | return isAnimationStarted;
81 | }
82 |
83 | @Override
84 | public void addAnimatorListener(SimpleValueAnimatorListener animatorListener) {
85 | if (animatorListener != null) this.animatorListener = animatorListener;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/focussurfaceview/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/focussurfaceview/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | FoucSurfaceView
3 |
4 |
--------------------------------------------------------------------------------
/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/CGmaybe10/FocusSurfaceView/26d0babfd5b4044e980adbd42c98ee2ffdd6f6ca/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 2015
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-2.14.1-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 --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --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 --path --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 |
--------------------------------------------------------------------------------
/screenshots/circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CGmaybe10/FocusSurfaceView/26d0babfd5b4044e980adbd42c98ee2ffdd6f6ca/screenshots/circle.png
--------------------------------------------------------------------------------
/screenshots/circle_pre.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CGmaybe10/FocusSurfaceView/26d0babfd5b4044e980adbd42c98ee2ffdd6f6ca/screenshots/circle_pre.png
--------------------------------------------------------------------------------
/screenshots/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CGmaybe10/FocusSurfaceView/26d0babfd5b4044e980adbd42c98ee2ffdd6f6ca/screenshots/demo.gif
--------------------------------------------------------------------------------
/screenshots/free.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CGmaybe10/FocusSurfaceView/26d0babfd5b4044e980adbd42c98ee2ffdd6f6ca/screenshots/free.png
--------------------------------------------------------------------------------
/screenshots/free_pre.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CGmaybe10/FocusSurfaceView/26d0babfd5b4044e980adbd42c98ee2ffdd6f6ca/screenshots/free_pre.png
--------------------------------------------------------------------------------
/screenshots/ratio_3_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CGmaybe10/FocusSurfaceView/26d0babfd5b4044e980adbd42c98ee2ffdd6f6ca/screenshots/ratio_3_4.png
--------------------------------------------------------------------------------
/screenshots/ratio_3_4_pre.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CGmaybe10/FocusSurfaceView/26d0babfd5b4044e980adbd42c98ee2ffdd6f6ca/screenshots/ratio_3_4_pre.png
--------------------------------------------------------------------------------
/screenshots/square.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CGmaybe10/FocusSurfaceView/26d0babfd5b4044e980adbd42c98ee2ffdd6f6ca/screenshots/square.png
--------------------------------------------------------------------------------
/screenshots/square_pre.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CGmaybe10/FocusSurfaceView/26d0babfd5b4044e980adbd42c98ee2ffdd6f6ca/screenshots/square_pre.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':focussurfaceview'
2 |
--------------------------------------------------------------------------------