├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ └── styles.xml
│ │ │ ├── drawable
│ │ │ │ ├── reminder_map.png
│ │ │ │ ├── reminder_coffee.png
│ │ │ │ ├── reminder_movie.png
│ │ │ │ ├── reminder_music.png
│ │ │ │ ├── reminder_sushi.png
│ │ │ │ ├── profile_gender_male.png
│ │ │ │ └── reminder_vintage_handheld.png
│ │ │ ├── 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
│ │ │ └── layout
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── my_action_dialog.xml
│ │ │ │ └── main_scroll_content.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── stone
│ │ │ └── dragsquare
│ │ │ ├── MyActionDialog.java
│ │ │ └── MainActivity.java
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── stone
│ │ │ └── dragsquare
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── stone
│ │ └── dragsquare
│ │ └── ApplicationTest.java
├── proguard-rules.pro
└── build.gradle
├── dragsquareimage
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── drawable
│ │ │ │ └── plus.png
│ │ │ ├── values
│ │ │ │ ├── dimens.xml
│ │ │ │ └── strings.xml
│ │ │ └── layout
│ │ │ │ ├── drag_item.xml
│ │ │ │ └── default_action_dialog.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── swifty
│ │ │ │ └── dragsquareimage
│ │ │ │ ├── ActionDialogClick.java
│ │ │ │ ├── DraggablePresenter.java
│ │ │ │ ├── ActionDialog.java
│ │ │ │ ├── DefaultActionDialog.java
│ │ │ │ ├── DraggablePresenterImpl.java
│ │ │ │ ├── DraggableItemView.java
│ │ │ │ └── DraggableSquareView.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── tinklabs
│ │ │ └── dragsquareimage
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── swifty
│ │ └── dragsquareimage
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── capture1.gif
├── capture2.gif
├── capture3.gif
├── crop
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── strings.xml
│ │ │ │ ├── attrs.xml
│ │ │ │ └── styles.xml
│ │ │ ├── values-land
│ │ │ │ └── dimens.xml
│ │ │ ├── values-large
│ │ │ │ └── dimens.xml
│ │ │ ├── values-v21
│ │ │ │ └── colors.xml
│ │ │ ├── drawable-xhdpi
│ │ │ │ ├── crop__tile.png
│ │ │ │ ├── crop__ic_done.png
│ │ │ │ └── crop__ic_cancel.png
│ │ │ ├── xml
│ │ │ │ └── provider_paths.xml
│ │ │ ├── drawable
│ │ │ │ ├── crop__texture.xml
│ │ │ │ └── crop__selectable_background.xml
│ │ │ ├── drawable-v21
│ │ │ │ └── crop__selectable_background.xml
│ │ │ ├── values-zh-rCN
│ │ │ │ └── strings.xml
│ │ │ ├── values-ja
│ │ │ │ └── strings.xml
│ │ │ ├── values-zh-rTW
│ │ │ │ └── strings.xml
│ │ │ ├── values-ko
│ │ │ │ └── strings.xml
│ │ │ ├── values-ar
│ │ │ │ └── strings.xml
│ │ │ ├── values-sv
│ │ │ │ └── strings.xml
│ │ │ ├── values-tr
│ │ │ │ └── strings.xml
│ │ │ ├── values-de
│ │ │ │ └── strings.xml
│ │ │ ├── values-es
│ │ │ │ └── strings.xml
│ │ │ ├── values-ca
│ │ │ │ └── strings.xml
│ │ │ ├── values-in
│ │ │ │ └── strings.xml
│ │ │ ├── values-it
│ │ │ │ └── strings.xml
│ │ │ ├── values-fr
│ │ │ │ └── strings.xml
│ │ │ ├── values-pt
│ │ │ │ └── strings.xml
│ │ │ ├── values-ru
│ │ │ │ └── strings.xml
│ │ │ └── layout
│ │ │ │ ├── crop__activity_crop.xml
│ │ │ │ └── crop__layout_done_cancel.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── soundcloud
│ │ │ │ └── android
│ │ │ │ └── crop
│ │ │ │ ├── provider
│ │ │ │ └── SwiftyFileProvider.java
│ │ │ │ ├── Log.java
│ │ │ │ ├── RotateBitmap.java
│ │ │ │ ├── MonitoredActivity.java
│ │ │ │ ├── CropImageView.java
│ │ │ │ ├── CropUtil.java
│ │ │ │ ├── Crop.java
│ │ │ │ ├── ImageViewTouchBase.java
│ │ │ │ ├── HighlightView.java
│ │ │ │ └── CropImageActivity.java
│ │ └── AndroidManifest.xml
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── soundcloud
│ │ └── android
│ │ └── crop
│ │ ├── BaseTestCase.java
│ │ └── CropBuilderTest.java
└── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── README.md
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/dragsquareimage/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':crop', ':dragsquareimage'
2 |
--------------------------------------------------------------------------------
/capture1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftyWang/android-drag-square/HEAD/capture1.gif
--------------------------------------------------------------------------------
/capture2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftyWang/android-drag-square/HEAD/capture2.gif
--------------------------------------------------------------------------------
/capture3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftyWang/android-drag-square/HEAD/capture3.gif
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | DragRankSquare
3 |
4 |
--------------------------------------------------------------------------------
/crop/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 56dp
4 |
5 |
--------------------------------------------------------------------------------
/crop/src/main/res/values-land/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 48dp
4 |
5 |
--------------------------------------------------------------------------------
/crop/src/main/res/values-large/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 64dp
4 |
5 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftyWang/android-drag-square/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/crop/src/main/res/values-v21/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #aaaaaa
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/reminder_map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftyWang/android-drag-square/HEAD/app/src/main/res/drawable/reminder_map.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/reminder_coffee.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftyWang/android-drag-square/HEAD/app/src/main/res/drawable/reminder_coffee.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/reminder_movie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftyWang/android-drag-square/HEAD/app/src/main/res/drawable/reminder_movie.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/reminder_music.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftyWang/android-drag-square/HEAD/app/src/main/res/drawable/reminder_music.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/reminder_sushi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftyWang/android-drag-square/HEAD/app/src/main/res/drawable/reminder_sushi.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftyWang/android-drag-square/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftyWang/android-drag-square/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftyWang/android-drag-square/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftyWang/android-drag-square/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/dragsquareimage/src/main/res/drawable/plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftyWang/android-drag-square/HEAD/dragsquareimage/src/main/res/drawable/plus.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftyWang/android-drag-square/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/crop/src/main/res/drawable-xhdpi/crop__tile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftyWang/android-drag-square/HEAD/crop/src/main/res/drawable-xhdpi/crop__tile.png
--------------------------------------------------------------------------------
/crop/src/main/res/xml/provider_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/profile_gender_male.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftyWang/android-drag-square/HEAD/app/src/main/res/drawable/profile_gender_male.png
--------------------------------------------------------------------------------
/crop/src/main/res/drawable-xhdpi/crop__ic_done.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftyWang/android-drag-square/HEAD/crop/src/main/res/drawable-xhdpi/crop__ic_done.png
--------------------------------------------------------------------------------
/crop/src/main/res/drawable-xhdpi/crop__ic_cancel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftyWang/android-drag-square/HEAD/crop/src/main/res/drawable-xhdpi/crop__ic_cancel.png
--------------------------------------------------------------------------------
/dragsquareimage/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 2dp
4 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/reminder_vintage_handheld.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftyWang/android-drag-square/HEAD/app/src/main/res/drawable/reminder_vintage_handheld.png
--------------------------------------------------------------------------------
/crop/src/main/res/drawable/crop__texture.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/dragsquareimage/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | DragSquareImage
3 | Select Image
4 | Delete
5 | Take Photo
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Nov 29 15:20:11 HKT 2016
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 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 | #555555
7 |
8 |
--------------------------------------------------------------------------------
/crop/src/main/res/drawable-v21/crop__selectable_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/crop/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #f3f3f3
4 | #666666
5 | #1a000000
6 | #77000000
7 |
8 |
--------------------------------------------------------------------------------
/crop/src/main/java/com/soundcloud/android/crop/provider/SwiftyFileProvider.java:
--------------------------------------------------------------------------------
1 | package com.soundcloud.android.crop.provider;
2 |
3 | import android.support.v4.content.FileProvider;
4 |
5 | /**
6 | * Created by kelvin_tang on 10/10/2017.
7 | */
8 |
9 | public class SwiftyFileProvider extends FileProvider {
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 | 16dp
4 | 5dp
5 | 10dp
6 | 15dp
7 |
8 |
--------------------------------------------------------------------------------
/dragsquareimage/src/main/java/com/swifty/dragsquareimage/ActionDialogClick.java:
--------------------------------------------------------------------------------
1 | package com.swifty.dragsquareimage;
2 |
3 | import android.view.View;
4 |
5 | /**
6 | * Created by swifty on 9/12/2016.
7 | */
8 | public interface ActionDialogClick {
9 |
10 | void onTakePhotoClick(View view);
11 |
12 | void onPickImageClick(View view);
13 |
14 | void onDeleteClick(View view);
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/crop/src/main/java/com/soundcloud/android/crop/Log.java:
--------------------------------------------------------------------------------
1 | package com.soundcloud.android.crop;
2 |
3 | class Log {
4 |
5 | private static final String TAG = "android-crop";
6 |
7 | public static void e(String msg) {
8 | android.util.Log.e(TAG, msg);
9 | }
10 |
11 | public static void e(String msg, Throwable e) {
12 | android.util.Log.e(TAG, msg, e);
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/crop/src/main/res/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 正在保存照片…
4 | 请等待…
5 | 无效的图片
6 |
7 | 完成
8 | 取消
9 |
10 |
11 |
--------------------------------------------------------------------------------
/crop/src/main/res/values-ja/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 保存中…
4 | お待ちください…
5 | 画像が見つかりません
6 |
7 | 決定
8 | キャンセル
9 |
10 |
11 |
--------------------------------------------------------------------------------
/crop/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 正在儲存相片…
4 | 請稍候…
5 | 沒有可用的圖片來源
6 |
7 | 完成
8 | 取消
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/test/java/com/stone/dragsquare/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.stone.dragsquare;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/crop/src/main/res/values-ko/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 사진을 저장중입니다…
4 | 잠시만 기다려주세요…
5 | 이미지가 존재하지 않습니다.
6 |
7 | 확인
8 | 취소
9 |
10 |
11 |
--------------------------------------------------------------------------------
/crop/src/main/res/values-ar/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | جارى حفظ الصورة …
4 | رجاء الأنتظار …
5 | الصورة غير متاحة
6 |
7 | تم
8 | الغاء
9 |
10 |
11 |
--------------------------------------------------------------------------------
/crop/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Saving picture…
4 | Please wait…
5 | No image sources available
6 |
7 | DONE
8 | CANCEL
9 |
10 |
11 |
--------------------------------------------------------------------------------
/crop/src/main/res/values-sv/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Sparar bild…
4 | Var god vänta…
5 | Inga bildkällor tillgängliga
6 |
7 | KLAR
8 | AVBRYT
9 |
10 |
11 |
--------------------------------------------------------------------------------
/crop/src/main/res/values-tr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Fotoğraf kaydediliyor…
4 | Lütfen bekleyin…
5 | Fotoğraf bulunamadı
6 |
7 | TAMAM
8 | ÇIKIŞ
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/stone/dragsquare/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.stone.dragsquare;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/crop/src/main/res/values-de/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Bild speichern…
4 | Bitte warten…
5 | Keine Bildquellen verfügbar
6 |
7 | übernehmen
8 | abbrechen
9 |
10 |
11 |
--------------------------------------------------------------------------------
/crop/src/main/res/values-es/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Guardando imagen…
4 | Por favor espere…
5 | No hay imágenes disponibles
6 |
7 | ACEPTAR
8 | CANCELAR
9 |
10 |
11 |
--------------------------------------------------------------------------------
/crop/src/main/res/values-ca/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Guardant imatge…
4 | Si us plau esperi…
5 | No hi ha imatges disponibles
6 |
7 | ACCEPTAR
8 | CANCEL·LAR
9 |
10 |
11 |
--------------------------------------------------------------------------------
/crop/src/main/res/values-in/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Menyimpan gambar…
4 | Silakan tunggu…
5 | Tidak ada sumber gambar yang tersedia
6 |
7 | SELESAI
8 | BATAL
9 |
10 |
11 |
--------------------------------------------------------------------------------
/crop/src/main/res/values-it/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Salvataggio immagine…
4 | Attendere prego…
5 | Nessuna immagine disponibile
6 |
7 | ACCETTA
8 | ANNULLA
9 |
10 |
11 |
--------------------------------------------------------------------------------
/crop/src/main/res/values-fr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Enregistrement de l\'image…
4 | Veuillez patienter…
5 | Aucune image disponible
6 |
7 | ACCEPTER
8 | ANNULER
9 |
10 |
11 |
--------------------------------------------------------------------------------
/crop/src/main/res/values-pt/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Salvando imagem…
4 | Por favor, aguarde…
5 | Sem fontes de imagem disponíveis
6 |
7 | FINALIZADO
8 | CANCELAR
9 |
10 |
11 |
--------------------------------------------------------------------------------
/crop/src/main/res/values-ru/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Изображение сохраняется…
4 | Пожалуйста, подождите…
5 | Нет доступных изображений
6 |
7 | ГОТОВО
8 | ОТМЕНА
9 |
10 |
11 |
--------------------------------------------------------------------------------
/dragsquareimage/src/test/java/com/tinklabs/dragsquareimage/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.swifty.dragsquareimage;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/crop/src/androidTest/java/com/soundcloud/android/crop/BaseTestCase.java:
--------------------------------------------------------------------------------
1 | package com.soundcloud.android.crop;
2 |
3 | import android.test.InstrumentationTestCase;
4 |
5 | public class BaseTestCase extends InstrumentationTestCase {
6 |
7 | @Override
8 | public void setUp() throws Exception {
9 | super.setUp();
10 | // Work around dexmaker issue when running tests on Android 4.3
11 | System.setProperty("dexmaker.dexcache",
12 | getInstrumentation().getTargetContext().getCacheDir().getPath());
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/dragsquareimage/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/crop/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/crop/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
9 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/crop/src/main/res/drawable/crop__selectable_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | -
7 |
8 |
9 |
10 |
11 |
12 | -
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/dragsquareimage/src/main/java/com/swifty/dragsquareimage/DraggablePresenter.java:
--------------------------------------------------------------------------------
1 | package com.swifty.dragsquareimage;
2 |
3 | import android.content.Intent;
4 | import android.net.Uri;
5 | import android.util.SparseArray;
6 |
7 | /**
8 | * Created by swifty on 29/11/2016.
9 | */
10 |
11 | public interface DraggablePresenter {
12 |
13 | void onActivityResult(int requestCode, int resultCode, Intent result);
14 |
15 | void beginCrop(Uri source);
16 |
17 | void handleCrop(int resultCode, Intent result);
18 |
19 | SparseArray getImageUrls();
20 |
21 | void setImages(String... imageUrls);
22 |
23 | void setCustomActionDialog(ActionDialog actionDialog);
24 | }
25 |
--------------------------------------------------------------------------------
/crop/src/main/res/layout/crop__activity_crop.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
10 |
11 |
17 |
18 |
--------------------------------------------------------------------------------
/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:\adt-bundle-windows-x86_64-20140702\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 |
--------------------------------------------------------------------------------
/dragsquareimage/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 /Users/swifty/Library/Android/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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Local configuration file (sdk path, etc)
2 | local.properties
3 | secrets.properties
4 |
5 | # Proguard folder generated by Eclipse
6 | proguard/
7 |
8 | # Android Studio
9 | *.iml
10 | .idea
11 |
12 | # Built application files
13 | *.apk
14 | *.ap_
15 |
16 | # Files for the Dalvik VM
17 | *.dex
18 |
19 | # Java class files
20 | *.class
21 |
22 | # Generated files
23 | bin/
24 | gen/
25 | captures/
26 |
27 | # Gradle files
28 | .gradle/
29 | gradlew.bat
30 | build/
31 | /*/build/
32 |
33 | # Log Files
34 | *.log
35 |
36 | # Prototype project
37 | prototype/*
38 |
39 | # Node modules for React Native, should initialize with commands:
40 | # npm init
41 | # npm install --save react-native
42 | # Windows meta files
43 | Thumbs.db
44 | Desktop.ini
45 |
46 | # Mac
47 | .DS_Store
48 |
49 | true/crashlytics-build.properties
50 | node_modules/
51 | projectFilesBackup/
52 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/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 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/dragsquareimage/src/androidTest/java/com/swifty/dragsquareimage/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.swifty.dragsquareimage;
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.swifty.dragsquareimage.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion rootProject.ext.compileSdkVersion
5 | buildToolsVersion rootProject.ext.buildToolsVersion
6 |
7 | defaultConfig {
8 | applicationId "com.stone.dragsquare"
9 | minSdkVersion rootProject.ext.minSdkVersion
10 | targetSdkVersion rootProject.ext.targetSdkVersion
11 | versionCode 1
12 | versionName "1.0"
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 | testCompile 'junit:junit:4.12'
25 | compile project(':dragsquareimage')
26 | compile "com.android.support:appcompat-v7:$rootProject.ext.supportVersion"
27 | compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
28 | }
29 |
--------------------------------------------------------------------------------
/crop/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'maven'
3 | apply plugin: 'signing'
4 | //apply from: '../.publishing/sonatype.gradle'
5 |
6 | archivesBaseName = 'android-crop'
7 |
8 | android {
9 | compileSdkVersion rootProject.ext.compileSdkVersion
10 | buildToolsVersion rootProject.ext.buildToolsVersion
11 |
12 | defaultConfig {
13 | minSdkVersion rootProject.ext.minSdkVersion
14 | targetSdkVersion rootProject.ext.targetSdkVersion
15 |
16 | testApplicationId 'com.soundcloud.android.crop.test'
17 | testInstrumentationRunner 'android.test.InstrumentationTestRunner'
18 | }
19 | }
20 |
21 | dependencies {
22 | compile "com.android.support:support-annotations:$rootProject.ext.supportVersion"
23 | compile "com.android.support:support-v4:$rootProject.ext.supportVersion"
24 | androidTestCompile 'com.squareup:fest-android:1.0.7'
25 | androidTestCompile "com.android.support:support-v4:$rootProject.ext.supportVersion"
26 | androidTestCompile 'org.mockito:mockito-core:1.9.5'
27 | androidTestCompile 'com.google.dexmaker:dexmaker:1.0'
28 | androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.0'
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/dragsquareimage/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion rootProject.ext.compileSdkVersion
5 | buildToolsVersion rootProject.ext.buildToolsVersion
6 |
7 | defaultConfig {
8 | minSdkVersion rootProject.ext.minSdkVersion
9 | targetSdkVersion rootProject.ext.targetSdkVersion
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:$rootProject.ext.supportVersion"
30 | testCompile 'junit:junit:4.12'
31 | compile 'com.facebook.rebound:rebound:0.3.8'
32 | compile 'com.squareup.picasso:picasso:2.5.2'
33 | compile project(':crop')
34 | }
35 |
--------------------------------------------------------------------------------
/dragsquareimage/src/main/res/layout/drag_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
14 |
15 |
22 |
23 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
18 |
19 |
24 |
25 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/my_action_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
21 |
22 |
30 |
31 |
35 |
36 |
44 |
45 |
--------------------------------------------------------------------------------
/dragsquareimage/src/main/res/layout/default_action_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
21 |
22 |
30 |
31 |
35 |
36 |
44 |
45 |
--------------------------------------------------------------------------------
/crop/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 |
15 |
20 |
21 |
31 |
32 |
35 |
36 |
39 |
40 |
--------------------------------------------------------------------------------
/crop/src/main/res/layout/crop__layout_done_cancel.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
12 |
13 |
17 |
18 |
23 |
24 |
25 |
31 |
32 |
33 |
40 |
41 |
45 |
46 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/dragsquareimage/src/main/java/com/swifty/dragsquareimage/ActionDialog.java:
--------------------------------------------------------------------------------
1 | package com.swifty.dragsquareimage;
2 |
3 | import android.content.Context;
4 | import android.content.DialogInterface;
5 | import android.os.Bundle;
6 | import android.support.annotation.NonNull;
7 | import android.support.annotation.Nullable;
8 | import android.support.annotation.StyleRes;
9 | import android.support.v7.app.AlertDialog;
10 | import android.view.View;
11 |
12 | /**
13 | * Created by swifty on 9/12/2016.
14 | */
15 |
16 | public abstract class ActionDialog extends AlertDialog implements DialogInterface.OnShowListener {
17 | protected boolean showDeleteButton;
18 | protected ActionDialogClick actionDialogClick;
19 |
20 | protected ActionDialog(@NonNull Context context) {
21 | super(context);
22 | }
23 |
24 | protected ActionDialog(@NonNull Context context, @StyleRes int themeResId) {
25 | super(context, themeResId);
26 | }
27 |
28 | protected ActionDialog(@NonNull Context context, boolean cancelable, @Nullable OnCancelListener cancelListener) {
29 | super(context, cancelable, cancelListener);
30 | }
31 |
32 |
33 | @Override
34 | protected void onCreate(Bundle savedInstanceState) {
35 | super.onCreate(savedInstanceState);
36 | setOnShowListener(this);
37 | }
38 |
39 | @Override
40 | public void onShow(DialogInterface dialog) {
41 | if (getDeleteButtonView() == null) return;
42 | if (showDeleteButton()) {
43 | getDeleteButtonView().setVisibility(View.VISIBLE);
44 | } else {
45 | getDeleteButtonView().setVisibility(View.GONE);
46 | }
47 | }
48 |
49 | public abstract View getDeleteButtonView();
50 |
51 | public abstract ActionDialog setActionDialogClick(ActionDialogClick actionDialogClick);
52 |
53 | public boolean showDeleteButton() {
54 | return showDeleteButton;
55 | }
56 |
57 | public ActionDialog setShowDeleteButton(boolean showDeleteButton) {
58 | this.showDeleteButton = showDeleteButton;
59 | return this;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/dragsquareimage/src/main/java/com/swifty/dragsquareimage/DefaultActionDialog.java:
--------------------------------------------------------------------------------
1 | package com.swifty.dragsquareimage;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 | import android.view.View;
6 |
7 | /**
8 | * Created by Administrator on 2016/5/27.
9 | */
10 | public class DefaultActionDialog extends ActionDialog {
11 |
12 | protected DefaultActionDialog(Context context) {
13 | super(context);
14 | }
15 |
16 | public DefaultActionDialog(Context context, int theme) {
17 | super(context, theme);
18 | }
19 |
20 | public DefaultActionDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
21 | super(context, cancelable, cancelListener);
22 | }
23 |
24 | @Override
25 | protected void onCreate(Bundle savedInstanceState) {
26 | super.onCreate(savedInstanceState);
27 | setContentView(R.layout.default_action_dialog);
28 | findViewById(R.id.take_photo).setOnClickListener(new View.OnClickListener() {
29 | @Override
30 | public void onClick(View v) {
31 | if (actionDialogClick != null) actionDialogClick.onTakePhotoClick(v);
32 | dismiss();
33 | }
34 | });
35 | findViewById(R.id.pick_image).setOnClickListener(new View.OnClickListener() {
36 | @Override
37 | public void onClick(View v) {
38 | if (actionDialogClick != null) actionDialogClick.onPickImageClick(v);
39 | dismiss();
40 | }
41 | });
42 | findViewById(R.id.delete).setOnClickListener(new View.OnClickListener() {
43 | @Override
44 | public void onClick(View v) {
45 | if (actionDialogClick != null) actionDialogClick.onDeleteClick(v);
46 | dismiss();
47 | }
48 | });
49 | }
50 |
51 | @Override
52 | public View getDeleteButtonView() {
53 | return findViewById(R.id.delete);
54 | }
55 |
56 | public ActionDialog setActionDialogClick(ActionDialogClick actionDialogClick) {
57 | this.actionDialogClick = actionDialogClick;
58 | return this;
59 | }
60 |
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/app/src/main/java/com/stone/dragsquare/MyActionDialog.java:
--------------------------------------------------------------------------------
1 | package com.stone.dragsquare;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 | import android.view.View;
6 | import android.view.Window;
7 |
8 | import com.swifty.dragsquareimage.ActionDialog;
9 | import com.swifty.dragsquareimage.ActionDialogClick;
10 |
11 | /**
12 | * Created by Administrator on 2016/5/27.
13 | */
14 | public class MyActionDialog extends ActionDialog {
15 |
16 | protected MyActionDialog(Context context) {
17 | super(context);
18 | init();
19 | }
20 |
21 |
22 | public MyActionDialog(Context context, int theme) {
23 | super(context, theme);
24 | init();
25 | }
26 |
27 | public MyActionDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
28 | super(context, cancelable, cancelListener);
29 | init();
30 | }
31 |
32 | private void init() {
33 | requestWindowFeature(Window.FEATURE_NO_TITLE);
34 | }
35 |
36 | @Override
37 | protected void onCreate(Bundle savedInstanceState) {
38 | super.onCreate(savedInstanceState);
39 | setContentView(R.layout.my_action_dialog);
40 |
41 | findViewById(com.swifty.dragsquareimage.R.id.pick_image).setOnClickListener(new View.OnClickListener() {
42 | @Override
43 | public void onClick(View v) {
44 | if (actionDialogClick != null) actionDialogClick.onPickImageClick(v);
45 | dismiss();
46 | }
47 | });
48 | findViewById(com.swifty.dragsquareimage.R.id.delete).setOnClickListener(new View.OnClickListener() {
49 | @Override
50 | public void onClick(View v) {
51 | if (actionDialogClick != null) actionDialogClick.onDeleteClick(v);
52 | dismiss();
53 | }
54 | });
55 | findViewById(com.swifty.dragsquareimage.R.id.take_photo).setOnClickListener(new View.OnClickListener() {
56 | @Override
57 | public void onClick(View v) {
58 | if (actionDialogClick != null) actionDialogClick.onTakePhotoClick(v);
59 | dismiss();
60 | }
61 | });
62 | }
63 |
64 | @Override
65 | public View getDeleteButtonView() {
66 | return findViewById(R.id.delete);
67 | }
68 |
69 | public ActionDialog setActionDialogClick(ActionDialogClick actionDialogClick) {
70 | this.actionDialogClick = actionDialogClick;
71 | return this;
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Modified by Swifty
2 | ## refactor the repository, easy to use with gradle import.
3 | [](https://jitpack.io/#SwiftyWang/android-drag-square)
4 |
5 | ## How to use
6 | Add it in your root build.gradle at the end of repositories:
7 |
8 | ```gradle
9 | allprojects {
10 | repositories {
11 | ...
12 | maven { url 'https://jitpack.io' }
13 | }
14 | }
15 | ```
16 |
17 | Add the dependency
18 | ```gradle
19 | dependencies {
20 | compile 'com.github.SwiftyWang.android-drag-square:dragsquareimage:1.2.3'
21 | }
22 | ```
23 |
24 | get DraggablePresenter
25 | ```java
26 | DraggableSquareView dragSquare = (DraggableSquareView) findViewById(R.id.drag_square);
27 | contentText = (TextView) findViewById(R.id.contentText);
28 | draggablePresent = new DraggablePresentImpl(fragment, dragSquare);
29 | draggablePresent = new DraggablePresentImpl(activity, dragSquare);
30 | ```
31 |
32 | need pass activity callback to DraggablePresentImpl
33 | ```java
34 | @Override
35 | protected void onActivityResult(int requestCode, int resultCode, Intent result) {
36 | draggablePresent.onActivityResult(requestCode, resultCode, result);
37 | }
38 | ```
39 |
40 | Set customer dialog, Customer dialog must extends ActionDialog.class
41 | ```java
42 | draggablePresent.setCustomActionDialog(new MyActionDialog(Context));
43 | ```
44 |
45 | listen image changes
46 | ```java
47 | dragSquare.setImageChangesListener(imageChangesListener);
48 |
49 | public interface ImageChangesListener {
50 | void onImageAdded(String uri, int index);
51 |
52 | void onImageEdited(String uri, int index);
53 |
54 | void onImageDeleted(String uri, int index);
55 | }
56 | ```
57 | All public apis
58 | ```java
59 | SparseArray getImageUrls();
60 |
61 | void setImages(String... imageUrls);
62 |
63 | void setCustomActionDialog(ActionDialog actionDialog);
64 | ```
65 |
66 |
67 | # android-drag-square
68 | edit personal data which enables users to drag and rank image order
69 |
70 | 编辑个人资料,图片可拖拽排序。有点像可拖拽的gridView,但是会更流畅。
71 | 这个demo是探探的个人资料编辑页面,受网上一位朋友的委托,该库模仿了其拖动效果。
72 | 探探的安卓工程师,应该特别牛逼吧。因为最初时,这种拖拽效果真的无从下手。反编译探探的源代码,发现它做了很严肃的混淆处理。然后用Hierarchy Viewer看了View的层级,这才有了一点点的思路。
73 | 在代码撰写的过程中,我也踩了不少坑。细看代码深处,或许你会有一丝丝的收获吧。
74 | 当然,在最初的最初,我搜了不少的draggable gridview的仓库,可惜用起来的时候发现不够流畅、不够灵活。
75 |
76 | ### 截图
77 |
78 |
79 |
80 |
81 | |
82 |
--------------------------------------------------------------------------------
/crop/src/androidTest/java/com/soundcloud/android/crop/CropBuilderTest.java:
--------------------------------------------------------------------------------
1 | package com.soundcloud.android.crop;
2 |
3 | import static org.fest.assertions.api.Assertions.assertThat;
4 | import static org.mockito.Mockito.mock;
5 | import static org.mockito.Mockito.when;
6 |
7 | import org.fest.assertions.api.ANDROID;
8 |
9 | import android.app.Activity;
10 | import android.content.Intent;
11 | import android.net.Uri;
12 | import android.provider.MediaStore;
13 |
14 | public class CropBuilderTest extends BaseTestCase {
15 |
16 | private Activity activity;
17 | private Crop builder;
18 |
19 | @Override
20 | public void setUp() throws Exception {
21 | super.setUp();
22 | activity = mock(Activity.class);
23 | when(activity.getPackageName()).thenReturn("com.example");
24 |
25 | builder = Crop.of(Uri.parse("image:input"), Uri.parse("image:output"));
26 | }
27 |
28 | public void testInputUriSetAsData() {
29 | ANDROID.assertThat(builder.getIntent(activity)).hasData("image:input");
30 | }
31 |
32 | public void testOutputUriSetAsExtra() {
33 | Intent intent = builder.getIntent(activity);
34 | Uri output = intent.getParcelableExtra(MediaStore.EXTRA_OUTPUT);
35 |
36 | assertThat(output.toString()).isEqualTo("image:output");
37 | }
38 |
39 | public void testAspectRatioSetAsExtras() {
40 | builder.withAspect(16, 10);
41 |
42 | Intent intent = builder.getIntent(activity);
43 |
44 | assertThat(intent.getIntExtra("aspect_x", 0)).isEqualTo(16);
45 | assertThat(intent.getIntExtra("aspect_y", 0)).isEqualTo(10);
46 | }
47 |
48 | public void testFixedAspectRatioSetAsExtras() {
49 | builder.asSquare();
50 |
51 | Intent intent = builder.getIntent(activity);
52 |
53 | assertThat(intent.getIntExtra("aspect_x", 0)).isEqualTo(1);
54 | assertThat(intent.getIntExtra("aspect_y", 0)).isEqualTo(1);
55 | }
56 |
57 | public void testMaxSizeSetAsExtras() {
58 | builder.withMaxSize(400, 300);
59 |
60 | Intent intent = builder.getIntent(activity);
61 |
62 | assertThat(intent.getIntExtra("max_x", 0)).isEqualTo(400);
63 | assertThat(intent.getIntExtra("max_y", 0)).isEqualTo(300);
64 | }
65 |
66 | public void testBuildsIntentWithMultipleOptions() {
67 | builder.asSquare().withMaxSize(200, 200);
68 |
69 | Intent intent = builder.getIntent(activity);
70 |
71 | assertThat(intent.getIntExtra("aspect_x", 0)).isEqualTo(1);
72 | assertThat(intent.getIntExtra("aspect_y", 0)).isEqualTo(1);
73 | assertThat(intent.getIntExtra("max_x", 0)).isEqualTo(200);
74 | assertThat(intent.getIntExtra("max_y", 0)).isEqualTo(200);
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/crop/src/main/java/com/soundcloud/android/crop/RotateBitmap.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2009 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.soundcloud.android.crop;
18 |
19 | import android.graphics.Bitmap;
20 | import android.graphics.Matrix;
21 |
22 | /*
23 | * Modified from original in AOSP.
24 | */
25 | class RotateBitmap {
26 |
27 | private Bitmap bitmap;
28 | private int rotation;
29 |
30 | public RotateBitmap(Bitmap bitmap, int rotation) {
31 | this.bitmap = bitmap;
32 | this.rotation = rotation % 360;
33 | }
34 |
35 | public void setRotation(int rotation) {
36 | this.rotation = rotation;
37 | }
38 |
39 | public int getRotation() {
40 | return rotation;
41 | }
42 |
43 | public Bitmap getBitmap() {
44 | return bitmap;
45 | }
46 |
47 | public void setBitmap(Bitmap bitmap) {
48 | this.bitmap = bitmap;
49 | }
50 |
51 | public Matrix getRotateMatrix() {
52 | // By default this is an identity matrix
53 | Matrix matrix = new Matrix();
54 | if (bitmap != null && rotation != 0) {
55 | // We want to do the rotation at origin, but since the bounding
56 | // rectangle will be changed after rotation, so the delta values
57 | // are based on old & new width/height respectively.
58 | int cx = bitmap.getWidth() / 2;
59 | int cy = bitmap.getHeight() / 2;
60 | matrix.preTranslate(-cx, -cy);
61 | matrix.postRotate(rotation);
62 | matrix.postTranslate(getWidth() / 2, getHeight() / 2);
63 | }
64 | return matrix;
65 | }
66 |
67 | public boolean isOrientationChanged() {
68 | return (rotation / 90) % 2 != 0;
69 | }
70 |
71 | public int getHeight() {
72 | if (bitmap == null) return 0;
73 | if (isOrientationChanged()) {
74 | return bitmap.getWidth();
75 | } else {
76 | return bitmap.getHeight();
77 | }
78 | }
79 |
80 | public int getWidth() {
81 | if (bitmap == null) return 0;
82 | if (isOrientationChanged()) {
83 | return bitmap.getHeight();
84 | } else {
85 | return bitmap.getWidth();
86 | }
87 | }
88 |
89 | public void recycle() {
90 | if (bitmap != null) {
91 | bitmap.recycle();
92 | bitmap = null;
93 | }
94 | }
95 | }
96 |
97 |
--------------------------------------------------------------------------------
/crop/src/main/java/com/soundcloud/android/crop/MonitoredActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2009 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.soundcloud.android.crop;
18 |
19 | import android.app.Activity;
20 | import android.os.Bundle;
21 |
22 | import java.util.ArrayList;
23 |
24 | /*
25 | * Modified from original in AOSP.
26 | */
27 | abstract class MonitoredActivity extends Activity {
28 |
29 | private final ArrayList listeners = new ArrayList();
30 |
31 | public static interface LifeCycleListener {
32 | public void onActivityCreated(MonitoredActivity activity);
33 | public void onActivityDestroyed(MonitoredActivity activity);
34 | public void onActivityStarted(MonitoredActivity activity);
35 | public void onActivityStopped(MonitoredActivity activity);
36 | }
37 |
38 | public static class LifeCycleAdapter implements LifeCycleListener {
39 | public void onActivityCreated(MonitoredActivity activity) {}
40 | public void onActivityDestroyed(MonitoredActivity activity) {}
41 | public void onActivityStarted(MonitoredActivity activity) {}
42 | public void onActivityStopped(MonitoredActivity activity) {}
43 | }
44 |
45 | public void addLifeCycleListener(LifeCycleListener listener) {
46 | if (listeners.contains(listener)) return;
47 | listeners.add(listener);
48 | }
49 |
50 | public void removeLifeCycleListener(LifeCycleListener listener) {
51 | listeners.remove(listener);
52 | }
53 |
54 | @Override
55 | protected void onCreate(Bundle savedInstanceState) {
56 | super.onCreate(savedInstanceState);
57 | for (LifeCycleListener listener : listeners) {
58 | listener.onActivityCreated(this);
59 | }
60 | }
61 |
62 | @Override
63 | protected void onDestroy() {
64 | super.onDestroy();
65 | for (LifeCycleListener listener : listeners) {
66 | listener.onActivityDestroyed(this);
67 | }
68 | }
69 |
70 | @Override
71 | protected void onStart() {
72 | super.onStart();
73 | for (LifeCycleListener listener : listeners) {
74 | listener.onActivityStarted(this);
75 | }
76 | }
77 |
78 | @Override
79 | protected void onStop() {
80 | super.onStop();
81 | for (LifeCycleListener listener : listeners) {
82 | listener.onActivityStopped(this);
83 | }
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/app/src/main/java/com/stone/dragsquare/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.stone.dragsquare;
2 |
3 | import android.content.ContentResolver;
4 | import android.content.Intent;
5 | import android.database.Cursor;
6 | import android.net.Uri;
7 | import android.os.Bundle;
8 | import android.provider.MediaStore;
9 | import android.support.v7.app.AppCompatActivity;
10 | import android.util.SparseArray;
11 | import android.view.MenuItem;
12 | import android.view.View;
13 | import android.widget.TextView;
14 |
15 | import com.swifty.dragsquareimage.DraggablePresenter;
16 | import com.swifty.dragsquareimage.DraggablePresenterImpl;
17 | import com.swifty.dragsquareimage.DraggableSquareView;
18 |
19 | public class MainActivity extends AppCompatActivity {
20 |
21 | private TextView contentText;
22 | private DraggablePresenter draggablePresent;
23 |
24 | @Override
25 | protected void onCreate(Bundle savedInstanceState) {
26 | super.onCreate(savedInstanceState);
27 | setContentView(R.layout.activity_main);
28 | getSupportActionBar().setTitle("编辑个人资料");
29 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
30 |
31 | DraggableSquareView dragSquare = (DraggableSquareView) findViewById(R.id.drag_square);
32 | contentText = (TextView) findViewById(R.id.contentText);
33 | draggablePresent = new DraggablePresenterImpl(this, dragSquare);
34 | draggablePresent.setCustomActionDialog(new MyActionDialog(this));
35 | draggablePresent.setImages(new String[]{"http://lorempixel.com/400/400?flag=0", "http://lorempixel.com/400/400?flag=1", "http://lorempixel.com/400/400?flag=2", "http://lorempixel.com/400/400?flag=3", "http://lorempixel.com/400/400?flag=4", "http://lorempixel.com/400/400?flag=5"});
36 | }
37 |
38 |
39 | @Override
40 | public boolean onOptionsItemSelected(MenuItem item) {
41 | switch (item.getItemId()) {
42 | case android.R.id.home:// 点击返回图标事件
43 | this.finish();
44 | default:
45 | return super.onOptionsItemSelected(item);
46 | }
47 | }
48 |
49 |
50 | @Override
51 | protected void onActivityResult(int requestCode, int resultCode, Intent result) {
52 | draggablePresent.onActivityResult(requestCode, resultCode, result);
53 | }
54 |
55 | /**
56 | * 根据Uri获取图片文件的绝对路径
57 | */
58 | public String getAbsolutePath(final Uri uri) {
59 | if (null == uri) {
60 | return null;
61 | }
62 |
63 | final String scheme = uri.getScheme();
64 | String data = null;
65 | if (scheme == null) {
66 | data = uri.getPath();
67 | } else if (ContentResolver.SCHEME_FILE.equals(scheme)) {
68 | data = uri.getPath();
69 | } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
70 | Cursor cursor = getContentResolver().query(uri,
71 | new String[]{MediaStore.Images.ImageColumns.DATA}, null, null, null);
72 | if (null != cursor) {
73 | if (cursor.moveToFirst()) {
74 | int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
75 | if (index > -1) {
76 | data = cursor.getString(index);
77 | }
78 | }
79 | cursor.close();
80 | }
81 | }
82 | return data;
83 | }
84 |
85 | public void showUrls(View view) {
86 | SparseArray array = draggablePresent.getImageUrls();
87 | if (array == null) return;
88 | StringBuilder stringBuffer = new StringBuilder();
89 | for (int i = 0; i < array.size(); i++) {
90 | String o = array.get(array.keyAt(i));
91 | stringBuffer.append(i).append(":").append(o).append("\n");
92 | }
93 | contentText.setText(stringBuffer.toString());
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/dragsquareimage/src/main/java/com/swifty/dragsquareimage/DraggablePresenterImpl.java:
--------------------------------------------------------------------------------
1 | package com.swifty.dragsquareimage;
2 |
3 | import android.app.Activity;
4 | import android.app.Fragment;
5 | import android.content.Intent;
6 | import android.net.Uri;
7 | import android.support.annotation.NonNull;
8 | import android.util.SparseArray;
9 | import android.widget.Toast;
10 |
11 | import com.soundcloud.android.crop.Crop;
12 |
13 | import java.io.File;
14 |
15 | /**
16 | * Created by swifty on 29/11/2016.
17 | */
18 |
19 | public class DraggablePresenterImpl implements DraggablePresenter, DraggableSquareView.Listener {
20 | private final DraggableSquareView dragSquare;
21 | private int imageStatus;
22 | private boolean isModify;
23 | private Activity activity;
24 | private Fragment fragment;
25 |
26 | public DraggablePresenterImpl(@NonNull Activity activity, @NonNull DraggableSquareView dragSquare) {
27 | this.activity = activity;
28 | this.dragSquare = dragSquare;
29 | this.dragSquare.post(new Runnable() {
30 | @Override
31 | public void run() {
32 | DraggablePresenterImpl.this.dragSquare.requestLayout();
33 | }
34 | });
35 | dragSquare.setListener(this);
36 | }
37 |
38 | public DraggablePresenterImpl(@NonNull Fragment fragment, @NonNull DraggableSquareView dragSquare) {
39 | this.fragment = fragment;
40 | this.dragSquare = dragSquare;
41 | this.dragSquare.post(new Runnable() {
42 | @Override
43 | public void run() {
44 | DraggablePresenterImpl.this.dragSquare.requestLayout();
45 | }
46 | });
47 | dragSquare.setListener(this);
48 | }
49 |
50 | @Override
51 | public void onActivityResult(int requestCode, int resultCode, Intent result) {
52 | if (requestCode == Crop.REQUEST_PHOTO && resultCode == Activity.RESULT_OK) {
53 | beginCrop(Crop.getOutputFileUri());
54 | } else if (requestCode == Crop.REQUEST_PICK && resultCode == Activity.RESULT_OK) {
55 | beginCrop(result.getData());
56 | } else if (requestCode == Crop.REQUEST_CROP) {
57 | Crop.clearCacheFile();
58 | handleCrop(resultCode, result);
59 | }
60 | }
61 |
62 | @Override
63 | public void beginCrop(Uri source) {
64 | if (activity != null) {
65 | Uri destination = Uri.fromFile(new File(activity.getCacheDir(), "cropped_" + System.currentTimeMillis() + ".jpg"));
66 | Crop.of(source, destination).asSquare().start(activity);
67 | } else if (fragment != null) {
68 | Uri destination = Uri.fromFile(new File(fragment.getActivity().getCacheDir(), "cropped_" + System.currentTimeMillis() + ".jpg"));
69 | Crop.of(source, destination).asSquare().start(fragment.getActivity(), fragment);
70 | }
71 | }
72 |
73 | @Override
74 | public void handleCrop(int resultCode, Intent result) {
75 | if (resultCode == Activity.RESULT_OK) {
76 | Uri uri = Crop.getOutput(result);
77 | String imagePath = uri.toString();
78 | dragSquare.fillItemImage(imageStatus, imagePath, isModify);
79 |
80 | } else if (resultCode == Crop.RESULT_ERROR) {
81 | Toast.makeText(dragSquare.getContext(), Crop.getError(result).getMessage(), Toast.LENGTH_SHORT).show();
82 | }
83 | }
84 |
85 | @Override
86 | public void pickImage(int imageStatus, boolean isModify) {
87 | this.imageStatus = imageStatus;
88 | this.isModify = isModify;
89 | if (activity != null) {
90 | Crop.pickImage(activity);
91 | } else if (fragment != null) {
92 | Crop.pickImage(fragment);
93 | }
94 | }
95 |
96 | @Override
97 | public void takePhoto(int imageStatus, boolean isModify) {
98 | this.imageStatus = imageStatus;
99 | this.isModify = isModify;
100 | if (activity != null) {
101 | Crop.takePhoto(activity);
102 | } else if (fragment != null) {
103 | Crop.takePhoto(fragment);
104 | }
105 | }
106 |
107 | @Override
108 | public SparseArray getImageUrls() {
109 | return dragSquare.getImageUrls();
110 | }
111 |
112 | @Override
113 | public void setImages(String... imageUrls) {
114 | if (imageUrls == null) return;
115 | for (int i = 0; i < (imageUrls.length > dragSquare.getImageSetSize() ? dragSquare.getImageSetSize() : imageUrls.length); i++) {
116 | dragSquare.fillItemImage(imageStatus, imageUrls[i], false);
117 | }
118 | }
119 |
120 | @Override
121 | public void setCustomActionDialog(ActionDialog actionDialog) {
122 | dragSquare.setCustomActionDialog(actionDialog);
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/crop/src/main/java/com/soundcloud/android/crop/CropImageView.java:
--------------------------------------------------------------------------------
1 | package com.soundcloud.android.crop;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Rect;
6 | import android.support.annotation.NonNull;
7 | import android.util.AttributeSet;
8 | import android.view.MotionEvent;
9 |
10 | import java.util.ArrayList;
11 |
12 | public class CropImageView extends ImageViewTouchBase {
13 |
14 | ArrayList highlightViews = new ArrayList();
15 | HighlightView motionHighlightView;
16 | Context context;
17 |
18 | private float lastX;
19 | private float lastY;
20 | private int motionEdge;
21 | private int validPointerId;
22 |
23 | public CropImageView(Context context) {
24 | super(context);
25 | }
26 |
27 | public CropImageView(Context context, AttributeSet attrs) {
28 | super(context, attrs);
29 | }
30 |
31 | public CropImageView(Context context, AttributeSet attrs, int defStyle) {
32 | super(context, attrs, defStyle);
33 | }
34 |
35 | @Override
36 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
37 | super.onLayout(changed, left, top, right, bottom);
38 | if (bitmapDisplayed.getBitmap() != null) {
39 | for (HighlightView hv : highlightViews) {
40 |
41 | hv.matrix.set(getUnrotatedMatrix());
42 | hv.invalidate();
43 | if (hv.hasFocus()) {
44 | centerBasedOnHighlightView(hv);
45 | }
46 | }
47 | }
48 | }
49 |
50 | @Override
51 | protected void zoomTo(float scale, float centerX, float centerY) {
52 | super.zoomTo(scale, centerX, centerY);
53 | for (HighlightView hv : highlightViews) {
54 | hv.matrix.set(getUnrotatedMatrix());
55 | hv.invalidate();
56 | }
57 | }
58 |
59 | @Override
60 | protected void zoomIn() {
61 | super.zoomIn();
62 | for (HighlightView hv : highlightViews) {
63 | hv.matrix.set(getUnrotatedMatrix());
64 | hv.invalidate();
65 | }
66 | }
67 |
68 | @Override
69 | protected void zoomOut() {
70 | super.zoomOut();
71 | for (HighlightView hv : highlightViews) {
72 | hv.matrix.set(getUnrotatedMatrix());
73 | hv.invalidate();
74 | }
75 | }
76 |
77 | @Override
78 | protected void postTranslate(float deltaX, float deltaY) {
79 | super.postTranslate(deltaX, deltaY);
80 | for (HighlightView hv : highlightViews) {
81 | hv.matrix.postTranslate(deltaX, deltaY);
82 | hv.invalidate();
83 | }
84 | }
85 |
86 | @Override
87 | public boolean onTouchEvent(@NonNull MotionEvent event) {
88 | CropImageActivity cropImageActivity = (CropImageActivity) context;
89 | if (cropImageActivity.isSaving()) {
90 | return false;
91 | }
92 |
93 | switch (event.getAction()) {
94 | case MotionEvent.ACTION_DOWN:
95 | for (HighlightView hv : highlightViews) {
96 | int edge = hv.getHit(event.getX(), event.getY());
97 | if (edge != HighlightView.GROW_NONE) {
98 | motionEdge = edge;
99 | motionHighlightView = hv;
100 | lastX = event.getX();
101 | lastY = event.getY();
102 | // Prevent multiple touches from interfering with crop area re-sizing
103 | validPointerId = event.getPointerId(event.getActionIndex());
104 | motionHighlightView.setMode((edge == HighlightView.MOVE)
105 | ? HighlightView.ModifyMode.Move
106 | : HighlightView.ModifyMode.Grow);
107 | break;
108 | }
109 | }
110 | break;
111 | case MotionEvent.ACTION_UP:
112 | if (motionHighlightView != null) {
113 | centerBasedOnHighlightView(motionHighlightView);
114 | motionHighlightView.setMode(HighlightView.ModifyMode.None);
115 | }
116 | motionHighlightView = null;
117 | center();
118 | break;
119 | case MotionEvent.ACTION_MOVE:
120 | if (motionHighlightView != null && event.getPointerId(event.getActionIndex()) == validPointerId) {
121 | motionHighlightView.handleMotion(motionEdge, event.getX()
122 | - lastX, event.getY() - lastY);
123 | lastX = event.getX();
124 | lastY = event.getY();
125 | }
126 |
127 | // If we're not zoomed then there's no point in even allowing the user to move the image around.
128 | // This call to center puts it back to the normalized location.
129 | if (getScale() == 1F) {
130 | center();
131 | }
132 | break;
133 | }
134 |
135 | return true;
136 | }
137 |
138 | // Pan the displayed image to make sure the cropping rectangle is visible.
139 | private void ensureVisible(HighlightView hv) {
140 | Rect r = hv.drawRect;
141 |
142 | int panDeltaX1 = Math.max(0, getLeft() - r.left);
143 | int panDeltaX2 = Math.min(0, getRight() - r.right);
144 |
145 | int panDeltaY1 = Math.max(0, getTop() - r.top);
146 | int panDeltaY2 = Math.min(0, getBottom() - r.bottom);
147 |
148 | int panDeltaX = panDeltaX1 != 0 ? panDeltaX1 : panDeltaX2;
149 | int panDeltaY = panDeltaY1 != 0 ? panDeltaY1 : panDeltaY2;
150 |
151 | if (panDeltaX != 0 || panDeltaY != 0) {
152 | panBy(panDeltaX, panDeltaY);
153 | }
154 | }
155 |
156 | // If the cropping rectangle's size changed significantly, change the
157 | // view's center and scale according to the cropping rectangle.
158 | private void centerBasedOnHighlightView(HighlightView hv) {
159 | Rect drawRect = hv.drawRect;
160 |
161 | float width = drawRect.width();
162 | float height = drawRect.height();
163 |
164 | float thisWidth = getWidth();
165 | float thisHeight = getHeight();
166 |
167 | float z1 = thisWidth / width * .6F;
168 | float z2 = thisHeight / height * .6F;
169 |
170 | float zoom = Math.min(z1, z2);
171 | zoom = zoom * this.getScale();
172 | zoom = Math.max(1F, zoom);
173 |
174 | if ((Math.abs(zoom - getScale()) / zoom) > .1) {
175 | float[] coordinates = new float[] { hv.cropRect.centerX(), hv.cropRect.centerY() };
176 | getUnrotatedMatrix().mapPoints(coordinates);
177 | zoomTo(zoom, coordinates[0], coordinates[1], 300F);
178 | }
179 |
180 | ensureVisible(hv);
181 | }
182 |
183 | @Override
184 | protected void onDraw(@NonNull Canvas canvas) {
185 | super.onDraw(canvas);
186 | for (HighlightView highlightView : highlightViews) {
187 | highlightView.draw(canvas);
188 | }
189 | }
190 |
191 | public void add(HighlightView hv) {
192 | highlightViews.add(hv);
193 | invalidate();
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/crop/src/main/java/com/soundcloud/android/crop/CropUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2009 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.soundcloud.android.crop;
18 |
19 | import android.app.ProgressDialog;
20 | import android.content.ContentResolver;
21 | import android.content.Context;
22 | import android.database.Cursor;
23 | import android.media.ExifInterface;
24 | import android.net.Uri;
25 | import android.os.Environment;
26 | import android.os.Handler;
27 | import android.os.ParcelFileDescriptor;
28 | import android.provider.MediaStore;
29 | import android.support.annotation.Nullable;
30 | import android.text.TextUtils;
31 |
32 | import java.io.Closeable;
33 | import java.io.File;
34 | import java.io.FileDescriptor;
35 | import java.io.FileInputStream;
36 | import java.io.FileOutputStream;
37 | import java.io.IOException;
38 |
39 | /*
40 | * Modified from original in AOSP.
41 | */
42 | class CropUtil {
43 |
44 | private static final String SCHEME_FILE = "file";
45 | private static final String SCHEME_CONTENT = "content";
46 |
47 | public static void closeSilently(@Nullable Closeable c) {
48 | if (c == null) return;
49 | try {
50 | c.close();
51 | } catch (Throwable t) {
52 | // Do nothing
53 | }
54 | }
55 |
56 | public static int getExifRotation(File imageFile) {
57 | if (imageFile == null) return 0;
58 | try {
59 | ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
60 | // We only recognize a subset of orientation tag values
61 | switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)) {
62 | case ExifInterface.ORIENTATION_ROTATE_90:
63 | return 90;
64 | case ExifInterface.ORIENTATION_ROTATE_180:
65 | return 180;
66 | case ExifInterface.ORIENTATION_ROTATE_270:
67 | return 270;
68 | default:
69 | return ExifInterface.ORIENTATION_UNDEFINED;
70 | }
71 | } catch (IOException e) {
72 | Log.e("Error getting Exif data", e);
73 | return 0;
74 | }
75 | }
76 |
77 | public static boolean copyExifRotation(File sourceFile, File destFile) {
78 | if (sourceFile == null || destFile == null) return false;
79 | try {
80 | ExifInterface exifSource = new ExifInterface(sourceFile.getAbsolutePath());
81 | ExifInterface exifDest = new ExifInterface(destFile.getAbsolutePath());
82 | exifDest.setAttribute(ExifInterface.TAG_ORIENTATION, exifSource.getAttribute(ExifInterface.TAG_ORIENTATION));
83 | exifDest.saveAttributes();
84 | return true;
85 | } catch (IOException e) {
86 | Log.e("Error copying Exif data", e);
87 | return false;
88 | }
89 | }
90 |
91 | @Nullable
92 | public static File getFromMediaUri(Context context, ContentResolver resolver, Uri uri) {
93 | if (uri == null) return null;
94 |
95 | if (SCHEME_FILE.equals(uri.getScheme())) {
96 | return new File(uri.getPath());
97 | } else if (SCHEME_CONTENT.equals(uri.getScheme())) {
98 | final String[] filePathColumn = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME};
99 | Cursor cursor = null;
100 | try {
101 | cursor = resolver.query(uri, filePathColumn, null, null, null);
102 | if (cursor != null && cursor.moveToFirst()) {
103 | final int columnIndex = (uri.toString().startsWith("content://com.google.android.gallery3d")) ?
104 | cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME) :
105 | cursor.getColumnIndex(MediaStore.MediaColumns.DATA);
106 | // Picasa images on API 13+
107 | if (columnIndex != -1) {
108 | String filePath = cursor.getString(columnIndex);
109 | if (!TextUtils.isEmpty(filePath)) {
110 | return new File(filePath);
111 | }
112 | }
113 | }
114 | } catch (IllegalArgumentException e) {
115 | // Google Drive images
116 | return getFromMediaUriPfd(context, resolver, uri);
117 | } catch (SecurityException ignored) {
118 | // Nothing we can do
119 | } finally {
120 | if (cursor != null) cursor.close();
121 | }
122 | }
123 | return null;
124 | }
125 |
126 | private static String getTempFilename(Context context) throws IOException {
127 | File outputDir = context.getCacheDir();
128 | File outputFile = File.createTempFile("image", "tmp", outputDir);
129 | return outputFile.getAbsolutePath();
130 | }
131 |
132 | @Nullable
133 | private static File getFromMediaUriPfd(Context context, ContentResolver resolver, Uri uri) {
134 | if (uri == null) return null;
135 |
136 | FileInputStream input = null;
137 | FileOutputStream output = null;
138 | try {
139 | ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
140 | FileDescriptor fd = pfd.getFileDescriptor();
141 | input = new FileInputStream(fd);
142 |
143 | String tempFilename = getTempFilename(context);
144 | output = new FileOutputStream(tempFilename);
145 |
146 | int read;
147 | byte[] bytes = new byte[4096];
148 | while ((read = input.read(bytes)) != -1) {
149 | output.write(bytes, 0, read);
150 | }
151 | return new File(tempFilename);
152 | } catch (IOException ignored) {
153 | // Nothing we can do
154 | } finally {
155 | closeSilently(input);
156 | closeSilently(output);
157 | }
158 | return null;
159 | }
160 |
161 | public static void startBackgroundJob(MonitoredActivity activity,
162 | String title, String message, Runnable job, Handler handler) {
163 | // Make the progress dialog uncancelable, so that we can guarantee
164 | // the thread will be done before the activity getting destroyed
165 | ProgressDialog dialog = ProgressDialog.show(
166 | activity, title, message, true, false);
167 | new Thread(new BackgroundJob(activity, job, dialog, handler)).start();
168 | }
169 |
170 | private static class BackgroundJob extends MonitoredActivity.LifeCycleAdapter implements Runnable {
171 |
172 | private final MonitoredActivity activity;
173 | private final ProgressDialog dialog;
174 | private final Runnable job;
175 | private final Handler handler;
176 | private final Runnable cleanupRunner = new Runnable() {
177 | public void run() {
178 | activity.removeLifeCycleListener(BackgroundJob.this);
179 | if (dialog.getWindow() != null) dialog.dismiss();
180 | }
181 | };
182 |
183 | public BackgroundJob(MonitoredActivity activity, Runnable job,
184 | ProgressDialog dialog, Handler handler) {
185 | this.activity = activity;
186 | this.dialog = dialog;
187 | this.job = job;
188 | this.activity.addLifeCycleListener(this);
189 | this.handler = handler;
190 | }
191 |
192 | public void run() {
193 | try {
194 | job.run();
195 | } finally {
196 | handler.post(cleanupRunner);
197 | }
198 | }
199 |
200 | @Override
201 | public void onActivityDestroyed(MonitoredActivity activity) {
202 | // We get here only when the onDestroyed being called before
203 | // the cleanupRunner. So, run it now and remove it from the queue
204 | cleanupRunner.run();
205 | handler.removeCallbacks(cleanupRunner);
206 | }
207 |
208 | @Override
209 | public void onActivityStopped(MonitoredActivity activity) {
210 | dialog.hide();
211 | }
212 |
213 | @Override
214 | public void onActivityStarted(MonitoredActivity activity) {
215 | dialog.show();
216 | }
217 | }
218 |
219 | public static File createImageFile() {
220 | try {
221 | return File.createTempFile(getImageFileName()[0], getImageFileName()[1],
222 | Environment.getExternalStorageDirectory());
223 | } catch (IOException e) {
224 | //do noting
225 | return null;
226 | }
227 | }
228 |
229 | public static String[] getImageFileName() {
230 | return new String[]{"TEMP_PHOTO", ".jpg"};
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/dragsquareimage/src/main/java/com/swifty/dragsquareimage/DraggableItemView.java:
--------------------------------------------------------------------------------
1 | package com.swifty.dragsquareimage;
2 |
3 | import android.animation.ObjectAnimator;
4 | import android.content.Context;
5 | import android.graphics.Point;
6 | import android.util.AttributeSet;
7 | import android.view.View;
8 | import android.view.ViewTreeObserver;
9 | import android.view.animation.DecelerateInterpolator;
10 | import android.widget.FrameLayout;
11 | import android.widget.ImageView;
12 |
13 | import com.facebook.rebound.SimpleSpringListener;
14 | import com.facebook.rebound.Spring;
15 | import com.facebook.rebound.SpringConfig;
16 | import com.facebook.rebound.SpringSystem;
17 | import com.squareup.picasso.Picasso;
18 |
19 | /**
20 | * Created by xmuSistone on 2016/5/23.
21 | */
22 | public class DraggableItemView extends FrameLayout implements ActionDialogClick {
23 |
24 | public static final int STATUS_LEFT_TOP = 0;
25 | public static final int STATUS_RIGHT_TOP = 1;
26 | public static final int STATUS_RIGHT_MIDDLE = 2;
27 | public static final int STATUS_RIGHT_BOTTOM = 3;
28 | public static final int STATUS_MIDDLE_BOTTOM = 4;
29 | public static final int STATUS_LEFT_BOTTOM = 5;
30 |
31 | public static final int SCALE_LEVEL_1 = 1; // 最大状态,缩放比例是100%
32 | public static final int SCALE_LEVEL_2 = 2; // 中间状态,缩放比例scaleRate
33 | public static final int SCALE_LEVEL_3 = 3; // 最小状态,缩放比例是smallerRate
34 |
35 | private ImageView imageView;
36 | private Listener listener;
37 | private View maskView;
38 | private int status;
39 | private float scaleRate = 0.5f;
40 | private float smallerRate = scaleRate * 0.9f;
41 | private Spring springX, springY;
42 | private ObjectAnimator scaleAnimator;
43 | private boolean hasSetCurrentSpringValue = false;
44 | private DraggableSquareView parentView;
45 | private SpringConfig springConfigCommon = SpringConfig.fromOrigamiTensionAndFriction(140, 7);
46 | private int moveDstX = Integer.MIN_VALUE, moveDstY = Integer.MIN_VALUE;
47 |
48 | private String imagePath;
49 | private View addView;
50 |
51 | public DraggableItemView(Context context) {
52 | this(context, null);
53 | }
54 |
55 | public DraggableItemView(Context context, AttributeSet attrs) {
56 | this(context, attrs, 0);
57 | }
58 |
59 | public DraggableItemView(Context context, AttributeSet attrs, int defStyleAttr) {
60 | super(context, attrs, defStyleAttr);
61 | inflate(context, R.layout.drag_item, this);
62 | imageView = (ImageView) findViewById(R.id.drag_item_imageview);
63 | maskView = findViewById(R.id.drag_item_mask_view);
64 | addView = findViewById(R.id.add_view);
65 |
66 | getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
67 | @Override
68 | public void onGlobalLayout() {
69 | if (!hasSetCurrentSpringValue) {
70 | adjustImageView();
71 | hasSetCurrentSpringValue = true;
72 | }
73 | }
74 | });
75 |
76 | maskView.setOnClickListener(new View.OnClickListener() {
77 | @Override
78 | public void onClick(View v) {
79 | if (!isDraggable()) {
80 | if (parentView.getActionDialog() == null) {
81 | DefaultActionDialog dialog = new DefaultActionDialog(getContext());
82 | dialog.setActionDialogClick(DraggableItemView.this)
83 | .setShowDeleteButton(false)
84 | .show();
85 | } else {
86 | parentView.getActionDialog()
87 | .setActionDialogClick(DraggableItemView.this)
88 | .setShowDeleteButton(false)
89 | .show();
90 | }
91 | } else {
92 | if (parentView.getActionDialog() == null) {
93 | DefaultActionDialog dialog = new DefaultActionDialog(getContext());
94 | dialog.setActionDialogClick(DraggableItemView.this)
95 | .setShowDeleteButton(true)
96 | .show();
97 | } else {
98 | parentView.getActionDialog().setActionDialogClick(DraggableItemView.this);
99 | parentView.getActionDialog()
100 | .setActionDialogClick(DraggableItemView.this)
101 | .setShowDeleteButton(true)
102 | .show();
103 | }
104 | }
105 | }
106 | });
107 |
108 | initSpring();
109 | }
110 |
111 | @Override
112 | public void onTakePhotoClick(View view) {
113 | if (listener != null) listener.takePhoto(status, isDraggable());
114 | }
115 |
116 | @Override
117 | public void onPickImageClick(View view) {
118 | if (listener != null) listener.pickImage(status, isDraggable());
119 | }
120 |
121 | @Override
122 | public void onDeleteClick(View view) {
123 | imagePath = null;
124 | imageView.setImageBitmap(null);
125 | addView.setVisibility(View.VISIBLE);
126 | parentView.onDeleteImage(DraggableItemView.this);
127 | }
128 |
129 | public void setListener(Listener listener) {
130 | this.listener = listener;
131 | }
132 |
133 | public interface Listener {
134 | void pickImage(int imageStatus, boolean isModify);
135 |
136 | void takePhoto(int imageStatus, boolean isModify);
137 | }
138 |
139 | /**
140 | * 初始化Spring相关
141 | */
142 | private void initSpring() {
143 | SpringSystem mSpringSystem = SpringSystem.create();
144 | springX = mSpringSystem.createSpring();
145 | springY = mSpringSystem.createSpring();
146 |
147 | springX.addListener(new SimpleSpringListener() {
148 | @Override
149 | public void onSpringUpdate(Spring spring) {
150 | int xPos = (int) spring.getCurrentValue();
151 | setScreenX(xPos);
152 | }
153 | });
154 |
155 | springY.addListener(new SimpleSpringListener() {
156 | @Override
157 | public void onSpringUpdate(Spring spring) {
158 | int yPos = (int) spring.getCurrentValue();
159 | setScreenY(yPos);
160 | }
161 | });
162 |
163 | springX.setSpringConfig(springConfigCommon);
164 | springY.setSpringConfig(springConfigCommon);
165 | }
166 |
167 | /**
168 | * 调整ImageView的宽度和高度各为FrameLayout的一半
169 | */
170 | private void adjustImageView() {
171 | if (status != STATUS_LEFT_TOP) {
172 | imageView.setScaleX(scaleRate);
173 | imageView.setScaleY(scaleRate);
174 |
175 | maskView.setScaleX(scaleRate);
176 | maskView.setScaleY(scaleRate);
177 | }
178 |
179 | setCurrentSpringPos(getLeft(), getTop());
180 | }
181 |
182 | public void setScaleRate(float scaleRate) {
183 | this.scaleRate = scaleRate;
184 | this.smallerRate = scaleRate * 0.9f;
185 | }
186 |
187 | /**
188 | * 从一个状态切换到另一个状态
189 | */
190 | public void switchPosition(int toStatus) {
191 | if (this.status == toStatus) {
192 | throw new RuntimeException("程序错乱");
193 | }
194 |
195 | if (toStatus == STATUS_LEFT_TOP) {
196 | scaleSize(SCALE_LEVEL_1);
197 | } else if (this.status == STATUS_LEFT_TOP) {
198 | scaleSize(SCALE_LEVEL_2);
199 | }
200 |
201 | this.status = toStatus;
202 | Point point = parentView.getOriginViewPos(status);
203 | this.moveDstX = point.x;
204 | this.moveDstY = point.y;
205 | animTo(moveDstX, moveDstY);
206 | }
207 |
208 | public void animTo(int xPos, int yPos) {
209 | springX.setEndValue(xPos);
210 | springY.setEndValue(yPos);
211 | }
212 |
213 | /**
214 | * 设置缩放大小
215 | */
216 | public void scaleSize(int scaleLevel) {
217 | float rate = scaleRate;
218 | if (scaleLevel == SCALE_LEVEL_1) {
219 | rate = 1.0f;
220 | } else if (scaleLevel == SCALE_LEVEL_3) {
221 | rate = smallerRate;
222 | }
223 |
224 | if (scaleAnimator != null && scaleAnimator.isRunning()) {
225 | scaleAnimator.cancel();
226 | }
227 |
228 | scaleAnimator = ObjectAnimator
229 | .ofFloat(this, "custScale", imageView.getScaleX(), rate)
230 | .setDuration(200);
231 | scaleAnimator.setInterpolator(new DecelerateInterpolator());
232 | scaleAnimator.start();
233 | }
234 |
235 | public void saveAnchorInfo(int downX, int downY) {
236 | int halfSide = getMeasuredWidth() / 2;
237 | moveDstX = downX - halfSide;
238 | moveDstY = downY - halfSide;
239 | }
240 |
241 | /**
242 | * 真正开始动画
243 | */
244 | public void startAnchorAnimation() {
245 | if (moveDstX == Integer.MIN_VALUE || moveDstX == Integer.MIN_VALUE) {
246 | return;
247 | }
248 |
249 | springX.setOvershootClampingEnabled(true);
250 | springY.setOvershootClampingEnabled(true);
251 | animTo(moveDstX, moveDstY);
252 | scaleSize(DraggableItemView.SCALE_LEVEL_3);
253 | }
254 |
255 | public void setScreenX(int screenX) {
256 | this.offsetLeftAndRight(screenX - getLeft());
257 | }
258 |
259 | public void setScreenY(int screenY) {
260 | this.offsetTopAndBottom(screenY - getTop());
261 | }
262 |
263 | public int computeDraggingX(int dx) {
264 | this.moveDstX += dx;
265 | return this.moveDstX;
266 | }
267 |
268 | public int computeDraggingY(int dy) {
269 | this.moveDstY += dy;
270 | return this.moveDstY;
271 | }
272 |
273 | /**
274 | * 设置当前spring位置
275 | */
276 | private void setCurrentSpringPos(int xPos, int yPos) {
277 | springX.setCurrentValue(xPos);
278 | springY.setCurrentValue(yPos);
279 | }
280 |
281 | public void setStatus(int status) {
282 | this.status = status;
283 | }
284 |
285 | public int getStatus() {
286 | return status;
287 | }
288 |
289 | public void setParentView(DraggableSquareView parentView) {
290 | this.parentView = parentView;
291 | }
292 |
293 | public void onDragRelease() {
294 | if (status == DraggableItemView.STATUS_LEFT_TOP) {
295 | scaleSize(DraggableItemView.SCALE_LEVEL_1);
296 | } else {
297 | scaleSize(DraggableItemView.SCALE_LEVEL_2);
298 | }
299 |
300 | springX.setOvershootClampingEnabled(false);
301 | springY.setOvershootClampingEnabled(false);
302 | springX.setSpringConfig(springConfigCommon);
303 | springY.setSpringConfig(springConfigCommon);
304 |
305 | Point point = parentView.getOriginViewPos(status);
306 | setCurrentSpringPos(getLeft(), getTop());
307 | this.moveDstX = point.x;
308 | this.moveDstY = point.y;
309 | animTo(moveDstX, moveDstY);
310 | }
311 |
312 | public void fillImageView(String imagePath) {
313 | this.imagePath = imagePath;
314 | addView.setVisibility(View.GONE);
315 | Picasso.with(getContext()).load(imagePath).into(imageView);
316 | }
317 |
318 | // 以下两个get、set方法是为自定义的属性动画CustScale服务,不能删
319 | public void setCustScale(float scale) {
320 | imageView.setScaleX(scale);
321 | imageView.setScaleY(scale);
322 |
323 | maskView.setScaleX(scale);
324 | maskView.setScaleY(scale);
325 | }
326 |
327 | public float getCustScale() {
328 | return imageView.getScaleX();
329 | }
330 |
331 | public void updateEndSpringX(int dx) {
332 | springX.setEndValue(springX.getEndValue() + dx);
333 | }
334 |
335 | public void updateEndSpringY(int dy) {
336 | springY.setEndValue(springY.getEndValue() + dy);
337 | }
338 |
339 | public boolean isDraggable() {
340 | return imagePath != null;
341 | }
342 |
343 | public String getImagePath() {
344 | return imagePath;
345 | }
346 | }
347 |
348 |
--------------------------------------------------------------------------------
/crop/src/main/java/com/soundcloud/android/crop/Crop.java:
--------------------------------------------------------------------------------
1 | package com.soundcloud.android.crop;
2 |
3 | import android.annotation.TargetApi;
4 | import android.app.Activity;
5 | import android.app.Fragment;
6 | import android.content.ActivityNotFoundException;
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import android.net.Uri;
10 | import android.os.Build;
11 | import android.provider.MediaStore;
12 | import android.support.v4.content.FileProvider;
13 | import android.widget.Toast;
14 |
15 | import java.io.File;
16 |
17 | /**
18 | * Builder for crop Intents and utils for handling result
19 | */
20 | public class Crop {
21 |
22 | public static final String TAG = "Crop";
23 | public static final int REQUEST_CROP = 6709;
24 | public static final int REQUEST_PICK = 9162;
25 | public static final int REQUEST_PHOTO = 9163;
26 | public static final int RESULT_ERROR = 404;
27 | private static Uri outputFileUri;
28 |
29 | public static Uri getOutputFileUri() {
30 | return outputFileUri;
31 | }
32 |
33 | public static void clearCacheFile() {
34 | try {
35 | if (outputFileUri != null) {
36 | File file = new File(outputFileUri.getPath());
37 | if (file.exists()) file.delete();
38 | }
39 | } catch (Exception e) {
40 | Log.e(TAG, e);
41 | }
42 | }
43 |
44 | interface Extra {
45 | String ASPECT_X = "aspect_x";
46 | String ASPECT_Y = "aspect_y";
47 | String MAX_X = "max_x";
48 | String MAX_Y = "max_y";
49 | String ERROR = "error";
50 | }
51 |
52 | private Intent cropIntent;
53 |
54 | /**
55 | * Create a crop Intent builder with source and destination image Uris
56 | *
57 | * @param source Uri for image to crop
58 | * @param destination Uri for saving the cropped image
59 | */
60 | public static Crop of(Uri source, Uri destination) {
61 | return new Crop(source, destination);
62 | }
63 |
64 | private Crop(Uri source, Uri destination) {
65 | cropIntent = new Intent();
66 | cropIntent.setData(source);
67 | cropIntent.putExtra(MediaStore.EXTRA_OUTPUT, destination);
68 | }
69 |
70 | /**
71 | * Set fixed aspect ratio for crop area
72 | *
73 | * @param x Aspect X
74 | * @param y Aspect Y
75 | */
76 | public Crop withAspect(int x, int y) {
77 | cropIntent.putExtra(Extra.ASPECT_X, x);
78 | cropIntent.putExtra(Extra.ASPECT_Y, y);
79 | return this;
80 | }
81 |
82 | /**
83 | * Crop area with fixed 1:1 aspect ratio
84 | */
85 | public Crop asSquare() {
86 | cropIntent.putExtra(Extra.ASPECT_X, 1);
87 | cropIntent.putExtra(Extra.ASPECT_Y, 1);
88 | return this;
89 | }
90 |
91 | /**
92 | * Set maximum crop size
93 | *
94 | * @param width Max width
95 | * @param height Max height
96 | */
97 | public Crop withMaxSize(int width, int height) {
98 | cropIntent.putExtra(Extra.MAX_X, width);
99 | cropIntent.putExtra(Extra.MAX_Y, height);
100 | return this;
101 | }
102 |
103 | /**
104 | * Send the crop Intent from an Activity
105 | *
106 | * @param activity Activity to receive result
107 | */
108 | public void start(Activity activity) {
109 | start(activity, REQUEST_CROP);
110 | }
111 |
112 | /**
113 | * Send the crop Intent from an Activity with a custom request code
114 | *
115 | * @param activity Activity to receive result
116 | * @param requestCode requestCode for result
117 | */
118 | public void start(Activity activity, int requestCode) {
119 | activity.startActivityForResult(getIntent(activity), requestCode);
120 | }
121 |
122 | /**
123 | * Send the crop Intent from a Fragment
124 | *
125 | * @param context Context
126 | * @param fragment Fragment to receive result
127 | */
128 | public void start(Context context, Fragment fragment) {
129 | start(context, fragment, REQUEST_CROP);
130 | }
131 |
132 | /**
133 | * Send the crop Intent from a support library Fragment
134 | *
135 | * @param context Context
136 | * @param fragment Fragment to receive result
137 | */
138 | public void start(Context context, android.support.v4.app.Fragment fragment) {
139 | start(context, fragment, REQUEST_CROP);
140 | }
141 |
142 | /**
143 | * Send the crop Intent with a custom request code
144 | *
145 | * @param context Context
146 | * @param fragment Fragment to receive result
147 | * @param requestCode requestCode for result
148 | */
149 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
150 | public void start(Context context, Fragment fragment, int requestCode) {
151 | fragment.startActivityForResult(getIntent(context), requestCode);
152 | }
153 |
154 | /**
155 | * Send the crop Intent with a custom request code
156 | *
157 | * @param context Context
158 | * @param fragment Fragment to receive result
159 | * @param requestCode requestCode for result
160 | */
161 | public void start(Context context, android.support.v4.app.Fragment fragment, int requestCode) {
162 | fragment.startActivityForResult(getIntent(context), requestCode);
163 | }
164 |
165 | /**
166 | * Get Intent to start crop Activity
167 | *
168 | * @param context Context
169 | * @return Intent for CropImageActivity
170 | */
171 | public Intent getIntent(Context context) {
172 | cropIntent.setClass(context, CropImageActivity.class);
173 | return cropIntent;
174 | }
175 |
176 | /**
177 | * Retrieve URI for cropped image, as set in the Intent builder
178 | *
179 | * @param result Output Image URI
180 | */
181 | public static Uri getOutput(Intent result) {
182 | return result.getParcelableExtra(MediaStore.EXTRA_OUTPUT);
183 | }
184 |
185 | /**
186 | * Retrieve error that caused crop to fail
187 | *
188 | * @param result Result Intent
189 | * @return Throwable handled in CropImageActivity
190 | */
191 | public static Throwable getError(Intent result) {
192 | return (Throwable) result.getSerializableExtra(Extra.ERROR);
193 | }
194 |
195 | /**
196 | * Pick image from an Activity
197 | *
198 | * @param activity Activity to receive result
199 | */
200 | public static void pickImage(Activity activity) {
201 | pickImage(activity, REQUEST_PICK);
202 | }
203 |
204 | public static void pickImage(Fragment fragment) {
205 | pickImage(fragment.getActivity(), fragment, REQUEST_PICK);
206 | }
207 |
208 | /**
209 | * Pick image from a Fragment
210 | *
211 | * @param context Context
212 | * @param fragment Fragment to receive result
213 | */
214 | public static void pickImage(Context context, Fragment fragment) {
215 | pickImage(context, fragment, REQUEST_PICK);
216 | }
217 |
218 | /**
219 | * Pick image from a support library Fragment
220 | *
221 | * @param context Context
222 | * @param fragment Fragment to receive result
223 | */
224 | public static void pickImage(Context context, android.support.v4.app.Fragment fragment) {
225 | pickImage(context, fragment, REQUEST_PICK);
226 | }
227 |
228 | /**
229 | * Pick image from an Activity with a custom request code
230 | *
231 | * @param activity Activity to receive result
232 | * @param requestCode requestCode for result
233 | */
234 | public static void pickImage(Activity activity, int requestCode) {
235 | try {
236 | activity.startActivityForResult(getImagePicker(), requestCode);
237 | } catch (ActivityNotFoundException e) {
238 | showImagePickerError(activity);
239 | }
240 | }
241 |
242 | /**
243 | * Pick image from a Fragment with a custom request code
244 | *
245 | * @param context Context
246 | * @param fragment Fragment to receive result
247 | * @param requestCode requestCode for result
248 | */
249 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
250 | public static void pickImage(Context context, Fragment fragment, int requestCode) {
251 | try {
252 | fragment.startActivityForResult(getImagePicker(), requestCode);
253 | } catch (ActivityNotFoundException e) {
254 | showImagePickerError(context);
255 | }
256 | }
257 |
258 | /**
259 | * Pick image from a support library Fragment with a custom request code
260 | *
261 | * @param context Context
262 | * @param fragment Fragment to receive result
263 | * @param requestCode requestCode for result
264 | */
265 | public static void pickImage(Context context, android.support.v4.app.Fragment fragment, int requestCode) {
266 | try {
267 | fragment.startActivityForResult(getImagePicker(), requestCode);
268 | } catch (ActivityNotFoundException e) {
269 | showImagePickerError(context);
270 | }
271 | }
272 |
273 | /**
274 | * Take photo from an Activity
275 | *
276 | * @param activity Activity to receive result
277 | */
278 | public static void takePhoto(Activity activity) {
279 | takePhoto(activity, REQUEST_PHOTO);
280 | }
281 |
282 | public static void takePhoto(Fragment fragment) {
283 | takePhoto(fragment.getActivity(), fragment, REQUEST_PHOTO);
284 | }
285 |
286 | /**
287 | * Take photo from a Fragment
288 | *
289 | * @param context Context
290 | * @param fragment Fragment to receive result
291 | */
292 | public static void takePhoto(Context context, Fragment fragment) {
293 | takePhoto(context, fragment, REQUEST_PHOTO);
294 | }
295 |
296 | /**
297 | * Take photo from a support library Fragment
298 | *
299 | * @param context Context
300 | * @param fragment Fragment to receive result
301 | */
302 | public static void takePhoto(Context context, android.support.v4.app.Fragment fragment) {
303 | takePhoto(context, fragment, REQUEST_PHOTO);
304 | }
305 |
306 | /**
307 | * Take photo from an Activity with a custom request code
308 | *
309 | * @param activity Activity to receive result
310 | * @param requestCode requestCode for result
311 | */
312 | public static void takePhoto(Activity activity, int requestCode) {
313 | try {
314 | activity.startActivityForResult(getTakePhoto(activity.getApplicationContext()), requestCode);
315 | } catch (ActivityNotFoundException e) {
316 | showTakePhotoError(activity);
317 | }
318 | }
319 |
320 | private static Intent getTakePhoto(Context context) {
321 | File file = CropUtil.createImageFile();
322 | outputFileUri = FileProvider.getUriForFile(context, "com.soundcloud.android.crop.provider.SwiftyFileProvider", file);
323 | Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
324 | intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
325 | return intent;
326 | }
327 |
328 | private static void showTakePhotoError(Context context) {
329 | Toast.makeText(context, R.string.crop__pick_error, Toast.LENGTH_SHORT).show();
330 | }
331 |
332 | /**
333 | * Take photo from a Fragment with a custom request code
334 | *
335 | * @param context Context
336 | * @param fragment Fragment to receive result
337 | * @param requestCode requestCode for result
338 | */
339 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
340 | public static void takePhoto(Context context, Fragment fragment, int requestCode) {
341 | try {
342 | fragment.startActivityForResult(getTakePhoto(context), requestCode);
343 | } catch (ActivityNotFoundException e) {
344 | showImagePickerError(context);
345 | }
346 | }
347 |
348 | /**
349 | * Take photo from a support library Fragment with a custom request code
350 | *
351 | * @param context Context
352 | * @param fragment Fragment to receive result
353 | * @param requestCode requestCode for result
354 | */
355 | public static void takePhoto(Context context, android.support.v4.app.Fragment fragment, int requestCode) {
356 | try {
357 | fragment.startActivityForResult(getTakePhoto(context), requestCode);
358 | } catch (ActivityNotFoundException e) {
359 | showTakePhotoError(context);
360 | }
361 | }
362 |
363 | private static Intent getImagePicker() {
364 | return new Intent(Intent.ACTION_GET_CONTENT).setType("image/*");
365 | }
366 |
367 | private static void showImagePickerError(Context context) {
368 | Toast.makeText(context, R.string.crop__pick_error, Toast.LENGTH_SHORT).show();
369 | }
370 |
371 | }
372 |
--------------------------------------------------------------------------------
/crop/src/main/java/com/soundcloud/android/crop/ImageViewTouchBase.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2009 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.soundcloud.android.crop;
18 |
19 | import android.content.Context;
20 | import android.graphics.Bitmap;
21 | import android.graphics.Matrix;
22 | import android.graphics.RectF;
23 | import android.graphics.drawable.Drawable;
24 | import android.os.Handler;
25 | import android.util.AttributeSet;
26 | import android.view.KeyEvent;
27 | import android.widget.ImageView;
28 |
29 | /*
30 | * Modified from original in AOSP.
31 | */
32 | abstract class ImageViewTouchBase extends ImageView {
33 |
34 | private static final float SCALE_RATE = 1.25F;
35 |
36 | // This is the base transformation which is used to show the image
37 | // initially. The current computation for this shows the image in
38 | // it's entirety, letterboxing as needed. One could choose to
39 | // show the image as cropped instead.
40 | //
41 | // This matrix is recomputed when we go from the thumbnail image to
42 | // the full size image.
43 | protected Matrix baseMatrix = new Matrix();
44 |
45 | // This is the supplementary transformation which reflects what
46 | // the user has done in terms of zooming and panning.
47 | //
48 | // This matrix remains the same when we go from the thumbnail image
49 | // to the full size image.
50 | protected Matrix suppMatrix = new Matrix();
51 |
52 | // This is the final matrix which is computed as the concatentation
53 | // of the base matrix and the supplementary matrix.
54 | private final Matrix displayMatrix = new Matrix();
55 |
56 | // Temporary buffer used for getting the values out of a matrix.
57 | private final float[] matrixValues = new float[9];
58 |
59 | // The current bitmap being displayed.
60 | protected final RotateBitmap bitmapDisplayed = new RotateBitmap(null, 0);
61 |
62 | int thisWidth = -1;
63 | int thisHeight = -1;
64 |
65 | float maxZoom;
66 |
67 | private Runnable onLayoutRunnable;
68 |
69 | protected Handler handler = new Handler();
70 |
71 | // ImageViewTouchBase will pass a Bitmap to the Recycler if it has finished
72 | // its use of that Bitmap
73 | public interface Recycler {
74 | public void recycle(Bitmap b);
75 | }
76 |
77 | private Recycler recycler;
78 |
79 | public ImageViewTouchBase(Context context) {
80 | super(context);
81 | init();
82 | }
83 |
84 | public ImageViewTouchBase(Context context, AttributeSet attrs) {
85 | super(context, attrs);
86 | init();
87 | }
88 |
89 | public ImageViewTouchBase(Context context, AttributeSet attrs, int defStyle) {
90 | super(context, attrs, defStyle);
91 | init();
92 | }
93 |
94 | public void setRecycler(Recycler recycler) {
95 | this.recycler = recycler;
96 | }
97 |
98 | @Override
99 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
100 | super.onLayout(changed, left, top, right, bottom);
101 | thisWidth = right - left;
102 | thisHeight = bottom - top;
103 | Runnable r = onLayoutRunnable;
104 | if (r != null) {
105 | onLayoutRunnable = null;
106 | r.run();
107 | }
108 | if (bitmapDisplayed.getBitmap() != null) {
109 | getProperBaseMatrix(bitmapDisplayed, baseMatrix, true);
110 | setImageMatrix(getImageViewMatrix());
111 | }
112 | }
113 |
114 | @Override
115 | public boolean onKeyDown(int keyCode, KeyEvent event) {
116 | if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
117 | event.startTracking();
118 | return true;
119 | }
120 | return super.onKeyDown(keyCode, event);
121 | }
122 |
123 | @Override
124 | public boolean onKeyUp(int keyCode, KeyEvent event) {
125 | if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && !event.isCanceled()) {
126 | if (getScale() > 1.0f) {
127 | // If we're zoomed in, pressing Back jumps out to show the
128 | // entire image, otherwise Back returns the user to the gallery
129 | zoomTo(1.0f);
130 | return true;
131 | }
132 | }
133 | return super.onKeyUp(keyCode, event);
134 | }
135 |
136 | @Override
137 | public void setImageBitmap(Bitmap bitmap) {
138 | setImageBitmap(bitmap, 0);
139 | }
140 |
141 | private void setImageBitmap(Bitmap bitmap, int rotation) {
142 | super.setImageBitmap(bitmap);
143 | Drawable d = getDrawable();
144 | if (d != null) {
145 | d.setDither(true);
146 | }
147 |
148 | Bitmap old = bitmapDisplayed.getBitmap();
149 | bitmapDisplayed.setBitmap(bitmap);
150 | bitmapDisplayed.setRotation(rotation);
151 |
152 | if (old != null && old != bitmap && recycler != null) {
153 | recycler.recycle(old);
154 | }
155 | }
156 |
157 | public void clear() {
158 | setImageBitmapResetBase(null, true);
159 | }
160 |
161 |
162 | // This function changes bitmap, reset base matrix according to the size
163 | // of the bitmap, and optionally reset the supplementary matrix
164 | public void setImageBitmapResetBase(final Bitmap bitmap, final boolean resetSupp) {
165 | setImageRotateBitmapResetBase(new RotateBitmap(bitmap, 0), resetSupp);
166 | }
167 |
168 | public void setImageRotateBitmapResetBase(final RotateBitmap bitmap, final boolean resetSupp) {
169 | final int viewWidth = getWidth();
170 |
171 | if (viewWidth <= 0) {
172 | onLayoutRunnable = new Runnable() {
173 | public void run() {
174 | setImageRotateBitmapResetBase(bitmap, resetSupp);
175 | }
176 | };
177 | return;
178 | }
179 |
180 | if (bitmap.getBitmap() != null) {
181 | getProperBaseMatrix(bitmap, baseMatrix, true);
182 | setImageBitmap(bitmap.getBitmap(), bitmap.getRotation());
183 | } else {
184 | baseMatrix.reset();
185 | setImageBitmap(null);
186 | }
187 |
188 | if (resetSupp) {
189 | suppMatrix.reset();
190 | }
191 | setImageMatrix(getImageViewMatrix());
192 | maxZoom = calculateMaxZoom();
193 | }
194 |
195 | // Center as much as possible in one or both axis. Centering is defined as follows:
196 | // * If the image is scaled down below the view's dimensions then center it.
197 | // * If the image is scaled larger than the view and is translated out of view then translate it back into view.
198 | protected void center() {
199 | final Bitmap bitmap = bitmapDisplayed.getBitmap();
200 | if (bitmap == null) {
201 | return;
202 | }
203 | Matrix m = getImageViewMatrix();
204 |
205 | RectF rect = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
206 | m.mapRect(rect);
207 |
208 | float height = rect.height();
209 | float width = rect.width();
210 |
211 | float deltaX = 0, deltaY = 0;
212 |
213 | deltaY = centerVertical(rect, height, deltaY);
214 | deltaX = centerHorizontal(rect, width, deltaX);
215 |
216 | postTranslate(deltaX, deltaY);
217 | setImageMatrix(getImageViewMatrix());
218 | }
219 |
220 | private float centerVertical(RectF rect, float height, float deltaY) {
221 | int viewHeight = getHeight();
222 | if (height < viewHeight) {
223 | deltaY = (viewHeight - height) / 2 - rect.top;
224 | } else if (rect.top > 0) {
225 | deltaY = -rect.top;
226 | } else if (rect.bottom < viewHeight) {
227 | deltaY = getHeight() - rect.bottom;
228 | }
229 | return deltaY;
230 | }
231 |
232 | private float centerHorizontal(RectF rect, float width, float deltaX) {
233 | int viewWidth = getWidth();
234 | if (width < viewWidth) {
235 | deltaX = (viewWidth - width) / 2 - rect.left;
236 | } else if (rect.left > 0) {
237 | deltaX = -rect.left;
238 | } else if (rect.right < viewWidth) {
239 | deltaX = viewWidth - rect.right;
240 | }
241 | return deltaX;
242 | }
243 |
244 | private void init() {
245 | setScaleType(ImageView.ScaleType.MATRIX);
246 | }
247 |
248 | protected float getValue(Matrix matrix, int whichValue) {
249 | matrix.getValues(matrixValues);
250 | return matrixValues[whichValue];
251 | }
252 |
253 | // Get the scale factor out of the matrix.
254 | protected float getScale(Matrix matrix) {
255 | return getValue(matrix, Matrix.MSCALE_X);
256 | }
257 |
258 | protected float getScale() {
259 | return getScale(suppMatrix);
260 | }
261 |
262 | // Setup the base matrix so that the image is centered and scaled properly.
263 | private void getProperBaseMatrix(RotateBitmap bitmap, Matrix matrix, boolean includeRotation) {
264 | float viewWidth = getWidth();
265 | float viewHeight = getHeight();
266 |
267 | float w = bitmap.getWidth();
268 | float h = bitmap.getHeight();
269 | matrix.reset();
270 |
271 | // We limit up-scaling to 3x otherwise the result may look bad if it's a small icon
272 | float widthScale = Math.min(viewWidth / w, 3.0f);
273 | float heightScale = Math.min(viewHeight / h, 3.0f);
274 | float scale = Math.min(widthScale, heightScale);
275 |
276 | if (includeRotation) {
277 | matrix.postConcat(bitmap.getRotateMatrix());
278 | }
279 | matrix.postScale(scale, scale);
280 | matrix.postTranslate((viewWidth - w * scale) / 2F, (viewHeight - h * scale) / 2F);
281 | }
282 |
283 | // Combine the base matrix and the supp matrix to make the final matrix
284 | protected Matrix getImageViewMatrix() {
285 | // The final matrix is computed as the concatentation of the base matrix
286 | // and the supplementary matrix
287 | displayMatrix.set(baseMatrix);
288 | displayMatrix.postConcat(suppMatrix);
289 | return displayMatrix;
290 | }
291 |
292 | public Matrix getUnrotatedMatrix(){
293 | Matrix unrotated = new Matrix();
294 | getProperBaseMatrix(bitmapDisplayed, unrotated, false);
295 | unrotated.postConcat(suppMatrix);
296 | return unrotated;
297 | }
298 |
299 | protected float calculateMaxZoom() {
300 | if (bitmapDisplayed.getBitmap() == null) {
301 | return 1F;
302 | }
303 |
304 | float fw = (float) bitmapDisplayed.getWidth() / (float) thisWidth;
305 | float fh = (float) bitmapDisplayed.getHeight() / (float) thisHeight;
306 | return Math.max(fw, fh) * 4; // 400%
307 | }
308 |
309 | protected void zoomTo(float scale, float centerX, float centerY) {
310 | if (scale > maxZoom) {
311 | scale = maxZoom;
312 | }
313 |
314 | float oldScale = getScale();
315 | float deltaScale = scale / oldScale;
316 |
317 | suppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);
318 | setImageMatrix(getImageViewMatrix());
319 | center();
320 | }
321 |
322 | protected void zoomTo(final float scale, final float centerX,
323 | final float centerY, final float durationMs) {
324 | final float incrementPerMs = (scale - getScale()) / durationMs;
325 | final float oldScale = getScale();
326 | final long startTime = System.currentTimeMillis();
327 |
328 | handler.post(new Runnable() {
329 | public void run() {
330 | long now = System.currentTimeMillis();
331 | float currentMs = Math.min(durationMs, now - startTime);
332 | float target = oldScale + (incrementPerMs * currentMs);
333 | zoomTo(target, centerX, centerY);
334 |
335 | if (currentMs < durationMs) {
336 | handler.post(this);
337 | }
338 | }
339 | });
340 | }
341 |
342 | protected void zoomTo(float scale) {
343 | float cx = getWidth() / 2F;
344 | float cy = getHeight() / 2F;
345 | zoomTo(scale, cx, cy);
346 | }
347 |
348 | protected void zoomIn() {
349 | zoomIn(SCALE_RATE);
350 | }
351 |
352 | protected void zoomOut() {
353 | zoomOut(SCALE_RATE);
354 | }
355 |
356 | protected void zoomIn(float rate) {
357 | if (getScale() >= maxZoom) {
358 | return; // Don't let the user zoom into the molecular level
359 | }
360 | if (bitmapDisplayed.getBitmap() == null) {
361 | return;
362 | }
363 |
364 | float cx = getWidth() / 2F;
365 | float cy = getHeight() / 2F;
366 |
367 | suppMatrix.postScale(rate, rate, cx, cy);
368 | setImageMatrix(getImageViewMatrix());
369 | }
370 |
371 | protected void zoomOut(float rate) {
372 | if (bitmapDisplayed.getBitmap() == null) {
373 | return;
374 | }
375 |
376 | float cx = getWidth() / 2F;
377 | float cy = getHeight() / 2F;
378 |
379 | // Zoom out to at most 1x
380 | Matrix tmp = new Matrix(suppMatrix);
381 | tmp.postScale(1F / rate, 1F / rate, cx, cy);
382 |
383 | if (getScale(tmp) < 1F) {
384 | suppMatrix.setScale(1F, 1F, cx, cy);
385 | } else {
386 | suppMatrix.postScale(1F / rate, 1F / rate, cx, cy);
387 | }
388 | setImageMatrix(getImageViewMatrix());
389 | center();
390 | }
391 |
392 | protected void postTranslate(float dx, float dy) {
393 | suppMatrix.postTranslate(dx, dy);
394 | }
395 |
396 | protected void panBy(float dx, float dy) {
397 | postTranslate(dx, dy);
398 | setImageMatrix(getImageViewMatrix());
399 | }
400 | }
401 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/main_scroll_content.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
22 |
23 |
30 |
31 |
37 |
38 |
39 |
40 |
48 |
49 |
54 |
55 |
63 |
64 |
71 |
72 |
77 |
78 |
84 |
85 |
86 |
87 |
88 |
95 |
96 |
101 |
102 |
108 |
109 |
110 |
111 |
112 |
119 |
120 |
125 |
126 |
132 |
133 |
134 |
135 |
136 |
143 |
144 |
149 |
150 |
156 |
157 |
158 |
159 |
160 |
167 |
168 |
173 |
174 |
180 |
181 |
182 |
183 |
184 |
189 |
190 |
198 |
199 |
200 |
207 |
208 |
213 |
214 |
220 |
221 |
222 |
223 |
224 |
231 |
232 |
237 |
238 |
244 |
245 |
246 |
247 |
248 |
255 |
256 |
261 |
262 |
268 |
269 |
270 |
271 |
272 |
279 |
280 |
285 |
286 |
292 |
293 |
294 |
295 |
296 |
301 |
302 |
310 |
311 |
318 |
319 |
324 |
325 |
331 |
332 |
333 |
334 |
335 |
342 |
343 |
348 |
349 |
355 |
356 |
357 |
358 |
359 |
366 |
367 |
372 |
373 |
379 |
380 |
381 |
382 |
383 |
391 |
392 |
397 |
398 |
404 |
405 |
406 |
407 |
--------------------------------------------------------------------------------
/crop/src/main/java/com/soundcloud/android/crop/HighlightView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2007 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.soundcloud.android.crop;
18 |
19 | import android.annotation.SuppressLint;
20 | import android.content.Context;
21 | import android.content.res.TypedArray;
22 | import android.graphics.Canvas;
23 | import android.graphics.Color;
24 | import android.graphics.Matrix;
25 | import android.graphics.Paint;
26 | import android.graphics.Path;
27 | import android.graphics.Rect;
28 | import android.graphics.RectF;
29 | import android.graphics.Region;
30 | import android.os.Build;
31 | import android.util.TypedValue;
32 | import android.view.View;
33 |
34 | /*
35 | * Modified from version in AOSP.
36 | *
37 | * This class is used to display a highlighted cropping rectangle
38 | * overlayed on the image. There are two coordinate spaces in use. One is
39 | * image, another is screen. computeLayout() uses matrix to map from image
40 | * space to screen space.
41 | */
42 | class HighlightView {
43 |
44 | public static final int GROW_NONE = (1 << 0);
45 | public static final int GROW_LEFT_EDGE = (1 << 1);
46 | public static final int GROW_RIGHT_EDGE = (1 << 2);
47 | public static final int GROW_TOP_EDGE = (1 << 3);
48 | public static final int GROW_BOTTOM_EDGE = (1 << 4);
49 | public static final int MOVE = (1 << 5);
50 |
51 | private static final int DEFAULT_HIGHLIGHT_COLOR = 0xFF33B5E5;
52 | private static final float HANDLE_RADIUS_DP = 12f;
53 | private static final float OUTLINE_DP = 2f;
54 |
55 | enum ModifyMode { None, Move, Grow }
56 | enum HandleMode { Changing, Always, Never }
57 |
58 | RectF cropRect; // Image space
59 | Rect drawRect; // Screen space
60 | Matrix matrix;
61 | private RectF imageRect; // Image space
62 |
63 | private final Paint outsidePaint = new Paint();
64 | private final Paint outlinePaint = new Paint();
65 | private final Paint handlePaint = new Paint();
66 |
67 | private View viewContext; // View displaying image
68 | private boolean showThirds;
69 | private boolean showCircle;
70 | private int highlightColor;
71 |
72 | private ModifyMode modifyMode = ModifyMode.None;
73 | private HandleMode handleMode = HandleMode.Changing;
74 | private boolean maintainAspectRatio;
75 | private float initialAspectRatio;
76 | private float handleRadius;
77 | private float outlineWidth;
78 | private boolean isFocused;
79 |
80 | public HighlightView(View context) {
81 | viewContext = context;
82 | initStyles(context.getContext());
83 | }
84 |
85 | private void initStyles(Context context) {
86 | TypedValue outValue = new TypedValue();
87 | context.getTheme().resolveAttribute(R.attr.cropImageStyle, outValue, true);
88 | TypedArray attributes = context.obtainStyledAttributes(outValue.resourceId, R.styleable.CropImageView);
89 | try {
90 | showThirds = attributes.getBoolean(R.styleable.CropImageView_showThirds, false);
91 | showCircle = attributes.getBoolean(R.styleable.CropImageView_showCircle, false);
92 | highlightColor = attributes.getColor(R.styleable.CropImageView_highlightColor,
93 | DEFAULT_HIGHLIGHT_COLOR);
94 | handleMode = HandleMode.values()[attributes.getInt(R.styleable.CropImageView_showHandles, 0)];
95 | } finally {
96 | attributes.recycle();
97 | }
98 | }
99 |
100 | public void setup(Matrix m, Rect imageRect, RectF cropRect, boolean maintainAspectRatio) {
101 | matrix = new Matrix(m);
102 |
103 | this.cropRect = cropRect;
104 | this.imageRect = new RectF(imageRect);
105 | this.maintainAspectRatio = maintainAspectRatio;
106 |
107 | initialAspectRatio = this.cropRect.width() / this.cropRect.height();
108 | drawRect = computeLayout();
109 |
110 | outsidePaint.setARGB(125, 50, 50, 50);
111 | outlinePaint.setStyle(Paint.Style.STROKE);
112 | outlinePaint.setAntiAlias(true);
113 | outlineWidth = dpToPx(OUTLINE_DP);
114 |
115 | handlePaint.setColor(highlightColor);
116 | handlePaint.setStyle(Paint.Style.FILL);
117 | handlePaint.setAntiAlias(true);
118 | handleRadius = dpToPx(HANDLE_RADIUS_DP);
119 |
120 | modifyMode = ModifyMode.None;
121 | }
122 |
123 | private float dpToPx(float dp) {
124 | return dp * viewContext.getResources().getDisplayMetrics().density;
125 | }
126 |
127 | protected void draw(Canvas canvas) {
128 | canvas.save();
129 | Path path = new Path();
130 | outlinePaint.setStrokeWidth(outlineWidth);
131 | if (!hasFocus()) {
132 | outlinePaint.setColor(Color.BLACK);
133 | canvas.drawRect(drawRect, outlinePaint);
134 | } else {
135 | Rect viewDrawingRect = new Rect();
136 | viewContext.getDrawingRect(viewDrawingRect);
137 |
138 | path.addRect(new RectF(drawRect), Path.Direction.CW);
139 | outlinePaint.setColor(highlightColor);
140 |
141 | if (isClipPathSupported(canvas)) {
142 | canvas.clipPath(path, Region.Op.DIFFERENCE);
143 | canvas.drawRect(viewDrawingRect, outsidePaint);
144 | } else {
145 | drawOutsideFallback(canvas);
146 | }
147 |
148 | canvas.restore();
149 | canvas.drawPath(path, outlinePaint);
150 |
151 | if (showThirds) {
152 | drawThirds(canvas);
153 | }
154 |
155 | if (showCircle) {
156 | drawCircle(canvas);
157 | }
158 |
159 | if (handleMode == HandleMode.Always ||
160 | (handleMode == HandleMode.Changing && modifyMode == ModifyMode.Grow)) {
161 | drawHandles(canvas);
162 | }
163 | }
164 | }
165 |
166 | /*
167 | * Fall back to naive method for darkening outside crop area
168 | */
169 | private void drawOutsideFallback(Canvas canvas) {
170 | canvas.drawRect(0, 0, canvas.getWidth(), drawRect.top, outsidePaint);
171 | canvas.drawRect(0, drawRect.bottom, canvas.getWidth(), canvas.getHeight(), outsidePaint);
172 | canvas.drawRect(0, drawRect.top, drawRect.left, drawRect.bottom, outsidePaint);
173 | canvas.drawRect(drawRect.right, drawRect.top, canvas.getWidth(), drawRect.bottom, outsidePaint);
174 | }
175 |
176 | /*
177 | * Clip path is broken, unreliable or not supported on:
178 | * - JellyBean MR1
179 | * - ICS & ICS MR1 with hardware acceleration turned on
180 | */
181 | @SuppressLint("NewApi")
182 | private boolean isClipPathSupported(Canvas canvas) {
183 | if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
184 | return false;
185 | } else if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH)
186 | || Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
187 | return true;
188 | } else {
189 | return !canvas.isHardwareAccelerated();
190 | }
191 | }
192 |
193 | private void drawHandles(Canvas canvas) {
194 | int xMiddle = drawRect.left + ((drawRect.right - drawRect.left) / 2);
195 | int yMiddle = drawRect.top + ((drawRect.bottom - drawRect.top) / 2);
196 |
197 | canvas.drawCircle(drawRect.left, yMiddle, handleRadius, handlePaint);
198 | canvas.drawCircle(xMiddle, drawRect.top, handleRadius, handlePaint);
199 | canvas.drawCircle(drawRect.right, yMiddle, handleRadius, handlePaint);
200 | canvas.drawCircle(xMiddle, drawRect.bottom, handleRadius, handlePaint);
201 | }
202 |
203 | private void drawThirds(Canvas canvas) {
204 | outlinePaint.setStrokeWidth(1);
205 | float xThird = (drawRect.right - drawRect.left) / 3;
206 | float yThird = (drawRect.bottom - drawRect.top) / 3;
207 |
208 | canvas.drawLine(drawRect.left + xThird, drawRect.top,
209 | drawRect.left + xThird, drawRect.bottom, outlinePaint);
210 | canvas.drawLine(drawRect.left + xThird * 2, drawRect.top,
211 | drawRect.left + xThird * 2, drawRect.bottom, outlinePaint);
212 | canvas.drawLine(drawRect.left, drawRect.top + yThird,
213 | drawRect.right, drawRect.top + yThird, outlinePaint);
214 | canvas.drawLine(drawRect.left, drawRect.top + yThird * 2,
215 | drawRect.right, drawRect.top + yThird * 2, outlinePaint);
216 | }
217 |
218 | private void drawCircle(Canvas canvas) {
219 | outlinePaint.setStrokeWidth(1);
220 | canvas.drawOval(new RectF(drawRect), outlinePaint);
221 | }
222 |
223 | public void setMode(ModifyMode mode) {
224 | if (mode != modifyMode) {
225 | modifyMode = mode;
226 | viewContext.invalidate();
227 | }
228 | }
229 |
230 | // Determines which edges are hit by touching at (x, y)
231 | public int getHit(float x, float y) {
232 | Rect r = computeLayout();
233 | final float hysteresis = 20F;
234 | int retval = GROW_NONE;
235 |
236 | // verticalCheck makes sure the position is between the top and
237 | // the bottom edge (with some tolerance). Similar for horizCheck.
238 | boolean verticalCheck = (y >= r.top - hysteresis)
239 | && (y < r.bottom + hysteresis);
240 | boolean horizCheck = (x >= r.left - hysteresis)
241 | && (x < r.right + hysteresis);
242 |
243 | // Check whether the position is near some edge(s)
244 | if ((Math.abs(r.left - x) < hysteresis) && verticalCheck) {
245 | retval |= GROW_LEFT_EDGE;
246 | }
247 | if ((Math.abs(r.right - x) < hysteresis) && verticalCheck) {
248 | retval |= GROW_RIGHT_EDGE;
249 | }
250 | if ((Math.abs(r.top - y) < hysteresis) && horizCheck) {
251 | retval |= GROW_TOP_EDGE;
252 | }
253 | if ((Math.abs(r.bottom - y) < hysteresis) && horizCheck) {
254 | retval |= GROW_BOTTOM_EDGE;
255 | }
256 |
257 | // Not near any edge but inside the rectangle: move
258 | if (retval == GROW_NONE && r.contains((int) x, (int) y)) {
259 | retval = MOVE;
260 | }
261 | return retval;
262 | }
263 |
264 | // Handles motion (dx, dy) in screen space.
265 | // The "edge" parameter specifies which edges the user is dragging.
266 | void handleMotion(int edge, float dx, float dy) {
267 | Rect r = computeLayout();
268 | if (edge == MOVE) {
269 | // Convert to image space before sending to moveBy()
270 | moveBy(dx * (cropRect.width() / r.width()),
271 | dy * (cropRect.height() / r.height()));
272 | } else {
273 | if (((GROW_LEFT_EDGE | GROW_RIGHT_EDGE) & edge) == 0) {
274 | dx = 0;
275 | }
276 |
277 | if (((GROW_TOP_EDGE | GROW_BOTTOM_EDGE) & edge) == 0) {
278 | dy = 0;
279 | }
280 |
281 | // Convert to image space before sending to growBy()
282 | float xDelta = dx * (cropRect.width() / r.width());
283 | float yDelta = dy * (cropRect.height() / r.height());
284 | growBy((((edge & GROW_LEFT_EDGE) != 0) ? -1 : 1) * xDelta,
285 | (((edge & GROW_TOP_EDGE) != 0) ? -1 : 1) * yDelta);
286 | }
287 | }
288 |
289 | // Grows the cropping rectangle by (dx, dy) in image space
290 | void moveBy(float dx, float dy) {
291 | Rect invalRect = new Rect(drawRect);
292 |
293 | cropRect.offset(dx, dy);
294 |
295 | // Put the cropping rectangle inside image rectangle
296 | cropRect.offset(
297 | Math.max(0, imageRect.left - cropRect.left),
298 | Math.max(0, imageRect.top - cropRect.top));
299 |
300 | cropRect.offset(
301 | Math.min(0, imageRect.right - cropRect.right),
302 | Math.min(0, imageRect.bottom - cropRect.bottom));
303 |
304 | drawRect = computeLayout();
305 | invalRect.union(drawRect);
306 | invalRect.inset(-(int) handleRadius, -(int) handleRadius);
307 | viewContext.invalidate(invalRect);
308 | }
309 |
310 | // Grows the cropping rectangle by (dx, dy) in image space.
311 | void growBy(float dx, float dy) {
312 | if (maintainAspectRatio) {
313 | if (dx != 0) {
314 | dy = dx / initialAspectRatio;
315 | } else if (dy != 0) {
316 | dx = dy * initialAspectRatio;
317 | }
318 | }
319 |
320 | // Don't let the cropping rectangle grow too fast.
321 | // Grow at most half of the difference between the image rectangle and
322 | // the cropping rectangle.
323 | RectF r = new RectF(cropRect);
324 | if (dx > 0F && r.width() + 2 * dx > imageRect.width()) {
325 | dx = (imageRect.width() - r.width()) / 2F;
326 | if (maintainAspectRatio) {
327 | dy = dx / initialAspectRatio;
328 | }
329 | }
330 | if (dy > 0F && r.height() + 2 * dy > imageRect.height()) {
331 | dy = (imageRect.height() - r.height()) / 2F;
332 | if (maintainAspectRatio) {
333 | dx = dy * initialAspectRatio;
334 | }
335 | }
336 |
337 | r.inset(-dx, -dy);
338 |
339 | // Don't let the cropping rectangle shrink too fast
340 | final float widthCap = 25F;
341 | if (r.width() < widthCap) {
342 | r.inset(-(widthCap - r.width()) / 2F, 0F);
343 | }
344 | float heightCap = maintainAspectRatio
345 | ? (widthCap / initialAspectRatio)
346 | : widthCap;
347 | if (r.height() < heightCap) {
348 | r.inset(0F, -(heightCap - r.height()) / 2F);
349 | }
350 |
351 | // Put the cropping rectangle inside the image rectangle
352 | if (r.left < imageRect.left) {
353 | r.offset(imageRect.left - r.left, 0F);
354 | } else if (r.right > imageRect.right) {
355 | r.offset(-(r.right - imageRect.right), 0F);
356 | }
357 | if (r.top < imageRect.top) {
358 | r.offset(0F, imageRect.top - r.top);
359 | } else if (r.bottom > imageRect.bottom) {
360 | r.offset(0F, -(r.bottom - imageRect.bottom));
361 | }
362 |
363 | cropRect.set(r);
364 | drawRect = computeLayout();
365 | viewContext.invalidate();
366 | }
367 |
368 | // Returns the cropping rectangle in image space with specified scale
369 | public Rect getScaledCropRect(float scale) {
370 | return new Rect((int) (cropRect.left * scale), (int) (cropRect.top * scale),
371 | (int) (cropRect.right * scale), (int) (cropRect.bottom * scale));
372 | }
373 |
374 | // Maps the cropping rectangle from image space to screen space
375 | private Rect computeLayout() {
376 | RectF r = new RectF(cropRect.left, cropRect.top,
377 | cropRect.right, cropRect.bottom);
378 | matrix.mapRect(r);
379 | return new Rect(Math.round(r.left), Math.round(r.top),
380 | Math.round(r.right), Math.round(r.bottom));
381 | }
382 |
383 | public void invalidate() {
384 | drawRect = computeLayout();
385 | }
386 |
387 | public boolean hasFocus() {
388 | return isFocused;
389 | }
390 |
391 | public void setFocus(boolean isFocused) {
392 | this.isFocused = isFocused;
393 | }
394 |
395 | }
396 |
--------------------------------------------------------------------------------
/crop/src/main/java/com/soundcloud/android/crop/CropImageActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2007 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.soundcloud.android.crop;
18 |
19 | import android.annotation.TargetApi;
20 | import android.content.Intent;
21 | import android.graphics.Bitmap;
22 | import android.graphics.BitmapFactory;
23 | import android.graphics.BitmapRegionDecoder;
24 | import android.graphics.Matrix;
25 | import android.graphics.Rect;
26 | import android.graphics.RectF;
27 | import android.net.Uri;
28 | import android.opengl.GLES10;
29 | import android.os.Build;
30 | import android.os.Bundle;
31 | import android.os.Handler;
32 | import android.provider.MediaStore;
33 | import android.view.View;
34 | import android.view.Window;
35 | import android.view.WindowManager;
36 |
37 | import java.io.IOException;
38 | import java.io.InputStream;
39 | import java.io.OutputStream;
40 | import java.util.concurrent.CountDownLatch;
41 |
42 | /*
43 | * Modified from original in AOSP.
44 | */
45 | public class CropImageActivity extends MonitoredActivity {
46 |
47 | private static final int SIZE_DEFAULT = 2048;
48 | private static final int SIZE_LIMIT = 4096;
49 |
50 | private final Handler handler = new Handler();
51 |
52 | private int aspectX;
53 | private int aspectY;
54 |
55 | // Output image
56 | private int maxX;
57 | private int maxY;
58 | private int exifRotation;
59 |
60 | private Uri sourceUri;
61 | private Uri saveUri;
62 |
63 | private boolean isSaving;
64 |
65 | private int sampleSize;
66 | private RotateBitmap rotateBitmap;
67 | private CropImageView imageView;
68 | private HighlightView cropView;
69 |
70 | @Override
71 | public void onCreate(Bundle icicle) {
72 | super.onCreate(icicle);
73 | setupWindowFlags();
74 | setupViews();
75 |
76 | loadInput();
77 | if (rotateBitmap == null) {
78 | finish();
79 | return;
80 | }
81 | startCrop();
82 | }
83 |
84 | @TargetApi(Build.VERSION_CODES.KITKAT)
85 | private void setupWindowFlags() {
86 | requestWindowFeature(Window.FEATURE_NO_TITLE);
87 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
88 | getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
89 | }
90 | }
91 |
92 | private void setupViews() {
93 | setContentView(R.layout.crop__activity_crop);
94 |
95 | imageView = (CropImageView) findViewById(R.id.crop_image);
96 | imageView.context = this;
97 | imageView.setRecycler(new ImageViewTouchBase.Recycler() {
98 | @Override
99 | public void recycle(Bitmap b) {
100 | b.recycle();
101 | System.gc();
102 | }
103 | });
104 |
105 | findViewById(R.id.btn_cancel).setOnClickListener(new View.OnClickListener() {
106 | public void onClick(View v) {
107 | setResult(RESULT_CANCELED);
108 | finish();
109 | }
110 | });
111 |
112 | findViewById(R.id.btn_done).setOnClickListener(new View.OnClickListener() {
113 | public void onClick(View v) {
114 | onSaveClicked();
115 | }
116 | });
117 | }
118 |
119 | private void loadInput() {
120 | Intent intent = getIntent();
121 | Bundle extras = intent.getExtras();
122 |
123 | if (extras != null) {
124 | aspectX = extras.getInt(Crop.Extra.ASPECT_X);
125 | aspectY = extras.getInt(Crop.Extra.ASPECT_Y);
126 | maxX = extras.getInt(Crop.Extra.MAX_X);
127 | maxY = extras.getInt(Crop.Extra.MAX_Y);
128 | saveUri = extras.getParcelable(MediaStore.EXTRA_OUTPUT);
129 | }
130 |
131 | sourceUri = intent.getData();
132 | if (sourceUri != null) {
133 | exifRotation = CropUtil.getExifRotation(CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri));
134 |
135 | InputStream is = null;
136 | try {
137 | sampleSize = calculateBitmapSampleSize(sourceUri);
138 | is = getContentResolver().openInputStream(sourceUri);
139 | BitmapFactory.Options option = new BitmapFactory.Options();
140 | option.inSampleSize = sampleSize;
141 | rotateBitmap = new RotateBitmap(BitmapFactory.decodeStream(is, null, option), exifRotation);
142 | } catch (IOException e) {
143 | Log.e("Error reading image: " + e.getMessage(), e);
144 | setResultException(e);
145 | } catch (OutOfMemoryError e) {
146 | Log.e("OOM reading image: " + e.getMessage(), e);
147 | setResultException(e);
148 | } finally {
149 | CropUtil.closeSilently(is);
150 | }
151 | }
152 | }
153 |
154 | private int calculateBitmapSampleSize(Uri bitmapUri) throws IOException {
155 | InputStream is = null;
156 | BitmapFactory.Options options = new BitmapFactory.Options();
157 | options.inJustDecodeBounds = true;
158 | try {
159 | is = getContentResolver().openInputStream(bitmapUri);
160 | BitmapFactory.decodeStream(is, null, options); // Just get image size
161 | } finally {
162 | CropUtil.closeSilently(is);
163 | }
164 |
165 | int maxSize = getMaxImageSize();
166 | int sampleSize = 1;
167 | while (options.outHeight / sampleSize > maxSize || options.outWidth / sampleSize > maxSize) {
168 | sampleSize = sampleSize << 1;
169 | }
170 | return sampleSize;
171 | }
172 |
173 | private int getMaxImageSize() {
174 | int textureLimit = getMaxTextureSize();
175 | if (textureLimit == 0) {
176 | return SIZE_DEFAULT;
177 | } else {
178 | return Math.min(textureLimit, SIZE_LIMIT);
179 | }
180 | }
181 |
182 | private int getMaxTextureSize() {
183 | // The OpenGL texture size is the maximum size that can be drawn in an ImageView
184 | int[] maxSize = new int[1];
185 | GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, maxSize, 0);
186 | return maxSize[0];
187 | }
188 |
189 | private void startCrop() {
190 | if (isFinishing()) {
191 | return;
192 | }
193 | imageView.setImageRotateBitmapResetBase(rotateBitmap, true);
194 | CropUtil.startBackgroundJob(this, null, getResources().getString(R.string.crop__wait),
195 | new Runnable() {
196 | public void run() {
197 | final CountDownLatch latch = new CountDownLatch(1);
198 | handler.post(new Runnable() {
199 | public void run() {
200 | if (imageView.getScale() == 1F) {
201 | imageView.center();
202 | }
203 | latch.countDown();
204 | }
205 | });
206 | try {
207 | latch.await();
208 | } catch (InterruptedException e) {
209 | throw new RuntimeException(e);
210 | }
211 | new Cropper().crop();
212 | }
213 | }, handler
214 | );
215 | }
216 |
217 | private class Cropper {
218 |
219 | private void makeDefault() {
220 | if (rotateBitmap == null) {
221 | return;
222 | }
223 |
224 | HighlightView hv = new HighlightView(imageView);
225 | final int width = rotateBitmap.getWidth();
226 | final int height = rotateBitmap.getHeight();
227 |
228 | Rect imageRect = new Rect(0, 0, width, height);
229 |
230 | // Make the default size about 4/5 of the width or height
231 | int cropWidth = Math.min(width, height) * 4 / 5;
232 | @SuppressWarnings("SuspiciousNameCombination")
233 | int cropHeight = cropWidth;
234 |
235 | if (aspectX != 0 && aspectY != 0) {
236 | if (aspectX > aspectY) {
237 | cropHeight = cropWidth * aspectY / aspectX;
238 | } else {
239 | cropWidth = cropHeight * aspectX / aspectY;
240 | }
241 | }
242 |
243 | int x = (width - cropWidth) / 2;
244 | int y = (height - cropHeight) / 2;
245 |
246 | RectF cropRect = new RectF(x, y, x + cropWidth, y + cropHeight);
247 | hv.setup(imageView.getUnrotatedMatrix(), imageRect, cropRect, aspectX != 0 && aspectY != 0);
248 | imageView.add(hv);
249 | }
250 |
251 | public void crop() {
252 | handler.post(new Runnable() {
253 | public void run() {
254 | makeDefault();
255 | imageView.invalidate();
256 | if (imageView.highlightViews.size() == 1) {
257 | cropView = imageView.highlightViews.get(0);
258 | cropView.setFocus(true);
259 | }
260 | }
261 | });
262 | }
263 | }
264 |
265 | private void onSaveClicked() {
266 | if (cropView == null || isSaving) {
267 | return;
268 | }
269 | isSaving = true;
270 |
271 | Bitmap croppedImage;
272 | Rect r = cropView.getScaledCropRect(sampleSize);
273 | int width = r.width();
274 | int height = r.height();
275 |
276 | int outWidth = width;
277 | int outHeight = height;
278 | if (maxX > 0 && maxY > 0 && (width > maxX || height > maxY)) {
279 | float ratio = (float) width / (float) height;
280 | if ((float) maxX / (float) maxY > ratio) {
281 | outHeight = maxY;
282 | outWidth = (int) ((float) maxY * ratio + .5f);
283 | } else {
284 | outWidth = maxX;
285 | outHeight = (int) ((float) maxX / ratio + .5f);
286 | }
287 | }
288 |
289 | try {
290 | croppedImage = decodeRegionCrop(r, outWidth, outHeight);
291 | } catch (IllegalArgumentException e) {
292 | setResultException(e);
293 | finish();
294 | return;
295 | }
296 |
297 | if (croppedImage != null) {
298 | imageView.setImageRotateBitmapResetBase(new RotateBitmap(croppedImage, exifRotation), true);
299 | imageView.center();
300 | imageView.highlightViews.clear();
301 | }
302 | saveImage(croppedImage);
303 | }
304 |
305 | private void saveImage(Bitmap croppedImage) {
306 | if (croppedImage != null) {
307 | final Bitmap b = croppedImage;
308 | CropUtil.startBackgroundJob(this, null, getResources().getString(R.string.crop__saving),
309 | new Runnable() {
310 | public void run() {
311 | saveOutput(b);
312 | }
313 | }, handler
314 | );
315 | } else {
316 | finish();
317 | }
318 | }
319 |
320 | private Bitmap decodeRegionCrop(Rect rect, int outWidth, int outHeight) {
321 | // Release memory now
322 | clearImageView();
323 |
324 | InputStream is = null;
325 | Bitmap croppedImage = null;
326 | try {
327 | is = getContentResolver().openInputStream(sourceUri);
328 | BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
329 | final int width = decoder.getWidth();
330 | final int height = decoder.getHeight();
331 |
332 | if (exifRotation != 0) {
333 | // Adjust crop area to account for image rotation
334 | Matrix matrix = new Matrix();
335 | matrix.setRotate(-exifRotation);
336 |
337 | RectF adjusted = new RectF();
338 | matrix.mapRect(adjusted, new RectF(rect));
339 |
340 | // Adjust to account for origin at 0,0
341 | adjusted.offset(adjusted.left < 0 ? width : 0, adjusted.top < 0 ? height : 0);
342 | rect = new Rect((int) adjusted.left, (int) adjusted.top, (int) adjusted.right, (int) adjusted.bottom);
343 | }
344 |
345 | try {
346 | croppedImage = decoder.decodeRegion(rect, new BitmapFactory.Options());
347 | if (croppedImage != null && (rect.width() > outWidth || rect.height() > outHeight)) {
348 | Matrix matrix = new Matrix();
349 | matrix.postScale((float) outWidth / rect.width(), (float) outHeight / rect.height());
350 | croppedImage = Bitmap.createBitmap(croppedImage, 0, 0, croppedImage.getWidth(), croppedImage.getHeight(), matrix, true);
351 | }
352 | } catch (IllegalArgumentException e) {
353 | // Rethrow with some extra information
354 | throw new IllegalArgumentException("Rectangle " + rect + " is outside of the image ("
355 | + width + "," + height + "," + exifRotation + ")", e);
356 | }
357 |
358 | } catch (IOException e) {
359 | Log.e("Error cropping image: " + e.getMessage(), e);
360 | setResultException(e);
361 | } catch (OutOfMemoryError e) {
362 | Log.e("OOM cropping image: " + e.getMessage(), e);
363 | setResultException(e);
364 | } finally {
365 | CropUtil.closeSilently(is);
366 | }
367 | return croppedImage;
368 | }
369 |
370 | private void clearImageView() {
371 | imageView.clear();
372 | if (rotateBitmap != null) {
373 | rotateBitmap.recycle();
374 | }
375 | System.gc();
376 | }
377 |
378 | private void saveOutput(Bitmap croppedImage) {
379 | if (saveUri != null) {
380 | OutputStream outputStream = null;
381 | try {
382 | outputStream = getContentResolver().openOutputStream(saveUri);
383 | if (outputStream != null) {
384 | croppedImage.compress(Bitmap.CompressFormat.JPEG, 90, outputStream);
385 | }
386 | } catch (IOException e) {
387 | setResultException(e);
388 | Log.e("Cannot open file: " + saveUri, e);
389 | } finally {
390 | CropUtil.closeSilently(outputStream);
391 | }
392 |
393 | CropUtil.copyExifRotation(
394 | CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri),
395 | CropUtil.getFromMediaUri(this, getContentResolver(), saveUri)
396 | );
397 |
398 | setResultUri(saveUri);
399 | }
400 |
401 | final Bitmap b = croppedImage;
402 | handler.post(new Runnable() {
403 | public void run() {
404 | imageView.clear();
405 | b.recycle();
406 | }
407 | });
408 |
409 | finish();
410 | }
411 |
412 | @Override
413 | protected void onDestroy() {
414 | super.onDestroy();
415 | if (rotateBitmap != null) {
416 | rotateBitmap.recycle();
417 | }
418 | }
419 |
420 | @Override
421 | public boolean onSearchRequested() {
422 | return false;
423 | }
424 |
425 | public boolean isSaving() {
426 | return isSaving;
427 | }
428 |
429 | private void setResultUri(Uri uri) {
430 | setResult(RESULT_OK, new Intent().putExtra(MediaStore.EXTRA_OUTPUT, uri));
431 | }
432 |
433 | private void setResultException(Throwable throwable) {
434 | setResult(Crop.RESULT_ERROR, new Intent().putExtra(Crop.Extra.ERROR, throwable));
435 | }
436 |
437 | }
438 |
--------------------------------------------------------------------------------
/dragsquareimage/src/main/java/com/swifty/dragsquareimage/DraggableSquareView.java:
--------------------------------------------------------------------------------
1 | package com.swifty.dragsquareimage;
2 |
3 | import android.content.Context;
4 | import android.graphics.Point;
5 | import android.os.Handler;
6 | import android.os.Message;
7 | import android.support.v4.view.GestureDetectorCompat;
8 | import android.support.v4.widget.ViewDragHelper;
9 | import android.util.AttributeSet;
10 | import android.util.SparseArray;
11 | import android.view.GestureDetector;
12 | import android.view.MotionEvent;
13 | import android.view.View;
14 | import android.view.ViewConfiguration;
15 | import android.view.ViewGroup;
16 |
17 | import java.util.ArrayList;
18 | import java.util.List;
19 |
20 | /**
21 | * 正方形的拖拽面板
22 | * Created by xmuSistone on 2016/5/23.
23 | */
24 | public class DraggableSquareView extends ViewGroup implements DraggableItemView.Listener {
25 | // ACTION_DOWN按下后超过这个时间,就直接touch拦截,不会调用底层view的onClick事件
26 | private static final int INTERCEPT_TIME_SLOP = 200;
27 | private final int[] allStatus = {DraggableItemView.STATUS_LEFT_TOP, DraggableItemView.STATUS_RIGHT_TOP,
28 | DraggableItemView.STATUS_RIGHT_MIDDLE, DraggableItemView.STATUS_RIGHT_BOTTOM,
29 | DraggableItemView.STATUS_MIDDLE_BOTTOM, DraggableItemView.STATUS_LEFT_BOTTOM};
30 | private ActionDialog actionDialog;
31 | private Listener listener;
32 |
33 | private int mTouchSlop = 5; // 判定为滑动的阈值,单位是像素
34 | private int spaceInterval = 4; // 小方块之间的间隔
35 | private final ViewDragHelper mDragHelper;
36 | private GestureDetectorCompat moveDetector;
37 |
38 | private List originViewPositionList = new ArrayList<>(); // 保存最初状态时每个itemView的坐标位置
39 | private DraggableItemView draggingView; // 正在拖拽的view
40 |
41 | private int sideLength; // 每一个小方块的边长
42 | private long downTime = 0; // 按下的时间
43 | private int downX, downY; // 按下时的坐标位置
44 | private Thread moveAnchorThread; // 按下的时候,itemView的重心移动,此为对应线程
45 | private Handler anchorHandler; // itemView需要移动重心,此为对应的Handler
46 | private Object synObj = new Object();
47 |
48 | public DraggableSquareView(Context context) {
49 | this(context, null);
50 | }
51 |
52 | public DraggableSquareView(Context context, AttributeSet attrs) {
53 | this(context, attrs, 0);
54 | }
55 |
56 | public DraggableSquareView(Context context, AttributeSet attrs, int defStyleAttr) {
57 | super(context, attrs, defStyleAttr);
58 | mDragHelper = ViewDragHelper
59 | .create(this, 10f, new DragHelperCallback());
60 | moveDetector = new GestureDetectorCompat(context,
61 | new MoveDetector());
62 | moveDetector.setIsLongpressEnabled(false); // 不能处理长按事件,否则违背最初设计的初衷
63 | spaceInterval = (int) getResources().getDimension(R.dimen.drag_square_interval); // 小方块之间的间隔
64 |
65 | // 滑动的距离阈值由系统提供
66 | ViewConfiguration configuration = ViewConfiguration.get(getContext());
67 | mTouchSlop = configuration.getScaledTouchSlop();
68 |
69 | anchorHandler = new Handler() {
70 | @Override
71 | public void handleMessage(Message msg) {
72 | if (draggingView != null) {
73 | // 开始移动重心的动画
74 | draggingView.startAnchorAnimation();
75 | }
76 | }
77 | };
78 | }
79 |
80 | @Override
81 | protected void onFinishInflate() {
82 | super.onFinishInflate();
83 |
84 | int len = allStatus.length;
85 | for (int i = 0; i < len; i++) {
86 | // 渲染结束之后,朝viewGroup中添加子View
87 | DraggableItemView itemView = new DraggableItemView(getContext());
88 | itemView.setStatus(allStatus[i]);
89 | itemView.setParentView(this);
90 | itemView.setListener(this);
91 | originViewPositionList.add(new Point()); // 原始位置点,由此初始化,一定与子View的status绑定
92 | addView(itemView);
93 | }
94 | }
95 |
96 | public Point getOriginViewPos(int status) {
97 | return originViewPositionList.get(status);
98 | }
99 |
100 | /**
101 | * 给imageView添加图片
102 | */
103 | public void fillItemImage(int imageStatus, String imagePath, boolean isModify) {
104 | // 1. 如果是修改图片,直接填充就好
105 | if (isModify) {
106 | DraggableItemView itemView = getItemViewByStatus(imageStatus);
107 | if (itemView != null) {
108 | itemView.fillImageView(imagePath);
109 | if (imageChangesListener != null) imageChangesListener.onImageEdited(imagePath, imageStatus);
110 | return;
111 | }
112 | }
113 |
114 | // 2. 新增图片
115 | for (int i = 0; i < allStatus.length; i++) {
116 | DraggableItemView itemView = getItemViewByStatus(i);
117 | if (itemView != null && !itemView.isDraggable()) {
118 | itemView.fillImageView(imagePath);
119 | if (imageChangesListener != null) imageChangesListener.onImageAdded(imagePath, imageStatus);
120 | break;
121 | }
122 | }
123 | }
124 |
125 | /**
126 | * 删除某一个ImageView时,该imageView变成空的,需要移动到队尾
127 | */
128 | public void onDeleteImage(DraggableItemView deleteView) {
129 | int status = deleteView.getStatus();
130 | int lastDraggableViewStatus = -1;
131 | // 顺次将可拖拽的view往前移
132 | for (int i = status + 1; i < allStatus.length; i++) {
133 | DraggableItemView itemView = getItemViewByStatus(i);
134 | if (itemView.isDraggable()) {
135 | // 可拖拽的view往前移
136 | lastDraggableViewStatus = i;
137 | switchPosition(i, i - 1);
138 | } else {
139 | break;
140 | }
141 | }
142 | if (lastDraggableViewStatus > 0) {
143 | // 被delete的view移动到队尾
144 | deleteView.switchPosition(lastDraggableViewStatus);
145 | }
146 | if (imageChangesListener != null) imageChangesListener.onImageDeleted(deleteView.getImagePath(), deleteView.getStatus());
147 | }
148 |
149 | @Override
150 | public void pickImage(int imageStatus, boolean isModify) {
151 | if (listener != null) listener.pickImage(imageStatus, isModify);
152 | }
153 |
154 | @Override
155 | public void takePhoto(int imageStatus, boolean isModify) {
156 | if (listener != null) listener.takePhoto(imageStatus, isModify);
157 | }
158 |
159 | public void setCustomActionDialog(ActionDialog actionDialog) {
160 | this.actionDialog = actionDialog;
161 | }
162 |
163 | public ActionDialog getActionDialog() {
164 | return actionDialog;
165 | }
166 |
167 | public void setListener(Listener listener) {
168 | this.listener = listener;
169 | }
170 |
171 | public interface Listener {
172 | void pickImage(int imageStatus, boolean isModify);
173 |
174 | void takePhoto(int imageStatus, boolean isModify);
175 | }
176 |
177 | /**
178 | * 这是viewdraghelper拖拽效果的主要逻辑
179 | */
180 | private class DragHelperCallback extends ViewDragHelper.Callback {
181 |
182 | @Override
183 | public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
184 | // draggingView拖动的时候,如果与其它子view交换位置,其他子view位置改变,也会进入这个回调
185 | // 所以此处加了一层判断,剔除不关心的回调,以优化性能
186 | if (changedView == draggingView) {
187 | DraggableItemView changedItemView = (DraggableItemView) changedView;
188 | switchPositionIfNeeded(changedItemView);
189 | }
190 | }
191 |
192 | @Override
193 | public boolean tryCaptureView(View child, int pointerId) {
194 | // 按下的时候,缩放到最小的级别
195 | draggingView = (DraggableItemView) child;
196 | // 手指按下的时候,需要把某些view bringToFront,否则的话,tryCapture将不按预期工作
197 | if (draggingView.isDraggable()) getParent().requestDisallowInterceptTouchEvent(true);
198 | return draggingView.isDraggable();
199 | }
200 |
201 | @Override
202 | public void onViewReleased(View releasedChild, float xvel, float yvel) {
203 | DraggableItemView itemView = (DraggableItemView) releasedChild;
204 | itemView.onDragRelease();
205 | }
206 |
207 | @Override
208 | public int clampViewPositionHorizontal(View child, int left, int dx) {
209 | DraggableItemView itemView = (DraggableItemView) child;
210 | return itemView.computeDraggingX(dx);
211 | }
212 |
213 | @Override
214 | public int clampViewPositionVertical(View child, int top, int dy) {
215 | DraggableItemView itemView = (DraggableItemView) child;
216 | return itemView.computeDraggingY(dy);
217 | }
218 | }
219 |
220 | /**
221 | * 根据draggingView的位置,看看是否需要与其它itemView互换位置
222 | */
223 | private void switchPositionIfNeeded(DraggableItemView draggingView) {
224 | int centerX = draggingView.getLeft() + sideLength / 2;
225 | int centerY = draggingView.getTop() + sideLength / 2;
226 | int everyWidth = getMeasuredWidth() / 3;
227 |
228 | int fromStatus = -1, toStatus = draggingView.getStatus();
229 |
230 | switch (draggingView.getStatus()) {
231 | case DraggableItemView.STATUS_LEFT_TOP:
232 | // 拖动的是左上角的大图
233 | // 依次将小图向上顶
234 | int fromChangeIndex = 0;
235 | if (centerX > everyWidth * 2) {
236 | // 大图往右越过了位置,一定会跟右侧的三个View交换位置才行
237 | if (centerY < everyWidth) {
238 | // 跟右上角的View交换位置
239 | fromChangeIndex = DraggableItemView.STATUS_RIGHT_TOP;
240 | } else if (centerY < everyWidth * 2) {
241 | fromChangeIndex = DraggableItemView.STATUS_RIGHT_MIDDLE;
242 | } else {
243 | fromChangeIndex = DraggableItemView.STATUS_RIGHT_BOTTOM;
244 | }
245 | } else if (centerY > everyWidth * 2) {
246 | if (centerX < everyWidth) {
247 | fromChangeIndex = DraggableItemView.STATUS_LEFT_BOTTOM;
248 | } else if (centerX < everyWidth * 2) {
249 | fromChangeIndex = DraggableItemView.STATUS_MIDDLE_BOTTOM;
250 | } else {
251 | fromChangeIndex = DraggableItemView.STATUS_RIGHT_BOTTOM;
252 | }
253 | }
254 |
255 | DraggableItemView toItemView = getItemViewByStatus(fromChangeIndex);
256 | if (!toItemView.isDraggable()) {
257 | return;
258 | }
259 |
260 | synchronized (this) {
261 | for (int i = 1; i <= fromChangeIndex; i++) {
262 | switchPosition(i, i - 1);
263 | }
264 | draggingView.setStatus(fromChangeIndex);
265 | }
266 | return;
267 | case DraggableItemView.STATUS_RIGHT_TOP:
268 | if (centerX < everyWidth * 2) {
269 | fromStatus = DraggableItemView.STATUS_LEFT_TOP;
270 | } else if (centerY > everyWidth) {
271 | fromStatus = DraggableItemView.STATUS_RIGHT_MIDDLE;
272 | }
273 | break;
274 |
275 | case DraggableItemView.STATUS_RIGHT_MIDDLE:
276 | if (centerX < everyWidth * 2 && centerY < everyWidth * 2) {
277 | fromStatus = DraggableItemView.STATUS_LEFT_TOP;
278 | } else if (centerY < everyWidth) {
279 | fromStatus = DraggableItemView.STATUS_RIGHT_TOP;
280 | } else if (centerY > everyWidth * 2) {
281 | fromStatus = DraggableItemView.STATUS_RIGHT_BOTTOM;
282 | }
283 | break;
284 | case DraggableItemView.STATUS_RIGHT_BOTTOM:
285 | if (centerX < everyWidth * 2 && centerY < everyWidth * 2) {
286 | fromStatus = DraggableItemView.STATUS_LEFT_TOP;
287 | } else if (centerX < everyWidth * 2) {
288 | fromStatus = DraggableItemView.STATUS_MIDDLE_BOTTOM;
289 | } else if (centerY < everyWidth * 2) {
290 | fromStatus = DraggableItemView.STATUS_RIGHT_MIDDLE;
291 | }
292 | break;
293 | case DraggableItemView.STATUS_MIDDLE_BOTTOM:
294 | if (centerX < everyWidth) {
295 | fromStatus = DraggableItemView.STATUS_LEFT_BOTTOM;
296 | } else if (centerX > everyWidth * 2) {
297 | fromStatus = DraggableItemView.STATUS_RIGHT_BOTTOM;
298 | } else if (centerY < everyWidth * 2) {
299 | fromStatus = DraggableItemView.STATUS_LEFT_TOP;
300 | }
301 | break;
302 | case DraggableItemView.STATUS_LEFT_BOTTOM:
303 | if (centerX > everyWidth) {
304 | fromStatus = DraggableItemView.STATUS_MIDDLE_BOTTOM;
305 | } else if (centerY < everyWidth * 2) {
306 | fromStatus = DraggableItemView.STATUS_LEFT_TOP;
307 | }
308 | break;
309 | default:
310 | break;
311 | }
312 |
313 | synchronized (synObj) {
314 | if (fromStatus > 0) {
315 | if (switchPosition(fromStatus, toStatus)) {
316 | draggingView.setStatus(fromStatus);
317 | }
318 | } else if (fromStatus == 0) {
319 | for (int i = toStatus - 1; i >= 0; i--) {
320 | switchPosition(i, i + 1);
321 | }
322 | draggingView.setStatus(fromStatus);
323 | }
324 | }
325 | }
326 |
327 | /**
328 | * 调换位置
329 | */
330 | private boolean switchPosition(int fromStatus, int toStatus) {
331 | DraggableItemView itemView = getItemViewByStatus(fromStatus);
332 | if (itemView.isDraggable()) {
333 | itemView.switchPosition(toStatus);
334 | return true;
335 | }
336 | return false;
337 | }
338 |
339 | /**
340 | * 根据status获取itemView
341 | */
342 | private DraggableItemView getItemViewByStatus(int status) {
343 | int num = getChildCount();
344 | for (int i = 0; i < num; i++) {
345 | DraggableItemView itemView = (DraggableItemView) getChildAt(i);
346 | if (itemView.getStatus() == status) {
347 | return itemView;
348 | }
349 | }
350 | return null;
351 | }
352 |
353 | class MoveDetector extends GestureDetector.SimpleOnGestureListener {
354 | @Override
355 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx,
356 | float dy) {
357 | // 拖动了,touch不往下传递
358 | return Math.abs(dy) + Math.abs(dx) > mTouchSlop;
359 | }
360 | }
361 |
362 | @Override
363 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
364 | int everyLength = (getMeasuredWidth() - 4 * spaceInterval) / 3;
365 | int itemLeft = 0;
366 | int itemTop = 0;
367 | int itemRight = 0;
368 | int itemBottom = 0;
369 | // 每个view的边长是everyLength * 2 + spaceInterval
370 | sideLength = everyLength * 2 + spaceInterval;
371 | int halfSideLength = sideLength / 2; // 边长的一半
372 | int rightCenter = r - spaceInterval - everyLength / 2;
373 | int bottomCenter = b - spaceInterval - everyLength / 2;
374 |
375 | float scaleRate = (float) everyLength / sideLength;
376 | int num = getChildCount();
377 | for (int i = 0; i < num; i++) {
378 | DraggableItemView itemView = (DraggableItemView) getChildAt(i);
379 | itemView.setScaleRate(scaleRate);
380 | switch (itemView.getStatus()) {
381 | case DraggableItemView.STATUS_LEFT_TOP:
382 | int centerPos = spaceInterval + everyLength + spaceInterval / 2;
383 | itemLeft = centerPos - halfSideLength;
384 | itemRight = centerPos + halfSideLength;
385 | itemTop = centerPos - halfSideLength;
386 | itemBottom = centerPos + halfSideLength;
387 | break;
388 | case DraggableItemView.STATUS_RIGHT_TOP:
389 | itemLeft = rightCenter - halfSideLength;
390 | itemRight = rightCenter + halfSideLength;
391 | int hCenter1 = spaceInterval + everyLength / 2;
392 | itemTop = hCenter1 - halfSideLength;
393 | itemBottom = hCenter1 + halfSideLength;
394 | break;
395 | case DraggableItemView.STATUS_RIGHT_MIDDLE:
396 | itemLeft = rightCenter - halfSideLength;
397 | itemRight = rightCenter + halfSideLength;
398 | int hCenter2 = t + getMeasuredHeight() / 2;
399 | itemTop = hCenter2 - halfSideLength;
400 | itemBottom = hCenter2 + halfSideLength;
401 | break;
402 | case DraggableItemView.STATUS_RIGHT_BOTTOM:
403 | itemLeft = rightCenter - halfSideLength;
404 | itemRight = rightCenter + halfSideLength;
405 | itemTop = bottomCenter - halfSideLength;
406 | itemBottom = bottomCenter + halfSideLength;
407 | break;
408 | case DraggableItemView.STATUS_MIDDLE_BOTTOM:
409 | int vCenter1 = l + getMeasuredWidth() / 2;
410 | itemLeft = vCenter1 - halfSideLength;
411 | itemRight = vCenter1 + halfSideLength;
412 | itemTop = bottomCenter - halfSideLength;
413 | itemBottom = bottomCenter + halfSideLength;
414 | break;
415 | case DraggableItemView.STATUS_LEFT_BOTTOM:
416 | int vCenter2 = l + spaceInterval + everyLength / 2;
417 | itemLeft = vCenter2 - halfSideLength;
418 | itemRight = vCenter2 + halfSideLength;
419 | itemTop = bottomCenter - halfSideLength;
420 | itemBottom = bottomCenter + halfSideLength;
421 | break;
422 | }
423 |
424 | ViewGroup.LayoutParams lp = itemView.getLayoutParams();
425 | lp.width = sideLength;
426 | lp.height = sideLength;
427 | itemView.setLayoutParams(lp);
428 |
429 | Point itemPoint = originViewPositionList.get(itemView.getStatus());
430 | itemPoint.x = itemLeft;
431 | itemPoint.y = itemTop;
432 |
433 | itemView.layout(itemLeft, itemTop, itemRight, itemBottom);
434 | }
435 | }
436 |
437 | @Override
438 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
439 | measureChildren(widthMeasureSpec, widthMeasureSpec);
440 | int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
441 | int width = resolveSizeAndState(maxWidth, widthMeasureSpec, 0);
442 | setMeasuredDimension(width, width);
443 | }
444 |
445 | @Override
446 | public boolean dispatchTouchEvent(MotionEvent ev) {
447 | if (ev.getAction() == MotionEvent.ACTION_DOWN) {
448 | downX = (int) ev.getX();
449 | downY = (int) ev.getY();
450 | downTime = System.currentTimeMillis();
451 | bringToFrontWhenTouchDown(downX, downY);
452 | } else if (ev.getAction() == MotionEvent.ACTION_UP) {
453 | if (draggingView != null) {
454 | draggingView.onDragRelease();
455 | }
456 | draggingView = null;
457 |
458 | if (null != moveAnchorThread) {
459 | moveAnchorThread.interrupt();
460 | moveAnchorThread = null;
461 | }
462 | }
463 | return super.dispatchTouchEvent(ev);
464 | }
465 |
466 | /**
467 | * 按下时根据触点的位置,将某个view bring到前台
468 | */
469 | private void bringToFrontWhenTouchDown(final int downX, final int downY) {
470 | int statusIndex = getStatusByDownPoint(downX, downY);
471 | final DraggableItemView itemView = getItemViewByStatus(statusIndex);
472 | if (indexOfChild(itemView) != getChildCount() - 1) {
473 | bringChildToFront(itemView);
474 | }
475 | if (!itemView.isDraggable()) {
476 | return;
477 | }
478 |
479 | itemView.saveAnchorInfo(downX, downY);
480 | moveAnchorThread = new Thread() {
481 | @Override
482 | public void run() {
483 | try {
484 | sleep(INTERCEPT_TIME_SLOP);
485 | } catch (InterruptedException e) {
486 | }
487 |
488 | Message msg = anchorHandler.obtainMessage();
489 | msg.sendToTarget();
490 | }
491 | };
492 | moveAnchorThread.start();
493 | }
494 |
495 | private int getStatusByDownPoint(int downX, int downY) {
496 | int everyWidth = getMeasuredWidth() / 3;
497 | if (downX < everyWidth) {
498 | if (downY < everyWidth * 2) {
499 | return DraggableItemView.STATUS_LEFT_TOP;
500 | } else {
501 | return DraggableItemView.STATUS_LEFT_BOTTOM;
502 | }
503 | } else if (downX < everyWidth * 2) {
504 | if (downY < everyWidth * 2) {
505 | return DraggableItemView.STATUS_LEFT_TOP;
506 | } else {
507 | return DraggableItemView.STATUS_MIDDLE_BOTTOM;
508 | }
509 | } else {
510 | if (downY < everyWidth) {
511 | return DraggableItemView.STATUS_RIGHT_TOP;
512 | } else if (downY < everyWidth * 2) {
513 | return DraggableItemView.STATUS_RIGHT_MIDDLE;
514 | } else {
515 | return DraggableItemView.STATUS_RIGHT_BOTTOM;
516 | }
517 | }
518 | }
519 |
520 | @Override
521 | public boolean onInterceptTouchEvent(MotionEvent ev) {
522 | if (downTime > 0 && System.currentTimeMillis() - downTime > INTERCEPT_TIME_SLOP) {
523 | return true;
524 | }
525 | boolean shouldIntercept = mDragHelper.shouldInterceptTouchEvent(ev);
526 | int action = ev.getActionMasked();
527 | if (action == MotionEvent.ACTION_DOWN) {
528 | mDragHelper.processTouchEvent(ev);
529 | }
530 |
531 | boolean moveFlag = moveDetector.onTouchEvent(ev);
532 | if (moveFlag) {
533 | if (null != moveAnchorThread) {
534 | moveAnchorThread.interrupt();
535 | moveAnchorThread = null;
536 | }
537 |
538 | if (null != draggingView && draggingView.isDraggable()) {
539 | draggingView.startAnchorAnimation();
540 | }
541 | }
542 | return shouldIntercept && moveFlag;
543 | }
544 |
545 | @Override
546 | public boolean onTouchEvent(MotionEvent e) {
547 | try {
548 | // 该行代码可能会抛异常,正式发布时请将这行代码加上try catch
549 | mDragHelper.processTouchEvent(e);
550 | } catch (Exception ex) {
551 | ex.printStackTrace();
552 | }
553 | return true;
554 | }
555 |
556 | /**
557 | * if Itemview is empty then return null
558 | *
559 | * @return
560 | */
561 | public SparseArray getImageUrls() {
562 | SparseArray stringSparseArray = new SparseArray<>();
563 | for (int i = 0; i < getChildCount(); i++) {
564 | if (getChildAt(i) instanceof DraggableItemView) {
565 | stringSparseArray.put(((DraggableItemView) getChildAt(i)).getStatus(), ((DraggableItemView) getChildAt(i)).getImagePath());
566 | }
567 | }
568 | return stringSparseArray;
569 | }
570 |
571 | public int getImageSetSize() {
572 | return allStatus.length;
573 | }
574 |
575 | public void setImageChangesListener(ImageChangesListener imageChangesListener) {
576 | this.imageChangesListener = imageChangesListener;
577 | }
578 |
579 | ImageChangesListener imageChangesListener;
580 |
581 | public interface ImageChangesListener {
582 | void onImageAdded(String uri, int index);
583 |
584 | void onImageEdited(String uri, int index);
585 |
586 | void onImageDeleted(String uri, int index);
587 | }
588 | }
--------------------------------------------------------------------------------