feat : app/src/module_album
161
app/src/module_album/java/com/example/matisse/Matisse.java
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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.example.matisse;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.example.matisse.internal.entity.CustomItem;
|
||||
import com.example.matisse.ui.MatisseActivity;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Entry for Matisse's media selection.
|
||||
*/
|
||||
public final class Matisse {
|
||||
|
||||
private final WeakReference<Activity> mContext;
|
||||
private final WeakReference<Fragment> mFragment;
|
||||
|
||||
private Matisse(Activity activity) {
|
||||
this(activity, null);
|
||||
}
|
||||
|
||||
private Matisse(Fragment fragment) {
|
||||
this(fragment.getActivity(), fragment);
|
||||
}
|
||||
|
||||
private Matisse(Activity activity, Fragment fragment) {
|
||||
mContext = new WeakReference<>(activity);
|
||||
mFragment = new WeakReference<>(fragment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start Matisse from an Activity.
|
||||
* <p>
|
||||
* This Activity's {@link Activity#onActivityResult(int, int, Intent)} will be called when user
|
||||
* finishes selecting.
|
||||
*
|
||||
* @param activity Activity instance.
|
||||
* @return Matisse instance.
|
||||
*/
|
||||
public static Matisse from(Activity activity) {
|
||||
return new Matisse(activity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start Matisse from a Fragment.
|
||||
* <p>
|
||||
* This Fragment's {@link Fragment#onActivityResult(int, int, Intent)} will be called when user
|
||||
* finishes selecting.
|
||||
*
|
||||
* @param fragment Fragment instance.
|
||||
* @return Matisse instance.
|
||||
*/
|
||||
public static Matisse from(Fragment fragment) {
|
||||
return new Matisse(fragment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain user selected media' {@link Uri} list in the starting Activity or Fragment.
|
||||
*
|
||||
* @param data Intent passed by {@link Activity#onActivityResult(int, int, Intent)} or
|
||||
* {@link Fragment#onActivityResult(int, int, Intent)}.
|
||||
* @return User selected media' {@link Uri} list.
|
||||
*/
|
||||
public static List<Uri> obtainResult(Intent data) {
|
||||
return data.getParcelableArrayListExtra(MatisseActivity.EXTRA_RESULT_SELECTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain user selected media path list in the starting Activity or Fragment.
|
||||
*
|
||||
* @param data Intent passed by {@link Activity#onActivityResult(int, int, Intent)} or
|
||||
* {@link Fragment#onActivityResult(int, int, Intent)}.
|
||||
* @return User selected media path list.
|
||||
*/
|
||||
public static List<CustomItem> obtainPathResult(Intent data) {
|
||||
return data.getParcelableArrayListExtra(MatisseActivity.EXTRA_RESULT_SELECTION_PATH);
|
||||
}
|
||||
|
||||
public static boolean obtainOriginalImageResult(Intent data) {
|
||||
return data.getBooleanExtra(MatisseActivity.EXTRA_RESULT_ORIGINAL_IMAGE, false);
|
||||
}
|
||||
|
||||
public static String obtainMineResult(Intent data) {
|
||||
return data.getStringExtra(MatisseActivity.EXTRA_RESULT_MIME_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain state whether user decide to use selected media in original
|
||||
*
|
||||
* @param data Intent passed by {@link Activity#onActivityResult(int, int, Intent)} or
|
||||
* {@link Fragment#onActivityResult(int, int, Intent)}.
|
||||
* @return Whether use original photo
|
||||
*/
|
||||
public static boolean obtainOriginalState(Intent data) {
|
||||
return data.getBooleanExtra(MatisseActivity.EXTRA_RESULT_ORIGINAL_ENABLE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* MIME types the selection constrains on.
|
||||
* <p>
|
||||
* Types not included in the set will still be shown in the grid but can't be chosen.
|
||||
*
|
||||
* @param mimeTypes MIME types set user can choose from.
|
||||
* @return {@link SelectionCreator} to build select specifications.
|
||||
* @see MimeType
|
||||
* @see SelectionCreator
|
||||
*/
|
||||
public SelectionCreator choose(Set<MimeType> mimeTypes) {
|
||||
return this.choose(mimeTypes, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* MIME types the selection constrains on.
|
||||
* <p>
|
||||
* Types not included in the set will still be shown in the grid but can't be chosen.
|
||||
*
|
||||
* @param mimeTypes MIME types set user can choose from.
|
||||
* @param mediaTypeExclusive Whether can choose images and videos at the same time during one single choosing
|
||||
* process. true corresponds to not being able to choose images and videos at the same
|
||||
* time, and false corresponds to being able to do this.
|
||||
* @return {@link SelectionCreator} to build select specifications.
|
||||
* @see MimeType
|
||||
* @see SelectionCreator
|
||||
*/
|
||||
public SelectionCreator choose(Set<MimeType> mimeTypes, boolean mediaTypeExclusive) {
|
||||
return new SelectionCreator(this, mimeTypes, mediaTypeExclusive);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Activity getActivity() {
|
||||
return mContext.get();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Fragment getFragment() {
|
||||
return mFragment != null ? mFragment.get() : null;
|
||||
}
|
||||
|
||||
}
|
176
app/src/module_album/java/com/example/matisse/MimeType.java
Normal file
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (C) 2014 nohana, Inc.
|
||||
* 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.example.matisse;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import androidx.collection.ArraySet;
|
||||
|
||||
import com.example.matisse.internal.utils.PhotoMetadataUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* MIME Type enumeration to restrict selectable media on the selection activity. Matisse only supports images and
|
||||
* videos.
|
||||
* <p>
|
||||
* Good example of mime types Android supports:
|
||||
* https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/MediaFile.java
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public enum MimeType {
|
||||
|
||||
// ============== images ==============
|
||||
JPEG("image/jpeg", arraySetOf(
|
||||
"jpg",
|
||||
"jpeg"
|
||||
)),
|
||||
PNG("image/png", arraySetOf(
|
||||
"png"
|
||||
)),
|
||||
GIF("image/gif", arraySetOf(
|
||||
"gif"
|
||||
)),
|
||||
BMP("image/x-ms-bmp", arraySetOf(
|
||||
"bmp"
|
||||
)),
|
||||
WEBP("image/webp", arraySetOf(
|
||||
"webp"
|
||||
)),
|
||||
|
||||
// ============== videos ==============
|
||||
MPEG("video/mpeg", arraySetOf(
|
||||
"mpeg",
|
||||
"mpg"
|
||||
)),
|
||||
MP4("video/mp4", arraySetOf(
|
||||
"mp4",
|
||||
"m4v"
|
||||
)),
|
||||
QUICKTIME("video/quicktime", arraySetOf(
|
||||
"mov"
|
||||
)),
|
||||
THREEGPP("video/3gpp", arraySetOf(
|
||||
"3gp",
|
||||
"3gpp"
|
||||
)),
|
||||
THREEGPP2("video/3gpp2", arraySetOf(
|
||||
"3g2",
|
||||
"3gpp2"
|
||||
)),
|
||||
MKV("video/x-matroska", arraySetOf(
|
||||
"mkv"
|
||||
)),
|
||||
WEBM("video/webm", arraySetOf(
|
||||
"webm"
|
||||
)),
|
||||
TS("video/mp2ts", arraySetOf(
|
||||
"ts"
|
||||
)),
|
||||
AVI("video/avi", arraySetOf(
|
||||
"avi"
|
||||
));
|
||||
|
||||
private final String mMimeTypeName;
|
||||
private final Set<String> mExtensions;
|
||||
|
||||
MimeType(String mimeTypeName, Set<String> extensions) {
|
||||
mMimeTypeName = mimeTypeName;
|
||||
mExtensions = extensions;
|
||||
}
|
||||
|
||||
public static Set<MimeType> ofAll() {
|
||||
return EnumSet.allOf(MimeType.class);
|
||||
}
|
||||
|
||||
public static Set<MimeType> of(MimeType type, MimeType... rest) {
|
||||
return EnumSet.of(type, rest);
|
||||
}
|
||||
|
||||
public static Set<MimeType> ofImage() {
|
||||
return EnumSet.of(JPEG, PNG, GIF, BMP, WEBP);
|
||||
}
|
||||
|
||||
public static Set<MimeType> ofGif() {
|
||||
return ofImage(true);
|
||||
}
|
||||
public static Set<MimeType> ofImage(boolean onlyGif) {
|
||||
return EnumSet.of(GIF);
|
||||
}
|
||||
|
||||
public static Set<MimeType> ofVideo() {
|
||||
return EnumSet.of(MPEG, MP4, QUICKTIME, THREEGPP, THREEGPP2, MKV, WEBM, TS, AVI);
|
||||
}
|
||||
|
||||
public static boolean isImage(String mimeType) {
|
||||
if (mimeType == null) return false;
|
||||
return mimeType.startsWith("image");
|
||||
}
|
||||
|
||||
public static boolean isVideo(String mimeType) {
|
||||
if (mimeType == null) return false;
|
||||
return mimeType.startsWith("video");
|
||||
}
|
||||
|
||||
public static boolean isGif(String mimeType) {
|
||||
if (mimeType == null) return false;
|
||||
return mimeType.equals(MimeType.GIF.toString());
|
||||
}
|
||||
|
||||
private static Set<String> arraySetOf(String... suffixes) {
|
||||
return new ArraySet<>(Arrays.asList(suffixes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mMimeTypeName;
|
||||
}
|
||||
|
||||
public boolean checkType(ContentResolver resolver, Uri uri) {
|
||||
MimeTypeMap map = MimeTypeMap.getSingleton();
|
||||
if (uri == null) {
|
||||
return false;
|
||||
}
|
||||
String type = map.getExtensionFromMimeType(resolver.getType(uri));
|
||||
String path = null;
|
||||
// lazy load the path and prevent resolve for multiple times
|
||||
boolean pathParsed = false;
|
||||
for (String extension : mExtensions) {
|
||||
if (extension.equals(type)) {
|
||||
return true;
|
||||
}
|
||||
if (!pathParsed) {
|
||||
// we only resolve the path for one time
|
||||
path = PhotoMetadataUtils.getPath(resolver, uri);
|
||||
if (!TextUtils.isEmpty(path)) {
|
||||
path = path.toLowerCase(Locale.US);
|
||||
}
|
||||
pathParsed = true;
|
||||
}
|
||||
if (path != null && path.endsWith(extension)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
* Copyright (C) 2014 nohana, Inc.
|
||||
* 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.example.matisse;
|
||||
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_USER;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
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.example.matisse.engine.ImageEngine;
|
||||
import com.example.matisse.filter.Filter;
|
||||
import com.example.matisse.internal.entity.CaptureStrategy;
|
||||
import com.example.matisse.internal.entity.SelectionSpec;
|
||||
import com.example.matisse.listener.OnCheckedListener;
|
||||
import com.example.matisse.listener.OnSelectedListener;
|
||||
import com.example.matisse.ui.MatisseActivity;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Fluent API for building media select specification.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public final class SelectionCreator {
|
||||
private final Matisse mMatisse;
|
||||
private final SelectionSpec mSelectionSpec;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||
@IntDef({
|
||||
SCREEN_ORIENTATION_UNSPECIFIED,
|
||||
SCREEN_ORIENTATION_LANDSCAPE,
|
||||
SCREEN_ORIENTATION_PORTRAIT,
|
||||
SCREEN_ORIENTATION_USER,
|
||||
SCREEN_ORIENTATION_BEHIND,
|
||||
SCREEN_ORIENTATION_SENSOR,
|
||||
SCREEN_ORIENTATION_NOSENSOR,
|
||||
SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
|
||||
SCREEN_ORIENTATION_SENSOR_PORTRAIT,
|
||||
SCREEN_ORIENTATION_REVERSE_LANDSCAPE,
|
||||
SCREEN_ORIENTATION_REVERSE_PORTRAIT,
|
||||
SCREEN_ORIENTATION_FULL_SENSOR,
|
||||
SCREEN_ORIENTATION_USER_LANDSCAPE,
|
||||
SCREEN_ORIENTATION_USER_PORTRAIT,
|
||||
SCREEN_ORIENTATION_FULL_USER,
|
||||
SCREEN_ORIENTATION_LOCKED
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface ScreenOrientation {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new specification builder on the context.
|
||||
*
|
||||
* @param matisse a requester context wrapper.
|
||||
* @param mimeTypes MIME type set to select.
|
||||
*/
|
||||
SelectionCreator(Matisse matisse, @NonNull Set<MimeType> mimeTypes, boolean mediaTypeExclusive) {
|
||||
mMatisse = matisse;
|
||||
mSelectionSpec = SelectionSpec.getCleanInstance();
|
||||
mSelectionSpec.mimeTypeSet = mimeTypes;
|
||||
mSelectionSpec.mediaTypeExclusive = mediaTypeExclusive;
|
||||
mSelectionSpec.orientation = SCREEN_ORIENTATION_UNSPECIFIED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to show only one media type if choosing medias are only images or videos.
|
||||
*
|
||||
* @param showSingleMediaType whether to show only one media type, either images or videos.
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
* @see SelectionSpec#onlyShowImages()
|
||||
* @see SelectionSpec#onlyShowVideos()
|
||||
*/
|
||||
public SelectionCreator showSingleMediaType(boolean showSingleMediaType) {
|
||||
mSelectionSpec.showSingleMediaType = showSingleMediaType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme for media selecting Activity.
|
||||
* <p>
|
||||
* There are two built-in themes:
|
||||
* 1. com.example.matisse.R.style.Matisse_Zhihu;
|
||||
* 2. com.example.matisse.R.style.Matisse_Dracula
|
||||
* you can define a custom theme derived from the above ones or other themes.
|
||||
*
|
||||
* @param themeId theme resource id. Default value is com.example.matisse.R.style.Matisse_Zhihu.
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator theme(@StyleRes int themeId) {
|
||||
mSelectionSpec.themeId = themeId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a auto-increased number or a check mark when user select media.
|
||||
*
|
||||
* @param countable true for a auto-increased number from 1, false for a check mark. Default
|
||||
* value is false.
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator countable(boolean countable) {
|
||||
mSelectionSpec.countable = countable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum selectable count.
|
||||
*
|
||||
* @param maxSelectable Maximum selectable count. Default value is 1.
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator maxSelectable(int maxSelectable) {
|
||||
if (maxSelectable < 1)
|
||||
throw new IllegalArgumentException("maxSelectable must be greater than or equal to one");
|
||||
if (mSelectionSpec.maxImageSelectable > 0 || mSelectionSpec.maxVideoSelectable > 0)
|
||||
throw new IllegalStateException("already set maxImageSelectable and maxVideoSelectable");
|
||||
mSelectionSpec.maxSelectable = maxSelectable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only useful when {@link SelectionSpec#mediaTypeExclusive} set true and you want to set different maximum
|
||||
* selectable files for image and video media types.
|
||||
*
|
||||
* @param maxImageSelectable Maximum selectable count for image.
|
||||
* @param maxVideoSelectable Maximum selectable count for video.
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator maxSelectablePerMediaType(int maxImageSelectable, int maxVideoSelectable) {
|
||||
if (maxImageSelectable < 1 || maxVideoSelectable < 1)
|
||||
throw new IllegalArgumentException(("max selectable must be greater than or equal to one"));
|
||||
mSelectionSpec.maxSelectable = -1;
|
||||
mSelectionSpec.maxImageSelectable = maxImageSelectable;
|
||||
mSelectionSpec.maxVideoSelectable = maxVideoSelectable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add filter to filter each selecting item.
|
||||
*
|
||||
* @param filter {@link Filter}
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator addFilter(@NonNull Filter filter) {
|
||||
if (mSelectionSpec.filters == null) {
|
||||
mSelectionSpec.filters = new ArrayList<>();
|
||||
}
|
||||
if (filter == null) throw new IllegalArgumentException("filter cannot be null");
|
||||
mSelectionSpec.filters.add(filter);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the photo capturing is enabled or not on the media grid view.
|
||||
* <p>
|
||||
* If this value is set true, photo capturing entry will appear only on All Media's page.
|
||||
*
|
||||
* @param enable Whether to enable capturing or not. Default value is false;
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator capture(boolean enable) {
|
||||
mSelectionSpec.capture = enable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a original photo check options.Let users decide whether use original photo after select
|
||||
*
|
||||
* @param enable Whether to enable original photo or not
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator originalEnable(boolean enable) {
|
||||
mSelectionSpec.originalable = enable;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines Whether to hide top and bottom toolbar in PreView mode ,when user tap the picture
|
||||
*
|
||||
* @param enable
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator autoHideToolbarOnSingleTap(boolean enable) {
|
||||
mSelectionSpec.autoHideToobar = enable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum original size,the unit is MB. Only useful when {link@originalEnable} set true
|
||||
*
|
||||
* @param size Maximum original size. Default value is Integer.MAX_VALUE
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator maxOriginalSize(int size) {
|
||||
mSelectionSpec.originalMaxSize = size;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture strategy provided for the location to save photos including internal and external
|
||||
* 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.
|
||||
*/
|
||||
public SelectionCreator captureStrategy(CaptureStrategy captureStrategy) {
|
||||
mSelectionSpec.captureStrategy = captureStrategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the desired orientation of this activity.
|
||||
*
|
||||
* @param orientation An orientation constant as used in {@link ScreenOrientation}.
|
||||
* Default value is {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_PORTRAIT}.
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
* @see Activity#setRequestedOrientation(int)
|
||||
*/
|
||||
public SelectionCreator restrictOrientation(@ScreenOrientation int orientation) {
|
||||
mSelectionSpec.orientation = orientation;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a fixed span count for the media grid. Same for different screen orientations.
|
||||
* <p>
|
||||
* This will be ignored when {@link #gridExpectedSize(int)} is set.
|
||||
*
|
||||
* @param spanCount Requested span count.
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator spanCount(int spanCount) {
|
||||
if (spanCount < 1) throw new IllegalArgumentException("spanCount cannot be less than 1");
|
||||
mSelectionSpec.spanCount = spanCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set expected size for media grid to adapt to different screen sizes. This won't necessarily
|
||||
* be applied cause the media grid should fill the view container. The measured media grid's
|
||||
* size will be as close to this value as possible.
|
||||
*
|
||||
* @param size Expected media grid size in pixel.
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator gridExpectedSize(int size) {
|
||||
mSelectionSpec.gridExpectedSize = size;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Photo thumbnail's scale compared to the View's size. It should be a float value in (0.0,
|
||||
* 1.0].
|
||||
*
|
||||
* @param scale Thumbnail's scale in (0.0, 1.0]. Default value is 0.5.
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator thumbnailScale(float scale) {
|
||||
if (scale <= 0f || scale > 1f)
|
||||
throw new IllegalArgumentException("Thumbnail scale must be between (0.0, 1.0]");
|
||||
mSelectionSpec.thumbnailScale = scale;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide an image engine.
|
||||
* <p>
|
||||
* There are two built-in image engines:
|
||||
* 1. {@link com.example.matisse.engine.impl.GlideEngine}
|
||||
* 2. {@link com.example.matisse.engine.impl.PicassoEngine}
|
||||
* And you can implement your own image engine.
|
||||
*
|
||||
* @param imageEngine {@link ImageEngine}
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator imageEngine(ImageEngine imageEngine) {
|
||||
mSelectionSpec.imageEngine = imageEngine;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set listener for callback immediately when user select or unselect something.
|
||||
* <p>
|
||||
* It's a redundant API with {@link Matisse#obtainResult(Intent)},
|
||||
* we only suggest you to use this API when you need to do something immediately.
|
||||
*
|
||||
* @param listener {@link OnSelectedListener}
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
@NonNull
|
||||
public SelectionCreator setOnSelectedListener(@Nullable OnSelectedListener listener) {
|
||||
mSelectionSpec.onSelectedListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set listener for callback immediately when user check or uncheck original.
|
||||
*
|
||||
* @param listener {@link OnSelectedListener}
|
||||
* @return {@link SelectionCreator} for fluent API.
|
||||
*/
|
||||
public SelectionCreator setOnCheckedListener(@Nullable OnCheckedListener listener) {
|
||||
mSelectionSpec.onCheckedListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start to select media and wait for result.
|
||||
*
|
||||
* @param requestCode Identity of the request Activity or Fragment.
|
||||
*/
|
||||
public void forResult(int requestCode) {
|
||||
Activity activity = mMatisse.getActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(activity, MatisseActivity.class);
|
||||
|
||||
Fragment fragment = mMatisse.getFragment();
|
||||
if (fragment != null) {
|
||||
fragment.startActivityForResult(intent, requestCode);
|
||||
} else {
|
||||
activity.startActivityForResult(intent, requestCode);
|
||||
}
|
||||
}
|
||||
|
||||
public SelectionCreator setType(int type) {
|
||||
mSelectionSpec.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SelectionCreator setOriginalImagee(boolean isOriginalImage) {
|
||||
mSelectionSpec.isOriginalImage = isOriginalImage;
|
||||
return this;
|
||||
}
|
||||
}
|
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.example.matisse.engine;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.widget.ImageView;
|
||||
|
||||
/**
|
||||
* Image loader interface. There are predefined {@link com.example.matisse.engine.impl.GlideEngine}
|
||||
* and {@link com.example.matisse.engine.impl.PicassoEngine}.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public interface ImageEngine {
|
||||
|
||||
/**
|
||||
* Load thumbnail of a static image resource.
|
||||
*
|
||||
* @param context Context
|
||||
* @param resize Desired size of the origin image
|
||||
* @param placeholder Placeholder drawable when image is not loaded yet
|
||||
* @param imageView ImageView widget
|
||||
* @param uri Uri of the loaded image
|
||||
*/
|
||||
void loadThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri);
|
||||
|
||||
/**
|
||||
* Load thumbnail of a gif image resource. You don't have to load an animated gif when it's only
|
||||
* a thumbnail tile.
|
||||
*
|
||||
* @param context Context
|
||||
* @param resize Desired size of the origin image
|
||||
* @param placeholder Placeholder drawable when image is not loaded yet
|
||||
* @param imageView ImageView widget
|
||||
* @param uri Uri of the loaded image
|
||||
*/
|
||||
void loadGifThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri);
|
||||
|
||||
/**
|
||||
* Load a static image resource.
|
||||
*
|
||||
* @param context Context
|
||||
* @param resizeX Desired x-size of the origin image
|
||||
* @param resizeY Desired y-size of the origin image
|
||||
* @param imageView ImageView widget
|
||||
* @param uri Uri of the loaded image
|
||||
*/
|
||||
void loadImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri);
|
||||
|
||||
/**
|
||||
* Load a gif image resource.
|
||||
*
|
||||
* @param context Context
|
||||
* @param resizeX Desired x-size of the origin image
|
||||
* @param resizeY Desired y-size of the origin image
|
||||
* @param imageView ImageView widget
|
||||
* @param uri Uri of the loaded image
|
||||
*/
|
||||
void loadGifImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri);
|
||||
|
||||
/**
|
||||
* Whether this implementation supports animated gif.
|
||||
* Just knowledge of it, convenient for users.
|
||||
*
|
||||
* @return true support animated gif, false do not support animated gif.
|
||||
*/
|
||||
boolean supportAnimatedGif();
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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.example.matisse.engine.impl;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.bumptech.glide.Priority;
|
||||
import com.netease.nim.uikit.support.glide.GlideApp;
|
||||
import com.example.matisse.engine.ImageEngine;
|
||||
|
||||
/**
|
||||
* {@link ImageEngine} implementation using Glide.
|
||||
*/
|
||||
|
||||
public class GlideEngine implements ImageEngine {
|
||||
|
||||
@Override
|
||||
public void loadThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri) {
|
||||
GlideApp.with(context)
|
||||
.asBitmap() // some .jpeg files are actually gif
|
||||
.load(uri)
|
||||
.placeholder(placeholder)
|
||||
.override(resize, resize)
|
||||
.centerCrop()
|
||||
.into(imageView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadGifThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView,
|
||||
Uri uri) {
|
||||
GlideApp.with(context)
|
||||
.asBitmap()
|
||||
.load(uri)
|
||||
.placeholder(placeholder)
|
||||
.override(resize, resize)
|
||||
.centerCrop()
|
||||
.into(imageView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) {
|
||||
GlideApp.with(context)
|
||||
.load(uri)
|
||||
.override(resizeX, resizeY)
|
||||
.priority(Priority.HIGH)
|
||||
.fitCenter()
|
||||
.into(imageView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadGifImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) {
|
||||
GlideApp.with(context)
|
||||
.asGif()
|
||||
.load(uri)
|
||||
.override(resizeX, resizeY)
|
||||
.priority(Priority.HIGH)
|
||||
.into(imageView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportAnimatedGif() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.example.matisse.filter;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.example.matisse.MimeType;
|
||||
import com.example.matisse.SelectionCreator;
|
||||
import com.example.matisse.internal.entity.IncapableCause;
|
||||
import com.example.matisse.internal.entity.Item;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Filter for choosing a {@link Item}. You can add multiple Filters through
|
||||
* {@link SelectionCreator#addFilter(Filter)}.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class Filter {
|
||||
/**
|
||||
* Convenient constant for a minimum value.
|
||||
*/
|
||||
public static final int MIN = 0;
|
||||
/**
|
||||
* Convenient constant for a maximum value.
|
||||
*/
|
||||
public static final int MAX = Integer.MAX_VALUE;
|
||||
/**
|
||||
* Convenient constant for 1024.
|
||||
*/
|
||||
public static final int K = 1024;
|
||||
|
||||
/**
|
||||
* Against what mime types this filter applies.
|
||||
*/
|
||||
protected abstract Set<MimeType> constraintTypes();
|
||||
|
||||
/**
|
||||
* Invoked for filtering each item.
|
||||
*
|
||||
* @return null if selectable, {@link IncapableCause} if not selectable.
|
||||
*/
|
||||
public abstract IncapableCause filter(Context context, Item item);
|
||||
|
||||
/**
|
||||
* Whether an {@link Item} need filtering.
|
||||
*/
|
||||
protected boolean needFiltering(Context context, Item item) {
|
||||
for (MimeType type : constraintTypes()) {
|
||||
if (type.checkType(context.getContentResolver(), item.getContentUri())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (C) 2014 nohana, Inc.
|
||||
* 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.example.matisse.internal.entity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.chwl.app.R;
|
||||
import com.example.matisse.internal.loader.AlbumLoader;
|
||||
|
||||
public class Album implements Parcelable {
|
||||
public static final Creator<Album> CREATOR = new Creator<Album>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public Album createFromParcel(Parcel source) {
|
||||
return new Album(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Album[] newArray(int size) {
|
||||
return new Album[size];
|
||||
}
|
||||
};
|
||||
public static final String ALBUM_ID_ALL = String.valueOf(-1);
|
||||
public static final String ALBUM_NAME_ALL = "All";
|
||||
|
||||
private final String mId;
|
||||
private final Uri mCoverUri;
|
||||
private final String mDisplayName;
|
||||
private long mCount;
|
||||
|
||||
public Album(String id, Uri coverUri, String albumName, long count) {
|
||||
mId = id;
|
||||
mCoverUri = coverUri;
|
||||
mDisplayName = albumName;
|
||||
mCount = count;
|
||||
}
|
||||
|
||||
private Album(Parcel source) {
|
||||
mId = source.readString();
|
||||
mCoverUri = source.readParcelable(Uri.class.getClassLoader());
|
||||
mDisplayName = source.readString();
|
||||
mCount = source.readLong();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Album} entity from the {@link Cursor}.
|
||||
* This method is not responsible for managing cursor resource, such as close, iterate, and so on.
|
||||
*/
|
||||
public static Album valueOf(Cursor cursor) {
|
||||
String clumn = cursor.getString(cursor.getColumnIndex(AlbumLoader.COLUMN_URI));
|
||||
return new Album(
|
||||
cursor.getString(cursor.getColumnIndex("bucket_id")),
|
||||
Uri.parse(clumn != null ? clumn : ""),
|
||||
cursor.getString(cursor.getColumnIndex("bucket_display_name")),
|
||||
cursor.getLong(cursor.getColumnIndex(AlbumLoader.COLUMN_COUNT)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mId);
|
||||
dest.writeParcelable(mCoverUri, 0);
|
||||
dest.writeString(mDisplayName);
|
||||
dest.writeLong(mCount);
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
public Uri getCoverUri() {
|
||||
return mCoverUri;
|
||||
}
|
||||
|
||||
public long getCount() {
|
||||
return mCount;
|
||||
}
|
||||
|
||||
public void addCaptureCount() {
|
||||
mCount++;
|
||||
}
|
||||
|
||||
public String getDisplayName(Context context) {
|
||||
if (isAll()) {
|
||||
return context.getString(R.string.album_name_all);
|
||||
}
|
||||
return mDisplayName;
|
||||
}
|
||||
|
||||
public boolean isAll() {
|
||||
return ALBUM_ID_ALL.equals(mId);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return mCount == 0;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.entity;
|
||||
|
||||
import com.netease.nim.uikit.common.util.log.LogUtil;
|
||||
import com.chwl.app.R;
|
||||
import com.chwl.library.utils.ResUtil;
|
||||
|
||||
public class CaptureStrategy {
|
||||
|
||||
public final boolean isPublic;
|
||||
public final String authority;
|
||||
public final String directory;
|
||||
|
||||
public CaptureStrategy(boolean isPublic, String authority) {
|
||||
this(isPublic, authority, null);
|
||||
}
|
||||
|
||||
public CaptureStrategy(boolean isPublic, String authority, String directory) {
|
||||
this.isPublic = isPublic;
|
||||
this.authority = authority;
|
||||
LogUtil.print(ResUtil.getString(R.string.internal_entity_capturestrategy_01) + directory);
|
||||
this.directory = directory;
|
||||
}
|
||||
}
|
@@ -0,0 +1,112 @@
|
||||
package com.example.matisse.internal.entity;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* create by lvzebiao @2019/11/19
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class CustomItem implements Parcelable, Serializable {
|
||||
|
||||
public final static int UNKOWN = -1;
|
||||
|
||||
public final static int IMAGE_NORMAL = 0;
|
||||
|
||||
public final static int GIF = 1;
|
||||
|
||||
public final static int VIDEO = 2;
|
||||
|
||||
private String path;
|
||||
/**
|
||||
* -1 未知
|
||||
* 0 图片
|
||||
* 1 gif
|
||||
* 2 视频
|
||||
*/
|
||||
private int fileType;
|
||||
|
||||
private String format;
|
||||
|
||||
private int width;
|
||||
|
||||
private int height;
|
||||
|
||||
public String getPath(){
|
||||
return path;
|
||||
}
|
||||
|
||||
public int getFileType(){
|
||||
return fileType;
|
||||
}
|
||||
|
||||
public boolean isImage() {
|
||||
return fileType == IMAGE_NORMAL;
|
||||
}
|
||||
|
||||
public boolean isGif() {
|
||||
return fileType == GIF;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是网络图片
|
||||
* @return -
|
||||
*/
|
||||
public boolean isNetImage() {
|
||||
try {
|
||||
return path != null && path.startsWith("http");
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public CustomItem(String path, int fileType) {
|
||||
this(path, fileType, "jpeg",0,0);
|
||||
}
|
||||
|
||||
public CustomItem() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(this.path);
|
||||
dest.writeInt(this.fileType);
|
||||
dest.writeString(this.format);
|
||||
dest.writeInt(this.width);
|
||||
dest.writeInt(this.height);
|
||||
}
|
||||
|
||||
protected CustomItem(Parcel in) {
|
||||
this.path = in.readString();
|
||||
this.fileType = in.readInt();
|
||||
this.format = in.readString();
|
||||
this.width = in.readInt();
|
||||
this.height = in.readInt();
|
||||
}
|
||||
|
||||
public static final Creator<CustomItem> CREATOR = new Creator<CustomItem>() {
|
||||
@Override
|
||||
public CustomItem createFromParcel(Parcel source) {
|
||||
return new CustomItem(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomItem[] newArray(int size) {
|
||||
return new CustomItem[size];
|
||||
}
|
||||
};
|
||||
}
|
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.entity;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.chwl.library.utils.SingleToastUtil;
|
||||
import com.example.matisse.internal.ui.widget.IncapableDialog;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class IncapableCause {
|
||||
public static final int TOAST = 0x00;
|
||||
public static final int DIALOG = 0x01;
|
||||
public static final int NONE = 0x02;
|
||||
|
||||
@Retention(SOURCE)
|
||||
@IntDef({TOAST, DIALOG, NONE})
|
||||
public @interface Form {
|
||||
}
|
||||
|
||||
private int mForm = TOAST;
|
||||
private String mTitle;
|
||||
private String mMessage;
|
||||
|
||||
public IncapableCause(String message) {
|
||||
mMessage = message;
|
||||
}
|
||||
|
||||
public IncapableCause(String title, String message) {
|
||||
mTitle = title;
|
||||
mMessage = message;
|
||||
}
|
||||
|
||||
public IncapableCause(@Form int form, String message) {
|
||||
mForm = form;
|
||||
mMessage = message;
|
||||
}
|
||||
|
||||
public IncapableCause(@Form int form, String title, String message) {
|
||||
mForm = form;
|
||||
mTitle = title;
|
||||
mMessage = message;
|
||||
}
|
||||
|
||||
public static void handleCause(Context context, IncapableCause cause) {
|
||||
if (cause == null)
|
||||
return;
|
||||
|
||||
switch (cause.mForm) {
|
||||
case NONE:
|
||||
// do nothing.
|
||||
break;
|
||||
case DIALOG:
|
||||
IncapableDialog incapableDialog = IncapableDialog.newInstance(cause.mTitle, cause.mMessage);
|
||||
incapableDialog.show(((FragmentActivity) context).getSupportFragmentManager(),
|
||||
IncapableDialog.class.getName());
|
||||
break;
|
||||
case TOAST:
|
||||
default:
|
||||
SingleToastUtil.showToast(cause.mMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (C) 2014 nohana, Inc.
|
||||
* 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.example.matisse.internal.entity;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.provider.MediaStore;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.example.matisse.MimeType;
|
||||
|
||||
public class Item implements Parcelable {
|
||||
public static final Creator<Item> CREATOR = new Creator<Item>() {
|
||||
@Override
|
||||
@Nullable
|
||||
public Item createFromParcel(Parcel source) {
|
||||
return new Item(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item[] newArray(int size) {
|
||||
return new Item[size];
|
||||
}
|
||||
};
|
||||
public static final long ITEM_ID_CAPTURE = -1;
|
||||
public static final String ITEM_DISPLAY_NAME_CAPTURE = "Capture";
|
||||
public final long id;
|
||||
public final String mimeType;
|
||||
public final Uri uri;
|
||||
public final long size;
|
||||
public final long duration; // only for video, in ms
|
||||
|
||||
private Item(long id, String mimeType, long size, long duration) {
|
||||
this.id = id;
|
||||
this.mimeType = mimeType;
|
||||
Uri contentUri;
|
||||
if (isImage()) {
|
||||
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if (isVideo()) {
|
||||
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
||||
} else {
|
||||
// ?
|
||||
contentUri = MediaStore.Files.getContentUri("external");
|
||||
}
|
||||
this.uri = ContentUris.withAppendedId(contentUri, id);
|
||||
this.size = size;
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public Item(String mimeType, Uri uri, long size, long duration) {
|
||||
this.id = 1;
|
||||
this.mimeType = mimeType;
|
||||
this.uri = uri;
|
||||
this.size = size;
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
private Item(Parcel source) {
|
||||
id = source.readLong();
|
||||
mimeType = source.readString();
|
||||
uri = source.readParcelable(Uri.class.getClassLoader());
|
||||
size = source.readLong();
|
||||
duration = source.readLong();
|
||||
}
|
||||
|
||||
public static Item valueOf(Cursor cursor) {
|
||||
return new Item(cursor.getLong(cursor.getColumnIndex(MediaStore.Files.FileColumns._ID)),
|
||||
cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE)),
|
||||
cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns.SIZE)),
|
||||
cursor.getLong(cursor.getColumnIndex("duration")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeLong(id);
|
||||
dest.writeString(mimeType);
|
||||
dest.writeParcelable(uri, 0);
|
||||
dest.writeLong(size);
|
||||
dest.writeLong(duration);
|
||||
}
|
||||
|
||||
public Uri getContentUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
public boolean isCapture() {
|
||||
return id == ITEM_ID_CAPTURE;
|
||||
}
|
||||
|
||||
public boolean isImage() {
|
||||
return MimeType.isImage(mimeType);
|
||||
}
|
||||
|
||||
public boolean isGif() {
|
||||
return MimeType.isGif(mimeType);
|
||||
}
|
||||
|
||||
public boolean isVideo() {
|
||||
return MimeType.isVideo(mimeType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof Item)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Item other = (Item) obj;
|
||||
return id == other.id
|
||||
&& (mimeType != null && mimeType.equals(other.mimeType)
|
||||
|| (mimeType == null && other.mimeType == null))
|
||||
&& (uri != null && uri.equals(other.uri)
|
||||
|| (uri == null && other.uri == null))
|
||||
&& size == other.size
|
||||
&& duration == other.duration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = 1;
|
||||
result = 31 * result + Long.valueOf(id).hashCode();
|
||||
if (mimeType != null) {
|
||||
result = 31 * result + mimeType.hashCode();
|
||||
}
|
||||
result = 31 * result + uri.hashCode();
|
||||
result = 31 * result + Long.valueOf(size).hashCode();
|
||||
result = 31 * result + Long.valueOf(duration).hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (C) 2014 nohana, Inc.
|
||||
* 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.example.matisse.internal.entity;
|
||||
|
||||
import android.content.pm.ActivityInfo;
|
||||
|
||||
import androidx.annotation.StyleRes;
|
||||
|
||||
import com.chwl.app.R;
|
||||
import com.example.matisse.MimeType;
|
||||
import com.example.matisse.engine.ImageEngine;
|
||||
import com.example.matisse.engine.impl.GlideEngine;
|
||||
import com.example.matisse.filter.Filter;
|
||||
import com.example.matisse.listener.OnCheckedListener;
|
||||
import com.example.matisse.listener.OnSelectedListener;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public final class SelectionSpec {
|
||||
|
||||
public Set<MimeType> mimeTypeSet;
|
||||
public boolean mediaTypeExclusive;
|
||||
public boolean showSingleMediaType;
|
||||
@StyleRes
|
||||
public int themeId;
|
||||
public int orientation;
|
||||
public boolean countable;
|
||||
public int maxSelectable;
|
||||
public int maxImageSelectable;
|
||||
public int maxVideoSelectable;
|
||||
public List<Filter> filters;
|
||||
public boolean capture;
|
||||
public CaptureStrategy captureStrategy;
|
||||
public int spanCount;
|
||||
public int gridExpectedSize;
|
||||
public float thumbnailScale;
|
||||
public ImageEngine imageEngine;
|
||||
public boolean hasInited;
|
||||
public OnSelectedListener onSelectedListener;
|
||||
public boolean originalable;
|
||||
public boolean autoHideToobar;
|
||||
public int originalMaxSize;
|
||||
public OnCheckedListener onCheckedListener;
|
||||
public int type; // 1 发布页跳转过来
|
||||
|
||||
public boolean isOriginalImage;
|
||||
|
||||
private SelectionSpec() {
|
||||
}
|
||||
|
||||
public static SelectionSpec getInstance() {
|
||||
return InstanceHolder.INSTANCE;
|
||||
}
|
||||
|
||||
public static SelectionSpec getCleanInstance() {
|
||||
SelectionSpec selectionSpec = getInstance();
|
||||
selectionSpec.reset();
|
||||
return selectionSpec;
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
mimeTypeSet = null;
|
||||
mediaTypeExclusive = true;
|
||||
showSingleMediaType = false;
|
||||
themeId = R.style.Matisse_Zhihu;
|
||||
orientation = 0;
|
||||
countable = false;
|
||||
maxSelectable = 1;
|
||||
maxImageSelectable = 0;
|
||||
maxVideoSelectable = 0;
|
||||
filters = null;
|
||||
capture = false;
|
||||
captureStrategy = null;
|
||||
spanCount = 3;
|
||||
gridExpectedSize = 0;
|
||||
thumbnailScale = 0.5f;
|
||||
imageEngine = new GlideEngine();
|
||||
hasInited = true;
|
||||
originalable = false;
|
||||
autoHideToobar = false;
|
||||
originalMaxSize = Integer.MAX_VALUE;
|
||||
isOriginalImage = false;
|
||||
}
|
||||
|
||||
public boolean singleSelectionModeEnabled() {
|
||||
return !countable && (maxSelectable == 1 || (maxImageSelectable == 1 && maxVideoSelectable == 1));
|
||||
}
|
||||
|
||||
public boolean needOrientationRestriction() {
|
||||
return orientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
|
||||
}
|
||||
|
||||
public boolean onlyShowGif() {
|
||||
return showSingleMediaType && MimeType.ofGif().equals(mimeTypeSet);
|
||||
}
|
||||
|
||||
public boolean onlyShowImages() {
|
||||
return showSingleMediaType && MimeType.ofImage().containsAll(mimeTypeSet);
|
||||
}
|
||||
|
||||
public boolean onlyShowVideos() {
|
||||
return showSingleMediaType && MimeType.ofVideo().containsAll(mimeTypeSet);
|
||||
}
|
||||
|
||||
private static final class InstanceHolder {
|
||||
private static final SelectionSpec INSTANCE = new SelectionSpec();
|
||||
}
|
||||
}
|
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Copyright (C) 2014 nohana, Inc.
|
||||
* 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.example.matisse.internal.loader;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.database.MergeCursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.MediaStore;
|
||||
|
||||
import androidx.loader.content.CursorLoader;
|
||||
|
||||
import com.example.matisse.MimeType;
|
||||
import com.example.matisse.internal.entity.Album;
|
||||
import com.example.matisse.internal.entity.SelectionSpec;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Load all albums (grouped by bucket_id) into a single cursor.
|
||||
*/
|
||||
public class AlbumLoader extends CursorLoader {
|
||||
|
||||
private static final String COLUMN_BUCKET_ID = "bucket_id";
|
||||
private static final String COLUMN_BUCKET_DISPLAY_NAME = "bucket_display_name";
|
||||
public static final String COLUMN_URI = "uri";
|
||||
public static final String COLUMN_COUNT = "count";
|
||||
private static final Uri QUERY_URI = MediaStore.Files.getContentUri("external");
|
||||
|
||||
private static final String[] COLUMNS = {
|
||||
MediaStore.Files.FileColumns._ID,
|
||||
COLUMN_BUCKET_ID,
|
||||
COLUMN_BUCKET_DISPLAY_NAME,
|
||||
MediaStore.MediaColumns.MIME_TYPE,
|
||||
COLUMN_URI,
|
||||
COLUMN_COUNT};
|
||||
|
||||
private static final String[] PROJECTION = {
|
||||
MediaStore.Files.FileColumns._ID,
|
||||
COLUMN_BUCKET_ID,
|
||||
COLUMN_BUCKET_DISPLAY_NAME,
|
||||
MediaStore.MediaColumns.MIME_TYPE,
|
||||
"COUNT(*) AS " + COLUMN_COUNT};
|
||||
|
||||
private static final String[] PROJECTION_29 = {
|
||||
MediaStore.Files.FileColumns._ID,
|
||||
COLUMN_BUCKET_ID,
|
||||
COLUMN_BUCKET_DISPLAY_NAME,
|
||||
MediaStore.MediaColumns.MIME_TYPE};
|
||||
|
||||
// === params for showSingleMediaType: false ===
|
||||
private static final String SELECTION =
|
||||
"(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " OR "
|
||||
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0"
|
||||
+ ") GROUP BY (bucket_id";
|
||||
private static final String SELECTION_29 =
|
||||
"(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " OR "
|
||||
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0";
|
||||
private static final String[] SELECTION_ARGS = {
|
||||
String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
|
||||
String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO),
|
||||
};
|
||||
// =============================================
|
||||
|
||||
// === params for showSingleMediaType: true ===
|
||||
private static final String SELECTION_FOR_SINGLE_MEDIA_TYPE =
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0"
|
||||
+ ") GROUP BY (bucket_id";
|
||||
private static final String SELECTION_FOR_SINGLE_MEDIA_TYPE_29 =
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0";
|
||||
|
||||
private static String[] getSelectionArgsForSingleMediaType(int mediaType) {
|
||||
return new String[]{String.valueOf(mediaType)};
|
||||
}
|
||||
// =============================================
|
||||
|
||||
// === params for showSingleMediaType: true ===
|
||||
private static final String SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE =
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0"
|
||||
+ " AND " + MediaStore.MediaColumns.MIME_TYPE + "=?"
|
||||
+ ") GROUP BY (bucket_id";
|
||||
private static final String SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE_29 =
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0"
|
||||
+ " AND " + MediaStore.MediaColumns.MIME_TYPE + "=?";
|
||||
|
||||
private static String[] getSelectionArgsForSingleMediaGifType(int mediaType) {
|
||||
return new String[]{String.valueOf(mediaType), "image/gif"};
|
||||
}
|
||||
// =============================================
|
||||
|
||||
private static final String BUCKET_ORDER_BY = "datetaken DESC";
|
||||
|
||||
private AlbumLoader(Context context, String selection, String[] selectionArgs) {
|
||||
super(
|
||||
context,
|
||||
QUERY_URI,
|
||||
beforeAndroidTen() ? PROJECTION : PROJECTION_29,
|
||||
selection,
|
||||
selectionArgs,
|
||||
BUCKET_ORDER_BY
|
||||
);
|
||||
}
|
||||
|
||||
public static CursorLoader newInstance(Context context) {
|
||||
String selection;
|
||||
String[] selectionArgs;
|
||||
if (SelectionSpec.getInstance().onlyShowGif()) {
|
||||
selection = beforeAndroidTen()
|
||||
? SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE : SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE_29;
|
||||
selectionArgs = getSelectionArgsForSingleMediaGifType(
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
|
||||
} else if (SelectionSpec.getInstance().onlyShowImages()) {
|
||||
selection = beforeAndroidTen()
|
||||
? SELECTION_FOR_SINGLE_MEDIA_TYPE : SELECTION_FOR_SINGLE_MEDIA_TYPE_29;
|
||||
selectionArgs = getSelectionArgsForSingleMediaType(
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
|
||||
} else if (SelectionSpec.getInstance().onlyShowVideos()) {
|
||||
selection = beforeAndroidTen()
|
||||
? SELECTION_FOR_SINGLE_MEDIA_TYPE : SELECTION_FOR_SINGLE_MEDIA_TYPE_29;
|
||||
selectionArgs = getSelectionArgsForSingleMediaType(
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO);
|
||||
} else {
|
||||
selection = beforeAndroidTen() ? SELECTION : SELECTION_29;
|
||||
selectionArgs = SELECTION_ARGS;
|
||||
}
|
||||
return new AlbumLoader(context, selection, selectionArgs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadInBackground() {
|
||||
Cursor albums = super.loadInBackground();
|
||||
MatrixCursor allAlbum = new MatrixCursor(COLUMNS);
|
||||
|
||||
if (beforeAndroidTen()) {
|
||||
int totalCount = 0;
|
||||
Uri allAlbumCoverUri = null;
|
||||
MatrixCursor otherAlbums = new MatrixCursor(COLUMNS);
|
||||
if (albums != null) {
|
||||
while (albums.moveToNext()) {
|
||||
long fileId = albums.getLong(
|
||||
albums.getColumnIndex(MediaStore.Files.FileColumns._ID));
|
||||
long bucketId = albums.getLong(
|
||||
albums.getColumnIndex(COLUMN_BUCKET_ID));
|
||||
String bucketDisplayName = albums.getString(
|
||||
albums.getColumnIndex(COLUMN_BUCKET_DISPLAY_NAME));
|
||||
String mimeType = albums.getString(
|
||||
albums.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE));
|
||||
Uri uri = getUri(albums);
|
||||
int count = albums.getInt(albums.getColumnIndex(COLUMN_COUNT));
|
||||
|
||||
otherAlbums.addRow(new String[]{
|
||||
Long.toString(fileId),
|
||||
Long.toString(bucketId), bucketDisplayName, mimeType, uri.toString(),
|
||||
String.valueOf(count)});
|
||||
totalCount += count;
|
||||
}
|
||||
if (albums.moveToFirst()) {
|
||||
allAlbumCoverUri = getUri(albums);
|
||||
}
|
||||
}
|
||||
|
||||
allAlbum.addRow(new String[]{
|
||||
Album.ALBUM_ID_ALL, Album.ALBUM_ID_ALL, Album.ALBUM_NAME_ALL, null,
|
||||
allAlbumCoverUri == null ? null : allAlbumCoverUri.toString(),
|
||||
String.valueOf(totalCount)});
|
||||
|
||||
return new MergeCursor(new Cursor[]{allAlbum, otherAlbums});
|
||||
} else {
|
||||
int totalCount = 0;
|
||||
Uri allAlbumCoverUri = null;
|
||||
|
||||
// Pseudo GROUP BY
|
||||
Map<Long, Long> countMap = new HashMap<>();
|
||||
if (albums != null) {
|
||||
while (albums.moveToNext()) {
|
||||
long bucketId = albums.getLong(albums.getColumnIndex(COLUMN_BUCKET_ID));
|
||||
|
||||
Long count = countMap.get(bucketId);
|
||||
if (count == null) {
|
||||
count = 1L;
|
||||
} else {
|
||||
count++;
|
||||
}
|
||||
countMap.put(bucketId, count);
|
||||
}
|
||||
}
|
||||
|
||||
MatrixCursor otherAlbums = new MatrixCursor(COLUMNS);
|
||||
if (albums != null) {
|
||||
if (albums.moveToFirst()) {
|
||||
allAlbumCoverUri = getUri(albums);
|
||||
|
||||
Set<Long> done = new HashSet<>();
|
||||
|
||||
do {
|
||||
long bucketId = albums.getLong(albums.getColumnIndex(COLUMN_BUCKET_ID));
|
||||
|
||||
if (done.contains(bucketId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
long fileId = albums.getLong(
|
||||
albums.getColumnIndex(MediaStore.Files.FileColumns._ID));
|
||||
String bucketDisplayName = albums.getString(
|
||||
albums.getColumnIndex(COLUMN_BUCKET_DISPLAY_NAME));
|
||||
String mimeType = albums.getString(
|
||||
albums.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE));
|
||||
Uri uri = getUri(albums);
|
||||
long count = countMap.get(bucketId);
|
||||
|
||||
otherAlbums.addRow(new String[]{
|
||||
Long.toString(fileId),
|
||||
Long.toString(bucketId),
|
||||
bucketDisplayName,
|
||||
mimeType,
|
||||
uri.toString(),
|
||||
String.valueOf(count)});
|
||||
done.add(bucketId);
|
||||
|
||||
totalCount += count;
|
||||
} while (albums.moveToNext());
|
||||
}
|
||||
}
|
||||
|
||||
allAlbum.addRow(new String[]{
|
||||
Album.ALBUM_ID_ALL,
|
||||
Album.ALBUM_ID_ALL, Album.ALBUM_NAME_ALL, null,
|
||||
allAlbumCoverUri == null ? null : allAlbumCoverUri.toString(),
|
||||
String.valueOf(totalCount)});
|
||||
|
||||
return new MergeCursor(new Cursor[]{allAlbum, otherAlbums});
|
||||
}
|
||||
}
|
||||
|
||||
private static Uri getUri(Cursor cursor) {
|
||||
long id = cursor.getLong(cursor.getColumnIndex(MediaStore.Files.FileColumns._ID));
|
||||
String mimeType = cursor.getString(
|
||||
cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE));
|
||||
Uri contentUri;
|
||||
|
||||
if (MimeType.isImage(mimeType)) {
|
||||
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if (MimeType.isVideo(mimeType)) {
|
||||
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
||||
} else {
|
||||
// ?
|
||||
contentUri = MediaStore.Files.getContentUri("external");
|
||||
}
|
||||
|
||||
Uri uri = ContentUris.withAppendedId(contentUri, id);
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContentChanged() {
|
||||
// FIXME a dirty way to fix loading multiple times
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否是 Android 10 (Q) 之前的版本
|
||||
*/
|
||||
private static boolean beforeAndroidTen() {
|
||||
return android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q;
|
||||
}
|
||||
}
|
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2014 nohana, Inc.
|
||||
* 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.example.matisse.internal.loader;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.database.MergeCursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
|
||||
import androidx.loader.content.CursorLoader;
|
||||
|
||||
import com.example.matisse.internal.entity.Album;
|
||||
import com.example.matisse.internal.entity.Item;
|
||||
import com.example.matisse.internal.entity.SelectionSpec;
|
||||
import com.example.matisse.internal.utils.MediaStoreCompat;
|
||||
|
||||
/**
|
||||
* Load images and videos into a single cursor.
|
||||
*/
|
||||
public class AlbumMediaLoader extends CursorLoader {
|
||||
private static final Uri QUERY_URI = MediaStore.Files.getContentUri("external");
|
||||
private static final String[] PROJECTION = {
|
||||
MediaStore.Files.FileColumns._ID,
|
||||
MediaStore.MediaColumns.DISPLAY_NAME,
|
||||
MediaStore.MediaColumns.MIME_TYPE,
|
||||
MediaStore.MediaColumns.SIZE,
|
||||
"duration"};
|
||||
|
||||
// === params for album ALL && showSingleMediaType: false ===
|
||||
private static final String SELECTION_ALL =
|
||||
"(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " OR "
|
||||
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0";
|
||||
private static final String[] SELECTION_ALL_ARGS = {
|
||||
String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
|
||||
String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO),
|
||||
};
|
||||
// ===========================================================
|
||||
|
||||
// === params for album ALL && showSingleMediaType: true ===
|
||||
private static final String SELECTION_ALL_FOR_SINGLE_MEDIA_TYPE =
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0";
|
||||
|
||||
private static String[] getSelectionArgsForSingleMediaType(int mediaType) {
|
||||
return new String[]{String.valueOf(mediaType)};
|
||||
}
|
||||
// =========================================================
|
||||
|
||||
// === params for ordinary album && showSingleMediaType: false ===
|
||||
private static final String SELECTION_ALBUM =
|
||||
"(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " OR "
|
||||
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)"
|
||||
+ " AND "
|
||||
+ " bucket_id=?"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0";
|
||||
|
||||
private static String[] getSelectionAlbumArgs(String albumId) {
|
||||
return new String[]{
|
||||
String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
|
||||
String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO),
|
||||
albumId
|
||||
};
|
||||
}
|
||||
// ===============================================================
|
||||
|
||||
// === params for ordinary album && showSingleMediaType: true ===
|
||||
private static final String SELECTION_ALBUM_FOR_SINGLE_MEDIA_TYPE =
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
|
||||
+ " AND "
|
||||
+ " bucket_id=?"
|
||||
+ " AND " + MediaStore.MediaColumns.SIZE + ">0";
|
||||
|
||||
private static String[] getSelectionAlbumArgsForSingleMediaType(int mediaType, String albumId) {
|
||||
return new String[]{String.valueOf(mediaType), albumId};
|
||||
}
|
||||
// ===============================================================
|
||||
|
||||
private static final String ORDER_BY = MediaStore.Images.Media.DATE_TAKEN + " DESC";
|
||||
private final boolean mEnableCapture;
|
||||
|
||||
private AlbumMediaLoader(Context context, String selection, String[] selectionArgs, boolean capture) {
|
||||
super(context, QUERY_URI, PROJECTION, selection, selectionArgs, ORDER_BY);
|
||||
mEnableCapture = capture;
|
||||
}
|
||||
|
||||
public static CursorLoader newInstance(Context context, Album album, boolean capture) {
|
||||
String selection;
|
||||
String[] selectionArgs;
|
||||
boolean enableCapture;
|
||||
|
||||
if (album.isAll()) {
|
||||
if (SelectionSpec.getInstance().onlyShowImages()) {
|
||||
selection = SELECTION_ALL_FOR_SINGLE_MEDIA_TYPE;
|
||||
selectionArgs = getSelectionArgsForSingleMediaType(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
|
||||
} else if (SelectionSpec.getInstance().onlyShowVideos()) {
|
||||
selection = SELECTION_ALL_FOR_SINGLE_MEDIA_TYPE;
|
||||
selectionArgs = getSelectionArgsForSingleMediaType(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO);
|
||||
} else {
|
||||
selection = SELECTION_ALL;
|
||||
selectionArgs = SELECTION_ALL_ARGS;
|
||||
}
|
||||
enableCapture = capture;
|
||||
} else {
|
||||
if (SelectionSpec.getInstance().onlyShowImages()) {
|
||||
selection = SELECTION_ALBUM_FOR_SINGLE_MEDIA_TYPE;
|
||||
selectionArgs = getSelectionAlbumArgsForSingleMediaType(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE,
|
||||
album.getId());
|
||||
} else if (SelectionSpec.getInstance().onlyShowVideos()) {
|
||||
selection = SELECTION_ALBUM_FOR_SINGLE_MEDIA_TYPE;
|
||||
selectionArgs = getSelectionAlbumArgsForSingleMediaType(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO,
|
||||
album.getId());
|
||||
} else {
|
||||
selection = SELECTION_ALBUM;
|
||||
selectionArgs = getSelectionAlbumArgs(album.getId());
|
||||
}
|
||||
enableCapture = false;
|
||||
}
|
||||
return new AlbumMediaLoader(context, selection, selectionArgs, enableCapture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadInBackground() {
|
||||
Cursor result = super.loadInBackground();
|
||||
if (!mEnableCapture || !MediaStoreCompat.hasCameraFeature(getContext())) {
|
||||
return result;
|
||||
}
|
||||
MatrixCursor dummy = new MatrixCursor(PROJECTION);
|
||||
dummy.addRow(new Object[]{Item.ITEM_ID_CAPTURE, Item.ITEM_DISPLAY_NAME_CAPTURE, "", 0, 0});
|
||||
return new MergeCursor(new Cursor[]{dummy, result});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContentChanged() {
|
||||
// FIXME a dirty way to fix loading multiple times
|
||||
}
|
||||
}
|
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (C) 2014 nohana, Inc.
|
||||
* 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.example.matisse.internal.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.Loader;
|
||||
|
||||
import com.example.matisse.internal.loader.AlbumLoader;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class AlbumCollection implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
private static final int LOADER_ID = 1;
|
||||
private static final String STATE_CURRENT_SELECTION = "state_current_selection";
|
||||
private WeakReference<Context> mContext;
|
||||
private LoaderManager mLoaderManager;
|
||||
private AlbumCallbacks mCallbacks;
|
||||
private int mCurrentSelection;
|
||||
private boolean mLoadFinished;
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
Context context = mContext.get();
|
||||
if (context == null) {
|
||||
return null;
|
||||
}
|
||||
mLoadFinished = false;
|
||||
return AlbumLoader.newInstance(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
Context context = mContext.get();
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mLoadFinished) {
|
||||
mLoadFinished = true;
|
||||
mCallbacks.onAlbumLoad(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
Context context = mContext.get();
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mCallbacks.onAlbumReset();
|
||||
}
|
||||
|
||||
public void onCreate(FragmentActivity activity, AlbumCallbacks callbacks) {
|
||||
mContext = new WeakReference<Context>(activity);
|
||||
mLoaderManager = activity.getSupportLoaderManager();
|
||||
mCallbacks = callbacks;
|
||||
}
|
||||
|
||||
public void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
if (savedInstanceState == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mCurrentSelection = savedInstanceState.getInt(STATE_CURRENT_SELECTION);
|
||||
}
|
||||
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
outState.putInt(STATE_CURRENT_SELECTION, mCurrentSelection);
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
if (mLoaderManager != null) {
|
||||
mLoaderManager.destroyLoader(LOADER_ID);
|
||||
}
|
||||
mCallbacks = null;
|
||||
}
|
||||
|
||||
public void loadAlbums() {
|
||||
mLoaderManager.initLoader(LOADER_ID, null, this);
|
||||
}
|
||||
|
||||
public int getCurrentSelection() {
|
||||
return mCurrentSelection;
|
||||
}
|
||||
|
||||
public void setStateCurrentSelection(int currentSelection) {
|
||||
mCurrentSelection = currentSelection;
|
||||
}
|
||||
|
||||
public interface AlbumCallbacks {
|
||||
void onAlbumLoad(Cursor cursor);
|
||||
|
||||
void onAlbumReset();
|
||||
}
|
||||
}
|
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2014 nohana, Inc.
|
||||
* 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.example.matisse.internal.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
|
||||
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.example.matisse.internal.entity.Album;
|
||||
import com.example.matisse.internal.loader.AlbumMediaLoader;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class AlbumMediaCollection implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
private static final int LOADER_ID = 2;
|
||||
private static final String ARGS_ALBUM = "args_album";
|
||||
private static final String ARGS_ENABLE_CAPTURE = "args_enable_capture";
|
||||
private WeakReference<Context> mContext;
|
||||
private LoaderManager mLoaderManager;
|
||||
private AlbumMediaCallbacks mCallbacks;
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
Context context = mContext.get();
|
||||
if (context == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Album album = args.getParcelable(ARGS_ALBUM);
|
||||
if (album == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return AlbumMediaLoader.newInstance(context, album,
|
||||
album.isAll() && args.getBoolean(ARGS_ENABLE_CAPTURE, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
Context context = mContext.get();
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mCallbacks.onAlbumMediaLoad(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
Context context = mContext.get();
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mCallbacks.onAlbumMediaReset();
|
||||
}
|
||||
|
||||
public void onCreate(@NonNull FragmentActivity context, @NonNull AlbumMediaCallbacks callbacks) {
|
||||
mContext = new WeakReference<Context>(context);
|
||||
mLoaderManager = context.getSupportLoaderManager();
|
||||
mCallbacks = callbacks;
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
if (mLoaderManager != null) {
|
||||
mLoaderManager.destroyLoader(LOADER_ID);
|
||||
}
|
||||
mCallbacks = null;
|
||||
}
|
||||
|
||||
public void load(@Nullable Album target) {
|
||||
load(target, false);
|
||||
}
|
||||
|
||||
public void load(@Nullable Album target, boolean enableCapture) {
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARGS_ALBUM, target);
|
||||
args.putBoolean(ARGS_ENABLE_CAPTURE, enableCapture);
|
||||
mLoaderManager.initLoader(LOADER_ID, args, this);
|
||||
}
|
||||
|
||||
public interface AlbumMediaCallbacks {
|
||||
|
||||
void onAlbumMediaLoad(Cursor cursor);
|
||||
|
||||
void onAlbumMediaReset();
|
||||
}
|
||||
}
|
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.chwl.app.R;
|
||||
import com.example.matisse.internal.entity.CustomItem;
|
||||
import com.example.matisse.internal.entity.IncapableCause;
|
||||
import com.example.matisse.internal.entity.Item;
|
||||
import com.example.matisse.internal.entity.SelectionSpec;
|
||||
import com.example.matisse.internal.ui.widget.CheckView;
|
||||
import com.example.matisse.internal.utils.PathUtils;
|
||||
import com.example.matisse.internal.utils.PhotoMetadataUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class SelectedItemCollection {
|
||||
|
||||
public static final String STATE_SELECTION = "state_selection";
|
||||
public static final String STATE_COLLECTION_TYPE = "state_collection_type";
|
||||
/**
|
||||
* Empty collection
|
||||
*/
|
||||
public static final int COLLECTION_UNDEFINED = 0x00;
|
||||
/**
|
||||
* Collection only with images
|
||||
*/
|
||||
public static final int COLLECTION_IMAGE = 0x01;
|
||||
/**
|
||||
* Collection only with videos
|
||||
*/
|
||||
public static final int COLLECTION_VIDEO = 0x01 << 1;
|
||||
/**
|
||||
* Collection with images and videos.
|
||||
*/
|
||||
public static final int COLLECTION_MIXED = COLLECTION_IMAGE | COLLECTION_VIDEO;
|
||||
private final Context mContext;
|
||||
private Set<Item> mItems;
|
||||
private int mCollectionType = COLLECTION_UNDEFINED;
|
||||
|
||||
public SelectedItemCollection(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public void onCreate(Bundle bundle) {
|
||||
if (bundle == null) {
|
||||
mItems = new LinkedHashSet<>();
|
||||
} else {
|
||||
List<Item> saved = bundle.getParcelableArrayList(STATE_SELECTION);
|
||||
mItems = new LinkedHashSet<>(saved);
|
||||
mCollectionType = bundle.getInt(STATE_COLLECTION_TYPE, COLLECTION_UNDEFINED);
|
||||
}
|
||||
}
|
||||
|
||||
public void setDefaultSelection(List<Item> uris) {
|
||||
mItems.addAll(uris);
|
||||
}
|
||||
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
outState.putParcelableArrayList(STATE_SELECTION, new ArrayList<>(mItems));
|
||||
outState.putInt(STATE_COLLECTION_TYPE, mCollectionType);
|
||||
}
|
||||
|
||||
public Bundle getDataWithBundle() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelableArrayList(STATE_SELECTION, new ArrayList<>(mItems));
|
||||
bundle.putInt(STATE_COLLECTION_TYPE, mCollectionType);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
public boolean add(Item item) {
|
||||
// if (typeConflict(item)) {
|
||||
// throw new IllegalArgumentException("Can't select images and videos at the same time.");
|
||||
// }
|
||||
boolean added = mItems.add(item);
|
||||
if (added) {
|
||||
if (mCollectionType == COLLECTION_UNDEFINED) {
|
||||
if (item.isImage()) {
|
||||
mCollectionType = COLLECTION_IMAGE;
|
||||
} else if (item.isVideo()) {
|
||||
mCollectionType = COLLECTION_VIDEO;
|
||||
}
|
||||
} else if (mCollectionType == COLLECTION_IMAGE) {
|
||||
if (item.isVideo()) {
|
||||
mCollectionType = COLLECTION_MIXED;
|
||||
}
|
||||
} else if (mCollectionType == COLLECTION_VIDEO) {
|
||||
if (item.isImage()) {
|
||||
mCollectionType = COLLECTION_MIXED;
|
||||
}
|
||||
}
|
||||
}
|
||||
return added;
|
||||
}
|
||||
|
||||
public boolean remove(Item item) {
|
||||
boolean removed = mItems.remove(item);
|
||||
if (removed) {
|
||||
if (mItems.size() == 0) {
|
||||
mCollectionType = COLLECTION_UNDEFINED;
|
||||
} else {
|
||||
if (mCollectionType == COLLECTION_MIXED) {
|
||||
refineCollectionType();
|
||||
}
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
public void overwrite(ArrayList<Item> items, int collectionType) {
|
||||
if (items.size() == 0) {
|
||||
mCollectionType = COLLECTION_UNDEFINED;
|
||||
} else {
|
||||
mCollectionType = collectionType;
|
||||
}
|
||||
mItems.clear();
|
||||
mItems.addAll(items);
|
||||
}
|
||||
|
||||
|
||||
public List<Item> asList() {
|
||||
return new ArrayList<>(mItems);
|
||||
}
|
||||
|
||||
public List<Uri> asListOfUri() {
|
||||
List<Uri> uris = new ArrayList<>();
|
||||
for (Item item : mItems) {
|
||||
uris.add(item.getContentUri());
|
||||
}
|
||||
return uris;
|
||||
}
|
||||
|
||||
public List<String> asListOfString() {
|
||||
List<String> paths = new ArrayList<>();
|
||||
for (Item item : mItems) {
|
||||
paths.add(PathUtils.getPath(mContext, item.getContentUri()));
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
public List<CustomItem> asListOfCustomItem() {
|
||||
List<CustomItem> paths = new ArrayList<>();
|
||||
for (Item item : mItems) {
|
||||
CustomItem customItem = new CustomItem();
|
||||
customItem.setPath(PathUtils.getPath(mContext, item.getContentUri()));
|
||||
customItem.setFileType(item.isGif() ? 1 : 0);
|
||||
paths.add(customItem);
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return mItems == null || mItems.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isSelected(Item item) {
|
||||
return mItems.contains(item);
|
||||
}
|
||||
|
||||
public IncapableCause isAcceptable(Item item) {
|
||||
if (maxSelectableReached()) {
|
||||
int maxSelectable = currentMaxSelectable();
|
||||
String cause;
|
||||
|
||||
try {
|
||||
cause = mContext.getResources().getQuantityString(
|
||||
R.string.error_over_count,
|
||||
maxSelectable,
|
||||
maxSelectable
|
||||
);
|
||||
} catch (Resources.NotFoundException e) {
|
||||
cause = mContext.getString(
|
||||
R.string.error_over_count,
|
||||
maxSelectable
|
||||
);
|
||||
} catch (NoClassDefFoundError e) {
|
||||
cause = mContext.getString(
|
||||
R.string.error_over_count,
|
||||
maxSelectable
|
||||
);
|
||||
}
|
||||
|
||||
return new IncapableCause(cause);
|
||||
} else if (typeConflict(item)) {
|
||||
return new IncapableCause(mContext.getString(R.string.error_type_conflict));
|
||||
}
|
||||
|
||||
return PhotoMetadataUtils.isAcceptable(mContext, item);
|
||||
}
|
||||
|
||||
public boolean maxSelectableReached() {
|
||||
return mItems.size() == currentMaxSelectable();
|
||||
}
|
||||
|
||||
// depends
|
||||
private int currentMaxSelectable() {
|
||||
SelectionSpec spec = SelectionSpec.getInstance();
|
||||
if (spec.maxSelectable > 0) {
|
||||
return spec.maxSelectable;
|
||||
} else if (mCollectionType == COLLECTION_IMAGE) {
|
||||
return spec.maxImageSelectable;
|
||||
} else if (mCollectionType == COLLECTION_VIDEO) {
|
||||
return spec.maxVideoSelectable;
|
||||
} else {
|
||||
return spec.maxSelectable;
|
||||
}
|
||||
}
|
||||
|
||||
public int getCollectionType() {
|
||||
return mCollectionType;
|
||||
}
|
||||
|
||||
private void refineCollectionType() {
|
||||
boolean hasImage = false;
|
||||
boolean hasVideo = false;
|
||||
for (Item i : mItems) {
|
||||
if (i.isImage() && !hasImage) hasImage = true;
|
||||
if (i.isVideo() && !hasVideo) hasVideo = true;
|
||||
}
|
||||
if (hasImage && hasVideo) {
|
||||
mCollectionType = COLLECTION_MIXED;
|
||||
} else if (hasImage) {
|
||||
mCollectionType = COLLECTION_IMAGE;
|
||||
} else if (hasVideo) {
|
||||
mCollectionType = COLLECTION_VIDEO;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether there will be conflict media types. A user can only select images and videos at the same time
|
||||
* while {@link SelectionSpec#mediaTypeExclusive} is set to false.
|
||||
*/
|
||||
public boolean typeConflict(Item item) {
|
||||
return SelectionSpec.getInstance().mediaTypeExclusive
|
||||
&& ((item.isImage() && (mCollectionType == COLLECTION_VIDEO || mCollectionType == COLLECTION_MIXED))
|
||||
|| (item.isVideo() && (mCollectionType == COLLECTION_IMAGE || mCollectionType == COLLECTION_MIXED)));
|
||||
}
|
||||
|
||||
public int count() {
|
||||
return mItems.size();
|
||||
}
|
||||
|
||||
public int checkedNumOf(Item item) {
|
||||
int index = new ArrayList<>(mItems).indexOf(item);
|
||||
return index == -1 ? CheckView.UNCHECKED : index + 1;
|
||||
}
|
||||
}
|
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.ui;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.example.matisse.internal.entity.Album;
|
||||
import com.example.matisse.internal.entity.Item;
|
||||
import com.example.matisse.internal.entity.SelectionSpec;
|
||||
import com.example.matisse.internal.model.AlbumMediaCollection;
|
||||
import com.example.matisse.internal.ui.adapter.PreviewPagerAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AlbumPreviewActivity extends BasePreviewActivity implements
|
||||
AlbumMediaCollection.AlbumMediaCallbacks {
|
||||
|
||||
public static final String EXTRA_ALBUM = "extra_album";
|
||||
public static final String EXTRA_ITEM = "extra_item";
|
||||
|
||||
private AlbumMediaCollection mCollection = new AlbumMediaCollection();
|
||||
|
||||
private boolean mIsAlreadySetPosition;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (!SelectionSpec.getInstance().hasInited) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
mCollection.onCreate(this, this);
|
||||
Album album = getIntent().getParcelableExtra(EXTRA_ALBUM);
|
||||
mCollection.load(album);
|
||||
|
||||
Item item = getIntent().getParcelableExtra(EXTRA_ITEM);
|
||||
if (mSpec.countable) {
|
||||
mCheckView.setCheckedNum(mSelectedCollection.checkedNumOf(item));
|
||||
} else {
|
||||
mCheckView.setChecked(mSelectedCollection.isSelected(item));
|
||||
}
|
||||
updateSize(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
mCollection.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAlbumMediaLoad(Cursor cursor) {
|
||||
if (cursor == null || cursor.isClosed()) {
|
||||
return;
|
||||
}
|
||||
List<Item> items = new ArrayList<>();
|
||||
while (cursor.moveToNext()) {
|
||||
items.add(Item.valueOf(cursor));
|
||||
}
|
||||
// cursor.close();
|
||||
|
||||
if (items.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PreviewPagerAdapter adapter = (PreviewPagerAdapter) mPager.getAdapter();
|
||||
adapter.addAll(items);
|
||||
adapter.notifyDataSetChanged();
|
||||
if (!mIsAlreadySetPosition) {
|
||||
//onAlbumMediaLoad is called many times..
|
||||
mIsAlreadySetPosition = true;
|
||||
Item selected = getIntent().getParcelableExtra(EXTRA_ITEM);
|
||||
int selectedIndex = items.indexOf(selected);
|
||||
mPager.setCurrentItem(selectedIndex, false);
|
||||
mPreviousPos = selectedIndex;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAlbumMediaReset() {
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,373 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
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.chwl.app.R;
|
||||
import com.chwl.app.base.TitleBar;
|
||||
import com.chwl.library.utils.ResUtil;
|
||||
import com.example.matisse.internal.entity.IncapableCause;
|
||||
import com.example.matisse.internal.entity.Item;
|
||||
import com.example.matisse.internal.entity.SelectionSpec;
|
||||
import com.example.matisse.internal.model.SelectedItemCollection;
|
||||
import com.example.matisse.internal.ui.adapter.PreviewPagerAdapter;
|
||||
import com.example.matisse.internal.ui.widget.CheckRadioView;
|
||||
import com.example.matisse.internal.ui.widget.CheckView;
|
||||
import com.example.matisse.internal.ui.widget.IncapableDialog;
|
||||
import com.example.matisse.internal.utils.PhotoMetadataUtils;
|
||||
import com.example.matisse.internal.utils.Platform;
|
||||
import com.example.matisse.listener.OnFragmentInteractionListener;
|
||||
import com.example.matisse.widget.ConfirmPickView;
|
||||
|
||||
public abstract class BasePreviewActivity extends AppCompatActivity implements View.OnClickListener,
|
||||
ViewPager.OnPageChangeListener, OnFragmentInteractionListener {
|
||||
|
||||
public static final String EXTRA_DEFAULT_BUNDLE = "extra_default_bundle";
|
||||
public static final String EXTRA_RESULT_BUNDLE = "extra_result_bundle";
|
||||
public static final String EXTRA_RESULT_APPLY = "extra_result_apply";
|
||||
public static final String EXTRA_RESULT_ORIGINAL_ENABLE = "extra_result_original_enable";
|
||||
public static final String CHECK_STATE = "checkState";
|
||||
|
||||
protected final SelectedItemCollection mSelectedCollection = new SelectedItemCollection(this);
|
||||
protected SelectionSpec mSpec;
|
||||
protected ViewPager mPager;
|
||||
|
||||
protected PreviewPagerAdapter mAdapter;
|
||||
|
||||
protected CheckView mCheckView;
|
||||
protected TextView mButtonBack;
|
||||
protected ConfirmPickView mButtonApply;
|
||||
protected TextView mSize;
|
||||
|
||||
protected int mPreviousPos = -1;
|
||||
|
||||
private LinearLayout mOriginalLayout;
|
||||
private CheckRadioView mOriginal;
|
||||
protected boolean mOriginalEnable;
|
||||
|
||||
private FrameLayout mBottomToolbar;
|
||||
private FrameLayout mTopToolbar;
|
||||
private boolean mIsToolbarHide = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
setTheme(SelectionSpec.getInstance().themeId);
|
||||
super.onCreate(savedInstanceState);
|
||||
if (!SelectionSpec.getInstance().hasInited) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
setContentView(R.layout.activity_media_preview);
|
||||
if (Platform.hasKitKat()) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
}
|
||||
|
||||
mSpec = SelectionSpec.getInstance();
|
||||
if (mSpec.needOrientationRestriction()) {
|
||||
setRequestedOrientation(mSpec.orientation);
|
||||
}
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
mSelectedCollection.onCreate(getIntent().getBundleExtra(EXTRA_DEFAULT_BUNDLE));
|
||||
mOriginalEnable = getIntent().getBooleanExtra(EXTRA_RESULT_ORIGINAL_ENABLE, false);
|
||||
} else {
|
||||
mSelectedCollection.onCreate(savedInstanceState);
|
||||
mOriginalEnable = savedInstanceState.getBoolean(CHECK_STATE);
|
||||
}
|
||||
mButtonBack = (TextView) findViewById(R.id.button_back);
|
||||
mButtonApply = findViewById(R.id.button_apply);
|
||||
mSize = (TextView) findViewById(R.id.size);
|
||||
mButtonBack.setOnClickListener(this);
|
||||
mButtonApply.setOnClickListener(this);
|
||||
|
||||
ImageView ivClose = findViewById(R.id.iv_close);
|
||||
ivClose.setOnClickListener(this);
|
||||
ViewGroup.LayoutParams params = ivClose.getLayoutParams();
|
||||
if (params instanceof RelativeLayout.LayoutParams) {
|
||||
((RelativeLayout.LayoutParams) params).topMargin = TitleBar.getStatusBarHeight();
|
||||
ivClose.setLayoutParams(params);
|
||||
}
|
||||
|
||||
mPager = (ViewPager) findViewById(R.id.pager);
|
||||
mPager.addOnPageChangeListener(this);
|
||||
mAdapter = new PreviewPagerAdapter(getSupportFragmentManager(), null);
|
||||
mPager.setAdapter(mAdapter);
|
||||
mCheckView = (CheckView) findViewById(R.id.check_view);
|
||||
mCheckView.setCountable(mSpec.countable);
|
||||
mBottomToolbar = findViewById(R.id.bottom_toolbar);
|
||||
mTopToolbar = findViewById(R.id.top_toolbar);
|
||||
|
||||
mCheckView.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Item item = mAdapter.getMediaItem(mPager.getCurrentItem());
|
||||
if (mSelectedCollection.isSelected(item)) {
|
||||
mSelectedCollection.remove(item);
|
||||
if (mSpec.countable) {
|
||||
mCheckView.setCheckedNum(CheckView.UNCHECKED);
|
||||
} else {
|
||||
mCheckView.setChecked(false);
|
||||
}
|
||||
} else {
|
||||
if (assertAddSelection(item)) {
|
||||
mSelectedCollection.add(item);
|
||||
if (mSpec.countable) {
|
||||
mCheckView.setCheckedNum(mSelectedCollection.checkedNumOf(item));
|
||||
} else {
|
||||
mCheckView.setChecked(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
updateApplyButton();
|
||||
|
||||
if (mSpec.onSelectedListener != null) {
|
||||
mSpec.onSelectedListener.onSelected(
|
||||
mSelectedCollection.asListOfUri(), mSelectedCollection.asListOfString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
mOriginalLayout = findViewById(R.id.originalLayout);
|
||||
mOriginal = findViewById(R.id.original);
|
||||
mOriginalLayout.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
int count = countOverMaxSize();
|
||||
if (count > 0) {
|
||||
IncapableDialog incapableDialog = IncapableDialog.newInstance("",
|
||||
getString(R.string.error_over_original_count, count, mSpec.originalMaxSize));
|
||||
incapableDialog.show(getSupportFragmentManager(),
|
||||
IncapableDialog.class.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
mOriginalEnable = !mOriginalEnable;
|
||||
mOriginal.setChecked(mOriginalEnable);
|
||||
if (!mOriginalEnable) {
|
||||
mOriginal.setColor(Color.WHITE);
|
||||
}
|
||||
|
||||
|
||||
if (mSpec.onCheckedListener != null) {
|
||||
mSpec.onCheckedListener.onCheck(mOriginalEnable);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
updateApplyButton();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
mSelectedCollection.onSaveInstanceState(outState);
|
||||
outState.putBoolean("checkState", mOriginalEnable);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
sendBackResult(false);
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v.getId() == R.id.button_back || v.getId() == R.id.iv_close) {
|
||||
onBackPressed();
|
||||
} else if (v.getId() == R.id.button_apply) {
|
||||
sendBackResult(true);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
if (!mSpec.autoHideToobar) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mIsToolbarHide) {
|
||||
mTopToolbar.animate()
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.translationYBy(mTopToolbar.getMeasuredHeight())
|
||||
.start();
|
||||
mBottomToolbar.animate()
|
||||
.translationYBy(-mBottomToolbar.getMeasuredHeight())
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.start();
|
||||
} else {
|
||||
mTopToolbar.animate()
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.translationYBy(-mTopToolbar.getMeasuredHeight())
|
||||
.start();
|
||||
mBottomToolbar.animate()
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.translationYBy(mBottomToolbar.getMeasuredHeight())
|
||||
.start();
|
||||
}
|
||||
|
||||
mIsToolbarHide = !mIsToolbarHide;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
PreviewPagerAdapter adapter = (PreviewPagerAdapter) mPager.getAdapter();
|
||||
if (mPreviousPos != -1 && mPreviousPos != position) {
|
||||
((PreviewItemFragment) adapter.instantiateItem(mPager, mPreviousPos)).resetView();
|
||||
|
||||
Item item = adapter.getMediaItem(position);
|
||||
if (mSpec.countable) {
|
||||
int checkedNum = mSelectedCollection.checkedNumOf(item);
|
||||
mCheckView.setCheckedNum(checkedNum);
|
||||
if (checkedNum > 0) {
|
||||
mCheckView.setEnabled(true);
|
||||
} else {
|
||||
mCheckView.setEnabled(!mSelectedCollection.maxSelectableReached());
|
||||
}
|
||||
} else {
|
||||
boolean checked = mSelectedCollection.isSelected(item);
|
||||
mCheckView.setChecked(checked);
|
||||
if (checked) {
|
||||
mCheckView.setEnabled(true);
|
||||
} else {
|
||||
mCheckView.setEnabled(!mSelectedCollection.maxSelectableReached());
|
||||
}
|
||||
}
|
||||
updateSize(item);
|
||||
}
|
||||
mPreviousPos = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrollStateChanged(int state) {
|
||||
|
||||
}
|
||||
|
||||
private void updateApplyButton() {
|
||||
int selectedCount = mSelectedCollection.count();
|
||||
if (selectedCount == 0) {
|
||||
mButtonApply.setText(ResUtil.getString(R.string.internal_ui_basepreviewactivity_01), 0);
|
||||
mButtonApply.setEnabled(false);
|
||||
} else if (selectedCount == 1 && mSpec.singleSelectionModeEnabled()) {
|
||||
mButtonApply.setText(ResUtil.getString(R.string.internal_ui_basepreviewactivity_02), 0);
|
||||
mButtonApply.setEnabled(true);
|
||||
} else {
|
||||
mButtonApply.setEnabled(true);
|
||||
mButtonApply.setText(ResUtil.getString(R.string.internal_ui_basepreviewactivity_03), selectedCount);
|
||||
}
|
||||
|
||||
if (mSpec.originalable) {
|
||||
mOriginalLayout.setVisibility(View.VISIBLE);
|
||||
updateOriginalState();
|
||||
} else {
|
||||
mOriginalLayout.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void updateOriginalState() {
|
||||
mOriginal.setChecked(mOriginalEnable);
|
||||
if (!mOriginalEnable) {
|
||||
mOriginal.setColor(Color.WHITE);
|
||||
}
|
||||
|
||||
if (countOverMaxSize() > 0) {
|
||||
|
||||
if (mOriginalEnable) {
|
||||
IncapableDialog incapableDialog = IncapableDialog.newInstance("",
|
||||
getString(R.string.error_over_original_size, mSpec.originalMaxSize));
|
||||
incapableDialog.show(getSupportFragmentManager(),
|
||||
IncapableDialog.class.getName());
|
||||
|
||||
mOriginal.setChecked(false);
|
||||
mOriginal.setColor(Color.WHITE);
|
||||
mOriginalEnable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int countOverMaxSize() {
|
||||
int count = 0;
|
||||
int selectedCount = mSelectedCollection.count();
|
||||
for (int i = 0; i < selectedCount; i++) {
|
||||
Item item = mSelectedCollection.asList().get(i);
|
||||
if (item.isImage()) {
|
||||
float size = PhotoMetadataUtils.getSizeInMB(item.size);
|
||||
if (size > mSpec.originalMaxSize) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
protected void updateSize(Item item) {
|
||||
if (item.isGif()) {
|
||||
mSize.setVisibility(View.VISIBLE);
|
||||
mSize.setText(PhotoMetadataUtils.getSizeInMB(item.size) + "M");
|
||||
} else {
|
||||
mSize.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (item.isVideo()) {
|
||||
mOriginalLayout.setVisibility(View.GONE);
|
||||
} else if (mSpec.originalable) {
|
||||
mOriginalLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
protected void sendBackResult(boolean apply) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(EXTRA_RESULT_BUNDLE, mSelectedCollection.getDataWithBundle());
|
||||
intent.putExtra(EXTRA_RESULT_APPLY, apply);
|
||||
intent.putExtra(EXTRA_RESULT_ORIGINAL_ENABLE, mOriginalEnable);
|
||||
setResult(Activity.RESULT_OK, intent);
|
||||
}
|
||||
|
||||
private boolean assertAddSelection(Item item) {
|
||||
IncapableCause cause = mSelectedCollection.isAcceptable(item);
|
||||
IncapableCause.handleCause(this, cause);
|
||||
return cause == null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.chwl.app.R;
|
||||
import com.example.matisse.internal.entity.Album;
|
||||
import com.example.matisse.internal.entity.Item;
|
||||
import com.example.matisse.internal.entity.SelectionSpec;
|
||||
import com.example.matisse.internal.model.AlbumMediaCollection;
|
||||
import com.example.matisse.internal.model.SelectedItemCollection;
|
||||
import com.example.matisse.internal.ui.adapter.AlbumMediaAdapter;
|
||||
import com.example.matisse.internal.ui.widget.MediaGridInset;
|
||||
import com.example.matisse.internal.utils.UIUtils;
|
||||
|
||||
public class MediaSelectionFragment extends Fragment implements
|
||||
AlbumMediaCollection.AlbumMediaCallbacks, AlbumMediaAdapter.CheckStateListener,
|
||||
AlbumMediaAdapter.OnMediaClickListener {
|
||||
|
||||
public static final String EXTRA_ALBUM = "extra_album";
|
||||
|
||||
private final AlbumMediaCollection mAlbumMediaCollection = new AlbumMediaCollection();
|
||||
private RecyclerView mRecyclerView;
|
||||
private AlbumMediaAdapter mAdapter;
|
||||
private SelectionProvider mSelectionProvider;
|
||||
private AlbumMediaAdapter.CheckStateListener mCheckStateListener;
|
||||
private AlbumMediaAdapter.OnMediaClickListener mOnMediaClickListener;
|
||||
|
||||
public static MediaSelectionFragment newInstance(Album album) {
|
||||
MediaSelectionFragment fragment = new MediaSelectionFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(EXTRA_ALBUM, album);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
if (context instanceof SelectionProvider) {
|
||||
mSelectionProvider = (SelectionProvider) context;
|
||||
} else {
|
||||
throw new IllegalStateException("Context must implement SelectionProvider.");
|
||||
}
|
||||
if (context instanceof AlbumMediaAdapter.CheckStateListener) {
|
||||
mCheckStateListener = (AlbumMediaAdapter.CheckStateListener) context;
|
||||
}
|
||||
if (context instanceof AlbumMediaAdapter.OnMediaClickListener) {
|
||||
mOnMediaClickListener = (AlbumMediaAdapter.OnMediaClickListener) context;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_media_selection, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerview);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
Album album = getArguments().getParcelable(EXTRA_ALBUM);
|
||||
|
||||
mAdapter = new AlbumMediaAdapter(getContext(),
|
||||
mSelectionProvider.provideSelectedItemCollection(), mRecyclerView);
|
||||
mAdapter.registerCheckStateListener(this);
|
||||
mAdapter.registerOnMediaClickListener(this);
|
||||
mRecyclerView.setHasFixedSize(true);
|
||||
|
||||
int spanCount;
|
||||
SelectionSpec selectionSpec = SelectionSpec.getInstance();
|
||||
if (selectionSpec.gridExpectedSize > 0) {
|
||||
spanCount = UIUtils.spanCount(getContext(), selectionSpec.gridExpectedSize);
|
||||
} else {
|
||||
spanCount = selectionSpec.spanCount;
|
||||
}
|
||||
mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), spanCount));
|
||||
|
||||
int spacing = getResources().getDimensionPixelSize(R.dimen.media_grid_spacing);
|
||||
mRecyclerView.addItemDecoration(new MediaGridInset(spanCount, spacing, false));
|
||||
mRecyclerView.setAdapter(mAdapter);
|
||||
mAlbumMediaCollection.onCreate(getActivity(), this);
|
||||
mAlbumMediaCollection.load(album, selectionSpec.capture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
mAlbumMediaCollection.onDestroy();
|
||||
}
|
||||
|
||||
public void refreshMediaGrid() {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void refreshSelection() {
|
||||
mAdapter.refreshSelection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAlbumMediaLoad(Cursor cursor) {
|
||||
mAdapter.swapCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAlbumMediaReset() {
|
||||
mAdapter.swapCursor(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate() {
|
||||
// notify outer Activity that check state changed
|
||||
if (mCheckStateListener != null) {
|
||||
mCheckStateListener.onUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaClick(Album album, Item item, int adapterPosition) {
|
||||
if (mOnMediaClickListener != null) {
|
||||
mOnMediaClickListener.onMediaClick((Album) getArguments().getParcelable(EXTRA_ALBUM),
|
||||
item, adapterPosition);
|
||||
}
|
||||
}
|
||||
|
||||
public interface SelectionProvider {
|
||||
SelectedItemCollection provideSelectedItemCollection();
|
||||
}
|
||||
}
|
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.ui;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Point;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.chwl.app.R;
|
||||
import com.chwl.library.utils.SingleToastUtil;
|
||||
import com.example.matisse.internal.entity.Item;
|
||||
import com.example.matisse.internal.entity.SelectionSpec;
|
||||
import com.example.matisse.internal.utils.PhotoMetadataUtils;
|
||||
import com.example.matisse.listener.OnFragmentInteractionListener;
|
||||
|
||||
import it.sephiroth.android.library.imagezoom.ImageViewTouch;
|
||||
import it.sephiroth.android.library.imagezoom.ImageViewTouchBase;
|
||||
|
||||
public class PreviewItemFragment extends Fragment {
|
||||
|
||||
private static final String ARGS_ITEM = "args_item";
|
||||
private OnFragmentInteractionListener mListener;
|
||||
|
||||
public static PreviewItemFragment newInstance(Item item) {
|
||||
PreviewItemFragment fragment = new PreviewItemFragment();
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(ARGS_ITEM, item);
|
||||
fragment.setArguments(bundle);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_preview_item, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
final Item item = getArguments().getParcelable(ARGS_ITEM);
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
View videoPlayButton = view.findViewById(R.id.video_play_button);
|
||||
if (item.isVideo()) {
|
||||
videoPlayButton.setVisibility(View.VISIBLE);
|
||||
videoPlayButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(item.uri, "video/*");
|
||||
try {
|
||||
startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
SingleToastUtil.showToast(R.string.error_no_video_activity);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
videoPlayButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
ImageViewTouch image = (ImageViewTouch) view.findViewById(R.id.image_view);
|
||||
image.setDisplayType(ImageViewTouchBase.DisplayType.FIT_TO_SCREEN);
|
||||
|
||||
image.setSingleTapListener(new ImageViewTouch.OnImageViewTouchSingleTapListener() {
|
||||
@Override
|
||||
public void onSingleTapConfirmed() {
|
||||
if (mListener != null) {
|
||||
mListener.onClick();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Point size = PhotoMetadataUtils.getBitmapSize(item.getContentUri(), getActivity());
|
||||
if (item.isGif()) {
|
||||
SelectionSpec.getInstance().imageEngine.loadGifImage(getContext(), size.x, size.y, image,
|
||||
item.getContentUri());
|
||||
} else {
|
||||
SelectionSpec.getInstance().imageEngine.loadImage(getContext(), size.x, size.y, image,
|
||||
item.getContentUri());
|
||||
}
|
||||
}
|
||||
|
||||
public void resetView() {
|
||||
if (getView() != null) {
|
||||
((ImageViewTouch) getView().findViewById(R.id.image_view)).resetMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
if (context instanceof OnFragmentInteractionListener) {
|
||||
mListener = (OnFragmentInteractionListener) context;
|
||||
} else {
|
||||
throw new RuntimeException(context.toString()
|
||||
+ " must implement OnFragmentInteractionListener");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
mListener = null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.ui;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.example.matisse.internal.entity.Item;
|
||||
import com.example.matisse.internal.entity.SelectionSpec;
|
||||
import com.example.matisse.internal.model.SelectedItemCollection;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SelectedPreviewActivity extends BasePreviewActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (!SelectionSpec.getInstance().hasInited) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Bundle bundle = getIntent().getBundleExtra(EXTRA_DEFAULT_BUNDLE);
|
||||
List<Item> selected = bundle.getParcelableArrayList(SelectedItemCollection.STATE_SELECTION);
|
||||
mAdapter.addAll(selected);
|
||||
mAdapter.notifyDataSetChanged();
|
||||
if (mSpec.countable) {
|
||||
mCheckView.setCheckedNum(1);
|
||||
} else {
|
||||
mCheckView.setChecked(true);
|
||||
}
|
||||
mPreviousPos = 0;
|
||||
updateSize(selected.get(0));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.ui.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.chwl.app.R;
|
||||
import com.example.matisse.internal.entity.Album;
|
||||
import com.example.matisse.internal.entity.IncapableCause;
|
||||
import com.example.matisse.internal.entity.Item;
|
||||
import com.example.matisse.internal.entity.SelectionSpec;
|
||||
import com.example.matisse.internal.model.SelectedItemCollection;
|
||||
import com.example.matisse.internal.ui.widget.CheckView;
|
||||
import com.example.matisse.internal.ui.widget.MediaGrid;
|
||||
|
||||
public class AlbumMediaAdapter extends
|
||||
RecyclerViewCursorAdapter<RecyclerView.ViewHolder> implements
|
||||
MediaGrid.OnMediaGridClickListener {
|
||||
|
||||
private static final int VIEW_TYPE_CAPTURE = 0x01;
|
||||
private static final int VIEW_TYPE_MEDIA = 0x02;
|
||||
private final SelectedItemCollection mSelectedCollection;
|
||||
private final Drawable mPlaceholder;
|
||||
private SelectionSpec mSelectionSpec;
|
||||
private CheckStateListener mCheckStateListener;
|
||||
private OnMediaClickListener mOnMediaClickListener;
|
||||
private RecyclerView mRecyclerView;
|
||||
private int mImageResize;
|
||||
|
||||
public AlbumMediaAdapter(Context context, SelectedItemCollection selectedCollection, RecyclerView recyclerView) {
|
||||
super(null);
|
||||
mSelectionSpec = SelectionSpec.getInstance();
|
||||
mSelectedCollection = selectedCollection;
|
||||
|
||||
TypedArray ta = context.getTheme().obtainStyledAttributes(new int[]{R.attr.item_placeholder});
|
||||
mPlaceholder = ta.getDrawable(0);
|
||||
ta.recycle();
|
||||
|
||||
mRecyclerView = recyclerView;
|
||||
}
|
||||
|
||||
@Override
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
return holder;
|
||||
} else if (viewType == VIEW_TYPE_MEDIA) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.media_grid_item, parent, false);
|
||||
return new MediaViewHolder(v);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindViewHolder(final RecyclerView.ViewHolder holder, Cursor cursor) {
|
||||
if (holder instanceof CaptureViewHolder) {
|
||||
// CaptureViewHolder captureViewHolder = (CaptureViewHolder) holder;
|
||||
// Drawable[] drawables = captureViewHolder.mHint.getCompoundDrawables();
|
||||
// TypedArray ta = holder.itemView.getContext().getTheme().obtainStyledAttributes(
|
||||
// new int[]{R.attr.capture_textColor});
|
||||
// int color = ta.getColor(0, 0);
|
||||
// ta.recycle();
|
||||
//
|
||||
// for (int i = 0; i < drawables.length; i++) {
|
||||
// Drawable drawable = drawables[i];
|
||||
// if (drawable != null) {
|
||||
// final Drawable.ConstantState state = drawable.getConstantState();
|
||||
// if (state == null) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// Drawable newDrawable = state.newDrawable().mutate();
|
||||
// newDrawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||
// newDrawable.setBounds(drawable.getBounds());
|
||||
// drawables[i] = newDrawable;
|
||||
// }
|
||||
// }
|
||||
// captureViewHolder.mHint.setCompoundDrawables(drawables[0], drawables[1], drawables[2], drawables[3]);
|
||||
} else if (holder instanceof MediaViewHolder) {
|
||||
MediaViewHolder mediaViewHolder = (MediaViewHolder) holder;
|
||||
|
||||
final Item item = Item.valueOf(cursor);
|
||||
((MediaViewHolder) holder).mMediaGrid.setCheckViewVisibility(mSelectionSpec.type == 1 && item.isVideo() ? View.GONE : View.VISIBLE);
|
||||
mediaViewHolder.mMediaGrid.preBindMedia(new MediaGrid.PreBindInfo(
|
||||
getImageResize(mediaViewHolder.mMediaGrid.getContext()),
|
||||
mPlaceholder,
|
||||
mSelectionSpec.countable,
|
||||
holder
|
||||
));
|
||||
mediaViewHolder.mMediaGrid.bindMedia(item);
|
||||
mediaViewHolder.mMediaGrid.setOnMediaGridClickListener(this);
|
||||
setCheckStatus(item, mediaViewHolder.mMediaGrid);
|
||||
}
|
||||
}
|
||||
|
||||
private void setCheckStatus(Item item, MediaGrid mediaGrid) {
|
||||
if (mSelectionSpec.countable) {
|
||||
int checkedNum = mSelectedCollection.checkedNumOf(item);
|
||||
if (checkedNum > 0) {
|
||||
mediaGrid.setCheckEnabled(true);
|
||||
mediaGrid.setCheckedNum(checkedNum);
|
||||
} else {
|
||||
if (mSelectedCollection.maxSelectableReached()) {
|
||||
mediaGrid.setCheckEnabled(false);
|
||||
mediaGrid.setCheckedNum(CheckView.UNCHECKED);
|
||||
} else {
|
||||
mediaGrid.setCheckEnabled(true);
|
||||
mediaGrid.setCheckedNum(checkedNum);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
boolean selected = mSelectedCollection.isSelected(item);
|
||||
if (selected) {
|
||||
mediaGrid.setCheckEnabled(true);
|
||||
mediaGrid.setChecked(true);
|
||||
} else {
|
||||
if (mSelectedCollection.maxSelectableReached()) {
|
||||
mediaGrid.setCheckEnabled(false);
|
||||
mediaGrid.setChecked(false);
|
||||
} else {
|
||||
mediaGrid.setCheckEnabled(true);
|
||||
mediaGrid.setChecked(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onThumbnailClicked(ImageView thumbnail, Item item, RecyclerView.ViewHolder holder) {
|
||||
if (mOnMediaClickListener != null) {
|
||||
mOnMediaClickListener.onMediaClick(null, item, holder.getAdapterPosition());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCheckViewClicked(CheckView checkView, 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();
|
||||
}
|
||||
} else {
|
||||
mSelectedCollection.remove(item);
|
||||
notifyCheckStateChanged();
|
||||
}
|
||||
} else {
|
||||
if (mSelectedCollection.isSelected(item)) {
|
||||
mSelectedCollection.remove(item);
|
||||
notifyCheckStateChanged();
|
||||
} else {
|
||||
if (assertAddSelection(holder.itemView.getContext(), item)) {
|
||||
mSelectedCollection.add(item);
|
||||
notifyCheckStateChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyCheckStateChanged() {
|
||||
notifyDataSetChanged();
|
||||
if (mCheckStateListener != null) {
|
||||
mCheckStateListener.onUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position, Cursor cursor) {
|
||||
return Item.valueOf(cursor).isCapture() ? VIEW_TYPE_CAPTURE : VIEW_TYPE_MEDIA;
|
||||
}
|
||||
|
||||
private boolean assertAddSelection(Context context, Item item) {
|
||||
IncapableCause cause = mSelectedCollection.isAcceptable(item);
|
||||
IncapableCause.handleCause(context, cause);
|
||||
return cause == null;
|
||||
}
|
||||
|
||||
|
||||
public void registerCheckStateListener(CheckStateListener listener) {
|
||||
mCheckStateListener = listener;
|
||||
}
|
||||
|
||||
public void unregisterCheckStateListener() {
|
||||
mCheckStateListener = null;
|
||||
}
|
||||
|
||||
public void registerOnMediaClickListener(OnMediaClickListener listener) {
|
||||
mOnMediaClickListener = listener;
|
||||
}
|
||||
|
||||
public void unregisterOnMediaClickListener() {
|
||||
mOnMediaClickListener = null;
|
||||
}
|
||||
|
||||
public void refreshSelection() {
|
||||
GridLayoutManager layoutManager = (GridLayoutManager) mRecyclerView.getLayoutManager();
|
||||
int first = layoutManager.findFirstVisibleItemPosition();
|
||||
int last = layoutManager.findLastVisibleItemPosition();
|
||||
if (first == -1 || last == -1) {
|
||||
return;
|
||||
}
|
||||
Cursor cursor = getCursor();
|
||||
for (int i = first; i <= last; i++) {
|
||||
RecyclerView.ViewHolder holder = mRecyclerView.findViewHolderForAdapterPosition(first);
|
||||
if (holder instanceof MediaViewHolder) {
|
||||
if (cursor.moveToPosition(i)) {
|
||||
setCheckStatus(Item.valueOf(cursor), ((MediaViewHolder) holder).mMediaGrid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getImageResize(Context context) {
|
||||
if (mImageResize == 0) {
|
||||
RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager();
|
||||
int spanCount = ((GridLayoutManager) lm).getSpanCount();
|
||||
int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
|
||||
int availableWidth = screenWidth - context.getResources().getDimensionPixelSize(
|
||||
R.dimen.media_grid_spacing) * (spanCount - 1);
|
||||
mImageResize = availableWidth / spanCount;
|
||||
mImageResize = (int) (mImageResize * mSelectionSpec.thumbnailScale);
|
||||
}
|
||||
return mImageResize;
|
||||
}
|
||||
|
||||
public interface CheckStateListener {
|
||||
void onUpdate();
|
||||
}
|
||||
|
||||
public interface OnMediaClickListener {
|
||||
void onMediaClick(Album album, Item item, int adapterPosition);
|
||||
}
|
||||
|
||||
public interface OnPhotoCapture {
|
||||
void capture();
|
||||
}
|
||||
|
||||
private static class MediaViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private MediaGrid mMediaGrid;
|
||||
|
||||
MediaViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
mMediaGrid = (MediaGrid) itemView;
|
||||
}
|
||||
}
|
||||
|
||||
private static class CaptureViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
// private TextView mHint;
|
||||
|
||||
CaptureViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
// mHint = (TextView) itemView.findViewById(R.id.hint);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.ui.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.chwl.app.R;
|
||||
import com.example.matisse.internal.entity.Album;
|
||||
import com.example.matisse.internal.entity.SelectionSpec;
|
||||
|
||||
public class AlbumsAdapter extends CursorAdapter {
|
||||
|
||||
private final Drawable mPlaceholder;
|
||||
|
||||
public AlbumsAdapter(Context context, Cursor c, boolean autoRequery) {
|
||||
super(context, c, autoRequery);
|
||||
|
||||
TypedArray ta = context.getTheme().obtainStyledAttributes(
|
||||
new int[]{R.attr.album_thumbnail_placeholder});
|
||||
mPlaceholder = ta.getDrawable(0);
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
public AlbumsAdapter(Context context, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
|
||||
TypedArray ta = context.getTheme().obtainStyledAttributes(
|
||||
new int[]{R.attr.album_thumbnail_placeholder});
|
||||
mPlaceholder = ta.getDrawable(0);
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return LayoutInflater.from(context).inflate(R.layout.album_list_item, parent, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
Album album = Album.valueOf(cursor);
|
||||
((TextView) view.findViewById(R.id.album_name)).setText(album.getDisplayName(context));
|
||||
((TextView) view.findViewById(R.id.album_media_count)).setText(String.valueOf(album.getCount()));
|
||||
|
||||
// do not need to load animated Gif
|
||||
SelectionSpec.getInstance().imageEngine.loadThumbnail(context, context.getResources().getDimensionPixelSize(R
|
||||
.dimen.media_grid_size), mPlaceholder,
|
||||
(ImageView) view.findViewById(R.id.album_cover), album.getCoverUri());
|
||||
}
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.ui.adapter;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
|
||||
import com.example.matisse.internal.entity.Item;
|
||||
import com.example.matisse.internal.ui.PreviewItemFragment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PreviewPagerAdapter extends FragmentPagerAdapter {
|
||||
|
||||
private ArrayList<Item> mItems = new ArrayList<>();
|
||||
private OnPrimaryItemSetListener mListener;
|
||||
|
||||
public PreviewPagerAdapter(FragmentManager manager, OnPrimaryItemSetListener listener) {
|
||||
super(manager);
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
return PreviewItemFragment.newInstance(mItems.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mItems.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrimaryItem(ViewGroup container, int position, Object object) {
|
||||
super.setPrimaryItem(container, position, object);
|
||||
if (mListener != null) {
|
||||
mListener.onPrimaryItemSet(position);
|
||||
}
|
||||
}
|
||||
|
||||
public Item getMediaItem(int position) {
|
||||
return mItems.get(position);
|
||||
}
|
||||
|
||||
public void addAll(List<Item> items) {
|
||||
mItems.addAll(items);
|
||||
}
|
||||
|
||||
interface OnPrimaryItemSetListener {
|
||||
|
||||
void onPrimaryItemSet(int position);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.ui.adapter;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.provider.MediaStore;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public abstract class RecyclerViewCursorAdapter<VH extends RecyclerView.ViewHolder> extends
|
||||
RecyclerView.Adapter<VH> {
|
||||
|
||||
private Cursor mCursor;
|
||||
private int mRowIDColumn;
|
||||
|
||||
RecyclerViewCursorAdapter(Cursor c) {
|
||||
setHasStableIds(true);
|
||||
swapCursor(c);
|
||||
}
|
||||
|
||||
protected abstract void onBindViewHolder(VH holder, Cursor cursor);
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(VH holder, int position) {
|
||||
if (!isDataValid(mCursor)) {
|
||||
throw new IllegalStateException("Cannot bind view holder when cursor is in invalid state.");
|
||||
}
|
||||
if (!mCursor.moveToPosition(position)) {
|
||||
throw new IllegalStateException("Could not move cursor to position " + position
|
||||
+ " when trying to bind view holder");
|
||||
}
|
||||
|
||||
onBindViewHolder(holder, mCursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (!mCursor.moveToPosition(position)) {
|
||||
throw new IllegalStateException("Could not move cursor to position " + position
|
||||
+ " when trying to get item view type.");
|
||||
}
|
||||
return getItemViewType(position, mCursor);
|
||||
}
|
||||
|
||||
protected abstract int getItemViewType(int position, Cursor cursor);
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (isDataValid(mCursor)) {
|
||||
return mCursor.getCount();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
if (!isDataValid(mCursor)) {
|
||||
throw new IllegalStateException("Cannot lookup item id when cursor is in invalid state.");
|
||||
}
|
||||
if (!mCursor.moveToPosition(position)) {
|
||||
throw new IllegalStateException("Could not move cursor to position " + position
|
||||
+ " when trying to get an item id");
|
||||
}
|
||||
|
||||
return mCursor.getLong(mRowIDColumn);
|
||||
}
|
||||
|
||||
public void swapCursor(Cursor newCursor) {
|
||||
if (newCursor == mCursor) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newCursor != null) {
|
||||
mCursor = newCursor;
|
||||
mRowIDColumn = mCursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID);
|
||||
// notify the observers about the new cursor
|
||||
notifyDataSetChanged();
|
||||
} else {
|
||||
notifyItemRangeRemoved(0, getItemCount());
|
||||
mCursor = null;
|
||||
mRowIDColumn = -1;
|
||||
}
|
||||
}
|
||||
|
||||
public Cursor getCursor() {
|
||||
return mCursor;
|
||||
}
|
||||
|
||||
private boolean isDataValid(Cursor cursor) {
|
||||
return cursor != null && !cursor.isClosed();
|
||||
}
|
||||
}
|
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
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.chwl.app.R;
|
||||
import com.example.matisse.internal.entity.Album;
|
||||
import com.example.matisse.internal.utils.Platform;
|
||||
|
||||
public class AlbumsSpinner {
|
||||
|
||||
private static final int MAX_SHOWN_COUNT = 6;
|
||||
private CursorAdapter mAdapter;
|
||||
private TextView mSelected;
|
||||
private ListPopupWindow mListPopupWindow;
|
||||
private AdapterView.OnItemSelectedListener mOnItemSelectedListener;
|
||||
|
||||
public AlbumsSpinner(@NonNull Context context) {
|
||||
mListPopupWindow = new ListPopupWindow(context, null, R.attr.listPopupWindowStyle);
|
||||
mListPopupWindow.setModal(true);
|
||||
float density = context.getResources().getDisplayMetrics().density;
|
||||
mListPopupWindow.setContentWidth((int) (216 * density));
|
||||
mListPopupWindow.setHorizontalOffset((int) (16 * density));
|
||||
mListPopupWindow.setVerticalOffset((int) (-48 * density));
|
||||
|
||||
mListPopupWindow.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
AlbumsSpinner.this.onItemSelected(parent.getContext(), position);
|
||||
if (mOnItemSelectedListener != null) {
|
||||
mOnItemSelectedListener.onItemSelected(parent, view, position, id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener) {
|
||||
mOnItemSelectedListener = listener;
|
||||
}
|
||||
|
||||
public void setSelection(Context context, int position) {
|
||||
mListPopupWindow.setSelection(position);
|
||||
onItemSelected(context, position);
|
||||
}
|
||||
|
||||
private void onItemSelected(Context context, int position) {
|
||||
mListPopupWindow.dismiss();
|
||||
Cursor cursor = mAdapter.getCursor();
|
||||
cursor.moveToPosition(position);
|
||||
Album album = Album.valueOf(cursor);
|
||||
String displayName = album.getDisplayName(context);
|
||||
if (mSelected.getVisibility() == View.VISIBLE) {
|
||||
mSelected.setText(displayName);
|
||||
} else {
|
||||
if (Platform.hasICS()) {
|
||||
mSelected.setAlpha(0.0f);
|
||||
mSelected.setVisibility(View.VISIBLE);
|
||||
mSelected.setText(displayName);
|
||||
mSelected.animate().alpha(1.0f).setDuration(context.getResources().getInteger(
|
||||
android.R.integer.config_longAnimTime)).start();
|
||||
} else {
|
||||
mSelected.setVisibility(View.VISIBLE);
|
||||
mSelected.setText(displayName);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void setAdapter(CursorAdapter adapter) {
|
||||
mListPopupWindow.setAdapter(adapter);
|
||||
mAdapter = adapter;
|
||||
}
|
||||
|
||||
public void setSelectedTextView(TextView textView) {
|
||||
mSelected = textView;
|
||||
// tint dropdown arrow icon
|
||||
Drawable[] drawables = mSelected.getCompoundDrawables();
|
||||
Drawable right = drawables[2];
|
||||
TypedArray ta = mSelected.getContext().getTheme().obtainStyledAttributes(
|
||||
new int[]{R.attr.album_element_color});
|
||||
int color = ta.getColor(0, 0);
|
||||
ta.recycle();
|
||||
right.setColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||
|
||||
mSelected.setVisibility(View.GONE);
|
||||
mSelected.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int itemHeight = v.getResources().getDimensionPixelSize(R.dimen.album_item_height);
|
||||
mListPopupWindow.setHeight(
|
||||
mAdapter.getCount() > MAX_SHOWN_COUNT ? itemHeight * MAX_SHOWN_COUNT
|
||||
: itemHeight * mAdapter.getCount());
|
||||
mListPopupWindow.show();
|
||||
}
|
||||
});
|
||||
mSelected.setOnTouchListener(mListPopupWindow.createDragToOpenListener(mSelected));
|
||||
}
|
||||
|
||||
public void setPopupAnchorView(View view) {
|
||||
mListPopupWindow.setAnchorView(view);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
package com.example.matisse.internal.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
import androidx.core.content.res.ResourcesCompat;
|
||||
|
||||
import com.chwl.app.R;
|
||||
|
||||
public class CheckRadioView extends AppCompatImageView {
|
||||
|
||||
private Drawable mDrawable;
|
||||
|
||||
private int mSelectedColor;
|
||||
private int mUnSelectUdColor;
|
||||
|
||||
public CheckRadioView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public CheckRadioView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
mSelectedColor = ResourcesCompat.getColor(
|
||||
getResources(), R.color.zhihu_item_checkCircle_backgroundColor,
|
||||
getContext().getTheme());
|
||||
mUnSelectUdColor = ResourcesCompat.getColor(
|
||||
getResources(), R.color.zhihu_check_original_radio_disable,
|
||||
getContext().getTheme());
|
||||
setChecked(false);
|
||||
}
|
||||
|
||||
public void setChecked(boolean enable) {
|
||||
if (enable) {
|
||||
setImageResource(R.drawable.ic_preview_radio_on);
|
||||
mDrawable = getDrawable();
|
||||
mDrawable.setColorFilter(mSelectedColor, PorterDuff.Mode.SRC_IN);
|
||||
} else {
|
||||
setImageResource(R.drawable.ic_preview_radio_off);
|
||||
mDrawable = getDrawable();
|
||||
mDrawable.setColorFilter(mUnSelectUdColor, PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setColor(int color) {
|
||||
if (mDrawable == null) {
|
||||
mDrawable = getDrawable();
|
||||
}
|
||||
mDrawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
}
|
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.RadialGradient;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.TextPaint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.core.content.res.ResourcesCompat;
|
||||
|
||||
import com.chwl.app.R;
|
||||
|
||||
public class CheckView extends View {
|
||||
|
||||
public static final int UNCHECKED = Integer.MIN_VALUE;
|
||||
private static final float STROKE_WIDTH = 1.5f; // dp
|
||||
private static final float SHADOW_WIDTH = 6.0f; // dp
|
||||
private static final int SIZE = 25; // dp
|
||||
private static final float STROKE_RADIUS = 8.5f; // dp
|
||||
private static final float BG_RADIUS = 9.5f; // dp
|
||||
private static final int CONTENT_SIZE = 16; // dp
|
||||
private boolean mCountable;
|
||||
private boolean mChecked;
|
||||
private int mCheckedNum;
|
||||
private Paint mStrokePaint;
|
||||
private Paint mBackgroundPaint;
|
||||
private TextPaint mTextPaint;
|
||||
private Paint mShadowPaint;
|
||||
private Drawable mCheckDrawable;
|
||||
private float mDensity;
|
||||
private Rect mCheckRect;
|
||||
private boolean mEnabled = true;
|
||||
|
||||
public CheckView(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public CheckView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public CheckView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
// fixed size 48dp x 48dp
|
||||
int sizeSpec = MeasureSpec.makeMeasureSpec((int) (SIZE * mDensity), MeasureSpec.EXACTLY);
|
||||
super.onMeasure(sizeSpec, sizeSpec);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
mDensity = context.getResources().getDisplayMetrics().density;
|
||||
|
||||
mStrokePaint = new Paint();
|
||||
mStrokePaint.setAntiAlias(true);
|
||||
mStrokePaint.setStyle(Paint.Style.STROKE);
|
||||
mStrokePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
|
||||
mStrokePaint.setStrokeWidth(STROKE_WIDTH * mDensity);
|
||||
TypedArray ta = getContext().getTheme().obtainStyledAttributes(new int[]{R.attr.item_checkCircle_borderColor});
|
||||
int defaultColor = ResourcesCompat.getColor(
|
||||
getResources(), R.color.zhihu_item_checkCircle_borderColor,
|
||||
getContext().getTheme());
|
||||
int color = ta.getColor(0, defaultColor);
|
||||
ta.recycle();
|
||||
mStrokePaint.setColor(Color.WHITE);
|
||||
|
||||
mCheckDrawable = ResourcesCompat.getDrawable(context.getResources(),
|
||||
R.drawable.ic_check_white_18dp, context.getTheme());
|
||||
}
|
||||
|
||||
public void setChecked(boolean checked) {
|
||||
if (mCountable) {
|
||||
throw new IllegalStateException("CheckView is countable, call setCheckedNum() instead.");
|
||||
}
|
||||
mChecked = checked;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setCountable(boolean countable) {
|
||||
mCountable = countable;
|
||||
}
|
||||
|
||||
public void setCheckedNum(int checkedNum) {
|
||||
if (!mCountable) {
|
||||
throw new IllegalStateException("CheckView is not countable, call setChecked() instead.");
|
||||
}
|
||||
if (checkedNum != UNCHECKED && checkedNum <= 0) {
|
||||
throw new IllegalArgumentException("checked num can't be negative.");
|
||||
}
|
||||
mCheckedNum = checkedNum;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
if (mEnabled != enabled) {
|
||||
mEnabled = enabled;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
// draw outer and inner shadow
|
||||
initShadowPaint();
|
||||
canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2,
|
||||
(STROKE_RADIUS + STROKE_WIDTH / 2 + SHADOW_WIDTH) * mDensity, mShadowPaint);
|
||||
|
||||
// draw white stroke
|
||||
canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2,
|
||||
STROKE_RADIUS * mDensity, mStrokePaint);
|
||||
|
||||
// draw content
|
||||
if (mCountable) {
|
||||
if (mCheckedNum != UNCHECKED) {
|
||||
initBackgroundPaint();
|
||||
canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2,
|
||||
BG_RADIUS * mDensity, mBackgroundPaint);
|
||||
initTextPaint();
|
||||
String text = String.valueOf(mCheckedNum);
|
||||
int baseX = (int) (canvas.getWidth() - mTextPaint.measureText(text)) / 2;
|
||||
int baseY = (int) (canvas.getHeight() - mTextPaint.descent() - mTextPaint.ascent()) / 2;
|
||||
canvas.drawText(text, baseX, baseY, mTextPaint);
|
||||
}
|
||||
} else {
|
||||
if (mChecked) {
|
||||
initBackgroundPaint();
|
||||
canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2,
|
||||
BG_RADIUS * mDensity, mBackgroundPaint);
|
||||
|
||||
mCheckDrawable.setBounds(getCheckRect());
|
||||
mCheckDrawable.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
// enable hint
|
||||
setAlpha(mEnabled ? 1.0f : 0.5f);
|
||||
}
|
||||
|
||||
private void initShadowPaint() {
|
||||
if (mShadowPaint == null) {
|
||||
mShadowPaint = new Paint();
|
||||
mShadowPaint.setAntiAlias(true);
|
||||
// all in dp
|
||||
float outerRadius = STROKE_RADIUS + STROKE_WIDTH / 2;
|
||||
float innerRadius = outerRadius - STROKE_WIDTH;
|
||||
float gradientRadius = outerRadius + SHADOW_WIDTH;
|
||||
float stop0 = (innerRadius - SHADOW_WIDTH) / gradientRadius;
|
||||
float stop1 = innerRadius / gradientRadius;
|
||||
float stop2 = outerRadius / gradientRadius;
|
||||
float stop3 = 1.0f;
|
||||
mShadowPaint.setShader(
|
||||
new RadialGradient((float) SIZE * mDensity / 2,
|
||||
(float) SIZE * mDensity / 2,
|
||||
gradientRadius * mDensity,
|
||||
new int[]{Color.parseColor("#00000000"), Color.parseColor("#0D000000"),
|
||||
Color.parseColor("#0D000000"), Color.parseColor("#00000000")},
|
||||
new float[]{stop0, stop1, stop2, stop3},
|
||||
Shader.TileMode.CLAMP));
|
||||
}
|
||||
}
|
||||
|
||||
private void initBackgroundPaint() {
|
||||
if (mBackgroundPaint == null) {
|
||||
mBackgroundPaint = new Paint();
|
||||
mBackgroundPaint.setAntiAlias(true);
|
||||
mBackgroundPaint.setStyle(Paint.Style.FILL);
|
||||
TypedArray ta = getContext().getTheme()
|
||||
.obtainStyledAttributes(new int[]{R.attr.item_checkCircle_backgroundColor});
|
||||
int defaultColor = ResourcesCompat.getColor(
|
||||
getResources(), R.color.zhihu_item_checkCircle_backgroundColor,
|
||||
getContext().getTheme());
|
||||
int color = ta.getColor(0, defaultColor);
|
||||
ta.recycle();
|
||||
mBackgroundPaint.setColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
private void initTextPaint() {
|
||||
if (mTextPaint == null) {
|
||||
mTextPaint = new TextPaint();
|
||||
mTextPaint.setAntiAlias(true);
|
||||
mTextPaint.setColor(Color.WHITE);
|
||||
mTextPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
|
||||
mTextPaint.setTextSize(12.0f * mDensity);
|
||||
}
|
||||
}
|
||||
|
||||
// rect for drawing checked number or mark
|
||||
private Rect getCheckRect() {
|
||||
if (mCheckRect == null) {
|
||||
int rectPadding = (int) (SIZE * mDensity / 2 - CONTENT_SIZE * mDensity / 2);
|
||||
mCheckRect = new Rect(rectPadding, rectPadding,
|
||||
(int) (SIZE * mDensity - rectPadding), (int) (SIZE * mDensity - rectPadding));
|
||||
}
|
||||
|
||||
return mCheckRect;
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.ui.widget;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.chwl.app.R;
|
||||
import com.chwl.app.common.widget.dialog.BaseAlertDialogBuilder;
|
||||
|
||||
public class IncapableDialog extends DialogFragment {
|
||||
|
||||
public static final String EXTRA_TITLE = "extra_title";
|
||||
public static final String EXTRA_MESSAGE = "extra_message";
|
||||
|
||||
public static IncapableDialog newInstance(String title, String message) {
|
||||
IncapableDialog dialog = new IncapableDialog();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(EXTRA_TITLE, title);
|
||||
args.putString(EXTRA_MESSAGE, message);
|
||||
dialog.setArguments(args);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
String title = getArguments().getString(EXTRA_TITLE);
|
||||
String message = getArguments().getString(EXTRA_MESSAGE);
|
||||
|
||||
AlertDialog.Builder builder = new BaseAlertDialogBuilder(getActivity());
|
||||
if (!TextUtils.isEmpty(title)) {
|
||||
builder.setTitle(title);
|
||||
}
|
||||
if (!TextUtils.isEmpty(message)) {
|
||||
builder.setMessage(message);
|
||||
}
|
||||
builder.setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
}
|
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.chwl.app.R;
|
||||
import com.example.matisse.internal.entity.Item;
|
||||
import com.example.matisse.internal.entity.SelectionSpec;
|
||||
|
||||
public class MediaGrid extends SquareFrameLayout implements View.OnClickListener {
|
||||
|
||||
private ImageView mThumbnail;
|
||||
private CheckView mCheckView;
|
||||
private ImageView mGifTag;
|
||||
private TextView mVideoDuration;
|
||||
|
||||
private Item mMedia;
|
||||
private PreBindInfo mPreBindInfo;
|
||||
private OnMediaGridClickListener mListener;
|
||||
|
||||
public MediaGrid(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public MediaGrid(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
LayoutInflater.from(context).inflate(R.layout.media_grid_content, this, true);
|
||||
|
||||
mThumbnail = (ImageView) findViewById(R.id.media_thumbnail);
|
||||
mCheckView = (CheckView) findViewById(R.id.check_view);
|
||||
mGifTag = (ImageView) findViewById(R.id.gif);
|
||||
mVideoDuration = (TextView) findViewById(R.id.video_duration);
|
||||
|
||||
mThumbnail.setOnClickListener(this);
|
||||
mCheckView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mListener != null) {
|
||||
if (v == mThumbnail) {
|
||||
mListener.onThumbnailClicked(mThumbnail, mMedia, mPreBindInfo.mViewHolder);
|
||||
} else if (v == mCheckView) {
|
||||
mListener.onCheckViewClicked(mCheckView, mMedia, mPreBindInfo.mViewHolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void preBindMedia(PreBindInfo info) {
|
||||
mPreBindInfo = info;
|
||||
}
|
||||
|
||||
public void bindMedia(Item item) {
|
||||
mMedia = item;
|
||||
setGifTag();
|
||||
initCheckView();
|
||||
setImage();
|
||||
setVideoDuration();
|
||||
}
|
||||
|
||||
public Item getMedia() {
|
||||
return mMedia;
|
||||
}
|
||||
|
||||
private void setGifTag() {
|
||||
mGifTag.setVisibility(mMedia.isGif() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void initCheckView() {
|
||||
mCheckView.setCountable(mPreBindInfo.mCheckViewCountable);
|
||||
}
|
||||
|
||||
public void setCheckEnabled(boolean enabled) {
|
||||
mCheckView.setEnabled(enabled);
|
||||
}
|
||||
|
||||
public void setCheckedNum(int checkedNum) {
|
||||
mCheckView.setCheckedNum(checkedNum);
|
||||
}
|
||||
|
||||
public void setChecked(boolean checked) {
|
||||
mCheckView.setChecked(checked);
|
||||
}
|
||||
|
||||
public void setCheckViewVisibility(int visibility) {
|
||||
mCheckView.setVisibility(visibility);
|
||||
}
|
||||
|
||||
private void setImage() {
|
||||
if (mMedia.isGif()) {
|
||||
SelectionSpec.getInstance().imageEngine.loadGifThumbnail(getContext(), mPreBindInfo.mResize,
|
||||
mPreBindInfo.mPlaceholder, mThumbnail, mMedia.getContentUri());
|
||||
} else {
|
||||
SelectionSpec.getInstance().imageEngine.loadThumbnail(getContext(), mPreBindInfo.mResize,
|
||||
mPreBindInfo.mPlaceholder, mThumbnail, mMedia.getContentUri());
|
||||
}
|
||||
}
|
||||
|
||||
private void setVideoDuration() {
|
||||
if (mMedia.isVideo()) {
|
||||
mVideoDuration.setVisibility(VISIBLE);
|
||||
mVideoDuration.setText(DateUtils.formatElapsedTime(mMedia.duration / 1000));
|
||||
} else {
|
||||
mVideoDuration.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnMediaGridClickListener(OnMediaGridClickListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public void removeOnMediaGridClickListener() {
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
public interface OnMediaGridClickListener {
|
||||
|
||||
void onThumbnailClicked(ImageView thumbnail, Item item, RecyclerView.ViewHolder holder);
|
||||
|
||||
void onCheckViewClicked(CheckView checkView, Item item, RecyclerView.ViewHolder holder);
|
||||
}
|
||||
|
||||
public static class PreBindInfo {
|
||||
int mResize;
|
||||
Drawable mPlaceholder;
|
||||
boolean mCheckViewCountable;
|
||||
RecyclerView.ViewHolder mViewHolder;
|
||||
|
||||
public PreBindInfo(int resize, Drawable placeholder, boolean checkViewCountable,
|
||||
RecyclerView.ViewHolder viewHolder) {
|
||||
mResize = resize;
|
||||
mPlaceholder = placeholder;
|
||||
mCheckViewCountable = checkViewCountable;
|
||||
mViewHolder = viewHolder;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.ui.widget;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class MediaGridInset extends RecyclerView.ItemDecoration {
|
||||
|
||||
private int mSpanCount;
|
||||
private int mSpacing;
|
||||
private boolean mIncludeEdge;
|
||||
|
||||
public MediaGridInset(int spanCount, int spacing, boolean includeEdge) {
|
||||
this.mSpanCount = spanCount;
|
||||
this.mSpacing = spacing;
|
||||
this.mIncludeEdge = includeEdge;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
|
||||
RecyclerView.State state) {
|
||||
int position = parent.getChildAdapterPosition(view); // item position
|
||||
int column = position % mSpanCount; // item column
|
||||
|
||||
if (mIncludeEdge) {
|
||||
// spacing - column * ((1f / spanCount) * spacing)
|
||||
outRect.left = mSpacing - column * mSpacing / mSpanCount;
|
||||
// (column + 1) * ((1f / spanCount) * spacing)
|
||||
outRect.right = (column + 1) * mSpacing / mSpanCount;
|
||||
|
||||
if (position < mSpanCount) { // top edge
|
||||
outRect.top = mSpacing;
|
||||
}
|
||||
outRect.bottom = mSpacing; // item bottom
|
||||
} else {
|
||||
// column * ((1f / spanCount) * spacing)
|
||||
outRect.left = column * mSpacing / mSpanCount;
|
||||
// spacing - (column + 1) * ((1f / spanCount) * spacing)
|
||||
outRect.right = mSpacing - (column + 1) * mSpacing / mSpanCount;
|
||||
if (position >= mSpanCount) {
|
||||
outRect.top = mSpacing; // item top
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
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 {
|
||||
|
||||
public PreviewViewPager(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
|
||||
if (v instanceof ImageViewTouch) {
|
||||
return ((ImageViewTouch) v).canScroll(dx) || super.canScroll(v, checkV, dx, x, y);
|
||||
}
|
||||
return super.canScroll(v, checkV, dx, x, y);
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.RectF;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
|
||||
public class RoundedRectangleImageView extends AppCompatImageView {
|
||||
|
||||
private float mRadius; // dp
|
||||
private Path mRoundedRectPath;
|
||||
private RectF mRectF;
|
||||
|
||||
public RoundedRectangleImageView(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public RoundedRectangleImageView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public RoundedRectangleImageView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
float density = context.getResources().getDisplayMetrics().density;
|
||||
mRadius = 2.0f * density;
|
||||
mRoundedRectPath = new Path();
|
||||
mRectF = new RectF();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
mRectF.set(0.0f, 0.0f, getMeasuredWidth(), getMeasuredHeight());
|
||||
mRoundedRectPath.addRoundRect(mRectF, mRadius, mRadius, Path.Direction.CW);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
canvas.clipPath(mRoundedRectPath);
|
||||
super.onDraw(canvas);
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
public class SquareFrameLayout extends FrameLayout {
|
||||
|
||||
public SquareFrameLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public SquareFrameLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
|
||||
}
|
||||
}
|
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.utils;
|
||||
|
||||
import android.media.ExifInterface;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* Bug fixture for ExifInterface constructor.
|
||||
*/
|
||||
final class ExifInterfaceCompat {
|
||||
private static final String TAG = ExifInterfaceCompat.class.getSimpleName();
|
||||
private static final int EXIF_DEGREE_FALLBACK_VALUE = -1;
|
||||
|
||||
/**
|
||||
* Do not instantiate this class.
|
||||
*/
|
||||
private ExifInterfaceCompat() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new instance of {@link ExifInterface}.
|
||||
* Original constructor won't check filename value, so if null value has been passed,
|
||||
* the process will be killed because of SIGSEGV.
|
||||
* Google Play crash report system cannot perceive this crash, so this method will throw
|
||||
* {@link NullPointerException} when the filename is null.
|
||||
*
|
||||
* @param filename a JPEG filename.
|
||||
* @return {@link ExifInterface} instance.
|
||||
* @throws IOException something wrong with I/O.
|
||||
*/
|
||||
public static ExifInterface newInstance(String filename) throws IOException {
|
||||
if (filename == null) throw new NullPointerException("filename should not be null");
|
||||
return new ExifInterface(filename);
|
||||
}
|
||||
|
||||
private static Date getExifDateTime(String filepath) {
|
||||
ExifInterface exif;
|
||||
try {
|
||||
// ExifInterface does not check whether file path is null or not,
|
||||
// so passing null file path argument to its constructor causing SIGSEGV.
|
||||
// We should avoid such a situation by checking file path string.
|
||||
exif = newInstance(filepath);
|
||||
} catch (IOException ex) {
|
||||
Log.e(TAG, "cannot read exif", ex);
|
||||
return null;
|
||||
}
|
||||
|
||||
String date = exif.getAttribute(ExifInterface.TAG_DATETIME);
|
||||
if (TextUtils.isEmpty(date)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
SimpleDateFormat formatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
|
||||
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
return formatter.parse(date);
|
||||
} catch (ParseException e) {
|
||||
Log.d(TAG, "failed to parse date taken", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read exif info and get datetime value of the photo.
|
||||
*
|
||||
* @param filepath to get datetime
|
||||
* @return when a photo taken.
|
||||
*/
|
||||
public static long getExifDateTimeInMillis(String filepath) {
|
||||
Date datetime = getExifDateTime(filepath);
|
||||
if (datetime == null) {
|
||||
return -1;
|
||||
}
|
||||
return datetime.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read exif info and get orientation value of the photo.
|
||||
*
|
||||
* @param filepath to get exif.
|
||||
* @return exif orientation value
|
||||
*/
|
||||
public static int getExifOrientation(String filepath) {
|
||||
ExifInterface exif;
|
||||
try {
|
||||
// ExifInterface does not check whether file path is null or not,
|
||||
// so passing null file path argument to its constructor causing SIGSEGV.
|
||||
// We should avoid such a situation by checking file path string.
|
||||
exif = newInstance(filepath);
|
||||
} catch (IOException ex) {
|
||||
Log.e(TAG, "cannot read exif", ex);
|
||||
return EXIF_DEGREE_FALLBACK_VALUE;
|
||||
}
|
||||
|
||||
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, EXIF_DEGREE_FALLBACK_VALUE);
|
||||
if (orientation == EXIF_DEGREE_FALLBACK_VALUE) {
|
||||
return 0;
|
||||
}
|
||||
// We only recognize a subset of orientation tag values.
|
||||
switch (orientation) {
|
||||
case ExifInterface.ORIENTATION_ROTATE_90:
|
||||
return 90;
|
||||
case ExifInterface.ORIENTATION_ROTATE_180:
|
||||
return 180;
|
||||
case ExifInterface.ORIENTATION_ROTATE_270:
|
||||
return 270;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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.example.matisse.internal.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.core.os.EnvironmentCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.example.matisse.internal.entity.CaptureStrategy;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class MediaStoreCompat {
|
||||
|
||||
private final WeakReference<Activity> mContext;
|
||||
private final WeakReference<Fragment> mFragment;
|
||||
private CaptureStrategy mCaptureStrategy;
|
||||
private Uri mCurrentPhotoUri;
|
||||
private String mCurrentPhotoPath;
|
||||
|
||||
public MediaStoreCompat(Activity activity) {
|
||||
mContext = new WeakReference<>(activity);
|
||||
mFragment = null;
|
||||
}
|
||||
|
||||
public MediaStoreCompat(Activity activity, Fragment fragment) {
|
||||
mContext = new WeakReference<>(activity);
|
||||
mFragment = new WeakReference<>(fragment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the device has a camera feature or not.
|
||||
*
|
||||
* @param context a context to check for camera feature.
|
||||
* @return true if the device has a camera feature. false otherwise.
|
||||
*/
|
||||
public static boolean hasCameraFeature(Context context) {
|
||||
PackageManager pm = context.getApplicationContext().getPackageManager();
|
||||
return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA);
|
||||
}
|
||||
|
||||
public void setCaptureStrategy(CaptureStrategy strategy) {
|
||||
mCaptureStrategy = strategy;
|
||||
}
|
||||
|
||||
public void dispatchCaptureIntent(Context context, int requestCode) {
|
||||
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
if (captureIntent.resolveActivity(context.getPackageManager()) != null) {
|
||||
File photoFile = null;
|
||||
try {
|
||||
photoFile = createImageFile();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (photoFile != null) {
|
||||
mCurrentPhotoPath = photoFile.getAbsolutePath();
|
||||
mCurrentPhotoUri = FileProvider.getUriForFile(mContext.get(), mCaptureStrategy.authority, photoFile);
|
||||
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mCurrentPhotoUri);
|
||||
captureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
List<ResolveInfo> resInfoList = context.getPackageManager()
|
||||
.queryIntentActivities(captureIntent, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
for (ResolveInfo resolveInfo : resInfoList) {
|
||||
String packageName = resolveInfo.activityInfo.packageName;
|
||||
context.grantUriPermission(packageName, mCurrentPhotoUri,
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
}
|
||||
}
|
||||
if (mFragment != null) {
|
||||
mFragment.get().startActivityForResult(captureIntent, requestCode);
|
||||
} else {
|
||||
mContext.get().startActivityForResult(captureIntent, requestCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
private File createImageFile() throws IOException {
|
||||
// Create an image file name
|
||||
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
|
||||
String imageFileName = String.format("JPEG_%s.jpg", timeStamp);
|
||||
File storageDir;
|
||||
if (mCaptureStrategy.isPublic) {
|
||||
storageDir = Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_PICTURES);
|
||||
if (!storageDir.exists()) storageDir.mkdirs();
|
||||
} else {
|
||||
storageDir = mContext.get().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
|
||||
}
|
||||
if (mCaptureStrategy.directory != null) {
|
||||
storageDir = new File(storageDir, mCaptureStrategy.directory);
|
||||
if (!storageDir.exists()) storageDir.mkdirs();
|
||||
}
|
||||
|
||||
// Avoid joining path components manually
|
||||
File tempFile = new File(storageDir, imageFileName);
|
||||
|
||||
// Handle the situation that user's external storage is not ready
|
||||
if (!Environment.MEDIA_MOUNTED.equals(EnvironmentCompat.getStorageState(tempFile))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
public Uri getCurrentPhotoUri() {
|
||||
return mCurrentPhotoUri;
|
||||
}
|
||||
|
||||
public String getCurrentPhotoPath() {
|
||||
return mCurrentPhotoPath;
|
||||
}
|
||||
}
|
@@ -0,0 +1,133 @@
|
||||
package com.example.matisse.internal.utils;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.MediaStore;
|
||||
|
||||
import com.chwl.library.common.file.FileHelper;
|
||||
|
||||
/**
|
||||
* http://stackoverflow.com/a/27271131/4739220
|
||||
*/
|
||||
|
||||
public class PathUtils {
|
||||
/**
|
||||
* Get a file path from a Uri. This will get the the path for Storage Access
|
||||
* Framework Documents, as well as the _data field for the MediaStore and
|
||||
* other file-based ContentProviders.
|
||||
*
|
||||
* @param context The context.
|
||||
* @param uri The Uri to query.
|
||||
* @author paulburke
|
||||
*/
|
||||
public static String getPath(final Context context, final Uri uri) {
|
||||
// DocumentProvider
|
||||
if (Platform.hasKitKat() && DocumentsContract.isDocumentUri(context, uri)) {
|
||||
// ExternalStorageProvider
|
||||
if (isExternalStorageDocument(uri)) {
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
final String[] split = docId.split(":");
|
||||
final String type = split[0];
|
||||
|
||||
if ("primary".equalsIgnoreCase(type)) {
|
||||
return FileHelper.getRootFilesDir(null).getAbsolutePath() + "/" + split[1];
|
||||
}
|
||||
|
||||
// TODO handle non-primary volumes
|
||||
} else if (isDownloadsDocument(uri)) { // DownloadsProvider
|
||||
|
||||
final String id = DocumentsContract.getDocumentId(uri);
|
||||
final Uri contentUri = ContentUris.withAppendedId(
|
||||
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
|
||||
|
||||
return getDataColumn(context, contentUri, null, null);
|
||||
} else if (isMediaDocument(uri)) { // MediaProvider
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
final String[] split = docId.split(":");
|
||||
final String type = split[0];
|
||||
|
||||
Uri contentUri = null;
|
||||
if ("image".equals(type)) {
|
||||
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if ("video".equals(type)) {
|
||||
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if ("audio".equals(type)) {
|
||||
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||
}
|
||||
|
||||
final String selection = "_id=?";
|
||||
final String[] selectionArgs = new String[]{
|
||||
split[1]
|
||||
};
|
||||
|
||||
return getDataColumn(context, contentUri, selection, selectionArgs);
|
||||
}
|
||||
} else if ("content".equalsIgnoreCase(uri.getScheme())) { // MediaStore (and general)
|
||||
return getDataColumn(context, uri, null, null);
|
||||
} else if ("file".equalsIgnoreCase(uri.getScheme())) { // File
|
||||
return uri.getPath();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the data column for this Uri. This is useful for
|
||||
* MediaStore Uris, and other file-based ContentProviders.
|
||||
*
|
||||
* @param context The context.
|
||||
* @param uri The Uri to query.
|
||||
* @param selection (Optional) Filter used in the query.
|
||||
* @param selectionArgs (Optional) Selection arguments used in the query.
|
||||
* @return The value of the _data column, which is typically a file path.
|
||||
*/
|
||||
public static String getDataColumn(Context context, Uri uri, String selection,
|
||||
String[] selectionArgs) {
|
||||
|
||||
Cursor cursor = null;
|
||||
final String column = "_data";
|
||||
final String[] projection = {
|
||||
column
|
||||
};
|
||||
|
||||
try {
|
||||
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
final int columnIndex = cursor.getColumnIndexOrThrow(column);
|
||||
return cursor.getString(columnIndex);
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is ExternalStorageProvider.
|
||||
*/
|
||||
public static boolean isExternalStorageDocument(Uri uri) {
|
||||
return "com.android.externalstorage.documents".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is DownloadsProvider.
|
||||
*/
|
||||
public static boolean isDownloadsDocument(Uri uri) {
|
||||
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is MediaProvider.
|
||||
*/
|
||||
public static boolean isMediaDocument(Uri uri) {
|
||||
return "com.android.providers.media.documents".equals(uri.getAuthority());
|
||||
}
|
||||
}
|
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (C) 2014 nohana, Inc.
|
||||
* 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.example.matisse.internal.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Point;
|
||||
import android.media.ExifInterface;
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
|
||||
import com.chwl.app.R;
|
||||
import com.example.matisse.MimeType;
|
||||
import com.example.matisse.filter.Filter;
|
||||
import com.example.matisse.internal.entity.IncapableCause;
|
||||
import com.example.matisse.internal.entity.Item;
|
||||
import com.example.matisse.internal.entity.SelectionSpec;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class PhotoMetadataUtils {
|
||||
private static final String TAG = PhotoMetadataUtils.class.getSimpleName();
|
||||
private static final int MAX_WIDTH = 1600;
|
||||
private static final String SCHEME_CONTENT = "content";
|
||||
|
||||
private PhotoMetadataUtils() {
|
||||
throw new AssertionError("oops! the utility class is about to be instantiated...");
|
||||
}
|
||||
|
||||
public static int getPixelsCount(ContentResolver resolver, Uri uri) {
|
||||
Point size = getBitmapBound(resolver, uri);
|
||||
return size.x * size.y;
|
||||
}
|
||||
|
||||
public static Point getBitmapSize(Uri uri, Activity activity) {
|
||||
ContentResolver resolver = activity.getContentResolver();
|
||||
Point imageSize = getBitmapBound(resolver, uri);
|
||||
int w = imageSize.x;
|
||||
int h = imageSize.y;
|
||||
if (PhotoMetadataUtils.shouldRotate(resolver, uri)) {
|
||||
w = imageSize.y;
|
||||
h = imageSize.x;
|
||||
}
|
||||
if (h == 0) return new Point(MAX_WIDTH, MAX_WIDTH);
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
|
||||
float screenWidth = (float) metrics.widthPixels;
|
||||
float screenHeight = (float) metrics.heightPixels;
|
||||
float widthScale = screenWidth / w;
|
||||
float heightScale = screenHeight / h;
|
||||
if (widthScale > heightScale) {
|
||||
return new Point((int) (w * widthScale), (int) (h * heightScale));
|
||||
}
|
||||
return new Point((int) (w * widthScale), (int) (h * heightScale));
|
||||
}
|
||||
|
||||
public static Point getBitmapBound(ContentResolver resolver, Uri uri) {
|
||||
InputStream is = null;
|
||||
try {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
is = resolver.openInputStream(uri);
|
||||
BitmapFactory.decodeStream(is, null, options);
|
||||
int width = options.outWidth;
|
||||
int height = options.outHeight;
|
||||
return new Point(width, height);
|
||||
} catch (FileNotFoundException e) {
|
||||
return new Point(0, 0);
|
||||
} finally {
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String getPath(ContentResolver resolver, Uri uri) {
|
||||
if (uri == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (SCHEME_CONTENT.equals(uri.getScheme())) {
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = resolver.query(uri, new String[]{MediaStore.Images.ImageColumns.DATA},
|
||||
null, null, null);
|
||||
if (cursor == null || !cursor.moveToFirst()) {
|
||||
return null;
|
||||
}
|
||||
return cursor.getString(cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA));
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
return uri.getPath();
|
||||
}
|
||||
|
||||
public static IncapableCause isAcceptable(Context context, Item item) {
|
||||
if (!isSelectableType(context, item)) {
|
||||
return new IncapableCause(context.getString(R.string.error_file_type));
|
||||
}
|
||||
|
||||
if (SelectionSpec.getInstance().filters != null) {
|
||||
for (Filter filter : SelectionSpec.getInstance().filters) {
|
||||
IncapableCause incapableCause = filter.filter(context, item);
|
||||
if (incapableCause != null) {
|
||||
return incapableCause;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isSelectableType(Context context, Item item) {
|
||||
if (context == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
for (MimeType type : SelectionSpec.getInstance().mimeTypeSet) {
|
||||
if (type.checkType(resolver, item.getContentUri())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean shouldRotate(ContentResolver resolver, Uri uri) {
|
||||
ExifInterface exif;
|
||||
try {
|
||||
exif = ExifInterfaceCompat.newInstance(getPath(resolver, uri));
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "could not read exif info of the image: " + uri);
|
||||
return false;
|
||||
}
|
||||
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
|
||||
return orientation == ExifInterface.ORIENTATION_ROTATE_90
|
||||
|| orientation == ExifInterface.ORIENTATION_ROTATE_270;
|
||||
}
|
||||
|
||||
public static float getSizeInMB(long sizeInBytes) {
|
||||
DecimalFormat df = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US);
|
||||
df.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ENGLISH));
|
||||
df.applyPattern("0.0");
|
||||
String result = df.format((float) sizeInBytes / 1024 / 1024);
|
||||
Log.e(TAG, "getSizeInMB: " + result);
|
||||
result = result.replaceAll(",", "."); // in some case , 0.0 will be 0,0
|
||||
return Float.valueOf(result);
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package com.example.matisse.internal.utils;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
/**
|
||||
* @author JoongWon Baik
|
||||
*/
|
||||
public class Platform {
|
||||
public static boolean hasICS() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
|
||||
}
|
||||
|
||||
public static boolean hasKitKat() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
package com.example.matisse.internal.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.MediaScannerConnection;
|
||||
import android.net.Uri;
|
||||
|
||||
/**
|
||||
* @author 工藤
|
||||
* @email gougou@16fan.com
|
||||
* create at 2018年10月23日12:17:59
|
||||
* description:媒体扫描
|
||||
*/
|
||||
public class SingleMediaScanner implements MediaScannerConnection.MediaScannerConnectionClient {
|
||||
|
||||
private MediaScannerConnection mMsc;
|
||||
private String mPath;
|
||||
private ScanListener mListener;
|
||||
|
||||
public interface ScanListener {
|
||||
|
||||
/**
|
||||
* scan finish
|
||||
*/
|
||||
void onScanFinish();
|
||||
}
|
||||
|
||||
public SingleMediaScanner(Context context, String mPath, ScanListener mListener) {
|
||||
this.mPath = mPath;
|
||||
this.mListener = mListener;
|
||||
this.mMsc = new MediaScannerConnection(context, this);
|
||||
this.mMsc.connect();
|
||||
}
|
||||
|
||||
@Override public void onMediaScannerConnected() {
|
||||
mMsc.scanFile(mPath, null);
|
||||
}
|
||||
|
||||
@Override public void onScanCompleted(String mPath, Uri mUri) {
|
||||
mMsc.disconnect();
|
||||
if (mListener != null) {
|
||||
mListener.onScanFinish();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.example.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;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
package com.example.matisse.listener;
|
||||
|
||||
|
||||
/**
|
||||
* when original is enabled , callback immediately when user check or uncheck original.
|
||||
*/
|
||||
public interface OnCheckedListener {
|
||||
void onCheck(boolean isChecked);
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
package com.example.matisse.listener;
|
||||
|
||||
/**
|
||||
* PreViewItemFragment 和 BasePreViewActivity 通信的接口 ,为了方便拿到 ImageViewTouch 的点击事件
|
||||
*/
|
||||
public interface OnFragmentInteractionListener {
|
||||
/**
|
||||
* ImageViewTouch 被点击了
|
||||
*/
|
||||
void onClick();
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.example.matisse.listener;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface OnSelectedListener {
|
||||
/**
|
||||
* @param uriList the selected item {@link Uri} list.
|
||||
* @param pathList the selected item file path list.
|
||||
*/
|
||||
void onSelected(@NonNull List<Uri> uriList, @NonNull List<String> pathList);
|
||||
}
|
@@ -0,0 +1,550 @@
|
||||
/*
|
||||
* 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.example.matisse.ui;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.TypedArray;
|
||||
import android.database.Cursor;
|
||||
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.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
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.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.netease.nim.uikit.common.util.log.LogUtil;
|
||||
import com.chwl.app.R;
|
||||
import com.chwl.library.utils.ResUtil;
|
||||
import com.example.matisse.internal.entity.Album;
|
||||
import com.example.matisse.internal.entity.CustomItem;
|
||||
import com.example.matisse.internal.entity.IncapableCause;
|
||||
import com.example.matisse.internal.entity.Item;
|
||||
import com.example.matisse.internal.entity.SelectionSpec;
|
||||
import com.example.matisse.internal.model.AlbumCollection;
|
||||
import com.example.matisse.internal.model.SelectedItemCollection;
|
||||
import com.example.matisse.internal.ui.AlbumPreviewActivity;
|
||||
import com.example.matisse.internal.ui.BasePreviewActivity;
|
||||
import com.example.matisse.internal.ui.MediaSelectionFragment;
|
||||
import com.example.matisse.internal.ui.SelectedPreviewActivity;
|
||||
import com.example.matisse.internal.ui.adapter.AlbumMediaAdapter;
|
||||
import com.example.matisse.internal.ui.adapter.AlbumsAdapter;
|
||||
import com.example.matisse.internal.ui.widget.AlbumsSpinner;
|
||||
import com.example.matisse.internal.ui.widget.CheckRadioView;
|
||||
import com.example.matisse.internal.ui.widget.IncapableDialog;
|
||||
import com.example.matisse.internal.utils.MediaStoreCompat;
|
||||
import com.example.matisse.internal.utils.PathUtils;
|
||||
import com.example.matisse.internal.utils.PhotoMetadataUtils;
|
||||
import com.example.matisse.internal.utils.SingleMediaScanner;
|
||||
import com.example.matisse.widget.ConfirmPickView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Main Activity to display albums and media content (images/videos) in each album
|
||||
* and also support media selecting operations.
|
||||
*/
|
||||
public class MatisseActivity extends AppCompatActivity implements
|
||||
AlbumCollection.AlbumCallbacks, AdapterView.OnItemSelectedListener,
|
||||
MediaSelectionFragment.SelectionProvider, View.OnClickListener,
|
||||
AlbumMediaAdapter.CheckStateListener, AlbumMediaAdapter.OnMediaClickListener,
|
||||
AlbumMediaAdapter.OnPhotoCapture {
|
||||
|
||||
public static final String EXTRA_RESULT_SELECTION = "extra_result_selection";
|
||||
public static final String EXTRA_RESULT_SELECTION_PATH = "extra_result_selection_path";
|
||||
public static final String EXTRA_RESULT_ORIGINAL_ENABLE = "extra_result_original_enable";
|
||||
public static final String EXTRA_RESULT_MIME_TYPE = "extra_result_mime_type";
|
||||
/**
|
||||
* ture 表示 上传原图到服务器
|
||||
*/
|
||||
public static final String EXTRA_RESULT_ORIGINAL_IMAGE = "EXTRA_RESULT_ORIGINAL_IMAGE";
|
||||
|
||||
private static final int REQUEST_CODE_PREVIEW = 23;
|
||||
private static final int REQUEST_CODE_CAPTURE = 24;
|
||||
private static final int REQUEST_CODE_EDIT_VIDEO = 25;
|
||||
public static final String CHECK_STATE = "checkState";
|
||||
private final AlbumCollection mAlbumCollection = new AlbumCollection();
|
||||
private MediaStoreCompat mMediaStoreCompat;
|
||||
private SelectedItemCollection mSelectedCollection = new SelectedItemCollection(this);
|
||||
private SelectionSpec mSpec;
|
||||
|
||||
private AlbumsSpinner mAlbumsSpinner;
|
||||
private AlbumsAdapter mAlbumsAdapter;
|
||||
private TextView mButtonPreview;
|
||||
private TextView mButtonApply;
|
||||
private View mContainer;
|
||||
private View mEmptyView;
|
||||
|
||||
private LinearLayout mOriginalLayout;
|
||||
private CheckRadioView mOriginal;
|
||||
private boolean mOriginalEnable;
|
||||
private View mIvTick;
|
||||
|
||||
private CheckBox cbSourceImage;
|
||||
|
||||
private ConfirmPickView tvConfirmPick;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
// programmatically set theme before super.onCreate()
|
||||
mSpec = SelectionSpec.getInstance();
|
||||
setTheme(mSpec.themeId);
|
||||
super.onCreate(savedInstanceState);
|
||||
if (!mSpec.hasInited) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
setContentView(R.layout.activity_matisse);
|
||||
|
||||
if (mSpec.needOrientationRestriction()) {
|
||||
setRequestedOrientation(mSpec.orientation);
|
||||
}
|
||||
|
||||
if (mSpec.capture) {
|
||||
mMediaStoreCompat = new MediaStoreCompat(this);
|
||||
if (mSpec.captureStrategy == null)
|
||||
throw new RuntimeException("Don't forget to set CaptureStrategy.");
|
||||
mMediaStoreCompat.setCaptureStrategy(mSpec.captureStrategy);
|
||||
}
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
View buttonToolbar = findViewById(R.id.bottom_toolbar);
|
||||
View llToolbar = findViewById(R.id.ll_toolbar);
|
||||
mButtonPreview = (TextView) findViewById(R.id.button_preview);
|
||||
mButtonApply = (TextView) findViewById(R.id.button_apply);
|
||||
mButtonPreview.setOnClickListener(this);
|
||||
mButtonApply.setOnClickListener(this);
|
||||
mContainer = findViewById(R.id.container);
|
||||
mEmptyView = findViewById(R.id.empty_view);
|
||||
mOriginalLayout = findViewById(R.id.originalLayout);
|
||||
mOriginal = findViewById(R.id.original);
|
||||
mOriginalLayout.setOnClickListener(this);
|
||||
mIvTick = findViewById(R.id.iv_tick);
|
||||
|
||||
cbSourceImage = findViewById(R.id.cb_source_image);
|
||||
tvConfirmPick = findViewById(R.id.tv_confirm_pick);
|
||||
tvConfirmPick.setOnClickListener(this);
|
||||
tvConfirmPick.setEnabled(false);
|
||||
|
||||
cbSourceImage.setChecked(mSpec.isOriginalImage);
|
||||
|
||||
if (mSpec.type == 1 || mSpec.type == 2) {
|
||||
View ivClose = findViewById(R.id.iv_close);
|
||||
ivClose.setOnClickListener(this);
|
||||
mIvTick.setOnClickListener(this);
|
||||
toolbar.setVisibility(View.INVISIBLE);
|
||||
buttonToolbar.setVisibility(View.GONE);
|
||||
llToolbar.setVisibility(View.VISIBLE);
|
||||
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) mContainer.getLayoutParams();
|
||||
layoutParams.addRule(RelativeLayout.BELOW, R.id.ll_toolbar);
|
||||
mContainer.setLayoutParams(layoutParams);
|
||||
} else {
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayShowTitleEnabled(false);
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
Drawable navigationIcon = toolbar.getNavigationIcon();
|
||||
TypedArray ta = getTheme().obtainStyledAttributes(new int[]{R.attr.album_element_color});
|
||||
int color = ta.getColor(0, 0);
|
||||
ta.recycle();
|
||||
navigationIcon.setColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
|
||||
mSelectedCollection.onCreate(savedInstanceState);
|
||||
if (savedInstanceState != null) {
|
||||
mOriginalEnable = savedInstanceState.getBoolean(CHECK_STATE);
|
||||
}
|
||||
updateBottomToolbar();
|
||||
|
||||
mAlbumsAdapter = new AlbumsAdapter(this, null, false);
|
||||
mAlbumsSpinner = new AlbumsSpinner(this);
|
||||
mAlbumsSpinner.setOnItemSelectedListener(this);
|
||||
mAlbumsSpinner.setSelectedTextView((TextView) findViewById(R.id.selected_album));
|
||||
mAlbumsSpinner.setPopupAnchorView(findViewById(R.id.toolbar));
|
||||
mAlbumsSpinner.setAdapter(mAlbumsAdapter);
|
||||
mAlbumCollection.onCreate(this, this);
|
||||
mAlbumCollection.onRestoreInstanceState(savedInstanceState);
|
||||
mAlbumCollection.loadAlbums();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
mSelectedCollection.onSaveInstanceState(outState);
|
||||
mAlbumCollection.onSaveInstanceState(outState);
|
||||
outState.putBoolean("checkState", mOriginalEnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
mAlbumCollection.onDestroy();
|
||||
mSpec.onCheckedListener = null;
|
||||
mSpec.onSelectedListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
setResult(Activity.RESULT_CANCELED);
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (resultCode != RESULT_OK && resultCode != 101 && resultCode != 102)// 101: 拍照 102:拍摄视频
|
||||
return;
|
||||
|
||||
if (requestCode == REQUEST_CODE_PREVIEW) {
|
||||
Bundle resultBundle = data.getBundleExtra(BasePreviewActivity.EXTRA_RESULT_BUNDLE);
|
||||
ArrayList<Item> selected = resultBundle.getParcelableArrayList(SelectedItemCollection.STATE_SELECTION);
|
||||
mOriginalEnable = data.getBooleanExtra(BasePreviewActivity.EXTRA_RESULT_ORIGINAL_ENABLE, false);
|
||||
int collectionType = resultBundle.getInt(SelectedItemCollection.STATE_COLLECTION_TYPE,
|
||||
SelectedItemCollection.COLLECTION_UNDEFINED);
|
||||
if (data.getBooleanExtra(BasePreviewActivity.EXTRA_RESULT_APPLY, false)) {
|
||||
Intent result = new Intent();
|
||||
ArrayList<Uri> selectedUris = new ArrayList<>();
|
||||
ArrayList<CustomItem> selectedPaths = new ArrayList<>();
|
||||
if (selected != null) {
|
||||
for (Item item : selected) {
|
||||
selectedUris.add(item.getContentUri());
|
||||
CustomItem customItem = new CustomItem();
|
||||
customItem.setFileType(item.isGif() ? 1 : 0);
|
||||
customItem.setPath(PathUtils.getPath(this, item.getContentUri()));
|
||||
selectedPaths.add(customItem);
|
||||
}
|
||||
}
|
||||
result.putParcelableArrayListExtra(EXTRA_RESULT_SELECTION, selectedUris);
|
||||
result.putParcelableArrayListExtra(EXTRA_RESULT_SELECTION_PATH, selectedPaths);
|
||||
result.putExtra(EXTRA_RESULT_ORIGINAL_ENABLE, mOriginalEnable);
|
||||
result.putExtra(EXTRA_RESULT_ORIGINAL_IMAGE, cbSourceImage.isChecked());
|
||||
setResult(RESULT_OK, result);
|
||||
finish();
|
||||
} else {
|
||||
mSelectedCollection.overwrite(selected, collectionType);
|
||||
Fragment mediaSelectionFragment = getSupportFragmentManager().findFragmentByTag(
|
||||
MediaSelectionFragment.class.getSimpleName());
|
||||
if (mediaSelectionFragment instanceof MediaSelectionFragment) {
|
||||
((MediaSelectionFragment) mediaSelectionFragment).refreshMediaGrid();
|
||||
}
|
||||
updateBottomToolbar();
|
||||
}
|
||||
} else if (requestCode == REQUEST_CODE_CAPTURE) {
|
||||
// Just pass the data back to previous calling Activity.
|
||||
Uri contentUri = mMediaStoreCompat.getCurrentPhotoUri();
|
||||
String path = mMediaStoreCompat.getCurrentPhotoPath();
|
||||
ArrayList<Uri> selected = new ArrayList<>();
|
||||
selected.add(contentUri);
|
||||
ArrayList<CustomItem> selectedPath = new ArrayList<>();
|
||||
CustomItem customItem = new CustomItem();
|
||||
customItem.setFileType(0);
|
||||
customItem.setPath(path);
|
||||
selectedPath.add(customItem);
|
||||
Intent result = new Intent();
|
||||
result.putParcelableArrayListExtra(EXTRA_RESULT_SELECTION, selected);
|
||||
result.putParcelableArrayListExtra(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);
|
||||
|
||||
new SingleMediaScanner(this.getApplicationContext(), path, () -> LogUtil.print("scan finish!"));
|
||||
finish();
|
||||
|
||||
} else if (requestCode == REQUEST_CODE_EDIT_VIDEO) {
|
||||
Bundle resultBundle = data.getBundleExtra(BasePreviewActivity.EXTRA_RESULT_BUNDLE);
|
||||
ArrayList<Item> selected = resultBundle.getParcelableArrayList(SelectedItemCollection.STATE_SELECTION);
|
||||
ArrayList<CustomItem> selectedPath = new ArrayList<>(1);
|
||||
Item item = selected.get(0);
|
||||
CustomItem customItem = new CustomItem();
|
||||
customItem.setFileType(2);
|
||||
customItem.setPath(item.uri.toString());
|
||||
selectedPath.add(customItem);
|
||||
Intent result = new Intent();
|
||||
result.putParcelableArrayListExtra(EXTRA_RESULT_SELECTION_PATH, selectedPath);
|
||||
result.putExtra(EXTRA_RESULT_MIME_TYPE, "video");
|
||||
setResult(RESULT_OK, result);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBottomToolbar() {
|
||||
int selectedCount = mSelectedCollection.count();
|
||||
if (selectedCount == 0) {
|
||||
mButtonPreview.setEnabled(false);
|
||||
mButtonApply.setEnabled(false);
|
||||
mButtonApply.setText(getString(R.string.button_sure_default));
|
||||
mIvTick.setEnabled(false);
|
||||
|
||||
tvConfirmPick.setEnabled(false);
|
||||
tvConfirmPick.setText(ResUtil.getString(R.string.matisse_ui_matisseactivity_01), 0);
|
||||
} else if (selectedCount == 1 && mSpec.singleSelectionModeEnabled()) {
|
||||
mButtonPreview.setEnabled(true);
|
||||
mButtonApply.setText(R.string.button_sure_default);
|
||||
mButtonApply.setEnabled(true);
|
||||
mIvTick.setEnabled(true);
|
||||
|
||||
tvConfirmPick.setEnabled(true);
|
||||
tvConfirmPick.setText(ResUtil.getString(R.string.matisse_ui_matisseactivity_02), 0);
|
||||
} else {
|
||||
mButtonPreview.setEnabled(true);
|
||||
mButtonApply.setEnabled(true);
|
||||
mButtonApply.setText(getString(R.string.button_sure, selectedCount));
|
||||
mIvTick.setEnabled(true);
|
||||
|
||||
tvConfirmPick.setEnabled(true);
|
||||
tvConfirmPick.setText(ResUtil.getString(R.string.matisse_ui_matisseactivity_03), selectedCount);
|
||||
}
|
||||
|
||||
if (mSpec.originalable) {
|
||||
mOriginalLayout.setVisibility(View.VISIBLE);
|
||||
updateOriginalState();
|
||||
} else {
|
||||
mOriginalLayout.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void updateOriginalState() {
|
||||
mOriginal.setChecked(mOriginalEnable);
|
||||
if (countOverMaxSize() > 0) {
|
||||
|
||||
if (mOriginalEnable) {
|
||||
IncapableDialog incapableDialog = IncapableDialog.newInstance("",
|
||||
getString(R.string.error_over_original_size, mSpec.originalMaxSize));
|
||||
incapableDialog.show(getSupportFragmentManager(),
|
||||
IncapableDialog.class.getName());
|
||||
|
||||
mOriginal.setChecked(false);
|
||||
mOriginalEnable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int countOverMaxSize() {
|
||||
int count = 0;
|
||||
int selectedCount = mSelectedCollection.count();
|
||||
for (int i = 0; i < selectedCount; i++) {
|
||||
Item item = mSelectedCollection.asList().get(i);
|
||||
|
||||
if (item.isImage()) {
|
||||
float size = PhotoMetadataUtils.getSizeInMB(item.size);
|
||||
if (size > mSpec.originalMaxSize) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v.getId() == R.id.button_preview) {
|
||||
Intent intent = new Intent(this, SelectedPreviewActivity.class);
|
||||
intent.putExtra(BasePreviewActivity.EXTRA_DEFAULT_BUNDLE, mSelectedCollection.getDataWithBundle());
|
||||
intent.putExtra(BasePreviewActivity.EXTRA_RESULT_ORIGINAL_ENABLE, mOriginalEnable);
|
||||
startActivityForResult(intent, REQUEST_CODE_PREVIEW);
|
||||
} else if (v.getId() == R.id.button_apply || v.getId() == R.id.iv_tick || v.getId() == R.id.tv_confirm_pick) {
|
||||
Intent result = new Intent();
|
||||
ArrayList<Uri> selectedUris = (ArrayList<Uri>) mSelectedCollection.asListOfUri();
|
||||
result.putParcelableArrayListExtra(EXTRA_RESULT_SELECTION, selectedUris);
|
||||
ArrayList<CustomItem> selectedPaths = (ArrayList<CustomItem>) mSelectedCollection.asListOfCustomItem();
|
||||
result.putParcelableArrayListExtra(EXTRA_RESULT_SELECTION_PATH, selectedPaths);
|
||||
result.putExtra(EXTRA_RESULT_ORIGINAL_ENABLE, mOriginalEnable);
|
||||
result.putExtra(EXTRA_RESULT_ORIGINAL_IMAGE, cbSourceImage.isChecked());
|
||||
setResult(RESULT_OK, result);
|
||||
finish();
|
||||
} else if (v.getId() == R.id.originalLayout) {
|
||||
int count = countOverMaxSize();
|
||||
if (count > 0) {
|
||||
IncapableDialog incapableDialog = IncapableDialog.newInstance("",
|
||||
getString(R.string.error_over_original_count, count, mSpec.originalMaxSize));
|
||||
incapableDialog.show(getSupportFragmentManager(),
|
||||
IncapableDialog.class.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
mOriginalEnable = !mOriginalEnable;
|
||||
mOriginal.setChecked(mOriginalEnable);
|
||||
|
||||
if (mSpec.onCheckedListener != null) {
|
||||
mSpec.onCheckedListener.onCheck(mOriginalEnable);
|
||||
}
|
||||
} else if (v.getId() == R.id.iv_close) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
mAlbumCollection.setStateCurrentSelection(position);
|
||||
mAlbumsAdapter.getCursor().moveToPosition(position);
|
||||
Album album = Album.valueOf(mAlbumsAdapter.getCursor());
|
||||
if (album.isAll() && SelectionSpec.getInstance().capture) {
|
||||
album.addCaptureCount();
|
||||
}
|
||||
onAlbumSelected(album);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAlbumLoad(final Cursor cursor) {
|
||||
mAlbumsAdapter.swapCursor(cursor);
|
||||
// select default album.
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.post(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
cursor.moveToPosition(mAlbumCollection.getCurrentSelection());
|
||||
mAlbumsSpinner.setSelection(MatisseActivity.this,
|
||||
mAlbumCollection.getCurrentSelection());
|
||||
Album album = Album.valueOf(cursor);
|
||||
if (album.isAll() && SelectionSpec.getInstance().capture) {
|
||||
album.addCaptureCount();
|
||||
}
|
||||
onAlbumSelected(album);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAlbumReset() {
|
||||
mAlbumsAdapter.swapCursor(null);
|
||||
}
|
||||
|
||||
private void onAlbumSelected(Album album) {
|
||||
if (album.isAll() && album.isEmpty()) {
|
||||
mContainer.setVisibility(View.GONE);
|
||||
mEmptyView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mContainer.setVisibility(View.VISIBLE);
|
||||
mEmptyView.setVisibility(View.GONE);
|
||||
Fragment fragment = MediaSelectionFragment.newInstance(album);
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.container, fragment, MediaSelectionFragment.class.getSimpleName())
|
||||
.commitAllowingStateLoss();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate() {
|
||||
// notify bottom toolbar that check state changed.
|
||||
updateBottomToolbar();
|
||||
|
||||
if (mSpec.onSelectedListener != null) {
|
||||
mSpec.onSelectedListener.onSelected(
|
||||
mSelectedCollection.asListOfUri(), mSelectedCollection.asListOfString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaClick(Album album, Item item, int adapterPosition) {
|
||||
if (mSelectedCollection.typeConflict(item)) {
|
||||
IncapableCause.handleCause(this, new IncapableCause(getString(R.string.error_type_conflict)));
|
||||
return;
|
||||
}
|
||||
if ((mSpec.type == 1 || mSpec.type == 2) && item.isVideo()) {
|
||||
Intent intent = new Intent();
|
||||
intent.setClassName(this, "com.aliyun.demo.crop.AliyunVideoCropActivity");
|
||||
intent.putExtra(BasePreviewActivity.EXTRA_DEFAULT_BUNDLE, mSelectedCollection.getDataWithBundle());
|
||||
intent.putExtra("video_path", PathUtils.getPath(this, item.getContentUri()));
|
||||
intent.putExtra("video_duration", item.duration);
|
||||
intent.putExtra("video_ratio", 2);
|
||||
intent.putExtra("video_RESOLUTION", 3);
|
||||
intent.putExtra("tail_animation", true);
|
||||
intent.putExtra("entrance", "community");
|
||||
intent.putExtra("type", 1);
|
||||
intent.putExtra("from", 11); // 11 动态发布
|
||||
startActivityForResult(intent, REQUEST_CODE_EDIT_VIDEO);
|
||||
} else {
|
||||
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 SelectedItemCollection provideSelectedItemCollection() {
|
||||
return mSelectedCollection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void capture() {
|
||||
// if (mMediaStoreCompat != null) {
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
|
||||
&& ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
|
||||
mMediaStoreCompat.dispatchCaptureIntent(this, REQUEST_CODE_CAPTURE);
|
||||
// startActivityForResult(new Intent(this, CameraActivity.class)
|
||||
// .putExtra(ConstantValue.KEY_TYPE, mSpec.type), REQUEST_CODE_CAPTURE);
|
||||
} else {
|
||||
ActivityCompat.requestPermissions(this, new String[]{
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
Manifest.permission.RECORD_AUDIO}, 10);
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED /*&& mMediaStoreCompat != null*/
|
||||
&& grantResults[1] == PackageManager.PERMISSION_GRANTED
|
||||
&& grantResults[2] == PackageManager.PERMISSION_GRANTED) {
|
||||
mMediaStoreCompat.dispatchCaptureIntent(this, REQUEST_CODE_CAPTURE);
|
||||
// startActivityForResult(new Intent(this, CameraActivity.class)
|
||||
// .putExtra(ConstantValue.KEY_TYPE, mSpec.type), REQUEST_CODE_CAPTURE);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
package com.example.matisse.widget;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.chwl.app.R;
|
||||
import com.chwl.app.ui.widget.magicindicator.buildins.UIUtil;
|
||||
|
||||
/**
|
||||
* create by lvzebiao @2019/12/2
|
||||
*/
|
||||
public class ConfirmPickView extends LinearLayout {
|
||||
|
||||
private TextView tvConfirmText;
|
||||
|
||||
private TextView tvCharText;
|
||||
|
||||
public ConfirmPickView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public ConfirmPickView(Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public ConfirmPickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
inflate(context, R.layout.layout_album_pick, this);
|
||||
tvConfirmText = findViewById(R.id.tv_confirm_text);
|
||||
tvCharText = findViewById(R.id.tv_char_text);
|
||||
setBackgroundResource(R.drawable.selector_dy_send_btn);
|
||||
}
|
||||
|
||||
public void setText(String confirmText, int pickCount) {
|
||||
tvConfirmText.setText(confirmText);
|
||||
int width = UIUtil.dip2px(getContext(), 55);
|
||||
if (pickCount > 0) {
|
||||
width = UIUtil.dip2px(getContext(), 65);
|
||||
tvCharText.setText("(" + pickCount + ")");
|
||||
setEnabled(true);
|
||||
} else {
|
||||
tvCharText.setText("");
|
||||
setEnabled(false);
|
||||
}
|
||||
tvCharText.invalidate();
|
||||
if (getLayoutParams() instanceof MarginLayoutParams) {
|
||||
MarginLayoutParams params = (MarginLayoutParams) getLayoutParams();
|
||||
if (params.width != width) {
|
||||
params.width = width;
|
||||
setLayoutParams(params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/dracula_bottom_toolbar_apply_text_disable"
|
||||
android:state_enabled="false"/>
|
||||
<item android:color="@color/dracula_bottom_toolbar_apply_text"/>
|
||||
</selector>
|
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/dracula_bottom_toolbar_preview_text_disable"
|
||||
android:state_enabled="false"/>
|
||||
<item android:color="@color/dracula_bottom_toolbar_preview_text"/>
|
||||
</selector>
|
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/dracula_preview_bottom_toolbar_apply_text_disable"
|
||||
android:state_enabled="false"/>
|
||||
<item android:color="@color/dracula_preview_bottom_toolbar_apply_text"/>
|
||||
</selector>
|
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/zhihu_bottom_toolbar_apply_text_disable"
|
||||
android:state_enabled="false"/>
|
||||
<item android:color="@color/zhihu_bottom_toolbar_apply_text"/>
|
||||
</selector>
|
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/zhihu_bottom_toolbar_preview_text_disable"
|
||||
android:state_enabled="false"/>
|
||||
<item android:color="@color/zhihu_bottom_toolbar_preview_text"/>
|
||||
</selector>
|
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/zhihu_preview_bottom_toolbar_apply_text_disable"
|
||||
android:state_enabled="false"/>
|
||||
<item android:color="@color/zhihu_preview_bottom_toolbar_apply_text"/>
|
||||
</selector>
|
After Width: | Height: | Size: 127 B |
BIN
app/src/module_album/res/drawable-hdpi/ic_check_white_18dp.webp
Normal file
After Width: | Height: | Size: 157 B |
BIN
app/src/module_album/res/drawable-hdpi/ic_empty_dracula.webp
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
app/src/module_album/res/drawable-hdpi/ic_empty_zhihu.webp
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
app/src/module_album/res/drawable-hdpi/ic_gif.webp
Normal file
After Width: | Height: | Size: 635 B |
After Width: | Height: | Size: 873 B |
BIN
app/src/module_album/res/drawable-hdpi/ic_preview_radio_off.webp
Normal file
After Width: | Height: | Size: 327 B |
BIN
app/src/module_album/res/drawable-hdpi/ic_preview_radio_on.webp
Normal file
After Width: | Height: | Size: 375 B |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#30ffffff" />
|
||||
<corners android:radius="20dp" />
|
||||
</shape>
|
After Width: | Height: | Size: 129 B |
BIN
app/src/module_album/res/drawable-xhdpi/ic_camera_matisse.webp
Normal file
After Width: | Height: | Size: 641 B |
BIN
app/src/module_album/res/drawable-xhdpi/ic_check_white_18dp.webp
Normal file
After Width: | Height: | Size: 183 B |
BIN
app/src/module_album/res/drawable-xhdpi/ic_close_black.webp
Normal file
After Width: | Height: | Size: 237 B |
BIN
app/src/module_album/res/drawable-xhdpi/ic_empty_dracula.webp
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/module_album/res/drawable-xhdpi/ic_empty_zhihu.webp
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
app/src/module_album/res/drawable-xhdpi/ic_gif.webp
Normal file
After Width: | Height: | Size: 679 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 581 B |
BIN
app/src/module_album/res/drawable-xhdpi/ic_preview_radio_on.webp
Normal file
After Width: | Height: | Size: 681 B |
BIN
app/src/module_album/res/drawable-xhdpi/ic_tick_black.webp
Normal file
After Width: | Height: | Size: 521 B |
BIN
app/src/module_album/res/drawable-xhdpi/ic_tick_disabled.webp
Normal file
After Width: | Height: | Size: 521 B |
BIN
app/src/module_album/res/drawable-xhdpi/icon_album_back.webp
Normal file
After Width: | Height: | Size: 171 B |
After Width: | Height: | Size: 303 B |
After Width: | Height: | Size: 337 B |
After Width: | Height: | Size: 145 B |
After Width: | Height: | Size: 195 B |
BIN
app/src/module_album/res/drawable-xxhdpi/ic_empty_dracula.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/module_album/res/drawable-xxhdpi/ic_empty_zhihu.webp
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
app/src/module_album/res/drawable-xxhdpi/ic_gif.webp
Normal file
After Width: | Height: | Size: 917 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 165 B |
After Width: | Height: | Size: 235 B |
BIN
app/src/module_album/res/drawable-xxxhdpi/ic_empty_dracula.webp
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
app/src/module_album/res/drawable-xxxhdpi/ic_empty_zhihu.webp
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
app/src/module_album/res/drawable-xxxhdpi/ic_gif.webp
Normal file
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 705 B |
After Width: | Height: | Size: 2.2 KiB |
6
app/src/module_album/res/drawable/bg_80000000_8dp.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#80000000" />
|
||||
<corners android:radius="8dp" />
|
||||
</shape>
|
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/icon_circle_check_true" android:state_checked="true" />
|
||||
<item android:drawable="@drawable/icon_circle_check_false" android:state_checked="false" />
|
||||
</selector>
|
5
app/src/module_album/res/drawable/tick_selector.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/ic_tick_disabled" android:state_enabled="false" />
|
||||
<item android:drawable="@drawable/ic_tick_black" />
|
||||
</selector>
|
206
app/src/module_album/res/layout/activity_matisse.xml
Normal file
@@ -0,0 +1,206 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
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.
|
||||
-->
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?colorPrimary"
|
||||
android:elevation="4dp"
|
||||
android:theme="?toolbar"
|
||||
tools:targetApi="lollipop">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/selected_album"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="?actionBarSize"
|
||||
android:drawableEnd="@drawable/ic_arrow_drop_down_white_24dp"
|
||||
android:foreground="?selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:textColor="?attr/album.element.color"
|
||||
android:textSize="18sp" />
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp"
|
||||
android:gravity="center_vertical"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_close"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:src="@drawable/ic_close_black" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text="@string/camera_roll"
|
||||
android:textColor="#222222"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/iv_tick"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:visibility="gone"
|
||||
android:background="@drawable/tick_selector"
|
||||
android:enabled="false" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/bottom_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:background="?attr/bottomToolbar.bg"
|
||||
android:elevation="4dp"
|
||||
tools:visibility="invisible"
|
||||
tools:targetApi="lollipop">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/button_preview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:foreground="?selectableItemBackground"
|
||||
android:padding="16dp"
|
||||
android:text="@string/button_preview"
|
||||
android:textColor="?attr/bottomToolbar.preview.textColor"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/originalLayout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:foreground="?selectableItemBackground"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:visibility="visible"
|
||||
tools:showIn="@layout/activity_matisse">
|
||||
|
||||
<com.example.matisse.internal.ui.widget.CheckRadioView
|
||||
android:id="@+id/original"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:src="@drawable/ic_preview_radio_off" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:enabled="true"
|
||||
android:paddingStart="4dp"
|
||||
android:text="@string/button_original"
|
||||
android:textColor="?attr/bottomToolbar.preview.textColor"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/button_apply"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:foreground="?selectableItemBackground"
|
||||
android:padding="16dp"
|
||||
android:textColor="?attr/bottomToolbar.apply.textColor"
|
||||
android:textSize="16sp" />
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/cl_custom_bottom_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:background="@color/white"
|
||||
android:layout_height="55dp" >
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cb_source_image"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:button="@drawable/selector_circle_check"
|
||||
android:text="@string/layout_activity_matisse_01"
|
||||
android:gravity="center_vertical"
|
||||
android:checked="false"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:textSize="@dimen/sp_15"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:layout_marginStart="17.5dp"
|
||||
android:textColor="@color/color_333333"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<com.example.matisse.widget.ConfirmPickView
|
||||
android:id="@+id/tv_confirm_pick"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_width="55dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:enabled="false"
|
||||
android:layout_height="@dimen/dp_27" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_above="@id/cl_custom_bottom_layout"
|
||||
android:layout_below="@id/toolbar"
|
||||
android:visibility="gone" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/empty_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_above="@id/bottom_toolbar"
|
||||
android:layout_below="@id/toolbar"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/empty_view_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:drawableTop="?attr/album.emptyView"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/empty_text"
|
||||
android:textColor="?attr/album.emptyView.textColor"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</FrameLayout>
|
||||
</RelativeLayout>
|
135
app/src/module_album/res/layout/activity_media_preview.xml
Normal file
@@ -0,0 +1,135 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.example.matisse.internal.ui.widget.PreviewViewPager
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/black"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_close"
|
||||
android:src="@drawable/icon_album_back"
|
||||
android:scaleType="centerInside"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/bottom_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="55dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:background="@color/white"
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/button_back"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:foreground="?selectableItemBackground"
|
||||
android:padding="16dp"
|
||||
android:layout_gravity="start"
|
||||
android:text="@string/button_back"
|
||||
android:visibility="gone"
|
||||
android:textColor="?attr/preview.bottomToolbar.back.textColor"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:padding="16dp"
|
||||
android:id="@+id/originalLayout"
|
||||
android:visibility="gone"
|
||||
android:orientation="horizontal"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="wrap_content"
|
||||
android:foreground="?selectableItemBackground"
|
||||
android:layout_height="wrap_content"
|
||||
tools:showIn="@layout/activity_matisse">
|
||||
|
||||
|
||||
<com.example.matisse.internal.ui.widget.CheckRadioView
|
||||
android:id="@+id/original"
|
||||
android:src="@drawable/ic_preview_radio_off"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:tint="#ffffff"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:enabled="true"
|
||||
android:paddingStart="4dp"
|
||||
android:text="@string/button_original"
|
||||
android:textColor="?attr/preview.bottomToolbar.back.textColor"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/size"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:textColor="@color/preview_bottom_size"
|
||||
android:textSize="16sp"
|
||||
android:visibility="gone"/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
||||
<com.example.matisse.widget.ConfirmPickView
|
||||
android:id="@+id/button_apply"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:layout_width="55dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:gravity="center"
|
||||
android:enabled="false"
|
||||
android:layout_height="@dimen/dp_27"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/top_toolbar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_margin="8dp"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.example.matisse.internal.ui.widget.CheckView
|
||||
android:id="@+id/check_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"/>
|
||||
</FrameLayout>
|
||||
|
||||
</RelativeLayout>
|
52
app/src/module_album/res/layout/album_list_item.xml
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/album_item_height">
|
||||
|
||||
<com.example.matisse.internal.ui.widget.RoundedRectangleImageView
|
||||
android:id="@+id/album_cover"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="16dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/album_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignTop="@id/album_cover"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?attr/album.dropdown.title.color"
|
||||
android:textSize="16sp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_toEndOf="@id/album_cover" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/album_media_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/album_name"
|
||||
android:layout_marginTop="4dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?album.dropdown.count.color"
|
||||
android:textSize="14sp"
|
||||
android:layout_alignStart="@id/album_name"
|
||||
android:layout_toEndOf="@id/album_cover" />
|
||||
</RelativeLayout>
|
31
app/src/module_album/res/layout/fragment_media_selection.xml
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/page.bg">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="@dimen/media_grid_spacing"
|
||||
android:paddingTop="@dimen/media_grid_spacing"/>
|
||||
|
||||
</FrameLayout>
|
32
app/src/module_album/res/layout/fragment_preview_item.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<it.sephiroth.android.library.imagezoom.ImageViewTouch
|
||||
android:id="@+id/image_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/video_play_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/ic_play_circle_outline_white_48dp"/>
|
||||
</FrameLayout>
|
29
app/src/module_album/res/layout/layout_album_pick.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
tools:background="@drawable/selector_dy_send_btn"
|
||||
tools:layout_height="@dimen/dp_30"
|
||||
tools:layout_width="@dimen/dp_60">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_confirm_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/layout_layout_album_pick_01"
|
||||
android:textColor="@color/color_FEFEFE"
|
||||
android:textSize="@dimen/sp_14"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.chwl.app.ui.widget.CharAlignTextView
|
||||
android:id="@+id/tv_char_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/color_FEFEFE"
|
||||
android:textSize="@dimen/sp_14"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
52
app/src/module_album/res/layout/media_grid_content.xml
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
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.
|
||||
-->
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/media_thumbnail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<com.example.matisse.internal.ui.widget.CheckView
|
||||
android:id="@+id/check_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|right" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/gif"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:src="@drawable/ic_gif"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_duration"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="15dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:background="@drawable/bg_80000000_8dp"
|
||||
android:gravity="center"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="11sp"
|
||||
tools:text="01:00" />
|
||||
</merge>
|