diff --git a/build.gradle b/build.gradle
index bebd3fbdb..e7e192d7e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -21,9 +21,9 @@ buildscript {
google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.1.4'
- classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.40'
- classpath 'com.novoda:bintray-release:0.8.1'
+ classpath 'com.android.tools.build:gradle:3.5.1'
+ classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.50'
+ classpath 'com.novoda:bintray-release:0.9.1'
}
}
diff --git a/gradle.properties b/gradle.properties
index aac7c9b46..9e6fce102 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -9,6 +9,8 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
+android.enableJetifier=true
+android.useAndroidX=true
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index cb65260eb..558c30580 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Wed Jun 27 11:32:18 CST 2018
+#Tue Oct 29 13:40:36 ICT 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
diff --git a/matisse/build.gradle b/matisse/build.gradle
index ca9b2f92b..298f18a6f 100644
--- a/matisse/build.gradle
+++ b/matisse/build.gradle
@@ -29,6 +29,10 @@ android {
lintOptions {
abortOnError true
}
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
}
ext.supportLibVersion = '28.0.0'
@@ -36,15 +40,17 @@ ext.supportLibVersion = '28.0.0'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation "com.android.support:support-v4:${supportLibVersion}"
- implementation "com.android.support:appcompat-v7:${supportLibVersion}"
- implementation "com.android.support:support-annotations:${supportLibVersion}"
- implementation "com.android.support:recyclerview-v7:${supportLibVersion}"
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'androidx.annotation:annotation:1.1.0'
+ implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'it.sephiroth.android.library.imagezoom:library:1.0.4'
implementation 'com.github.yalantis:ucrop:2.2.3-native'
implementation 'info.androidhive:imagefilters:1.0.7'
- implementation 'com.github.bumptech.glide:glide:4.9.0'
+ implementation 'com.github.bumptech.glide:glide:4.10.0'
compileOnly 'com.squareup.picasso:picasso:2.5.2'
+ implementation 'com.github.MasayukiSuda:Mp4Composer-android:v0.3.3'
+ implementation 'com.google.android.exoplayer:exoplayer:2.10.4'
}
// jcenter configuration for novoda's bintray-release
diff --git a/matisse/src/main/AndroidManifest.xml b/matisse/src/main/AndroidManifest.xml
index a06ca9d4f..e08d1df18 100644
--- a/matisse/src/main/AndroidManifest.xml
+++ b/matisse/src/main/AndroidManifest.xml
@@ -25,5 +25,6 @@
+
\ No newline at end of file
diff --git a/matisse/src/main/java/com/zhihu/matisse/Matisse.java b/matisse/src/main/java/com/zhihu/matisse/Matisse.java
index 74763215d..6831a4de8 100644
--- a/matisse/src/main/java/com/zhihu/matisse/Matisse.java
+++ b/matisse/src/main/java/com/zhihu/matisse/Matisse.java
@@ -18,8 +18,9 @@
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
-import android.support.annotation.Nullable;
-import android.support.v4.app.Fragment;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
import com.zhihu.matisse.ui.MatisseActivity;
diff --git a/matisse/src/main/java/com/zhihu/matisse/MimeType.java b/matisse/src/main/java/com/zhihu/matisse/MimeType.java
index 4b6c39cad..69bdc3c62 100644
--- a/matisse/src/main/java/com/zhihu/matisse/MimeType.java
+++ b/matisse/src/main/java/com/zhihu/matisse/MimeType.java
@@ -19,9 +19,10 @@
import android.content.ContentResolver;
import android.net.Uri;
import android.text.TextUtils;
-import android.support.v4.util.ArraySet;
import android.webkit.MimeTypeMap;
+import androidx.collection.ArraySet;
+
import com.zhihu.matisse.internal.utils.PhotoMetadataUtils;
import java.util.Arrays;
diff --git a/matisse/src/main/java/com/zhihu/matisse/SelectionCreator.java b/matisse/src/main/java/com/zhihu/matisse/SelectionCreator.java
index c4c835c02..0cf67ac56 100644
--- a/matisse/src/main/java/com/zhihu/matisse/SelectionCreator.java
+++ b/matisse/src/main/java/com/zhihu/matisse/SelectionCreator.java
@@ -19,12 +19,13 @@
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
-import android.support.annotation.IntDef;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RequiresApi;
-import android.support.annotation.StyleRes;
-import android.support.v4.app.Fragment;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.StyleRes;
+import androidx.fragment.app.Fragment;
import com.zhihu.matisse.engine.ImageEngine;
import com.zhihu.matisse.filter.Filter;
@@ -257,9 +258,20 @@ public SelectionCreator filterEnable(boolean enable) {
return this;
}
+ /**
+ * Enable/disable the trim Video.
+ *
+ * @param enable is enable. Default value is true
+ * @return {@link SelectionCreator} for fluent API.
+ */
+ public SelectionCreator trimVideoEnable(boolean enable) {
+ mSelectionSpec.hasTrimVideo = enable;
+ return this;
+ }
+
/**
* Capture strategy provided for the location to save photos including internal and external
- * storage and also a authority for {@link android.support.v4.content.FileProvider}.
+ * storage and also a authority for {@link androidx.core.content.FileProvider}.
*
* @param captureStrategy {@link CaptureStrategy}, needed only when capturing is enabled.
* @return {@link SelectionCreator} for fluent API.
@@ -365,6 +377,11 @@ public SelectionCreator setOnCheckedListener(@Nullable OnCheckedListener listene
return this;
}
+// public SelectionCreator showPreview(boolean showPreview) {
+// mSelectionSpec.showPreview = showPreview;
+// return this;
+// }
+
/**
* Start to select media and wait for result.
*
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/entity/Album.java b/matisse/src/main/java/com/zhihu/matisse/internal/entity/Album.java
index 9ebafb8c5..f375c8e39 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/entity/Album.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/entity/Album.java
@@ -21,7 +21,8 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.MediaStore;
-import android.support.annotation.Nullable;
+
+import androidx.annotation.Nullable;
import com.zhihu.matisse.R;
import com.zhihu.matisse.internal.loader.AlbumLoader;
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/entity/IncapableCause.java b/matisse/src/main/java/com/zhihu/matisse/internal/entity/IncapableCause.java
index 5c3920693..3fc0b980c 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/entity/IncapableCause.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/entity/IncapableCause.java
@@ -16,10 +16,11 @@
package com.zhihu.matisse.internal.entity;
import android.content.Context;
-import android.support.annotation.IntDef;
-import android.support.v4.app.FragmentActivity;
import android.widget.Toast;
+import androidx.annotation.IntDef;
+import androidx.fragment.app.FragmentActivity;
+
import com.zhihu.matisse.internal.ui.widget.IncapableDialog;
import java.lang.annotation.Retention;
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/entity/Item.java b/matisse/src/main/java/com/zhihu/matisse/internal/entity/Item.java
index b9087d7d8..98e67a5e0 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/entity/Item.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/entity/Item.java
@@ -22,7 +22,8 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.MediaStore;
-import android.support.annotation.Nullable;
+
+import androidx.annotation.Nullable;
import com.zhihu.matisse.MimeType;
@@ -43,8 +44,7 @@ public Item[] newArray(int size) {
public static final String ITEM_DISPLAY_NAME_CAPTURE = "Capture";
public final long id;
public final String mimeType;
- public final Uri uri;
- public Uri uriCrop;
+ public Uri uri;
public final long size;
public final long duration; // only for video, in ms
@@ -61,7 +61,6 @@ private Item(long id, String mimeType, long size, long duration) {
contentUri = MediaStore.Files.getContentUri("external");
}
this.uri = ContentUris.withAppendedId(contentUri, id);
- this.uriCrop = null;
this.size = size;
this.duration = duration;
}
@@ -72,7 +71,6 @@ private Item(Parcel source) {
uri = source.readParcelable(Uri.class.getClassLoader());
size = source.readLong();
duration = source.readLong();
- uriCrop = source.readParcelable(Uri.class.getClassLoader());
}
public static Item valueOf(Cursor cursor) {
@@ -92,7 +90,6 @@ public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
dest.writeString(mimeType);
dest.writeParcelable(uri, 0);
- dest.writeParcelable(uriCrop, 0);
dest.writeLong(size);
dest.writeLong(duration);
}
@@ -127,10 +124,10 @@ public boolean equals(Object obj) {
return id == other.id
&& (mimeType != null && mimeType.equals(other.mimeType)
|| (mimeType == null && other.mimeType == null))
- && (uri != null && uri.equals(other.uri)
+ /*&& (uri != null && uri.equals(other.uri)
|| (uri == null && other.uri == null))
&& size == other.size
- && duration == other.duration;
+ && duration == other.duration*/;
}
@Override
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/entity/SelectionSpec.java b/matisse/src/main/java/com/zhihu/matisse/internal/entity/SelectionSpec.java
index 85b77d621..ffc4f6191 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/entity/SelectionSpec.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/entity/SelectionSpec.java
@@ -17,7 +17,8 @@
package com.zhihu.matisse.internal.entity;
import android.content.pm.ActivityInfo;
-import android.support.annotation.StyleRes;
+
+import androidx.annotation.StyleRes;
import com.zhihu.matisse.MimeType;
import com.zhihu.matisse.R;
@@ -56,6 +57,8 @@ public final class SelectionSpec {
public int originalMaxSize;
public int cropMaxSize;
public boolean hasFilter;
+ public boolean hasTrimVideo;
+ public boolean showPreview;
public OnCheckedListener onCheckedListener;
private SelectionSpec() {
@@ -94,6 +97,8 @@ private void reset() {
originalMaxSize = Integer.MAX_VALUE;
cropMaxSize = 4000;
hasFilter = true;
+ hasTrimVideo = true;
+ showPreview = false;
}
public boolean singleSelectionModeEnabled() {
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/loader/AlbumLoader.java b/matisse/src/main/java/com/zhihu/matisse/internal/loader/AlbumLoader.java
index f328296a9..7bf05db4e 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/loader/AlbumLoader.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/loader/AlbumLoader.java
@@ -22,7 +22,8 @@
import android.database.MergeCursor;
import android.net.Uri;
import android.provider.MediaStore;
-import android.support.v4.content.CursorLoader;
+
+import androidx.loader.content.CursorLoader;
import com.zhihu.matisse.internal.entity.Album;
import com.zhihu.matisse.internal.entity.SelectionSpec;
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/loader/AlbumMediaLoader.java b/matisse/src/main/java/com/zhihu/matisse/internal/loader/AlbumMediaLoader.java
index ea2b2919e..709905be0 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/loader/AlbumMediaLoader.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/loader/AlbumMediaLoader.java
@@ -22,7 +22,8 @@
import android.database.MergeCursor;
import android.net.Uri;
import android.provider.MediaStore;
-import android.support.v4.content.CursorLoader;
+
+import androidx.loader.content.CursorLoader;
import com.zhihu.matisse.internal.entity.Album;
import com.zhihu.matisse.internal.entity.Item;
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/model/AlbumCollection.java b/matisse/src/main/java/com/zhihu/matisse/internal/model/AlbumCollection.java
index bdfa925e1..7d0b48ec8 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/model/AlbumCollection.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/model/AlbumCollection.java
@@ -19,9 +19,10 @@
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.Loader;
+
+import androidx.fragment.app.FragmentActivity;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.Loader;
import com.zhihu.matisse.internal.loader.AlbumLoader;
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/model/AlbumMediaCollection.java b/matisse/src/main/java/com/zhihu/matisse/internal/model/AlbumMediaCollection.java
index 90a938ab7..770178e6b 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/model/AlbumMediaCollection.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/model/AlbumMediaCollection.java
@@ -19,11 +19,12 @@
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.Loader;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.FragmentActivity;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.Loader;
import com.zhihu.matisse.internal.entity.Album;
import com.zhihu.matisse.internal.loader.AlbumMediaLoader;
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/model/SelectedItemCollection.java b/matisse/src/main/java/com/zhihu/matisse/internal/model/SelectedItemCollection.java
index 096f29651..134db848d 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/model/SelectedItemCollection.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/model/SelectedItemCollection.java
@@ -145,7 +145,7 @@ public List- asList() {
public List asListOfUri() {
List uris = new ArrayList<>();
for (Item item : mItems) {
- uris.add(item.uriCrop);
+ uris.add(item.uri);
}
return uris;
}
@@ -167,8 +167,8 @@ public boolean isSelected(Item item) {
}
public IncapableCause isAcceptable(Item item) {
- if (maxSelectableReached()) {
- int maxSelectable = currentMaxSelectable();
+ if (maxSelectableReached(item)) {
+ int maxSelectable = currentMaxSelectable(item);
String cause;
try {
@@ -197,18 +197,27 @@ public IncapableCause isAcceptable(Item item) {
return PhotoMetadataUtils.isAcceptable(mContext, item);
}
- public boolean maxSelectableReached() {
- return mItems.size() == currentMaxSelectable();
+ public boolean maxSelectableReached(Item item) {
+ SelectionSpec spec = SelectionSpec.getInstance();
+ int maxSelectable = currentMaxSelectable(item);
+ if (spec.maxSelectable > 0) {
+ return count() >= maxSelectable;
+ } else if (item.isImage()) {
+ return countImage() >= maxSelectable;
+ } else if (item.isVideo()) {
+ return countVideo() >= maxSelectable;
+ }
+ return count() >= maxSelectable;
}
// depends
- private int currentMaxSelectable() {
+ private int currentMaxSelectable(Item item) {
SelectionSpec spec = SelectionSpec.getInstance();
if (spec.maxSelectable > 0) {
return spec.maxSelectable;
- } else if (mCollectionType == COLLECTION_IMAGE) {
+ } else if (item.isImage()) {
return spec.maxImageSelectable;
- } else if (mCollectionType == COLLECTION_VIDEO) {
+ } else if (item.isVideo()) {
return spec.maxVideoSelectable;
} else {
return spec.maxSelectable;
@@ -249,8 +258,36 @@ public int count() {
return mItems.size();
}
+ public int countImage() {
+ int count = 0;
+ for (Item item : mItems) if (item.isImage()) count++;
+ return count;
+ }
+
+ public int countVideo() {
+ int count = 0;
+ for (Item item : mItems) if (item.isVideo()) count++;
+ return count;
+ }
+
public int checkedNumOf(Item item) {
int index = new ArrayList<>(mItems).indexOf(item);
return index == -1 ? CheckView.UNCHECKED : index + 1;
}
+
+ public boolean hasImage() {
+ return mCollectionType == COLLECTION_IMAGE || mCollectionType == COLLECTION_MIXED;
+ }
+
+ public boolean hasVideo() {
+ return mCollectionType == COLLECTION_VIDEO || mCollectionType == COLLECTION_MIXED;
+ }
+
+ public void updateItem(Item result) {
+ for (Item item : mItems)
+ if (item.isVideo()) {
+ item.uri = result.uri;
+ break;
+ }
+ }
}
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/ui/AlbumPreviewActivity.java b/matisse/src/main/java/com/zhihu/matisse/internal/ui/AlbumPreviewActivity.java
index 73ff53e2a..ac694cf2d 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/ui/AlbumPreviewActivity.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/ui/AlbumPreviewActivity.java
@@ -17,7 +17,8 @@
import android.database.Cursor;
import android.os.Bundle;
-import android.support.annotation.Nullable;
+
+import androidx.annotation.Nullable;
import com.zhihu.matisse.internal.entity.Album;
import com.zhihu.matisse.internal.entity.Item;
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/ui/BasePreviewActivity.java b/matisse/src/main/java/com/zhihu/matisse/internal/ui/BasePreviewActivity.java
index 82383fd76..24cccc0d7 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/ui/BasePreviewActivity.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/ui/BasePreviewActivity.java
@@ -19,16 +19,17 @@
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v4.view.ViewPager;
-import android.support.v4.view.animation.FastOutSlowInInterpolator;
-import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
+import androidx.viewpager.widget.ViewPager;
+
import com.zhihu.matisse.R;
import com.zhihu.matisse.internal.entity.IncapableCause;
import com.zhihu.matisse.internal.entity.Item;
@@ -247,7 +248,7 @@ public void onPageSelected(int position) {
if (checkedNum > 0) {
mCheckView.setEnabled(true);
} else {
- mCheckView.setEnabled(!mSelectedCollection.maxSelectableReached());
+ mCheckView.setEnabled(!mSelectedCollection.maxSelectableReached(item));
}
} else {
boolean checked = mSelectedCollection.isSelected(item);
@@ -255,7 +256,7 @@ public void onPageSelected(int position) {
if (checked) {
mCheckView.setEnabled(true);
} else {
- mCheckView.setEnabled(!mSelectedCollection.maxSelectableReached());
+ mCheckView.setEnabled(!mSelectedCollection.maxSelectableReached(item));
}
}
updateSize(item);
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/ui/MediaSelectionFragment.java b/matisse/src/main/java/com/zhihu/matisse/internal/ui/MediaSelectionFragment.java
index 6cc6c719c..5b32ce9f9 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/ui/MediaSelectionFragment.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/ui/MediaSelectionFragment.java
@@ -19,17 +19,25 @@
import android.content.ContextWrapper;
import android.database.Cursor;
import android.graphics.Bitmap;
+import android.graphics.Point;
import android.net.Uri;
import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v4.app.Fragment;
-import android.support.v7.widget.GridLayoutManager;
-import android.support.v7.widget.RecyclerView;
import android.util.Log;
+import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.daasuu.mp4compose.FillMode;
+import com.daasuu.mp4compose.FillModeCustomItem;
+import com.daasuu.mp4compose.composer.Mp4Composer;
import com.yalantis.ucrop.UCrop;
import com.yalantis.ucrop.UCropFragment;
import com.zhihu.matisse.R;
@@ -39,9 +47,12 @@
import com.zhihu.matisse.internal.model.AlbumMediaCollection;
import com.zhihu.matisse.internal.model.SelectedItemCollection;
import com.zhihu.matisse.internal.ui.adapter.AlbumMediaAdapter;
-import com.zhihu.matisse.internal.ui.widget.CheckView;
import com.zhihu.matisse.internal.ui.widget.MediaGridInset;
-import com.zhihu.matisse.internal.utils.UIUtils;
+import com.zhihu.matisse.internal.utils.PathUtils;
+import com.zhihu.matisse.internal.utils.Utils;
+import com.zhihu.matisse.ui.MatisseActivity;
+import com.zhihu.matisse.ui.widget.GesturePlayerTextureView;
+import com.zhihu.matisse.ui.widget.SceneCropColor;
import java.io.File;
import java.util.Calendar;
@@ -56,6 +67,7 @@ public class MediaSelectionFragment extends Fragment implements
private final AlbumMediaCollection mAlbumMediaCollection = new AlbumMediaCollection();
private RecyclerView mRecyclerView;
+ private GesturePlayerTextureView playerTextureView;
private AlbumMediaAdapter mAdapter;
private SelectionProvider mSelectionProvider;
private AlbumMediaAdapter.CheckStateListener mCheckStateListener;
@@ -64,6 +76,7 @@ public class MediaSelectionFragment extends Fragment implements
private Uri destinationUri;
private Album album;
private boolean isFirst = true;
+ private Context context;
public static MediaSelectionFragment newInstance(Album album) {
MediaSelectionFragment fragment = new MediaSelectionFragment();
@@ -76,6 +89,7 @@ public static MediaSelectionFragment newInstance(Album album) {
@Override
public void onAttach(Context context) {
super.onAttach(context);
+ this.context = context;
if (context instanceof SelectionProvider) {
mSelectionProvider = (SelectionProvider) context;
} else {
@@ -107,7 +121,7 @@ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
album = getArguments().getParcelable(EXTRA_ALBUM);
- mAdapter = new AlbumMediaAdapter(getContext(),
+ mAdapter = new AlbumMediaAdapter(context,
mSelectionProvider.provideSelectedItemCollection(), mRecyclerView);
mAdapter.registerCheckStateListener(this);
mAdapter.registerOnMediaClickListener(this);
@@ -116,11 +130,11 @@ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
int spanCount;
SelectionSpec selectionSpec = SelectionSpec.getInstance();
if (selectionSpec.gridExpectedSize > 0) {
- spanCount = UIUtils.spanCount(getContext(), selectionSpec.gridExpectedSize);
+ spanCount = Utils.Companion.spanCount(context, selectionSpec.gridExpectedSize);
} else {
spanCount = selectionSpec.spanCount;
}
- mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), spanCount));
+ mRecyclerView.setLayoutManager(new GridLayoutManager(context, spanCount));
int spacing = getResources().getDimensionPixelSize(R.dimen.media_grid_spacing);
mRecyclerView.addItemDecoration(new MediaGridInset(spanCount, spacing, false));
@@ -149,7 +163,7 @@ public void onAlbumMediaLoad(Cursor cursor) {
if (isFirst) {
isFirst = false;
cursor.moveToPosition(album.isAll() ? 1 : 0);
- showPreviewImage(Item.valueOf(cursor).uri);
+ showPreviewItem(Item.valueOf(cursor));
// SelectedItemCollection collection = mSelectionProvider.provideSelectedItemCollection();
// if (collection.isEmpty()) collection.add(Item.valueOf(cursor));
}
@@ -169,26 +183,75 @@ public void onUpdate() {
}
@Override
- public void onMediaClick(Album album, Item item, Item mPrevious, int adapterPosition) {
- if (!item.equals(mPrevious)) {
- cropCurrentImage(mPrevious);
+ public void onMediaClick(Album album, Item item, int adapterPosition) {
+ if (mOnMediaClickListener != null) {
+ mOnMediaClickListener.onMediaClick(getArguments().getParcelable(EXTRA_ALBUM),
+ item, adapterPosition);
}
+ }
+
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (playerTextureView != null) {
+ playerTextureView.play();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (playerTextureView != null) {
+ playerTextureView.pause();
+ }
+ }
+
+ @Override
+ public void onMediaAdded(Item item, Item prev) {
+ cropItem(prev);
- if (!item.equals(mPrevious))
- showPreviewImage(item.uri);
+ showPreviewItem(item);
}
public boolean onNextButtonClick() {
- return cropCurrentImage(mAdapter.mPrevious);
+ return cropItem(mAdapter.mPrevious);
}
- private boolean cropCurrentImage(Item item) {
+ private boolean cropItem(Item item) {
+ if (!mSelectionProvider.provideSelectedItemCollection().isSelected(item)) return true;
+
+ if (item.isImage()) return cropImage(item);
+ else return cropVideo(item);
+ }
+
+ private boolean cropVideo(Item item) {
+ MatisseActivity activity = (MatisseActivity) getActivity();
+ activity.showProgress();
+ File file = getFile(false);
+ destinationUri = Uri.fromFile(file);
+ if (destinationUri.getPath() == null) return true;
+
+ String path = PathUtils.getPath(context, item.getContentUri());
+ FillModeCustomItem fillModeCustomItem = Utils.Companion.getFillMode(playerTextureView, path);
+
+ new Mp4Composer(path, file.getPath())
+ .size(720, 720)
+ .filter(Utils.Companion.getFill(SceneCropColor.WHITE))
+ .fillMode(FillMode.CUSTOM)
+ .customFillMode(fillModeCustomItem)
+ .listener(activity)
+ .start();
+
+ item.uri = destinationUri;
+ return false;
+ }
+
+ private boolean cropImage(Item item) {
// crop and save Current image
- SelectedItemCollection collection = mSelectionProvider.provideSelectedItemCollection();
- int checkedNum = collection.checkedNumOf(item);
- if (fragment != null && fragment.isAdded() && checkedNum != CheckView.UNCHECKED) {
+ if (fragment != null && fragment.isAdded()) {
fragment.cropAndSaveImage();
- item.uriCrop = destinationUri;
+ item.uri = destinationUri;
Log.d("cropAndSaveImage: ", destinationUri.toString());
return false;
}
@@ -196,11 +259,33 @@ private boolean cropCurrentImage(Item item) {
}
- public void showPreviewImage(Uri uri) {
- // load new image
- String destinationFileName = String.format("%s.jpeg", Calendar.getInstance().getTimeInMillis());
+ private void showPreviewItem(Item item) {
+ if (item.isImage()) showPreviewImage(item.uri);
+ else showPreviewVideo(item);
+ }
+
+ private void showPreviewVideo(Item item) {
+ FrameLayout parent = getView().findViewById(R.id.mPreview);
+ parent.removeAllViews();
+ playerTextureView = new GesturePlayerTextureView(context, item.uri, null);
- destinationUri = Uri.fromFile(new File(new ContextWrapper(getContext()).getCacheDir(), destinationFileName));
+ Point size = new Point();
+ ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getSize(size);
+ float baseWidthSize = size.x;
+ playerTextureView.setBaseWidthSize(baseWidthSize);
+
+ FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
+ lp.gravity = Gravity.CENTER;
+ playerTextureView.setLayoutParams(lp);
+
+ parent.addView(playerTextureView);
+ }
+
+ private void showPreviewImage(Uri uri) {
+ FrameLayout parent = getView().findViewById(R.id.mPreview);
+ parent.removeAllViews();
+
+ destinationUri = Uri.fromFile(getFile(true));
UCrop uCrop = UCrop.of(uri, destinationUri);
uCrop = setupConfig(uCrop);
@@ -211,6 +296,14 @@ public void showPreviewImage(Uri uri) {
.commitAllowingStateLoss();
}
+ private File getFile(boolean isImage) {
+ String name = String.valueOf(Calendar.getInstance().getTimeInMillis());
+ if (isImage) name += ".jpeg";
+ else name += ".mp4";
+ File file = new File(new ContextWrapper(context).getCacheDir(), name);
+ Log.d("getFile: ", file.getPath());
+ return file;
+ }
private UCrop setupConfig(UCrop uCrop) {
UCrop.Options options = new UCrop.Options();
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/ui/PreviewItemFragment.java b/matisse/src/main/java/com/zhihu/matisse/internal/ui/PreviewItemFragment.java
index ef7fb1231..ec3cab8d6 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/ui/PreviewItemFragment.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/ui/PreviewItemFragment.java
@@ -20,13 +20,14 @@
import android.content.Intent;
import android.graphics.Point;
import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
import com.zhihu.matisse.R;
import com.zhihu.matisse.internal.entity.Item;
import com.zhihu.matisse.internal.entity.SelectionSpec;
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/ui/SelectedPreviewActivity.java b/matisse/src/main/java/com/zhihu/matisse/internal/ui/SelectedPreviewActivity.java
index 4bd6e25a7..7b8132d84 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/ui/SelectedPreviewActivity.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/ui/SelectedPreviewActivity.java
@@ -16,7 +16,8 @@
package com.zhihu.matisse.internal.ui;
import android.os.Bundle;
-import android.support.annotation.Nullable;
+
+import androidx.annotation.Nullable;
import com.zhihu.matisse.internal.entity.Item;
import com.zhihu.matisse.internal.entity.SelectionSpec;
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/AlbumMediaAdapter.java b/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/AlbumMediaAdapter.java
index 305425665..9b6349654 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/AlbumMediaAdapter.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/AlbumMediaAdapter.java
@@ -20,14 +20,15 @@
import android.database.Cursor;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
-import android.support.v7.widget.GridLayoutManager;
-import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
import com.zhihu.matisse.R;
import com.zhihu.matisse.internal.entity.Album;
import com.zhihu.matisse.internal.entity.IncapableCause;
@@ -69,12 +70,9 @@ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType
if (viewType == VIEW_TYPE_CAPTURE) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.photo_capture_item, parent, false);
CaptureViewHolder holder = new CaptureViewHolder(v);
- holder.itemView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (v.getContext() instanceof OnPhotoCapture) {
- ((OnPhotoCapture) v.getContext()).capture();
- }
+ holder.itemView.setOnClickListener(v1 -> {
+ if (v1.getContext() instanceof OnPhotoCapture) {
+ ((OnPhotoCapture) v1.getContext()).capture();
}
});
return holder;
@@ -133,7 +131,7 @@ private void setCheckStatus(Item item, MediaGrid mediaGrid) {
mediaGrid.setCheckEnabled(true);
mediaGrid.setCheckedNum(checkedNum);
} else {
- if (mSelectedCollection.maxSelectableReached()) {
+ if (mSelectedCollection.maxSelectableReached(item)) {
mediaGrid.setCheckEnabled(false);
mediaGrid.setCheckedNum(CheckView.UNCHECKED);
} else {
@@ -147,7 +145,7 @@ private void setCheckStatus(Item item, MediaGrid mediaGrid) {
mediaGrid.setCheckEnabled(true);
mediaGrid.setChecked(true);
} else {
- if (mSelectedCollection.maxSelectableReached()) {
+ if (mSelectedCollection.maxSelectableReached(item)) {
mediaGrid.setCheckEnabled(false);
mediaGrid.setChecked(false);
} else {
@@ -160,30 +158,29 @@ private void setCheckStatus(Item item, MediaGrid mediaGrid) {
@Override
public void onThumbnailClicked(ImageView thumbnail, Item item, RecyclerView.ViewHolder holder) {
- if (mOnMediaClickListener != null) {
- int checkedNum = mSelectedCollection.checkedNumOf(item);
- if (checkedNum == CheckView.UNCHECKED) {
- if (assertAddSelection(holder.itemView.getContext(), item)) {
- mSelectedCollection.add(item);
- mOnMediaClickListener.onMediaClick(null, item, mPrevious, holder.getAdapterPosition());
- }
- } else {
- mSelectedCollection.remove(item);
+ if (mSelectionSpec.showPreview) {
+ if (mOnMediaClickListener != null) {
+ mOnMediaClickListener.onMediaClick(null, item, holder.getAdapterPosition());
}
- notifyCheckStateChanged();
-
- mPrevious = item;
+ } else {
+ updateSelectedItem(item, holder);
}
}
@Override
public void onCheckViewClicked(CheckView checkView, Item item, RecyclerView.ViewHolder holder) {
+ updateSelectedItem(item, holder);
+ }
+
+ private void updateSelectedItem(Item item, RecyclerView.ViewHolder holder) {
if (mSelectionSpec.countable) {
int checkedNum = mSelectedCollection.checkedNumOf(item);
if (checkedNum == CheckView.UNCHECKED) {
if (assertAddSelection(holder.itemView.getContext(), item)) {
mSelectedCollection.add(item);
notifyCheckStateChanged();
+ mOnMediaClickListener.onMediaAdded(item, mPrevious);
+ mPrevious = item;
}
} else {
mSelectedCollection.remove(item);
@@ -197,6 +194,8 @@ public void onCheckViewClicked(CheckView checkView, Item item, RecyclerView.View
if (assertAddSelection(holder.itemView.getContext(), item)) {
mSelectedCollection.add(item);
notifyCheckStateChanged();
+ mOnMediaClickListener.onMediaAdded(item, mPrevious);
+ mPrevious = item;
}
}
}
@@ -273,7 +272,8 @@ public interface CheckStateListener {
}
public interface OnMediaClickListener {
- void onMediaClick(Album album, Item item, Item mPrevious, int adapterPosition);
+ void onMediaClick(Album album, Item item, int adapterPosition);
+ void onMediaAdded(Item item, Item prev);
}
public interface OnPhotoCapture {
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/PreviewPagerAdapter.java b/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/PreviewPagerAdapter.java
index eef46f055..7dcbf594b 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/PreviewPagerAdapter.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/PreviewPagerAdapter.java
@@ -15,11 +15,12 @@
*/
package com.zhihu.matisse.internal.ui.adapter;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentPagerAdapter;
import android.view.ViewGroup;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentPagerAdapter;
+
import com.zhihu.matisse.internal.entity.Item;
import com.zhihu.matisse.internal.ui.PreviewItemFragment;
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/RecyclerViewCursorAdapter.java b/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/RecyclerViewCursorAdapter.java
index 6557dde4d..feee6533c 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/RecyclerViewCursorAdapter.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/RecyclerViewCursorAdapter.java
@@ -17,7 +17,8 @@
import android.database.Cursor;
import android.provider.MediaStore;
-import android.support.v7.widget.RecyclerView;
+
+import androidx.recyclerview.widget.RecyclerView;
public abstract class RecyclerViewCursorAdapter extends
RecyclerView.Adapter {
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/AlbumsSpinner.java b/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/AlbumsSpinner.java
index b3c9534b7..e4a3b1a22 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/AlbumsSpinner.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/AlbumsSpinner.java
@@ -20,13 +20,14 @@
import android.database.Cursor;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
-import android.support.annotation.NonNull;
-import android.support.v7.widget.ListPopupWindow;
import android.view.View;
import android.widget.AdapterView;
import android.widget.CursorAdapter;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.appcompat.widget.ListPopupWindow;
+
import com.zhihu.matisse.R;
import com.zhihu.matisse.internal.entity.Album;
import com.zhihu.matisse.internal.utils.Platform;
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/CheckRadioView.java b/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/CheckRadioView.java
index f860c58cf..d539dacba 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/CheckRadioView.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/CheckRadioView.java
@@ -3,10 +3,11 @@
import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
-import android.support.v4.content.res.ResourcesCompat;
-import android.support.v7.widget.AppCompatImageView;
import android.util.AttributeSet;
+import androidx.appcompat.widget.AppCompatImageView;
+import androidx.core.content.res.ResourcesCompat;
+
import com.zhihu.matisse.R;
public class CheckRadioView extends AppCompatImageView {
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/CheckView.java b/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/CheckView.java
index b95811365..6a77f4dff 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/CheckView.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/CheckView.java
@@ -27,11 +27,12 @@
import android.graphics.Shader;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
-import android.support.v4.content.res.ResourcesCompat;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;
+import androidx.core.content.res.ResourcesCompat;
+
import com.zhihu.matisse.R;
public class CheckView extends View {
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/IncapableDialog.java b/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/IncapableDialog.java
index 1599fce21..a13a697e5 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/IncapableDialog.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/IncapableDialog.java
@@ -18,11 +18,12 @@
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v4.app.DialogFragment;
-import android.support.v7.app.AlertDialog;
import android.text.TextUtils;
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.DialogFragment;
+
import com.zhihu.matisse.R;
public class IncapableDialog extends DialogFragment {
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/MediaGrid.java b/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/MediaGrid.java
index b795cafc9..9458cde5c 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/MediaGrid.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/MediaGrid.java
@@ -17,7 +17,6 @@
import android.content.Context;
import android.graphics.drawable.Drawable;
-import android.support.v7.widget.RecyclerView;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
@@ -25,6 +24,8 @@
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.recyclerview.widget.RecyclerView;
+
import com.zhihu.matisse.R;
import com.zhihu.matisse.internal.entity.Item;
import com.zhihu.matisse.internal.entity.SelectionSpec;
@@ -59,7 +60,7 @@ private void init(Context context) {
mVideoDuration = (TextView) findViewById(R.id.video_duration);
mThumbnail.setOnClickListener(this);
-// mCheckView.setOnClickListener(this);
+ mCheckView.setOnClickListener(this);
}
@Override
@@ -68,7 +69,7 @@ public void onClick(View v) {
if (v == mThumbnail) {
mListener.onThumbnailClicked(mThumbnail, mMedia, mPreBindInfo.mViewHolder);
} else if (v == mCheckView) {
-// mListener.onCheckViewClicked(mCheckView, mMedia, mPreBindInfo.mViewHolder);
+ mListener.onCheckViewClicked(mCheckView, mMedia, mPreBindInfo.mViewHolder);
}
}
}
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/MediaGridInset.java b/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/MediaGridInset.java
index eebcd429b..67358134f 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/MediaGridInset.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/MediaGridInset.java
@@ -16,9 +16,10 @@
package com.zhihu.matisse.internal.ui.widget;
import android.graphics.Rect;
-import android.support.v7.widget.RecyclerView;
import android.view.View;
+import androidx.recyclerview.widget.RecyclerView;
+
public class MediaGridInset extends RecyclerView.ItemDecoration {
private int mSpanCount;
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/PreviewViewPager.java b/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/PreviewViewPager.java
index 56fd37042..1771bae7b 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/PreviewViewPager.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/PreviewViewPager.java
@@ -16,10 +16,11 @@
package com.zhihu.matisse.internal.ui.widget;
import android.content.Context;
-import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.View;
+import androidx.viewpager.widget.ViewPager;
+
import it.sephiroth.android.library.imagezoom.ImageViewTouch;
public class PreviewViewPager extends ViewPager {
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/RoundedRectangleImageView.java b/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/RoundedRectangleImageView.java
index 285515702..54aaa11cd 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/RoundedRectangleImageView.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/RoundedRectangleImageView.java
@@ -19,9 +19,10 @@
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.RectF;
-import android.support.v7.widget.AppCompatImageView;
import android.util.AttributeSet;
+import androidx.appcompat.widget.AppCompatImageView;
+
public class RoundedRectangleImageView extends AppCompatImageView {
private float mRadius; // dp
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/SquareAppBarLayout.java b/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/SquareAppBarLayout.java
index 202bf624e..ef97a036e 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/SquareAppBarLayout.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/SquareAppBarLayout.java
@@ -3,7 +3,9 @@
import android.content.Context;
import android.util.AttributeSet;
-public class SquareAppBarLayout extends android.support.design.widget.AppBarLayout {
+import com.google.android.material.appbar.AppBarLayout;
+
+public class SquareAppBarLayout extends AppBarLayout {
public SquareAppBarLayout(Context context) {
super(context);
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/utils/MediaStoreCompat.java b/matisse/src/main/java/com/zhihu/matisse/internal/utils/MediaStoreCompat.java
index 1ddfd9207..952c6aeb5 100644
--- a/matisse/src/main/java/com/zhihu/matisse/internal/utils/MediaStoreCompat.java
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/utils/MediaStoreCompat.java
@@ -24,9 +24,10 @@
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
-import android.support.v4.app.Fragment;
-import android.support.v4.content.FileProvider;
-import android.support.v4.os.EnvironmentCompat;
+
+import androidx.core.content.FileProvider;
+import androidx.core.os.EnvironmentCompat;
+import androidx.fragment.app.Fragment;
import com.zhihu.matisse.internal.entity.CaptureStrategy;
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/utils/UIUtils.java b/matisse/src/main/java/com/zhihu/matisse/internal/utils/UIUtils.java
deleted file mode 100644
index 129b59946..000000000
--- a/matisse/src/main/java/com/zhihu/matisse/internal/utils/UIUtils.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2017 Zhihu Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.zhihu.matisse.internal.utils;
-
-import android.content.Context;
-
-public class UIUtils {
-
- public static int spanCount(Context context, int gridExpectedSize) {
- int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
- float expected = (float) screenWidth / (float) gridExpectedSize;
- int spanCount = Math.round(expected);
- if (spanCount == 0) {
- spanCount = 1;
- }
- return spanCount;
- }
-
-}
diff --git a/matisse/src/main/java/com/zhihu/matisse/internal/utils/Utils.kt b/matisse/src/main/java/com/zhihu/matisse/internal/utils/Utils.kt
new file mode 100644
index 000000000..66a5d0f72
--- /dev/null
+++ b/matisse/src/main/java/com/zhihu/matisse/internal/utils/Utils.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2017 Zhihu Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.zhihu.matisse.internal.utils
+
+import android.content.ContentValues
+import android.content.Context
+import android.content.Intent
+import android.media.MediaMetadataRetriever
+import android.net.Uri
+import android.provider.MediaStore
+import android.util.Size
+import com.daasuu.mp4compose.FillModeCustomItem
+import com.daasuu.mp4compose.filter.GlFilter
+import com.zhihu.matisse.ui.widget.GesturePlayerTextureView
+import com.zhihu.matisse.ui.widget.SceneCropColor
+import kotlin.math.roundToInt
+
+class Utils {
+ companion object {
+ fun spanCount(context: Context, gridExpectedSize: Int): Int {
+ val screenWidth = context.resources.displayMetrics.widthPixels
+ val expected = screenWidth.toFloat() / gridExpectedSize.toFloat()
+ var spanCount = expected.roundToInt()
+ if (spanCount == 0) {
+ spanCount = 1
+ }
+ return spanCount
+ }
+
+ fun getFillMode(playerTextureView: GesturePlayerTextureView, path: String?): FillModeCustomItem {
+ val resolution = getVideoResolution(path)
+ return FillModeCustomItem(
+ playerTextureView!!.scaleX,
+ playerTextureView!!.rotation,
+ playerTextureView!!.translationX / playerTextureView!!.baseWidthSize * 2f,
+ playerTextureView!!.translationY / playerTextureView!!.baseWidthSize * 2f,
+ resolution.width.toFloat(),
+ resolution.height.toFloat()
+ )
+ }
+
+
+ fun getFill(sceneCropColor: SceneCropColor = SceneCropColor.WHITE): GlFilter {
+ val glFilter = GlFilter()
+ val clearColorItem = sceneCropColor.clearColorItem
+ glFilter.setClearColor(clearColorItem.red, clearColorItem.green, clearColorItem.blue, clearColorItem.alpha)
+ return glFilter
+ }
+
+ fun exportMp4ToGallery(context: Context, filePath: String) {
+ val values = ContentValues(2)
+ values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4")
+ values.put(MediaStore.Video.Media.DATA, filePath)
+ context.contentResolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values)
+ context.sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
+ Uri.parse("file://$filePath")))
+ }
+
+ fun getVideoResolution(path: String?): Size {
+ val retriever = MediaMetadataRetriever()
+ retriever.setDataSource(path)
+ val width = Integer.valueOf(
+ retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)
+ )
+ val height = Integer.valueOf(
+ retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)
+ )
+ retriever.release()
+ val rotation = getVideoRotation(path)
+ return if (rotation == 90 || rotation == 270) {
+ Size(height, width)
+ } else Size(width, height)
+ }
+
+
+ fun getVideoRotation(videoFilePath: String?): Int {
+ val mediaMetadataRetriever = MediaMetadataRetriever()
+ mediaMetadataRetriever.setDataSource(videoFilePath)
+ val orientation = mediaMetadataRetriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION
+ )
+ return Integer.valueOf(orientation)
+ }
+ }
+}
diff --git a/matisse/src/main/java/com/zhihu/matisse/listener/OnSelectedListener.java b/matisse/src/main/java/com/zhihu/matisse/listener/OnSelectedListener.java
index 33e374e62..515ab9148 100644
--- a/matisse/src/main/java/com/zhihu/matisse/listener/OnSelectedListener.java
+++ b/matisse/src/main/java/com/zhihu/matisse/listener/OnSelectedListener.java
@@ -17,7 +17,8 @@
package com.zhihu.matisse.listener;
import android.net.Uri;
-import android.support.annotation.NonNull;
+
+import androidx.annotation.NonNull;
import java.util.List;
diff --git a/matisse/src/main/java/com/zhihu/matisse/ui/FilterActivity.kt b/matisse/src/main/java/com/zhihu/matisse/ui/FilterActivity.kt
index b8ee3928d..d9246a60a 100644
--- a/matisse/src/main/java/com/zhihu/matisse/ui/FilterActivity.kt
+++ b/matisse/src/main/java/com/zhihu/matisse/ui/FilterActivity.kt
@@ -5,16 +5,16 @@ import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
-import android.support.v4.view.PagerAdapter
-import android.support.v4.view.ViewPager
-import android.support.v7.app.AppCompatActivity
-import android.support.v7.widget.LinearLayoutManager
import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
+import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.viewpager.widget.PagerAdapter
+import androidx.viewpager.widget.ViewPager
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
diff --git a/matisse/src/main/java/com/zhihu/matisse/ui/MatisseActivity.java b/matisse/src/main/java/com/zhihu/matisse/ui/MatisseActivity.java
index d571ca66a..0ce3e570a 100644
--- a/matisse/src/main/java/com/zhihu/matisse/ui/MatisseActivity.java
+++ b/matisse/src/main/java/com/zhihu/matisse/ui/MatisseActivity.java
@@ -24,16 +24,9 @@
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v4.app.Fragment;
-import android.support.v7.app.ActionBar;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
@@ -42,6 +35,14 @@
import android.widget.TextView;
import android.widget.Toast;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.fragment.app.Fragment;
+
+import com.daasuu.mp4compose.composer.Mp4Composer;
import com.yalantis.ucrop.UCrop;
import com.yalantis.ucrop.UCropFragment;
import com.yalantis.ucrop.UCropFragmentCallback;
@@ -51,6 +52,7 @@
import com.zhihu.matisse.internal.entity.SelectionSpec;
import com.zhihu.matisse.internal.model.AlbumCollection;
import com.zhihu.matisse.internal.model.SelectedItemCollection;
+import com.zhihu.matisse.internal.ui.AlbumPreviewActivity;
import com.zhihu.matisse.internal.ui.BasePreviewActivity;
import com.zhihu.matisse.internal.ui.MediaSelectionFragment;
import com.zhihu.matisse.internal.ui.SelectedPreviewActivity;
@@ -74,7 +76,7 @@ public class MatisseActivity extends AppCompatActivity implements UCropFragmentC
AlbumCollection.AlbumCallbacks, AdapterView.OnItemSelectedListener,
MediaSelectionFragment.SelectionProvider, View.OnClickListener,
AlbumMediaAdapter.CheckStateListener, AlbumMediaAdapter.OnMediaClickListener,
- AlbumMediaAdapter.OnPhotoCapture {
+ AlbumMediaAdapter.OnPhotoCapture, Mp4Composer.Listener {
public static final String EXTRA_RESULT_SELECTION = "extra_result_selection";
public static final String EXTRA_RESULT_SELECTION_PATH = "extra_result_selection_path";
@@ -82,6 +84,7 @@ public class MatisseActivity extends AppCompatActivity implements UCropFragmentC
private static final int REQUEST_CODE_PREVIEW = 23;
private static final int REQUEST_CODE_CAPTURE = 24;
private static final int REQUEST_CODE_FILTER = 25;
+ private static final int REQUEST_CODE_TRIM_VIDEO = 26;
public static final String CHECK_STATE = "checkState";
private final AlbumCollection mAlbumCollection = new AlbumCollection();
private MediaStoreCompat mMediaStoreCompat;
@@ -99,7 +102,9 @@ public class MatisseActivity extends AppCompatActivity implements UCropFragmentC
private CheckRadioView mOriginal;
private boolean mOriginalEnable;
private ProgressDialog mProgressDialog;
+
private boolean clickNextButton = false;
+ private boolean isTrimVideo = false;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -213,12 +218,18 @@ public void onCropFinish(UCropFragment.UCropResult result) {
if (clickNextButton) {
clickNextButton = false;
- if (mSpec.hasFilter) startFilterActivity();
- else returnResult();
+ handleNextAction();
}
hideProgress();
}
+ private void handleNextAction() {
+ if (mSpec.hasTrimVideo && mSelectedCollection.hasVideo() && !isTrimVideo)
+ startVideoEditorActivity();
+ else if (mSpec.hasFilter && mSelectedCollection.hasImage()) startFilterActivity();
+ else returnResult();
+ }
+
private void returnResult() {
Intent intent = new Intent();
ArrayList selectedUris = (ArrayList) mSelectedCollection.asListOfUri();
@@ -230,6 +241,16 @@ private void returnResult() {
finish();
}
+ private void startVideoEditorActivity() {
+ Intent intent = new Intent(this, VideoEditorActivity.class);
+ for (Item item : mSelectedCollection.asList())
+ if (item.isVideo()) {
+ intent.putExtra(VideoEditorActivity.PATH_ARG, item);
+ break;
+ }
+ startActivityForResult(intent, REQUEST_CODE_TRIM_VIDEO);
+ }
+
private void startFilterActivity() {
Intent intent = new Intent(this, FilterActivity.class);
ArrayList selectedUris = (ArrayList) mSelectedCollection.asListOfUri();
@@ -238,7 +259,7 @@ private void startFilterActivity() {
}
public void showProgress() {
- hideProgress();
+ if (mProgressDialog != null && mProgressDialog.isShowing()) return;
mProgressDialog = ProgressDialog.show(this, null, "Please wait...");
}
@@ -311,19 +332,17 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
result.putParcelableArrayListExtra(EXTRA_RESULT_SELECTION, selected);
result.putStringArrayListExtra(EXTRA_RESULT_SELECTION_PATH, selectedPath);
setResult(RESULT_OK, result);
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
- MatisseActivity.this.revokeUriPermission(contentUri,
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
galleryAddPic(getApplicationContext());
Handler handler = new Handler(Looper.getMainLooper());
- handler.postDelayed(new Runnable() {
- @Override
- public void run() {
- onAlbumSelected(Album.valueOf(mAlbumsAdapter.getCursor()));
- }
- }, 400);
+ handler.postDelayed(() -> onAlbumSelected(Album.valueOf(mAlbumsAdapter.getCursor())), 400);
} else if (requestCode == REQUEST_CODE_FILTER) {
returnResult();
+ } else if (requestCode == REQUEST_CODE_TRIM_VIDEO) {
+ Item result = data.getParcelableExtra(VideoEditorActivity.PATH_ARG);
+ mSelectedCollection.updateItem(result);
+
+ isTrimVideo = true;
+ handleNextAction();
}
}
@@ -411,8 +430,7 @@ public void onClick(View v) {
MediaSelectionFragment.class.getSimpleName());
if (mediaSelectionFragment instanceof MediaSelectionFragment) {
if (((MediaSelectionFragment) mediaSelectionFragment).onNextButtonClick()) {
- if (mSpec.hasFilter) startFilterActivity();
- else returnResult();
+ handleNextAction();
}
}
} else if (v.getId() == R.id.originalLayout) {
@@ -499,13 +517,18 @@ public void onUpdate() {
}
@Override
- public void onMediaClick(Album album, Item item, Item mPrevious, int adapterPosition) {
-// Intent intent = new Intent(this, AlbumPreviewActivity.class);
-// intent.putExtra(AlbumPreviewActivity.EXTRA_ALBUM, album);
-// intent.putExtra(AlbumPreviewActivity.EXTRA_ITEM, item);
-// intent.putExtra(BasePreviewActivity.EXTRA_DEFAULT_BUNDLE, mSelectedCollection.getDataWithBundle());
-// intent.putExtra(BasePreviewActivity.EXTRA_RESULT_ORIGINAL_ENABLE, mOriginalEnable);
- //startActivityForResult(intent, REQUEST_CODE_PREVIEW);
+ public void onMediaClick(Album album, Item item, int adapterPosition) {
+ Intent intent = new Intent(this, AlbumPreviewActivity.class);
+ intent.putExtra(AlbumPreviewActivity.EXTRA_ALBUM, album);
+ intent.putExtra(AlbumPreviewActivity.EXTRA_ITEM, item);
+ intent.putExtra(BasePreviewActivity.EXTRA_DEFAULT_BUNDLE, mSelectedCollection.getDataWithBundle());
+ intent.putExtra(BasePreviewActivity.EXTRA_RESULT_ORIGINAL_ENABLE, mOriginalEnable);
+ startActivityForResult(intent, REQUEST_CODE_PREVIEW);
+ }
+
+ @Override
+ public void onMediaAdded(Item item, Item prev) {
+
}
@Override
@@ -520,4 +543,29 @@ public void capture() {
}
}
+ @Override
+ public void onProgress(double progress) {
+ Log.d(this + "", "onProgress = " + progress);
+ showProgress();
+ }
+
+ @Override
+ public void onCompleted() {
+ Log.d(this + "", "onCompleted()");
+ if (clickNextButton) {
+ clickNextButton = false;
+ handleNextAction();
+ }
+ hideProgress();
+ }
+
+ @Override
+ public void onCanceled() {
+ }
+
+ @Override
+ public void onFailed(Exception exception) {
+ Log.d(this + "", "onFailed()");
+ hideProgress();
+ }
}
diff --git a/matisse/src/main/java/com/zhihu/matisse/ui/ThumbnailsAdapter.kt b/matisse/src/main/java/com/zhihu/matisse/ui/ThumbnailsAdapter.kt
index 9334b8959..c56458051 100644
--- a/matisse/src/main/java/com/zhihu/matisse/ui/ThumbnailsAdapter.kt
+++ b/matisse/src/main/java/com/zhihu/matisse/ui/ThumbnailsAdapter.kt
@@ -1,14 +1,13 @@
package com.zhihu.matisse.ui
-import android.support.v7.widget.RecyclerView
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
import com.zhihu.matisse.R
-
import com.zomato.photofilters.utils.ThumbnailCallback
import com.zomato.photofilters.utils.ThumbnailItem
diff --git a/matisse/src/main/java/com/zhihu/matisse/ui/VideoEditorActivity.kt b/matisse/src/main/java/com/zhihu/matisse/ui/VideoEditorActivity.kt
new file mode 100644
index 000000000..7f76b84f7
--- /dev/null
+++ b/matisse/src/main/java/com/zhihu/matisse/ui/VideoEditorActivity.kt
@@ -0,0 +1,160 @@
+package com.zhihu.matisse.ui
+
+import android.app.Activity
+import android.app.AlertDialog
+import android.app.ProgressDialog
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.Intent
+import android.graphics.Point
+import android.net.Uri
+import android.os.Bundle
+import android.os.Environment
+import android.view.Gravity
+import android.view.WindowManager
+import android.widget.FrameLayout.LayoutParams
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
+import com.daasuu.mp4compose.FillMode
+import com.daasuu.mp4compose.composer.Mp4Composer
+import com.zhihu.matisse.R
+import com.zhihu.matisse.internal.entity.Item
+import com.zhihu.matisse.internal.utils.PathUtils
+import com.zhihu.matisse.internal.utils.Utils
+import com.zhihu.matisse.ui.widget.GesturePlayerTextureView
+import com.zhihu.matisse.ui.widget.SceneCropColor
+import kotlinx.android.synthetic.main.activity_video_trim.*
+import java.io.File
+import java.util.*
+
+class VideoEditorActivity : AppCompatActivity() {
+ companion object {
+ internal const val PATH_ARG = "PATH_ARG"
+ }
+
+ private lateinit var item: Item
+ private lateinit var srcPath: Uri
+ private var baseWidthSize: Float = 0.toFloat()
+ private var playerTextureView: GesturePlayerTextureView? = null
+ private var clearColorDialog: AlertDialog? = null
+ private var sceneCropColor = SceneCropColor.WHITE
+
+ private val windowHeight: Int
+ get() {
+ val size = Point()
+ (getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.getSize(size)
+ return size.x
+ }
+
+ private val videoFilePath: String
+ get() = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)
+ .absolutePath + "/necosta_" + Date().time + ".mp4"
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_video_trim)
+
+ if (intent == null) {
+ finish()
+ return
+ }
+ item = intent.getParcelableExtra(PATH_ARG)
+ srcPath = item.uri
+
+ btn_rotate?.setOnClickListener { playerTextureView!!.updateRotate() }
+ done?.setOnClickListener { codec() }
+
+ btn_color_change?.setOnClickListener { v ->
+ if (clearColorDialog == null) {
+ val builder = AlertDialog.Builder(v.context)
+ builder.setTitle(getString(R.string.video_background_color_title))
+ builder.setOnDismissListener { clearColorDialog = null }
+
+ val items = SceneCropColor.values()
+ val charList = arrayOfNulls(items.size)
+ var i = 0
+ val n = items.size
+ while (i < n) {
+ charList[i] = items[i].name
+ i++
+ }
+ builder.setItems(charList) { _, item ->
+ sceneCropColor = items[item]
+ layout_crop_trim_video?.setBackgroundColor(ContextCompat.getColor(v.context,
+ sceneCropColor.colorRes))
+ }
+ clearColorDialog = builder.show()
+ } else {
+ clearColorDialog!!.dismiss()
+ }
+ }
+
+ initPlayer()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (playerTextureView != null) {
+ playerTextureView!!.play()
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+ if (playerTextureView != null) {
+ playerTextureView!!.pause()
+ }
+ }
+
+ private fun initPlayer() {
+ playerTextureView = GesturePlayerTextureView(applicationContext, srcPath, timeSelector)
+
+ val layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
+ layoutParams.gravity = Gravity.CENTER
+
+ playerTextureView!!.layoutParams = layoutParams
+ baseWidthSize = windowHeight.toFloat()
+ playerTextureView!!.setBaseWidthSize(baseWidthSize)
+
+ layout_crop_trim_video?.addView(playerTextureView)
+ }
+
+ private fun codec() {
+ val progress = ProgressDialog.show(this, null, "Please wait...")
+ btn_rotate?.isEnabled = false
+ btn_color_change?.isEnabled = false
+
+ val file = File(ContextWrapper(this).cacheDir, "${Calendar.getInstance().timeInMillis}.mp4")
+ val timeCrop = timeSelector.getCurrent()
+
+ Mp4Composer(PathUtils.getPath(this, srcPath), file.path)
+ .size(720, 720)
+ .trim(timeCrop[0], timeCrop[1])
+ .filter(Utils.getFill(sceneCropColor))
+ .fillMode(FillMode.CUSTOM)
+ .customFillMode(Utils.getFillMode(playerTextureView!!, srcPath.path))
+ .listener(object : Mp4Composer.Listener {
+ override fun onProgress(progress: Double) {
+ }
+
+ override fun onCompleted() {
+ progress.dismiss()
+// exportMp4ToGallery(applicationContext, videoPath)
+ runOnUiThread {
+ val intent = Intent()
+ item.uri = Uri.fromFile(file)
+ intent.putExtra(PATH_ARG, item)
+ setResult(Activity.RESULT_OK, intent)
+ finish()
+ }
+ }
+
+ override fun onCanceled() {}
+
+ override fun onFailed(exception: Exception) {
+ progress.dismiss()
+ }
+ })
+ .start()
+ }
+}
diff --git a/matisse/src/main/java/com/zhihu/matisse/ui/widget/AllGestureDetector.java b/matisse/src/main/java/com/zhihu/matisse/ui/widget/AllGestureDetector.java
new file mode 100644
index 000000000..35bb52924
--- /dev/null
+++ b/matisse/src/main/java/com/zhihu/matisse/ui/widget/AllGestureDetector.java
@@ -0,0 +1,123 @@
+package com.zhihu.matisse.ui.widget;
+
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.View;
+
+class AllGestureDetector implements DragGestureDetector.DragGestureListener, RotateGestureDetector.RotateGestureListener, PinchGestureDetector.PinchGestureListener {
+
+ private static final float DEFAULT_LIMIT_SCALE_MAX = 2.7f;
+ private static final float DEFAULT_LIMIT_SCALE_MIN = 0.5f;
+
+ private float limitScaleMax = DEFAULT_LIMIT_SCALE_MAX;
+ private float limitScaleMin = DEFAULT_LIMIT_SCALE_MIN;
+
+ private float scaleFactor = 1.0f;
+
+ private final RotateGestureDetector rotateGestureDetector;
+ private final DragGestureDetector dragGestureDetector;
+ private final PinchGestureDetector pinchGestureDetector;
+ private final View view;
+
+ private float angle = 0f;
+ private boolean rotateFlag = true;
+
+ private MoveDragXYListener moveDragXYListener;
+
+ AllGestureDetector(View view) {
+ dragGestureDetector = new DragGestureDetector(this);
+ rotateGestureDetector = new RotateGestureDetector(this);
+ pinchGestureDetector = new PinchGestureDetector(this);
+ this.view = view;
+ }
+
+ void onTouch(MotionEvent event) {
+ if (rotateFlag) {
+ rotateGestureDetector.onTouchEvent(event);
+ }
+ dragGestureDetector.onTouchEvent(event);
+ pinchGestureDetector.onTouchEvent(event);
+ }
+
+ void noRotate() {
+ rotateFlag = false;
+ }
+
+ public void setMoveDragXYListener(MoveDragXYListener moveDragXYListener) {
+ this.moveDragXYListener = moveDragXYListener;
+ }
+
+ void updateAngle() {
+ this.angle = view.getRotation();
+ }
+
+ public void setLimitScaleMax(float limit) {
+ this.limitScaleMax = limit;
+ }
+
+ void setLimitScaleMin(float limit) {
+ this.limitScaleMin = limit;
+ }
+
+
+ @Override
+ public void onPinchGestureListener(float scale) {
+ float tmpScale = scaleFactor * scale;
+
+ if (limitScaleMin <= tmpScale && tmpScale <= limitScaleMax) {
+ scaleFactor = tmpScale;
+ view.setScaleX(scaleFactor);
+ view.setScaleY(scaleFactor);
+ }
+
+ }
+
+
+ // rotate
+ @Override
+ public void onRotation(float deltaAngle) {
+ angle += deltaAngle;
+ view.setRotation(view.getRotation() + deltaAngle);
+ }
+
+
+ @Override
+ synchronized public void onDragGestureListener(float deltaX, float deltaY) {
+
+ // touch move
+
+ float dx = deltaX;
+ float dy = deltaY;
+ PointF pf = createRotatePointF(0, 0, angle, dx, dy);
+
+ dx = pf.x;
+ dy = pf.y;
+
+ float x = view.getX() + dx * scaleFactor;
+ float y = view.getY() + dy * scaleFactor;
+
+ if (moveDragXYListener != null) {
+ moveDragXYListener.onMove(x, y);
+ }
+
+ view.setX(x);
+ view.setY(y);
+ }
+
+
+ private static PointF createRotatePointF(float centerX, float centerY, float angle, float x, float y) {
+
+ double rad = Math.toRadians(angle);
+
+ float resultX = (float) ((x - centerX) * Math.cos(rad) - (y - centerY) * Math.sin(rad) + centerX);
+ float resultY = (float) ((x - centerX) * Math.sin(rad) + (y - centerY) * Math.cos(rad) + centerY);
+
+ return new PointF(resultX, resultY);
+ }
+
+ public interface MoveDragXYListener {
+ void onMove(float x, float y);
+ }
+
+}
+
diff --git a/matisse/src/main/java/com/zhihu/matisse/ui/widget/DragGestureDetector.java b/matisse/src/main/java/com/zhihu/matisse/ui/widget/DragGestureDetector.java
new file mode 100644
index 000000000..802f9824c
--- /dev/null
+++ b/matisse/src/main/java/com/zhihu/matisse/ui/widget/DragGestureDetector.java
@@ -0,0 +1,139 @@
+package com.zhihu.matisse.ui.widget;
+
+import android.util.Log;
+import android.view.MotionEvent;
+
+import java.util.HashMap;
+
+class DragGestureDetector {
+
+ private static final String TAG = DragGestureDetector.class.getName();
+
+ private int originalIndex;
+
+ private HashMap pointMap = new HashMap<>();
+
+ private DragGestureListener dragGestureListener;
+
+ public interface DragGestureListener {
+ void onDragGestureListener(float deltaX, float deltaY);
+ }
+
+ DragGestureDetector(DragGestureListener dragGestureListener) {
+ this.dragGestureListener = dragGestureListener;
+ pointMap.put(0, createPoint(0.f, 0.f));
+ originalIndex = 0;
+ }
+
+ synchronized boolean onTouchEvent(MotionEvent event) {
+
+ if (event.getPointerCount() >= 3) {
+ return false;
+ }
+
+ float eventX = event.getX(originalIndex);
+ float eventY = event.getY(originalIndex);
+
+ int action = event.getAction() & MotionEvent.ACTION_MASK;
+ int actionPointer = event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK;
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+
+ // 最初のpointしか来ない
+
+ TouchPoint downPoint = pointMap.get(0);
+ if (downPoint != null) {
+ downPoint.setXY(eventX, eventY);
+ } else {
+ downPoint = createPoint(eventX, eventY);
+ pointMap.put(0, downPoint);
+ }
+
+ originalIndex = 0;
+
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+
+ TouchPoint originalPoint = pointMap.get(originalIndex);
+ if (originalPoint != null) {
+ float deltaX = eventX - originalPoint.x;
+ float deltaY = eventY - originalPoint.y;
+
+ if (dragGestureListener != null) {// && (count < 2)) {
+ dragGestureListener.onDragGestureListener(deltaX, deltaY);
+ }
+ }
+
+ break;
+ }
+ case MotionEvent.ACTION_POINTER_DOWN: {
+
+ int downId = actionPointer >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+
+ float multiTouchX = event.getX(downId);
+ float multiTouchY = event.getY(downId);
+
+ TouchPoint p = pointMap.get(downId);
+
+ if (p != null) {
+ p.x = multiTouchX;
+ p.y = multiTouchY;
+ } else {
+ pointMap.put(downId, createPoint(multiTouchX, multiTouchY));
+ }
+
+ break;
+ }
+ case MotionEvent.ACTION_POINTER_UP: {
+
+ int upId = actionPointer >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ Log.d(TAG, "ACTION_POINTER_UP id : " + upId);
+
+ if (originalIndex == upId) {
+ Log.d(TAG, "ACTION_POINTER_UP orig up");
+ pointMap.remove(upId);
+
+ TouchPoint secondPoint = null;
+ for (int index = 0, n = pointMap.size(); index < n; index++) {
+ if (originalIndex != index) {
+ secondPoint = pointMap.get(index);
+ if (secondPoint != null) {
+ secondPoint.setXY(event.getX(index), event.getY(index));
+ originalIndex = index;
+ break;
+ }
+ }
+ }
+ }
+
+ break;
+ }
+
+ default:
+ }
+ return false;
+ }
+
+ private TouchPoint createPoint(float x, float y) {
+ return new TouchPoint(x, y);
+ }
+
+ class TouchPoint {
+ float x;
+ float y;
+
+ TouchPoint(float x, float y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ TouchPoint setXY(float x, float y) {
+ this.x = x;
+ this.y = y;
+ return this;
+ }
+ }
+
+}
diff --git a/matisse/src/main/java/com/zhihu/matisse/ui/widget/GesturePlayerTextureView.java b/matisse/src/main/java/com/zhihu/matisse/ui/widget/GesturePlayerTextureView.java
new file mode 100644
index 000000000..37f831e77
--- /dev/null
+++ b/matisse/src/main/java/com/zhihu/matisse/ui/widget/GesturePlayerTextureView.java
@@ -0,0 +1,78 @@
+package com.zhihu.matisse.ui.widget;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.net.Uri;
+import android.view.MotionEvent;
+import android.view.View;
+
+@SuppressLint("ViewConstructor")
+public class GesturePlayerTextureView extends PlayerTextureView implements View.OnTouchListener {
+
+ private final AllGestureDetector allGestureDetector;
+
+ // 基準となる枠のサイズ
+ public float baseWidthSize = 0;
+
+ public GesturePlayerTextureView(Context context, Uri path, TimeSelectorView timeSelector) {
+ super(context, path, timeSelector);
+ setOnTouchListener(this);
+ allGestureDetector = new AllGestureDetector(this);
+ allGestureDetector.setLimitScaleMin(0.1f);
+ allGestureDetector.noRotate();
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ allGestureDetector.onTouch(event);
+ return true;
+ }
+
+ public void setBaseWidthSize(float baseSize) {
+ this.baseWidthSize = baseSize;
+ requestLayout();
+ }
+
+ public void updateRotate() {
+ final int rotation = (int) getRotation();
+
+ switch (rotation) {
+ case 0:
+ super.setRotation(90f);
+ break;
+ case 90:
+ super.setRotation(180f);
+ break;
+ case 180:
+ super.setRotation(270f);
+ break;
+ case 270:
+ super.setRotation(0f);
+ break;
+ }
+
+ allGestureDetector.updateAngle();
+ }
+
+
+ @Override
+ public void setRotation(float rotation) {
+ // do nothing
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ if (getVideoAspect() == Companion.getDEFAULT_ASPECT() || baseWidthSize == 0) return;
+
+ // 正方形
+ if (getVideoAspect() == 1.0f) {
+ setMeasuredDimension((int) baseWidthSize, (int) baseWidthSize);
+ return;
+ }
+
+ // 縦長 or 横長
+ setMeasuredDimension((int) baseWidthSize, (int) (baseWidthSize / getVideoAspect()));
+ }
+}
+
diff --git a/matisse/src/main/java/com/zhihu/matisse/ui/widget/PinchGestureDetector.java b/matisse/src/main/java/com/zhihu/matisse/ui/widget/PinchGestureDetector.java
new file mode 100644
index 000000000..84bd85086
--- /dev/null
+++ b/matisse/src/main/java/com/zhihu/matisse/ui/widget/PinchGestureDetector.java
@@ -0,0 +1,112 @@
+package com.zhihu.matisse.ui.widget;
+
+import android.view.MotionEvent;
+
+class PinchGestureDetector {
+ private float scale = 1.0f;
+
+ private float adjustDistanceRate = 1f;
+
+ private float distance;
+
+ private float preDistance;
+
+ private PinchGestureListener pinchGestureListener;
+
+ public interface PinchGestureListener {
+ void onPinchGestureListener(float scale);
+ }
+
+ PinchGestureDetector(PinchGestureListener dragGestureListener) {
+ this.pinchGestureListener = dragGestureListener;
+ }
+
+ public float getScale() {
+ return this.scale;
+ }
+
+ public float getDistance() {
+ return this.distance;
+ }
+
+ public float getPreDistance() {
+ return this.preDistance;
+ }
+
+ synchronized public boolean onTouchEvent(MotionEvent event) {
+
+ float eventX = event.getX() * scale;
+ float eventY = event.getY() * scale;
+ int count = event.getPointerCount();
+
+ int action = event.getAction() & MotionEvent.ACTION_MASK;
+ int actionPointerIndex = event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK;
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+
+ /** 最初のpointしか来ない */
+
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+
+ if (count == 2) {
+
+ float multiTouchX = event.getX(1) * scale;
+ float multiTouchY = event.getY(1) * scale;
+
+ distance = culcDistance(eventX, eventY, multiTouchX, multiTouchY);
+
+ float adjustDistance = distance + ((preDistance - distance) * adjustDistanceRate);
+
+ pinchGestureListener.onPinchGestureListener(distance / adjustDistance);
+ scale *= distance / preDistance;
+ preDistance = distance;
+
+ }
+
+ break;
+ }
+ case MotionEvent.ACTION_POINTER_DOWN: {
+
+ /** 2本の位置を記録 以後、moveにて距離の差分を算出 */
+
+ if (count == 2) {
+ int downId = actionPointerIndex >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+
+ float multiTouchX = event.getX(downId) * scale;
+ float multiTouchY = event.getY(downId) * scale;
+
+ distance = culcDistance(eventX, eventY, multiTouchX, multiTouchY);
+ float adjustDistance = distance + ((preDistance - distance) * adjustDistanceRate);
+ pinchGestureListener.onPinchGestureListener(adjustDistance);
+ preDistance = distance;
+ }
+
+ break;
+ }
+ case MotionEvent.ACTION_POINTER_UP: {
+
+ distance = 0;
+ preDistance = 0;
+ scale = 1.0f;
+
+ break;
+ }
+
+ default:
+ }
+ return false;
+ }
+
+ private float culcDistance(float x1, float y1, float x2, float y2) {
+ final float dx = x1 - x2;
+ final float dy = y1 - y2;
+ return (float) Math.sqrt(dx * dx + dy * dy);
+ }
+
+ public void setAdjustDistanceRate(float adjustDistanceRate) {
+ this.adjustDistanceRate = adjustDistanceRate;
+ }
+}
diff --git a/matisse/src/main/java/com/zhihu/matisse/ui/widget/PlayerTextureView.kt b/matisse/src/main/java/com/zhihu/matisse/ui/widget/PlayerTextureView.kt
new file mode 100644
index 000000000..eec8fc625
--- /dev/null
+++ b/matisse/src/main/java/com/zhihu/matisse/ui/widget/PlayerTextureView.kt
@@ -0,0 +1,156 @@
+package com.zhihu.matisse.ui.widget
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.SurfaceTexture
+import android.media.MediaMetadataRetriever
+import android.net.Uri
+import android.util.Log
+import android.view.Surface
+import android.view.TextureView
+import com.google.android.exoplayer2.C
+import com.google.android.exoplayer2.ExoPlayerFactory
+import com.google.android.exoplayer2.SimpleExoPlayer
+import com.google.android.exoplayer2.source.ClippingMediaSource
+import com.google.android.exoplayer2.source.LoopingMediaSource
+import com.google.android.exoplayer2.source.MediaSource
+import com.google.android.exoplayer2.source.ProgressiveMediaSource
+import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
+import com.google.android.exoplayer2.video.VideoListener
+import java.util.concurrent.TimeUnit
+
+@SuppressLint("ViewConstructor")
+open class PlayerTextureView(context: Context, path: Uri, private val timeSelector: TimeSelectorView?)
+ : TextureView(context, null, 0), TextureView.SurfaceTextureListener, VideoListener {
+
+ private val VIDEO_LIMIT_US = TimeUnit.SECONDS.toMicros(30) // 30秒
+ private var duration = TimeUnit.SECONDS.toMicros(1)
+ private val player: SimpleExoPlayer?
+ protected var videoAspect = DEFAULT_ASPECT
+
+ init {
+ timeSelector?.apply {
+ selectionStartListener = { start, left, right ->
+ if (start) pause()
+ else {
+ play()
+ onSelectionChanged(context, path, left, right)
+ }
+ }
+ initDuration(context, path)
+ }
+
+ // SimpleExoPlayer
+ player = ExoPlayerFactory.newSimpleInstance(context)
+ player!!.addVideoListener(this)
+
+ // Prepare the player with the source.
+ player.prepare(createLoopingMediaSource(context, path))
+ surfaceTextureListener = this
+ }
+
+ private fun initDuration(context: Context, path: Uri) {
+ val retriever = MediaMetadataRetriever()
+ //use one of overloaded setDataSource() functions to set your data source
+ retriever.setDataSource(context, path)
+ val time = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
+ duration = TimeUnit.MILLISECONDS.toMicros(java.lang.Long.parseLong(time))
+ timeSelector?.setLimit(VIDEO_LIMIT_US.toFloat() / duration)
+ timeSelector?.duration = duration
+ Log.d(TAG, "duration = $duration")
+ retriever.release()
+ }
+
+ private fun createLoopingMediaSource(context: Context, path: Uri): MediaSource {
+ // Produces DataSource instances through which media data is loaded.
+ val dataSourceFactory = DefaultDataSourceFactory(context, "No-Agent")
+
+ // This is the MediaSource representing the media to be played.
+ val videoSource = ProgressiveMediaSource.Factory(dataSourceFactory)
+ .createMediaSource(path)
+
+ return LoopingMediaSource(videoSource)
+ }
+
+ private fun onSelectionChanged(context: Context, path: Uri, left: Float?, right: Float?) {
+ // Produces DataSource instances through which media data is loaded.
+ val dataSourceFactory = DefaultDataSourceFactory(context, "No-Agent")
+
+ // This is the MediaSource representing the media to be played.
+ var videoSource: MediaSource = ProgressiveMediaSource.Factory(dataSourceFactory)
+ .createMediaSource(path)
+
+ val start = (left!! * duration).toLong()
+ val end = (right!! * duration).toLong()
+ if (start != C.TIME_UNSET) videoSource = ClippingMediaSource(videoSource, start, end)
+
+ player?.prepare(LoopingMediaSource(videoSource))
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+
+ if (videoAspect == DEFAULT_ASPECT) return
+
+ val measuredWidth = measuredWidth
+ val viewHeight = (measuredWidth / videoAspect).toInt()
+ Log.d(TAG, "onMeasure videoAspect = $videoAspect")
+ Log.d(TAG, "onMeasure viewWidth = $measuredWidth viewHeight = $viewHeight")
+
+ setMeasuredDimension(measuredWidth, viewHeight)
+ }
+
+
+ override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
+ Log.d(TAG, "onSurfaceTextureAvailable width = $width height = $height")
+
+ //3. bind the player to the view
+ player!!.setVideoSurface(Surface(surface))
+ player.playWhenReady = true
+ }
+
+ override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {
+
+ }
+
+ override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
+ player!!.stop()
+ player.release()
+ return false
+ }
+
+ override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
+
+ }
+
+ override fun onVideoSizeChanged(width: Int, height: Int, unappliedRotationDegrees: Int, pixelWidthHeightRatio: Float) {
+ Log.d(TAG, "width = " + width + " height = " + height + " unappliedRotationDegrees = "
+ + unappliedRotationDegrees + " pixelWidthHeightRatio = " + pixelWidthHeightRatio)
+ videoAspect = width.toFloat() / height * pixelWidthHeightRatio
+ Log.d(TAG, "videoAspect = $videoAspect")
+ requestLayout()
+ }
+
+ override fun onSurfaceSizeChanged(width: Int, height: Int) {
+
+ }
+
+ override fun onRenderedFirstFrame() {
+
+ }
+
+ fun play() {
+ player!!.playWhenReady = true
+ }
+
+ fun pause() {
+ player!!.playWhenReady = false
+ }
+
+ companion object {
+
+ private val TAG = PlayerTextureView::class.java.simpleName
+ protected val DEFAULT_ASPECT = -1f
+ }
+
+}
diff --git a/matisse/src/main/java/com/zhihu/matisse/ui/widget/RotateGestureDetector.java b/matisse/src/main/java/com/zhihu/matisse/ui/widget/RotateGestureDetector.java
new file mode 100644
index 000000000..757298a9c
--- /dev/null
+++ b/matisse/src/main/java/com/zhihu/matisse/ui/widget/RotateGestureDetector.java
@@ -0,0 +1,120 @@
+package com.zhihu.matisse.ui.widget;
+
+import android.view.MotionEvent;
+
+class RotateGestureDetector {
+
+ private final static int SLOPE_0 = 10000;
+
+ private RotateGestureListener rotationGestureListener;
+
+ private float angle;
+ private float downX = 0;
+ private float downY = 0;
+ private float downX2 = 0;
+ private float downY2 = 0;
+ private boolean isFirstPointerUp = false;
+
+ public interface RotateGestureListener {
+ void onRotation(float deltaAngle);
+ }
+
+ RotateGestureDetector(RotateGestureListener rotationGestureListener2) {
+ this.rotationGestureListener = rotationGestureListener2;
+ }
+
+ @SuppressWarnings("deprecation")
+ synchronized public boolean onTouchEvent(MotionEvent event) {
+
+ float eventX = event.getX();
+ float eventY = event.getY();
+ int count = event.getPointerCount();
+
+ switch (event.getAction() & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ downX = eventX;
+ downY = eventY;
+ if (count >= 2) {
+ downX2 = event.getX(1);
+ downY2 = event.getY(1);
+ }
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ downX2 = event.getX(1);
+ downY2 = event.getY(1);
+ break;
+ case MotionEvent.ACTION_MOVE:
+
+ if (count >= 2) {
+
+ // 回転角度の取得
+ float angle = getAngle(downX, downY, downX2, downY2, eventX, eventY, event.getX(1), event.getY(1));
+
+ // 画像の回転
+ if (angle != SLOPE_0) {
+ this.angle -= angle * 180d / Math.PI;
+ }
+
+ downX2 = event.getX(1);
+ downY2 = event.getY(1);
+
+ if (rotationGestureListener != null) {
+ rotationGestureListener.onRotation(getDeltaAngle());
+ }
+ }
+
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_POINTER_1_UP:
+ isFirstPointerUp = true;
+ break;
+ default:
+ }
+ break;
+ default:
+ }
+
+ if (isFirstPointerUp) {
+ downX = downX2;
+ downY = downY2;
+ isFirstPointerUp = false;
+ } else {
+ downX = eventX;
+ downY = eventY;
+ }
+
+ return true;
+ }
+
+ private float getDeltaAngle() {
+ return angle;
+ }
+
+ private static float getAngle(float xi1, float yi1, float xm1, float ym1, float xi2, float yi2, float xm2, float ym2) {
+
+ // 2本の直線の傾き・y切片を算出
+ float firstLinearSlope;
+ if ((xm1 - xi1) != 0 && (ym1 - yi1) != 0) {
+ firstLinearSlope = (xm1 - xi1) / (ym1 - yi1);
+ } else {
+ return SLOPE_0;
+ }
+
+ float secondLinearSlope = (xm2 - xi2) / (ym2 - yi2);
+ if ((xm2 - xi2) != 0 && (ym2 - yi2) != 0) {
+ secondLinearSlope = (xm2 - xi2) / (ym2 - yi2);
+ } else {
+ return SLOPE_0;
+ }
+
+ if (firstLinearSlope * secondLinearSlope == -1) {
+ return 90.0f;
+ }
+
+ float tan = (secondLinearSlope - firstLinearSlope) / (1 + secondLinearSlope * firstLinearSlope);
+
+ return (float) Math.atan(tan);
+ }
+
+}
diff --git a/matisse/src/main/java/com/zhihu/matisse/ui/widget/SceneCropColor.kt b/matisse/src/main/java/com/zhihu/matisse/ui/widget/SceneCropColor.kt
new file mode 100644
index 000000000..9446cdfd7
--- /dev/null
+++ b/matisse/src/main/java/com/zhihu/matisse/ui/widget/SceneCropColor.kt
@@ -0,0 +1,19 @@
+package com.zhihu.matisse.ui.widget
+
+import androidx.annotation.ColorRes
+import com.zhihu.matisse.R
+
+enum class SceneCropColor(@param:ColorRes val colorRes: Int, val clearColorItem: ClearColorItem) {
+
+ WHITE(R.color.white, ClearColorItem(1f, 1f, 1f, 1f)),
+ GRAY(R.color.crop_background_gray, ClearColorItem(0.867f, 0.867f, 0.867f, 1f)),
+ DARK(R.color.crop_background_dark, ClearColorItem(0.267f, 0.267f, 0.267f, 1f)),
+ BLACK(R.color.black, ClearColorItem(0f, 0f, 0f, 1f)),
+ PINK(R.color.crop_background_pink, ClearColorItem(1f, 0.827f, 0.87f, 1f)),
+ FLESH(R.color.crop_background_flesh, ClearColorItem(1f, 0.945f, 0.768f, 1f)),
+ GREEN(R.color.crop_background_green, ClearColorItem(0.905f, 1f, 0.898f, 1f)),
+ BLUE(R.color.crop_background_blue, ClearColorItem(0.898f, 0.937f, 1f, 1f)),
+ BROWN(R.color.crop_background_brown, ClearColorItem(0.85f, 0.807f, 0.745f, 1f))
+}
+
+class ClearColorItem(val red: Float, val green: Float, val blue: Float, val alpha: Float)
\ No newline at end of file
diff --git a/matisse/src/main/java/com/zhihu/matisse/ui/widget/TimeSelectorView.kt b/matisse/src/main/java/com/zhihu/matisse/ui/widget/TimeSelectorView.kt
new file mode 100644
index 000000000..16235b2f0
--- /dev/null
+++ b/matisse/src/main/java/com/zhihu/matisse/ui/widget/TimeSelectorView.kt
@@ -0,0 +1,245 @@
+package com.zhihu.matisse.ui.widget
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.View
+import androidx.annotation.FloatRange
+import androidx.core.content.ContextCompat
+import androidx.core.math.MathUtils
+import com.zhihu.matisse.R
+import java.util.concurrent.TimeUnit
+
+class TimeSelectorView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : View(context, attrs, defStyleAttr) {
+
+ companion object {
+ private const val ALPHA_SELECTED = 255
+ private const val ALPHA_UNSELECTED_ = (255 * 0.38f).toInt()
+
+ private val STATE_PRESSED = intArrayOf(android.R.attr.state_pressed, android.R.attr.state_enabled)
+ private val STATE_ENABLED = intArrayOf(android.R.attr.state_enabled)
+ private val STATE_DISABLED = intArrayOf()
+ }
+
+ private val leftThumbDrawable: Drawable = ContextCompat.getDrawable(context, R.drawable.icon_time_selector)!!
+ .mutate()
+ .let {
+ it.setBounds(0, 0, it.intrinsicWidth, it.intrinsicHeight)
+ it.callback = this
+ it
+ }
+ private val rightThumbDrawable: Drawable = ContextCompat.getDrawable(context, R.drawable.icon_time_selector)!!
+ .mutate()
+ .let {
+ it.setBounds(0, 0, it.intrinsicWidth, it.intrinsicHeight)
+ it.callback = this
+ it
+ }
+ private val progressLineEnabledColor = ContextCompat.getColor(context, R.color.colorAccent)
+ private val progressLineDisabledColor = ContextCompat.getColor(context, R.color.gray)
+ private val progressLinePaint = Paint().apply {
+ style = Paint.Style.STROKE
+ strokeWidth = 2 * context.resources.displayMetrics.density
+ }
+ private val halfSlop = leftThumbDrawable.intrinsicWidth / 2f
+
+ /** Left pointer position is in range [0..1] of available width. */
+ @FloatRange(from = 0.0, to = 1.0)
+ private var leftPointerPosition: Float = 0f
+ /** Right pointer position is in range [0..1] of available width. */
+ @FloatRange(from = 0.0, to = 1.0)
+ private var rightPointerPosition: Float = 1f
+ /** Limit of pointer's positions. */
+ @FloatRange(from = 0.0)
+ private var limit: Float = 1f
+ var duration: Long = 0
+
+ /** Left pointer position in pixels from left of view. */
+ private val realLeftPointerPosition: Float
+ get() = paddingLeft + leftPointerPosition * (width - paddingRight - paddingLeft)
+ /** Right pointer position in pixels from left of view. */
+ private val realRightPointerPosition: Float
+ get() = paddingLeft + rightPointerPosition * (width - paddingRight - paddingLeft)
+
+ /** [onTouchEvent] implementation's variables. */
+ private var oldX: Float = 0f
+ private var oldY: Float = 0f
+ private var touched: Boolean = false
+ private var touchedRight: Boolean = false
+
+ /** Listeners. **/
+ var selectionStartListener: ((Boolean, Float, Float) -> Unit)? = null
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ val heightMode = MeasureSpec.getMode(heightMeasureSpec)
+ val newHeightMeasureSpec = if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
+ MeasureSpec.makeMeasureSpec(
+ leftThumbDrawable.intrinsicHeight + paddingTop + paddingBottom, MeasureSpec.EXACTLY)
+ } else {
+ heightMeasureSpec
+ }
+ super.onMeasure(widthMeasureSpec, newHeightMeasureSpec)
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+ val lineWidth = width - paddingRight - paddingLeft
+ val drawLeft = paddingLeft.toFloat()
+ val drawRight = (width - paddingRight).toFloat()
+ val lineY = height / 2f
+ progressLinePaint.color = if (isEnabled) progressLineEnabledColor else progressLineDisabledColor
+ progressLinePaint.alpha = ALPHA_UNSELECTED_
+ canvas.drawLine(drawLeft, lineY, drawRight, lineY, progressLinePaint)
+ val leftBorder = drawLeft + lineWidth * leftPointerPosition
+ val rightBorder = drawLeft + lineWidth * rightPointerPosition
+ progressLinePaint.alpha = ALPHA_SELECTED
+ canvas.drawLine(leftBorder, lineY, rightBorder, lineY, progressLinePaint)
+ canvas.save()
+ canvas.translate(
+ leftBorder - leftThumbDrawable.intrinsicWidth / 2,
+ lineY - rightThumbDrawable.intrinsicHeight / 2
+ )
+ leftThumbDrawable.draw(canvas)
+ canvas.restore()
+ canvas.save()
+ canvas.translate(
+ rightBorder - rightThumbDrawable.intrinsicWidth / 2,
+ lineY - rightThumbDrawable.intrinsicHeight / 2
+ )
+ rightThumbDrawable.draw(canvas)
+ canvas.restore()
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ // ClickableViewAccessibility - super.onTouchEvent called.
+ if (isEnabled) {
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ oldX = event.x
+ oldY = event.y
+ if (event.x > realRightPointerPosition - halfSlop &&
+ event.x < realRightPointerPosition + halfSlop) {
+ touched = true
+ touchedRight = true
+ } else if (event.x > realLeftPointerPosition - halfSlop &&
+ event.x < realLeftPointerPosition + halfSlop) {
+ touched = true
+ touchedRight = false
+ }
+ if (touched) {
+ refreshDrawableState()
+ notifyStartChanged(true)
+ invalidate()
+ super.onTouchEvent(event)
+ return true
+ }
+ }
+ MotionEvent.ACTION_MOVE -> {
+ if (touched) {
+ changePosition(event)
+ oldX = event.x
+ oldY = event.y
+ invalidate()
+ super.onTouchEvent(event)
+ return true
+ }
+ }
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
+ if (touched) {
+ touched = false
+ refreshDrawableState()
+ notifyStartChanged(false)
+ invalidate()
+ super.onTouchEvent(event)
+ return true
+ }
+ }
+ }
+ }
+ return super.onTouchEvent(event)
+ }
+
+ override fun drawableStateChanged() {
+ super.drawableStateChanged()
+ var changed = false
+ if (isEnabled) {
+ changed = changed or leftThumbDrawable.setState(
+ if (touched && !touchedRight) STATE_PRESSED else STATE_ENABLED)
+ changed = changed or rightThumbDrawable.setState(
+ if (touched && touchedRight) STATE_PRESSED else STATE_ENABLED)
+ } else {
+ changed = changed or leftThumbDrawable.setState(STATE_DISABLED)
+ changed = changed or rightThumbDrawable.setState(STATE_DISABLED)
+ }
+ if (changed) {
+ invalidate()
+ }
+ }
+
+ /**
+ * Set limit for selection.
+ * User can't move pointer such way, that pointer's positions delta is more than [range].
+ * Default value is ```1```.
+ * @throws IllegalArgumentException if range less than 0.
+ */
+ fun setLimit(@FloatRange(from = 0.0) range: Float) {
+ require(range > 0f) { "Limit can't be less than 0" }
+
+ limit = range
+ validateRightPosition()
+ validateLeftPosition()
+ }
+
+ private fun changePosition(event: MotionEvent) {
+ val deltaX = (event.x - oldX) / (width - paddingLeft - paddingRight)
+ if (touchedRight) {
+ rightPointerPosition += deltaX
+ validateRightPosition()
+ } else {
+ leftPointerPosition += deltaX
+ validateLeftPosition()
+ }
+ }
+
+ private fun validateRightPosition() {
+ rightPointerPosition = MathUtils.clamp(rightPointerPosition, 0f, 1f)
+ // it is not possible to swap pointers
+ if (rightPointerPosition < leftPointerPosition) {
+ leftPointerPosition = rightPointerPosition
+ }
+ // it is not possible to move pointer above limit
+ if (rightPointerPosition - leftPointerPosition > limit) {
+ rightPointerPosition = leftPointerPosition + limit
+ }
+ }
+
+ private fun validateLeftPosition() {
+ leftPointerPosition = MathUtils.clamp(leftPointerPosition, 0f, 1f)
+ // it is not possible to swap pointers
+ if (leftPointerPosition > rightPointerPosition) {
+ rightPointerPosition = leftPointerPosition
+ }
+ // it is not possible to move pointer above limit
+ if (rightPointerPosition - leftPointerPosition > limit) {
+ leftPointerPosition = rightPointerPosition - limit
+ }
+ }
+
+ private fun notifyStartChanged(started: Boolean) {
+ selectionStartListener?.apply { invoke(started, leftPointerPosition, rightPointerPosition) }
+ }
+
+ fun getCurrent(): List =
+ arrayOf(leftPointerPosition * duration , rightPointerPosition * duration)
+ .map { TimeUnit.MICROSECONDS.toMillis(it.toLong()) }
+
+}
diff --git a/matisse/src/main/res/drawable/icon_time_selector.xml b/matisse/src/main/res/drawable/icon_time_selector.xml
new file mode 100644
index 000000000..c15911541
--- /dev/null
+++ b/matisse/src/main/res/drawable/icon_time_selector.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/matisse/src/main/res/drawable/icon_time_selector_disabled.xml b/matisse/src/main/res/drawable/icon_time_selector_disabled.xml
new file mode 100644
index 000000000..c712e3afe
--- /dev/null
+++ b/matisse/src/main/res/drawable/icon_time_selector_disabled.xml
@@ -0,0 +1,12 @@
+
+
+
+ -
+
+
+
+
+
+
diff --git a/matisse/src/main/res/drawable/icon_time_selector_normal.xml b/matisse/src/main/res/drawable/icon_time_selector_normal.xml
new file mode 100644
index 000000000..13c09c854
--- /dev/null
+++ b/matisse/src/main/res/drawable/icon_time_selector_normal.xml
@@ -0,0 +1,26 @@
+
+
+
+ -
+
+
+
+
+
+ -
+
+
-
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/matisse/src/main/res/drawable/icon_time_selector_size.xml b/matisse/src/main/res/drawable/icon_time_selector_size.xml
new file mode 100644
index 000000000..f0845507c
--- /dev/null
+++ b/matisse/src/main/res/drawable/icon_time_selector_size.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/matisse/src/main/res/layout/activity_matisse.xml b/matisse/src/main/res/layout/activity_matisse.xml
index 99aacd18b..86cff3995 100644
--- a/matisse/src/main/res/layout/activity_matisse.xml
+++ b/matisse/src/main/res/layout/activity_matisse.xml
@@ -20,7 +20,7 @@
android:layout_height="match_parent"
android:orientation="vertical">
-
-
+
-
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/matisse/src/main/res/layout/fragment_media_selection.xml b/matisse/src/main/res/layout/fragment_media_selection.xml
index 77642ac35..5b4ac2951 100644
--- a/matisse/src/main/res/layout/fragment_media_selection.xml
+++ b/matisse/src/main/res/layout/fragment_media_selection.xml
@@ -30,10 +30,9 @@
android:id="@+id/mPreview"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
-
-
-
+ #62B384
+ #808080
+ #FFFFFF
+ #000000
#CC000000
#61FFFFFF
#FFF
#0645AD
+
+ #dddddd
+ #444444
+ #ffd3de
+ #fff1c4
+ #e7ffe5
+ #e5efff
+ #d9cebe
\ No newline at end of file
diff --git a/matisse/src/main/res/values/dimens.xml b/matisse/src/main/res/values/dimens.xml
index 6d6bb4dd3..0901dc5d6 100644
--- a/matisse/src/main/res/values/dimens.xml
+++ b/matisse/src/main/res/values/dimens.xml
@@ -25,4 +25,8 @@
100dp
8dp
32dp
+
+ 48dp
+ 16dp
+ 24dp
\ No newline at end of file
diff --git a/matisse/src/main/res/values/strings.xml b/matisse/src/main/res/values/strings.xml
index 4e3f2b728..552bb9294 100644
--- a/matisse/src/main/res/values/strings.xml
+++ b/matisse/src/main/res/values/strings.xml
@@ -39,8 +39,13 @@
Original
Next
Next(%1$d)
- Filter
- Next
+ フィルタ
+ トリミング
+ 次へ
+
+ 背景色を選択してください
+ 背景色の変更
+ 回転
android.support.design.widget.AppBarLayout$ScrollingViewBehavior
diff --git a/sample/build.gradle b/sample/build.gradle
index 1f378dd1f..2bb87674b 100644
--- a/sample/build.gradle
+++ b/sample/build.gradle
@@ -30,8 +30,8 @@ android {
abortOnError false
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_7
- targetCompatibility JavaVersion.VERSION_1_7
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
}
}
@@ -43,10 +43,10 @@ dependencies {
// implementation 'com.zhihu.android:matisse:0.5.1-beta1'
implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation "com.android.support:appcompat-v7:${supportLibVersion}"
- implementation "com.android.support:recyclerview-v7:${supportLibVersion}"
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.5@aar'
- implementation 'io.reactivex.rxjava2:rxjava:2.1.9'
+ implementation 'io.reactivex.rxjava2:rxjava:2.2.12'
implementation 'com.github.bumptech.glide:glide:4.9.0'
implementation 'com.squareup.picasso:picasso:2.5.2'
}
diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
index 1e75bde1e..17180c4c1 100644
--- a/sample/src/main/AndroidManifest.xml
+++ b/sample/src/main/AndroidManifest.xml
@@ -15,13 +15,15 @@
limitations under the License.
-->
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.zhihu.matisse.sample">
+ android:theme="@style/AppTheme"
+ tools:ignore="GoogleAppIndexingWarning">
@@ -32,7 +34,7 @@
diff --git a/sample/src/main/java/com/zhihu/matisse/sample/SampleActivity.java b/sample/src/main/java/com/zhihu/matisse/sample/SampleActivity.java
index eb486d981..dfa0837f8 100644
--- a/sample/src/main/java/com/zhihu/matisse/sample/SampleActivity.java
+++ b/sample/src/main/java/com/zhihu/matisse/sample/SampleActivity.java
@@ -20,10 +20,6 @@
import android.content.pm.ActivityInfo;
import android.net.Uri;
import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -31,6 +27,11 @@
import android.widget.TextView;
import android.widget.Toast;
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
import com.tbruyelle.rxpermissions2.RxPermissions;
import com.zhihu.matisse.Matisse;
import com.zhihu.matisse.MimeType;
@@ -84,7 +85,7 @@ public void onNext(Boolean aBoolean) {
.capture(true)
.captureStrategy(
new CaptureStrategy(true, "com.zhihu.matisse.sample.fileprovider","test"))
- .maxSelectable(9)
+ .maxSelectablePerMediaType(5,1)
.addFilter(new GifSizeFilter(320, 320, 5 * Filter.K * Filter.K))
.gridExpectedSize(
getResources().getDimensionPixelSize(R.dimen.grid_expected_size))
@@ -104,7 +105,7 @@ public void onSelected(
.originalEnable(true)
.maxOriginalSize(10)
.maxCropSize(3000)
- .filterEnable(false)
+ .filterEnable(true)
.autoHideToolbarOnSingleTap(true)
.setOnCheckedListener(new OnCheckedListener() {
@Override
@@ -154,7 +155,9 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_CHOOSE && resultCode == RESULT_OK) {
mAdapter.setData(Matisse.obtainResult(data), Matisse.obtainPathResult(data));
- Log.e("OnActivityResult ", String.valueOf(Matisse.obtainOriginalState(data)));
+ Log.i("OnActivityResult ", String.valueOf(data));
+ Log.i("OnActivityResult ", String.valueOf(Matisse.obtainResult(data)));
+ Log.i("OnActivityResult ", String.valueOf(Matisse.obtainPathResult(data)));
}
}
diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml
index eed3e726a..0514e529d 100644
--- a/sample/src/main/res/layout/activity_main.xml
+++ b/sample/src/main/res/layout/activity_main.xml
@@ -63,7 +63,7 @@
android:theme="@style/DraculaOverlay"/>
-
-
+