first commit
This commit is contained in:
73
easyPhotos/.gitignore
vendored
Normal file
73
easyPhotos/.gitignore
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
.DS_Store
|
||||
|
||||
# Built application files
|
||||
*.apk
|
||||
*.ap_
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
out/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
# Android Studio Navigation editor temp files
|
||||
.navigation/
|
||||
|
||||
# Android Studio captures folder
|
||||
captures/
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea
|
||||
.idea/workspace.xml
|
||||
.idea/modules.xml
|
||||
.idea/tasks.xml
|
||||
.idea/gradle.xml
|
||||
.idea/assetWizardSettings.xml
|
||||
.idea/dictionaries
|
||||
.idea/libraries
|
||||
.idea/caches
|
||||
|
||||
# Keystore files
|
||||
# Uncomment the following line if you do not want to check your keystore files in.
|
||||
#*.jks
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
google-services.json
|
||||
|
||||
# Freeline
|
||||
freeline.py
|
||||
freeline/
|
||||
freeline_project_description.json
|
||||
|
||||
# fastlane
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots
|
||||
fastlane/test_output
|
||||
fastlane/readme.md
|
||||
|
||||
# test
|
||||
/src/test
|
||||
/src/androidTest
|
||||
57
easyPhotos/build.gradle
Normal file
57
easyPhotos/build.gradle
Normal file
@@ -0,0 +1,57 @@
|
||||
//apply plugin: 'com.android.library'
|
||||
//apply plugin: 'com.github.dcendents.android-maven'
|
||||
//group = 'com.github.HuanTanSheng'
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 33
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
namespace 'com.huantansheng.easyphotos'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'com.google.android.material:material:1.10.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.1'
|
||||
implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
|
||||
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||
}
|
||||
//
|
||||
//// 指定编码
|
||||
//tasks.withType(JavaCompile) {
|
||||
// options.encoding = "UTF-8"
|
||||
//}
|
||||
|
||||
// 打包源码
|
||||
//task sourcesJar(type: Jar) {
|
||||
// from android.sourceSets.main.java.srcDirs
|
||||
// classifier = 'sources'
|
||||
//}
|
||||
|
||||
//制作文档
|
||||
//task javadoc(type: Javadoc) {
|
||||
// failOnError false
|
||||
// source = android.sourceSets.main.java.sourceFiles
|
||||
// classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
|
||||
// classpath += configurations.compile
|
||||
//}
|
||||
|
||||
// 打包文档
|
||||
//task javadocJar(type: Jar, dependsOn: javadoc) {
|
||||
// classifier = 'javadoc'
|
||||
// from javadoc.destinationDir
|
||||
//}
|
||||
|
||||
//artifacts {
|
||||
// archives sourcesJar
|
||||
//// archives javadocJar
|
||||
//}
|
||||
25
easyPhotos/proguard-rules.pro
vendored
Normal file
25
easyPhotos/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in C:\androidSpace\android\sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
46
easyPhotos/src/main/AndroidManifest.xml
Normal file
46
easyPhotos/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:supportsRtl="true">
|
||||
<activity
|
||||
android:name=".ui.EasyPhotosActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/EasyPhotosTheme" />
|
||||
<activity
|
||||
android:name=".ui.PreviewActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/EasyPhotosFullscreenTheme" />
|
||||
<activity
|
||||
android:name=".ui.PuzzleActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/EasyPhotosTheme"
|
||||
android:windowSoftInputMode="adjustPan" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.PuzzleSelectorActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/EasyPhotosTheme" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,678 @@
|
||||
package com.huantansheng.easyphotos.Builder;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.huantansheng.easyphotos.callback.SelectCallback;
|
||||
import com.huantansheng.easyphotos.constant.Type;
|
||||
import com.huantansheng.easyphotos.engine.ImageEngine;
|
||||
import com.huantansheng.easyphotos.models.ad.AdListener;
|
||||
import com.huantansheng.easyphotos.models.album.entity.Photo;
|
||||
import com.huantansheng.easyphotos.result.Result;
|
||||
import com.huantansheng.easyphotos.setting.Setting;
|
||||
import com.huantansheng.easyphotos.ui.EasyPhotosActivity;
|
||||
import com.huantansheng.easyphotos.utils.result.EasyResult;
|
||||
import com.huantansheng.easyphotos.utils.uri.UriUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* EasyPhotos的启动管理器
|
||||
* Created by huan on 2017/10/18.
|
||||
*/
|
||||
public class AlbumBuilder {
|
||||
|
||||
/**
|
||||
* 启动模式
|
||||
* CAMERA-相机
|
||||
* ALBUM-相册专辑
|
||||
* ALBUM_CAMERA-带有相机按钮的相册专辑
|
||||
*/
|
||||
private enum StartupType {
|
||||
CAMERA,
|
||||
ALBUM,
|
||||
ALBUM_CAMERA
|
||||
}
|
||||
|
||||
private static final String TAG = "com.huantansheng.easyphotos";
|
||||
private static AlbumBuilder instance;
|
||||
private WeakReference<Activity> mActivity;
|
||||
private WeakReference<Fragment> mFragmentV;
|
||||
private WeakReference<android.app.Fragment> mFragment;
|
||||
private StartupType startupType;
|
||||
private WeakReference<AdListener> adListener;
|
||||
|
||||
//私有构造函数,不允许外部调用,真正实例化通过静态方法实现
|
||||
private AlbumBuilder(Activity activity, StartupType startupType) {
|
||||
mActivity = new WeakReference<Activity>(activity);
|
||||
this.startupType = startupType;
|
||||
}
|
||||
|
||||
private AlbumBuilder(android.app.Fragment fragment, StartupType startupType) {
|
||||
mFragment = new WeakReference<android.app.Fragment>(fragment);
|
||||
this.startupType = startupType;
|
||||
}
|
||||
|
||||
private AlbumBuilder(FragmentActivity activity, StartupType startupType) {
|
||||
mActivity = new WeakReference<Activity>(activity);
|
||||
this.startupType = startupType;
|
||||
}
|
||||
|
||||
private AlbumBuilder(Fragment fragment, StartupType startupType) {
|
||||
mFragmentV = new WeakReference<Fragment>(fragment);
|
||||
this.startupType = startupType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部处理相机和相册的实例
|
||||
*
|
||||
* @param activity Activity的实例
|
||||
* @return AlbumBuilder EasyPhotos的实例
|
||||
*/
|
||||
|
||||
private static AlbumBuilder with(Activity activity, StartupType startupType) {
|
||||
clear();
|
||||
instance = new AlbumBuilder(activity, startupType);
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
private static AlbumBuilder with(android.app.Fragment fragment, StartupType startupType) {
|
||||
clear();
|
||||
instance = new AlbumBuilder(fragment, startupType);
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static AlbumBuilder with(FragmentActivity activity, StartupType startupType) {
|
||||
clear();
|
||||
instance = new AlbumBuilder(activity, startupType);
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static AlbumBuilder with(Fragment fragmentV, StartupType startupType) {
|
||||
clear();
|
||||
instance = new AlbumBuilder(fragmentV, startupType);
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建相机
|
||||
*
|
||||
* @param activity 上下文
|
||||
* @return AlbumBuilder
|
||||
*/
|
||||
|
||||
public static AlbumBuilder createCamera(Activity activity) {
|
||||
return AlbumBuilder.with(activity, StartupType.CAMERA);
|
||||
}
|
||||
|
||||
|
||||
public static AlbumBuilder createCamera(android.app.Fragment fragment) {
|
||||
return AlbumBuilder.with(fragment, StartupType.CAMERA);
|
||||
}
|
||||
|
||||
public static AlbumBuilder createCamera(FragmentActivity activity) {
|
||||
return AlbumBuilder.with(activity, StartupType.CAMERA);
|
||||
}
|
||||
|
||||
public static AlbumBuilder createCamera(Fragment fragmentV) {
|
||||
return AlbumBuilder.with(fragmentV, StartupType.CAMERA);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建相册
|
||||
*
|
||||
* @param activity 上下文
|
||||
* @param isShowCamera 是否显示相机按钮
|
||||
* @param imageEngine 图片加载引擎的具体实现
|
||||
* @return
|
||||
*/
|
||||
public static AlbumBuilder createAlbum(Activity activity, boolean isShowCamera,
|
||||
@NonNull ImageEngine imageEngine) {
|
||||
if (Setting.imageEngine != imageEngine) {
|
||||
Setting.imageEngine = imageEngine;
|
||||
}
|
||||
if (isShowCamera) {
|
||||
return AlbumBuilder.with(activity, StartupType.ALBUM_CAMERA);
|
||||
} else {
|
||||
return AlbumBuilder.with(activity, StartupType.ALBUM);
|
||||
}
|
||||
}
|
||||
|
||||
public static AlbumBuilder createAlbum(android.app.Fragment fragment, boolean isShowCamera,
|
||||
@NonNull ImageEngine imageEngine) {
|
||||
if (Setting.imageEngine != imageEngine) {
|
||||
Setting.imageEngine = imageEngine;
|
||||
}
|
||||
if (isShowCamera) {
|
||||
return AlbumBuilder.with(fragment, StartupType.ALBUM_CAMERA);
|
||||
} else {
|
||||
return AlbumBuilder.with(fragment, StartupType.ALBUM);
|
||||
}
|
||||
}
|
||||
|
||||
public static AlbumBuilder createAlbum(FragmentActivity activity, boolean isShowCamera,
|
||||
@NonNull ImageEngine imageEngine) {
|
||||
if (Setting.imageEngine != imageEngine) {
|
||||
Setting.imageEngine = imageEngine;
|
||||
}
|
||||
if (isShowCamera) {
|
||||
return AlbumBuilder.with(activity, StartupType.ALBUM_CAMERA);
|
||||
} else {
|
||||
return AlbumBuilder.with(activity, StartupType.ALBUM);
|
||||
}
|
||||
}
|
||||
|
||||
public static AlbumBuilder createAlbum(Fragment fragmentV, boolean isShowCamera,
|
||||
@NonNull ImageEngine imageEngine) {
|
||||
if (Setting.imageEngine != imageEngine) {
|
||||
Setting.imageEngine = imageEngine;
|
||||
}
|
||||
if (isShowCamera) {
|
||||
return AlbumBuilder.with(fragmentV, StartupType.ALBUM_CAMERA);
|
||||
} else {
|
||||
return AlbumBuilder.with(fragmentV, StartupType.ALBUM);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置fileProvider字段
|
||||
*
|
||||
* @param fileProviderAuthority fileProvider字段
|
||||
* @return AlbumBuilder
|
||||
*/
|
||||
public AlbumBuilder setFileProviderAuthority(String fileProviderAuthority) {
|
||||
Setting.fileProviderAuthority = fileProviderAuthority;
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置选择数
|
||||
*
|
||||
* @param selectorMaxCount 最大选择数
|
||||
* @return AlbumBuilder
|
||||
*/
|
||||
public AlbumBuilder setCount(int selectorMaxCount) {
|
||||
if (Setting.complexSelector) return AlbumBuilder.this;
|
||||
Setting.count = selectorMaxCount;
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否使用宽高数据
|
||||
*
|
||||
* @param useWidth 是否使用宽高数据,需要使用写true,不用写false。
|
||||
* true:会保证宽高数据的正确性,返回速度慢,耗时。
|
||||
* false:宽高数据为0。
|
||||
* @return AlbumBuilder
|
||||
*/
|
||||
public AlbumBuilder setUseWidth(boolean useWidth) {
|
||||
Setting.useWidth = useWidth;
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持复杂选择情况
|
||||
*
|
||||
* @param singleType 是否只能选择一种文件类型,如用户选择视频后不可以选择图片,若false则可以同时选择
|
||||
* @param videoCount 可选择视频类型文件的最大数
|
||||
* @param pictureCount 可选择图片类型文件的最大数
|
||||
* @return
|
||||
*/
|
||||
public AlbumBuilder complexSelector(boolean singleType, int videoCount, int pictureCount) {
|
||||
Setting.complexSelector = true;
|
||||
Setting.complexSingleType = singleType;
|
||||
Setting.complexVideoCount = videoCount;
|
||||
Setting.complexPictureCount = pictureCount;
|
||||
Setting.count = videoCount + pictureCount;
|
||||
Setting.showVideo = true;
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置相机按钮位置
|
||||
*
|
||||
* @param cLocation 使用Material Design风格相机按钮 默认 BOTTOM_RIGHT
|
||||
* @return AlbumBuilder
|
||||
*/
|
||||
public AlbumBuilder setCameraLocation(@Setting.Location int cLocation) {
|
||||
Setting.cameraLocation = cLocation;
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置显示照片的最小文件大小
|
||||
*
|
||||
* @param minFileSize 最小文件大小,单位Bytes
|
||||
* @return AlbumBuilder
|
||||
*/
|
||||
public AlbumBuilder setMinFileSize(long minFileSize) {
|
||||
Setting.minSize = minFileSize;
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置显示照片的最小宽度
|
||||
*
|
||||
* @param minWidth 照片的最小宽度,单位Px
|
||||
* @return AlbumBuilder
|
||||
*/
|
||||
public AlbumBuilder setMinWidth(int minWidth) {
|
||||
Setting.minWidth = minWidth;
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置显示照片的最小高度
|
||||
*
|
||||
* @param minHeight 显示照片的最小高度,单位Px
|
||||
* @return AlbumBuilder
|
||||
*/
|
||||
public AlbumBuilder setMinHeight(int minHeight) {
|
||||
Setting.minHeight = minHeight;
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认选择图片集合
|
||||
*
|
||||
* @param selectedPhotos 默认选择图片集合
|
||||
* @return AlbumBuilder
|
||||
*/
|
||||
public AlbumBuilder setSelectedPhotos(ArrayList<Photo> selectedPhotos) {
|
||||
Setting.selectedPhotos.clear();
|
||||
if (selectedPhotos.isEmpty()) {
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
Setting.selectedPhotos.addAll(selectedPhotos);
|
||||
Setting.selectedOriginal = selectedPhotos.get(0).selectedOriginal;
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认选择图片集合
|
||||
*
|
||||
* @param selectedPhotos 默认选择图片集合
|
||||
* @param isSequentialSelectedPhotos 当传入已选中图片时,是否按照之前选中的顺序排序
|
||||
* @return AlbumBuilder
|
||||
*/
|
||||
public AlbumBuilder setSelectedPhotos(ArrayList<Photo> selectedPhotos,boolean isSequentialSelectedPhotos) {
|
||||
Setting.selectedPhotos.clear();
|
||||
Setting.isSequentialSelectedPhotos = isSequentialSelectedPhotos;
|
||||
if (selectedPhotos.isEmpty()) {
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
Setting.selectedPhotos.addAll(selectedPhotos);
|
||||
Setting.selectedOriginal = selectedPhotos.get(0).selectedOriginal;
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认选择图片地址集合
|
||||
*
|
||||
* @param selectedPhotoPaths 默认选择图片地址集合
|
||||
* @return AlbumBuilder
|
||||
* @Deprecated android 10 不推荐使用直接使用Path方式,推荐使用Photo类
|
||||
*/
|
||||
@Deprecated
|
||||
public AlbumBuilder setSelectedPhotoPaths(ArrayList<String> selectedPhotoPaths) {
|
||||
Setting.selectedPhotos.clear();
|
||||
ArrayList<Photo> selectedPhotos = new ArrayList<>();
|
||||
for (String path : selectedPhotoPaths) {
|
||||
File file = new File(path);
|
||||
Uri uri = null;
|
||||
if (null != mActivity && null != mActivity.get()) {
|
||||
uri = UriUtils.getUri(mActivity.get(), file);
|
||||
}
|
||||
if (null != mFragment && null != mFragment.get()) {
|
||||
uri = UriUtils.getUri(mFragment.get().getActivity(), file);
|
||||
}
|
||||
if (null != mFragmentV && null != mFragmentV.get()) {
|
||||
uri = UriUtils.getUri(mFragmentV.get().getActivity(), file);
|
||||
}
|
||||
if (uri == null) {
|
||||
uri = Uri.fromFile(file);
|
||||
}
|
||||
Photo photo = new Photo(null, uri, path, 0, 0, 0, 0, 0, 0, null);
|
||||
selectedPhotos.add(photo);
|
||||
}
|
||||
Setting.selectedPhotos.addAll(selectedPhotos);
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认选择图片地址集合
|
||||
*
|
||||
* @param selectedPhotoPaths 默认选择图片地址集合
|
||||
* @param isSequentialSelectedPhotos 当传入已选中图片时,是否按照之前选中的顺序排序
|
||||
* @return AlbumBuilder
|
||||
* @Deprecated android 10 不推荐使用直接使用Path方式,推荐使用Photo类
|
||||
*/
|
||||
@Deprecated
|
||||
public AlbumBuilder setSelectedPhotoPaths(ArrayList<String> selectedPhotoPaths,boolean isSequentialSelectedPhotos) {
|
||||
Setting.selectedPhotos.clear();
|
||||
Setting.isSequentialSelectedPhotos = isSequentialSelectedPhotos;
|
||||
ArrayList<Photo> selectedPhotos = new ArrayList<>();
|
||||
for (String path : selectedPhotoPaths) {
|
||||
File file = new File(path);
|
||||
Uri uri = null;
|
||||
if (null != mActivity && null != mActivity.get()) {
|
||||
uri = UriUtils.getUri(mActivity.get(), file);
|
||||
}
|
||||
if (null != mFragment && null != mFragment.get()) {
|
||||
uri = UriUtils.getUri(mFragment.get().getActivity(), file);
|
||||
}
|
||||
if (null != mFragmentV && null != mFragmentV.get()) {
|
||||
uri = UriUtils.getUri(mFragmentV.get().getActivity(), file);
|
||||
}
|
||||
if (uri == null) {
|
||||
uri = Uri.fromFile(file);
|
||||
}
|
||||
Photo photo = new Photo(null, uri, path, 0, 0, 0, 0, 0, 0, null);
|
||||
selectedPhotos.add(photo);
|
||||
}
|
||||
Setting.selectedPhotos.addAll(selectedPhotos);
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 原图按钮设置,不调用该方法不显示原图按钮
|
||||
*
|
||||
* @param isChecked 原图选项默认状态是否为选中状态
|
||||
* @param usable 原图按钮是否可使用
|
||||
* @param unusableHint 原图按钮不可使用时给用户的文字提示
|
||||
* @return AlbumBuilder
|
||||
*/
|
||||
public AlbumBuilder setOriginalMenu(boolean isChecked, boolean usable, String unusableHint) {
|
||||
Setting.showOriginalMenu = true;
|
||||
Setting.selectedOriginal = isChecked;
|
||||
Setting.originalMenuUsable = usable;
|
||||
Setting.originalMenuUnusableHint = unusableHint;
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 是否显示拼图按钮
|
||||
*
|
||||
* @param shouldShow 是否显示
|
||||
* @return AlbumBuilder
|
||||
*/
|
||||
public AlbumBuilder setPuzzleMenu(boolean shouldShow) {
|
||||
Setting.showPuzzleMenu = shouldShow;
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 只显示Video
|
||||
*
|
||||
* @return @return AlbumBuilder
|
||||
*/
|
||||
|
||||
public AlbumBuilder onlyVideo() {
|
||||
return filter(Type.VIDEO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤
|
||||
*
|
||||
* @param types {@link Type}
|
||||
* @return @return AlbumBuilder
|
||||
*/
|
||||
public AlbumBuilder filter(String... types) {
|
||||
Setting.filterTypes = Arrays.asList(types);
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否显示gif图
|
||||
*
|
||||
* @param shouldShow 是否显示
|
||||
* @return @return AlbumBuilder
|
||||
*/
|
||||
public AlbumBuilder setGif(boolean shouldShow) {
|
||||
Setting.showGif = shouldShow;
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否显示video
|
||||
*
|
||||
* @param shouldShow 是否显示
|
||||
* @return @return AlbumBuilder
|
||||
*/
|
||||
public AlbumBuilder setVideo(boolean shouldShow) {
|
||||
Setting.showVideo = shouldShow;
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示最少多少秒的视频
|
||||
*
|
||||
* @param second 秒
|
||||
* @return @return AlbumBuilder
|
||||
*/
|
||||
public AlbumBuilder setVideoMinSecond(int second) {
|
||||
Setting.videoMinSecond = second * 1000;
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示最多多少秒的视频
|
||||
*
|
||||
* @param second 秒
|
||||
* @return @return AlbumBuilder
|
||||
*/
|
||||
public AlbumBuilder setVideoMaxSecond(int second) {
|
||||
Setting.videoMaxSecond = second * 1000;
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 相册选择页是否显示清空按钮
|
||||
*
|
||||
* @param shouldShow
|
||||
* @return
|
||||
*/
|
||||
public AlbumBuilder setCleanMenu(boolean shouldShow) {
|
||||
Setting.showCleanMenu = shouldShow;
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
|
||||
private void setSettingParams() {
|
||||
switch (startupType) {
|
||||
case CAMERA:
|
||||
Setting.onlyStartCamera = true;
|
||||
Setting.isShowCamera = true;
|
||||
break;
|
||||
case ALBUM:
|
||||
Setting.isShowCamera = false;
|
||||
break;
|
||||
case ALBUM_CAMERA:
|
||||
Setting.isShowCamera = true;
|
||||
break;
|
||||
}
|
||||
if (!Setting.filterTypes.isEmpty()) {
|
||||
if (Setting.isFilter(Type.GIF)) {
|
||||
Setting.showGif = true;
|
||||
}
|
||||
if (Setting.isFilter(Type.VIDEO)) {
|
||||
Setting.showVideo = true;
|
||||
}
|
||||
}
|
||||
if (Setting.isOnlyVideo()) {
|
||||
//只选择视频 暂不支持拍照/拼图等
|
||||
Setting.isShowCamera = false;
|
||||
Setting.showPuzzleMenu = false;
|
||||
Setting.showGif = false;
|
||||
Setting.showVideo = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动,onActivityResult方式
|
||||
*
|
||||
* @param requestCode startActivityForResult的请求码
|
||||
*/
|
||||
|
||||
public void start(int requestCode) {
|
||||
setSettingParams();
|
||||
launchEasyPhotosActivity(requestCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动,链式调用
|
||||
*/
|
||||
public void start(SelectCallback callback) {
|
||||
setSettingParams();
|
||||
if (null != mActivity && null != mActivity.get() && mActivity.get() instanceof FragmentActivity) {
|
||||
EasyResult.get((FragmentActivity) mActivity.get()).startEasyPhoto(callback);
|
||||
return;
|
||||
}
|
||||
if (null != mFragmentV && null != mFragmentV.get()) {
|
||||
EasyResult.get(mFragmentV.get()).startEasyPhoto(callback);
|
||||
return;
|
||||
}
|
||||
throw new RuntimeException("mActivity or mFragmentV maybe null, you can not use this " +
|
||||
"method... ");
|
||||
}
|
||||
|
||||
/**
|
||||
* 正式启动
|
||||
*
|
||||
* @param requestCode startActivityForResult的请求码
|
||||
*/
|
||||
private void launchEasyPhotosActivity(int requestCode) {
|
||||
if (null != mActivity && null != mActivity.get()) {
|
||||
EasyPhotosActivity.start(mActivity.get(), requestCode);
|
||||
return;
|
||||
}
|
||||
if (null != mFragment && null != mFragment.get()) {
|
||||
EasyPhotosActivity.start(mFragment.get(), requestCode);
|
||||
return;
|
||||
}
|
||||
if (null != mFragmentV && null != mFragmentV.get()) {
|
||||
EasyPhotosActivity.start(mFragmentV.get(), requestCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有数据
|
||||
*/
|
||||
private static void clear() {
|
||||
Result.clear();
|
||||
Setting.clear();
|
||||
instance = null;
|
||||
}
|
||||
|
||||
//*********************AD************************************
|
||||
|
||||
/**
|
||||
* 设置广告(不设置该选项则表示不使用广告)
|
||||
*
|
||||
* @param photosAdView 使用图片列表的广告View
|
||||
* @param photosAdIsLoaded 图片列表广告是否加载完毕
|
||||
* @param albumItemsAdView 使用专辑项目列表的广告View
|
||||
* @param albumItemsAdIsLoaded 专辑项目列表广告是否加载完毕
|
||||
* @return AlbumBuilder
|
||||
*/
|
||||
public AlbumBuilder setAdView(View photosAdView, boolean photosAdIsLoaded,
|
||||
View albumItemsAdView, boolean albumItemsAdIsLoaded) {
|
||||
Setting.photosAdView = new WeakReference<View>(photosAdView);
|
||||
Setting.albumItemsAdView = new WeakReference<View>(albumItemsAdView);
|
||||
Setting.photoAdIsOk = photosAdIsLoaded;
|
||||
Setting.albumItemsAdIsOk = albumItemsAdIsLoaded;
|
||||
return AlbumBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置广告监听
|
||||
* 内部使用,无需关心
|
||||
*
|
||||
* @param adListener 广告监听
|
||||
*/
|
||||
public static void setAdListener(AdListener adListener) {
|
||||
if (null == instance) return;
|
||||
if (instance.startupType == StartupType.CAMERA) return;
|
||||
instance.adListener = new WeakReference<AdListener>(adListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新图片列表广告数据
|
||||
*/
|
||||
public static void notifyPhotosAdLoaded() {
|
||||
if (Setting.photoAdIsOk) {
|
||||
return;
|
||||
}
|
||||
if (null == instance) {
|
||||
return;
|
||||
}
|
||||
if (instance.startupType == StartupType.CAMERA) {
|
||||
return;
|
||||
}
|
||||
if (null == instance.adListener) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (null != instance && null != instance.adListener) {
|
||||
Setting.photoAdIsOk = true;
|
||||
instance.adListener.get().onPhotosAdLoaded();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
return;
|
||||
}
|
||||
Setting.photoAdIsOk = true;
|
||||
instance.adListener.get().onPhotosAdLoaded();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新专辑项目列表广告
|
||||
*/
|
||||
public static void notifyAlbumItemsAdLoaded() {
|
||||
if (Setting.albumItemsAdIsOk) {
|
||||
return;
|
||||
}
|
||||
if (null == instance) {
|
||||
return;
|
||||
}
|
||||
if (instance.startupType == StartupType.CAMERA) {
|
||||
return;
|
||||
}
|
||||
if (null == instance.adListener) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (null != instance && null != instance.adListener) {
|
||||
Setting.albumItemsAdIsOk = true;
|
||||
instance.adListener.get().onAlbumItemsAdLoaded();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
return;
|
||||
}
|
||||
Setting.albumItemsAdIsOk = true;
|
||||
instance.adListener.get().onAlbumItemsAdLoaded();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
package com.huantansheng.easyphotos;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.huantansheng.easyphotos.Builder.AlbumBuilder;
|
||||
import com.huantansheng.easyphotos.callback.PuzzleCallback;
|
||||
import com.huantansheng.easyphotos.engine.ImageEngine;
|
||||
import com.huantansheng.easyphotos.models.ad.AdListener;
|
||||
import com.huantansheng.easyphotos.models.album.AlbumModel;
|
||||
import com.huantansheng.easyphotos.models.album.entity.Photo;
|
||||
import com.huantansheng.easyphotos.models.sticker.StickerModel;
|
||||
import com.huantansheng.easyphotos.models.sticker.entity.TextStickerData;
|
||||
import com.huantansheng.easyphotos.ui.PuzzleActivity;
|
||||
import com.huantansheng.easyphotos.utils.bitmap.BitmapUtils;
|
||||
import com.huantansheng.easyphotos.utils.bitmap.SaveBitmapCallBack;
|
||||
import com.huantansheng.easyphotos.utils.media.MediaScannerConnectionUtils;
|
||||
import com.huantansheng.easyphotos.utils.result.EasyResult;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* EasyPhotos的启动管理器
|
||||
* Created by huan on 2017/10/18.
|
||||
*/
|
||||
public class EasyPhotos {
|
||||
|
||||
//easyPhotos的返回数据Key
|
||||
public static final String RESULT_PHOTOS = "keyOfEasyPhotosResult";
|
||||
public static final String RESULT_SELECTED_ORIGINAL = "keyOfEasyPhotosResultSelectedOriginal";
|
||||
|
||||
/**
|
||||
* 预加载
|
||||
* 调不调用该方法都可以,不调用不影响EasyPhotos正常使用
|
||||
* 第一次扫描媒体库可能会慢,调用预加载会使真正打开相册的速度加快
|
||||
* 若调用该方法,建议自行判断代码书写位置,建议在用户打开相册的3秒前调用,比如app主页面或调用相册的上一页
|
||||
* 该方法如果没有授权读取权限的话,是无效的,所以外部加不加权限控制都可以,加的话保证执行,不加也不影响程序正常使用
|
||||
*
|
||||
* @param cxt 上下文
|
||||
*/
|
||||
public static void preLoad(Context cxt) {
|
||||
AlbumModel.getInstance().query(cxt, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预加载
|
||||
* 调不调用该方法都可以,不调用不影响EasyPhotos正常使用
|
||||
* 第一次扫描媒体库可能会慢,调用预加载会使真正打开相册的速度加快
|
||||
* 若调用该方法,建议自行判断代码书写位置,建议在用户打开相册的3秒前调用,比如app主页面或调用相册的上一页
|
||||
* 该方法如果没有授权读取权限的话,是无效的,所以外部加不加权限控制都可以,加的话保证执行,不加也不影响程序正常使用
|
||||
*
|
||||
* @param cxt 上下文
|
||||
* @param callBack 预加载完成的回调,若进行UI操作,需自行切回主线程。
|
||||
*/
|
||||
public static void preLoad(Context cxt, AlbumModel.CallBack callBack) {
|
||||
AlbumModel.getInstance().query(cxt, callBack);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建相机
|
||||
*
|
||||
* @param activity 上下文
|
||||
* @param useWidth 是否使用宽高数据
|
||||
* @return AlbumBuilder
|
||||
*/
|
||||
public static AlbumBuilder createCamera(Activity activity,
|
||||
boolean useWidth) {
|
||||
return AlbumBuilder.createCamera(activity).setUseWidth(useWidth);
|
||||
}
|
||||
|
||||
public static AlbumBuilder createCamera(Fragment fragment,
|
||||
boolean useWidth) {
|
||||
return AlbumBuilder.createCamera(fragment).setUseWidth(useWidth);
|
||||
}
|
||||
|
||||
public static AlbumBuilder createCamera(FragmentActivity activity,
|
||||
boolean useWidth) {
|
||||
return AlbumBuilder.createCamera(activity).setUseWidth(useWidth);
|
||||
}
|
||||
|
||||
public static AlbumBuilder createCamera(androidx.fragment.app.Fragment fragmentV,
|
||||
boolean useWidth) {
|
||||
return AlbumBuilder.createCamera(fragmentV).setUseWidth(useWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建相册
|
||||
*
|
||||
* @param activity 上下文
|
||||
* @param isShowCamera 是否显示相机按钮
|
||||
* @param useWidth 是否使用宽高数据。
|
||||
* true:会保证宽高数据的正确性,返回速度慢,耗时,尤其在华为mate30上,可能点击完成后会加载三四秒才能返回。
|
||||
* false:有宽高数据但不保证正确性,点击完成后秒回,但可能有因旋转问题导致的宽高相反的情况,以及极少数的宽高为0情况。
|
||||
* @param imageEngine 图片加载引擎的具体实现
|
||||
* @return AlbumBuilder 建造者模式配置其他选项
|
||||
*/
|
||||
public static AlbumBuilder createAlbum(Activity activity, boolean isShowCamera,
|
||||
boolean useWidth, @NonNull ImageEngine imageEngine) {
|
||||
return AlbumBuilder.createAlbum(activity, isShowCamera, imageEngine).setUseWidth(useWidth);
|
||||
}
|
||||
|
||||
public static AlbumBuilder createAlbum(Fragment fragment, boolean isShowCamera,
|
||||
boolean useWidth, @NonNull ImageEngine imageEngine) {
|
||||
return AlbumBuilder.createAlbum(fragment, isShowCamera, imageEngine).setUseWidth(useWidth);
|
||||
}
|
||||
|
||||
public static AlbumBuilder createAlbum(FragmentActivity activity, boolean isShowCamera,
|
||||
boolean useWidth, @NonNull ImageEngine imageEngine) {
|
||||
return AlbumBuilder.createAlbum(activity, isShowCamera, imageEngine).setUseWidth(useWidth);
|
||||
}
|
||||
|
||||
public static AlbumBuilder createAlbum(androidx.fragment.app.Fragment fragmentV,
|
||||
boolean isShowCamera, boolean useWidth,
|
||||
@NonNull ImageEngine imageEngine) {
|
||||
return AlbumBuilder.createAlbum(fragmentV, isShowCamera, imageEngine).setUseWidth(useWidth);
|
||||
}
|
||||
|
||||
|
||||
//*********************AD************************************
|
||||
|
||||
|
||||
/**
|
||||
* 设置广告监听
|
||||
* 内部使用,无需关心
|
||||
*
|
||||
* @param adListener 广告监听
|
||||
*/
|
||||
public static void setAdListener(AdListener adListener) {
|
||||
AlbumBuilder.setAdListener(adListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新图片列表广告数据
|
||||
*/
|
||||
public static void notifyPhotosAdLoaded() {
|
||||
AlbumBuilder.notifyPhotosAdLoaded();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新专辑项目列表广告
|
||||
*/
|
||||
public static void notifyAlbumItemsAdLoaded() {
|
||||
AlbumBuilder.notifyAlbumItemsAdLoaded();
|
||||
}
|
||||
|
||||
|
||||
//*************************bitmap功能***********************************/
|
||||
|
||||
/**
|
||||
* 回收bitmap
|
||||
*
|
||||
* @param bitmap 要回收的bitmap
|
||||
*/
|
||||
public static void recycle(Bitmap bitmap) {
|
||||
BitmapUtils.recycle(bitmap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回收bitmap数组中的所有图片
|
||||
*
|
||||
* @param bitmaps 要回收的bitmap数组
|
||||
*/
|
||||
public static void recycle(Bitmap... bitmaps) {
|
||||
BitmapUtils.recycle(bitmaps);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回收bitmap集合中的所有图片
|
||||
*
|
||||
* @param bitmaps 要回收的bitmap集合
|
||||
*/
|
||||
public static void recycle(List<Bitmap> bitmaps) {
|
||||
BitmapUtils.recycle(bitmaps);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给图片添加水印,水印会根据图片宽高自动缩放处理
|
||||
*
|
||||
* @param watermark 水印
|
||||
* @param image 添加水印的图片
|
||||
* @param srcImageWidth 水印对应的原图片宽度,即ui制作水印时参考的要添加水印的图片的宽度
|
||||
* @param offsetX 添加水印的X轴偏移量
|
||||
* @param offsetY 添加水印的Y轴偏移量
|
||||
* @param addInLeft true 在左下角添加水印,false 在右下角添加水印
|
||||
* @param orientation Bitmap的旋转角度。当useWidth为true时,Photo实体类中会有orientation,若bitmap
|
||||
* 不是用户手机内图片,填0即可。
|
||||
* @return 添加水印后的bitmap
|
||||
*/
|
||||
public static Bitmap addWatermark(Bitmap watermark, Bitmap image, int srcImageWidth,
|
||||
int offsetX, int offsetY, boolean addInLeft,
|
||||
int orientation) {
|
||||
return BitmapUtils.addWatermark(watermark, image, srcImageWidth, offsetX, offsetY,
|
||||
addInLeft, orientation);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给图片添加带文字和图片的水印,水印会根据图片宽高自动缩放处理
|
||||
*
|
||||
* @param watermark 水印图片
|
||||
* @param image 要加水印的图片
|
||||
* @param srcImageWidth 水印对应的原图片宽度,即ui制作水印时参考的要添加水印的图片的宽度
|
||||
* @param text 要添加的文字
|
||||
* @param offsetX 添加水印的X轴偏移量
|
||||
* @param offsetY 添加水印的Y轴偏移量
|
||||
* @param addInLeft true 在左下角添加水印,false 在右下角添加水印
|
||||
* @param orientation Bitmap的旋转角度。当useWidth为true时,Photo实体类中会有orientation,若bitmap
|
||||
* 不是用户手机内图片,填0即可。
|
||||
* @return 添加水印后的bitmap
|
||||
*/
|
||||
public static Bitmap addWatermarkWithText(Bitmap watermark, Bitmap image, int srcImageWidth,
|
||||
@NonNull String text, int offsetX, int offsetY,
|
||||
boolean addInLeft, int orientation) {
|
||||
return BitmapUtils.addWatermarkWithText(watermark, image, srcImageWidth, text, offsetX,
|
||||
offsetY,
|
||||
addInLeft, orientation);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存Bitmap到指定文件夹
|
||||
*
|
||||
* @param act 上下文
|
||||
* @param dirPath 文件夹全路径
|
||||
* @param bitmap bitmap
|
||||
* @param namePrefix 保存文件的前缀名,文件最终名称格式为:前缀名+自动生成的唯一数字字符+.png
|
||||
* @param notifyMedia 是否更新到媒体库
|
||||
* @param callBack 保存图片后的回调,回调已经处于UI线程
|
||||
*/
|
||||
public static void saveBitmapToDir(Activity act, String dirPath, String namePrefix,
|
||||
Bitmap bitmap, boolean notifyMedia,
|
||||
SaveBitmapCallBack callBack) {
|
||||
BitmapUtils.saveBitmapToDir(act, dirPath, namePrefix, bitmap, notifyMedia, callBack);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 把View画成Bitmap
|
||||
*
|
||||
* @param view 要处理的View
|
||||
* @return Bitmap
|
||||
*/
|
||||
public static Bitmap createBitmapFromView(View view) {
|
||||
return BitmapUtils.createBitmapFromView(view);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动拼图(最多对9张图片进行拼图)
|
||||
*
|
||||
* @param act 上下文
|
||||
* @param photos 图片集合(最多对9张图片进行拼图)
|
||||
* @param puzzleSaveDirPath 拼图完成保存的文件夹全路径
|
||||
* @param puzzleSaveNamePrefix 拼图完成保存的文件名前缀,最终格式:前缀+默认生成唯一数字标识+.png
|
||||
* @param requestCode 请求code
|
||||
* @param replaceCustom 单击替换拼图中的某张图片时,是否以startForResult的方式启动你的自定义界面,该界面与传进来的act
|
||||
* 为同一界面。false则在EasyPhotos内部完成,正常需求直接写false即可。
|
||||
* true的情况适用于:用于拼图的图片集合中包含网络图片,是在你的act界面中获取并下载的(也可以直接用网络地址,不用下载后的本地地址,也就是可以不下载下来),而非单纯本地相册。举例:你的act中有两个按钮,一个指向本地相册,一个指向网络相册,用户在该界面任意选择,选择好图片后跳转到拼图界面,用户在拼图界面点击替换按钮,将会启动一个新的act界面,这时,act只让用户在网络相册和本地相册选择一张图片,选择好执行
|
||||
* Intent intent = new Intent();
|
||||
* intent.putParcelableArrayListExtra(AlbumBuilder.RESULT_PHOTOS
|
||||
* , photos);
|
||||
* act.setResult(RESULT_OK,intent); 并关闭act,回到拼图界面,完成替换。
|
||||
* @param imageEngine 图片加载引擎的具体实现
|
||||
*/
|
||||
|
||||
public static void startPuzzleWithPhotos(Activity act, ArrayList<Photo> photos,
|
||||
String puzzleSaveDirPath,
|
||||
String puzzleSaveNamePrefix, int requestCode,
|
||||
boolean replaceCustom,
|
||||
@NonNull ImageEngine imageEngine) {
|
||||
act.setResult(Activity.RESULT_OK);
|
||||
PuzzleActivity.startWithPhotos(act, photos, puzzleSaveDirPath, puzzleSaveNamePrefix,
|
||||
requestCode, replaceCustom, imageEngine);
|
||||
}
|
||||
|
||||
public static void startPuzzleWithPhotos(FragmentActivity act, ArrayList<Photo> photos,
|
||||
String puzzleSaveDirPath,
|
||||
String puzzleSaveNamePrefix, boolean replaceCustom,
|
||||
@NonNull ImageEngine imageEngine,
|
||||
PuzzleCallback callback) {
|
||||
act.setResult(Activity.RESULT_OK);
|
||||
EasyResult.get(act).startPuzzleWithPhotos(photos, puzzleSaveDirPath, puzzleSaveNamePrefix
|
||||
, replaceCustom, imageEngine, callback);
|
||||
}
|
||||
|
||||
|
||||
//**************更新媒体库***********************
|
||||
|
||||
/**
|
||||
* 更新媒体文件到媒体库
|
||||
*
|
||||
* @param cxt 上下文
|
||||
* @param filePaths 更新的文件地址
|
||||
*/
|
||||
public static void notifyMedia(Context cxt, String... filePaths) {
|
||||
MediaScannerConnectionUtils.refresh(cxt, filePaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新媒体文件到媒体库
|
||||
*
|
||||
* @param cxt 上下文
|
||||
* @param files 更新的文件
|
||||
*/
|
||||
public static void notifyMedia(Context cxt, File... files) {
|
||||
MediaScannerConnectionUtils.refresh(cxt, files);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新媒体文件到媒体库
|
||||
*
|
||||
* @param cxt 上下文
|
||||
* @param fileList 更新的文件地址集合
|
||||
*/
|
||||
public static void notifyMedia(Context cxt, List<String> fileList) {
|
||||
MediaScannerConnectionUtils.refresh(cxt, fileList);
|
||||
}
|
||||
|
||||
|
||||
//*********************************贴纸***************************
|
||||
|
||||
|
||||
/**
|
||||
* 添加文字贴纸的文字数据
|
||||
*
|
||||
* @param textStickerData 文字贴纸的文字数据
|
||||
*/
|
||||
public static void addTextStickerData(TextStickerData... textStickerData) {
|
||||
StickerModel.textDataList.addAll(Arrays.asList(textStickerData));
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空文字贴纸的数据
|
||||
*/
|
||||
public static void clearTextStickerDataList() {
|
||||
StickerModel.textDataList.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.huantansheng.easyphotos.callback;
|
||||
|
||||
import com.huantansheng.easyphotos.models.album.entity.Photo;
|
||||
|
||||
/**
|
||||
* PuzzleCallback
|
||||
*
|
||||
* @author joker
|
||||
* @date 2019/4/9.
|
||||
*/
|
||||
public abstract class PuzzleCallback {
|
||||
/**
|
||||
* 选择结果回调
|
||||
*
|
||||
* @param photo 返回对象:如果你需要了解图片的宽、高、大小、用户是否选中原图选项等信息,可以用这个
|
||||
*/
|
||||
public abstract void onResult(Photo photo);
|
||||
/**
|
||||
* 什么都没选,取消选择回调
|
||||
*/
|
||||
public abstract void onCancel();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.huantansheng.easyphotos.callback;
|
||||
|
||||
import com.huantansheng.easyphotos.models.album.entity.Photo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* SelectCallback
|
||||
*
|
||||
* @author joker
|
||||
* @date 2019/4/9.
|
||||
*/
|
||||
public abstract class SelectCallback {
|
||||
/**
|
||||
* 选择结果回调
|
||||
*
|
||||
* @param photos 返回对象集合:如果你需要了解图片的宽、高、大小、用户是否选中原图选项等信息,可以用这个
|
||||
* @param isOriginal 返回图片地址集合时如果你需要知道用户选择图片时是否选择了原图选项,用如下方法获取
|
||||
*/
|
||||
public abstract void onResult(ArrayList<Photo> photos, boolean isOriginal);
|
||||
|
||||
/**
|
||||
* 什么都没选,取消选择回调
|
||||
*/
|
||||
public abstract void onCancel();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.huantansheng.easyphotos.constant;
|
||||
|
||||
/**
|
||||
* Code常量
|
||||
* Created by huan on 2017/10/19.
|
||||
*/
|
||||
|
||||
public class Code {
|
||||
//相机请求码
|
||||
public static final int REQUEST_CAMERA = 11;
|
||||
//权限请求码
|
||||
public static final int REQUEST_PERMISSION = 12;
|
||||
//预览activity请求码
|
||||
public static final int REQUEST_PREVIEW_ACTIVITY = 13;
|
||||
//请求应用详情
|
||||
public static final int REQUEST_SETTING_APP_DETAILS = 14;
|
||||
//拼图activity请求吗
|
||||
public static final int REQUEST_PUZZLE = 15;
|
||||
//拼图选择activity请求吗
|
||||
public static final int REQUEST_PUZZLE_SELECTOR = 16;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.huantansheng.easyphotos.constant;
|
||||
|
||||
/**
|
||||
* key的常量
|
||||
* Created by huan on 2017/10/19.
|
||||
*/
|
||||
|
||||
public class Key {
|
||||
//预览图片的当前角标
|
||||
public static final String PREVIEW_PHOTO_INDEX = "keyOfPreviewPhotoIndex";
|
||||
//当前预览界面的专辑index
|
||||
public static final String PREVIEW_ALBUM_ITEM_INDEX = "keyOfPreviewAlbumItemIndex";
|
||||
//预览界面是否点击完成
|
||||
public static final String PREVIEW_CLICK_DONE = "keyOfPreviewClickDone";
|
||||
//拼图界面图片类型,true-Photo,false-String
|
||||
public static final String PUZZLE_FILE_IS_PHOTO = "keyOfPuzzleFilesTypeIsPhoto";
|
||||
//拼图界面图片结合
|
||||
public static final String PUZZLE_FILES = "keyOfPuzzleFiles";
|
||||
//拼图界面图片保存文件夹地址
|
||||
public static final String PUZZLE_SAVE_DIR = "keyOfPuzzleSaveDir";
|
||||
//拼图界面图片保存文件名前缀
|
||||
public static final String PUZZLE_SAVE_NAME_PREFIX = "keyOfPuzzleSaveNamePrefix";
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.huantansheng.easyphotos.constant;
|
||||
|
||||
/**
|
||||
* Created by huan on 2018/1/9.
|
||||
*/
|
||||
|
||||
public class Type {
|
||||
public static final String GIF = "gif";
|
||||
public static final String VIDEO = "video";
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.huantansheng.easyphotos.engine;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* 自定义图片加载方式
|
||||
* Created by huan on 2018/1/15.
|
||||
*/
|
||||
public interface ImageEngine {
|
||||
/**
|
||||
* 加载图片到ImageView
|
||||
*
|
||||
* @param context 上下文
|
||||
* @param uri 图片Uri
|
||||
* @param imageView 加载到的ImageView
|
||||
*/
|
||||
//安卓10推荐uri,并且path的方式不再可用
|
||||
void loadPhoto(@NonNull Context context, @NonNull Uri uri,@NonNull ImageView imageView);
|
||||
|
||||
/**
|
||||
* 加载gif动图图片到ImageView,gif动图不动
|
||||
*
|
||||
* @param context 上下文
|
||||
* @param gifUri gif动图路径Uri
|
||||
* @param imageView 加载到的ImageView
|
||||
* <p>
|
||||
* 备注:不支持动图显示的情况下可以不写
|
||||
*/
|
||||
//安卓10推荐uri,并且path的方式不再可用
|
||||
void loadGifAsBitmap(@NonNull Context context,@NonNull Uri gifUri,@NonNull ImageView imageView);
|
||||
|
||||
/**
|
||||
* 加载gif动图到ImageView,gif动图动
|
||||
*
|
||||
* @param context 上下文
|
||||
* @param gifUri gif动图路径Uri
|
||||
* @param imageView 加载动图的ImageView
|
||||
* <p>
|
||||
* 备注:不支持动图显示的情况下可以不写
|
||||
*/
|
||||
//安卓10推荐uri,并且path的方式不再可用
|
||||
void loadGif(@NonNull Context context,@NonNull Uri gifUri,@NonNull ImageView imageView);
|
||||
|
||||
/**
|
||||
* 获取图片加载框架中的缓存Bitmap,不用拼图功能可以直接返回null
|
||||
*
|
||||
* @param context 上下文
|
||||
* @param uri 图片路径
|
||||
* @param width 图片宽度
|
||||
* @param height 图片高度
|
||||
* @return Bitmap
|
||||
* @throws Exception 异常直接抛出,EasyPhotos内部处理
|
||||
*/
|
||||
//安卓10推荐uri,并且path的方式不再可用
|
||||
Bitmap getCacheBitmap(@NonNull Context context,@NonNull Uri uri, int width, int height) throws Exception;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.huantansheng.easyphotos.models.ad;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* 广告实体
|
||||
* Created by huan on 2017/10/24.
|
||||
*/
|
||||
|
||||
public class AdEntity {
|
||||
public View adView;
|
||||
public int lineIndex;
|
||||
|
||||
public AdEntity(View adView, int lineIndex) {
|
||||
this.adView = adView;
|
||||
this.lineIndex = lineIndex;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.huantansheng.easyphotos.models.ad;
|
||||
|
||||
/**
|
||||
* 广告监听
|
||||
* Created by huan on 2017/10/24.
|
||||
*/
|
||||
|
||||
public interface AdListener {
|
||||
void onPhotosAdLoaded();
|
||||
void onAlbumItemsAdLoaded();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.huantansheng.easyphotos.models.ad;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.huantansheng.easyphotos.R;
|
||||
|
||||
/**
|
||||
* 广告viewolder
|
||||
* Created by huan on 2017/10/28.
|
||||
*/
|
||||
|
||||
public class AdViewHolder extends RecyclerView.ViewHolder {
|
||||
public FrameLayout adFrame;
|
||||
public AdViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
adFrame = (FrameLayout) itemView.findViewById(R.id.ad_frame_easy_photos);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
package com.huantansheng.easyphotos.models.album;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.content.PermissionChecker;
|
||||
|
||||
import com.huantansheng.easyphotos.R;
|
||||
import com.huantansheng.easyphotos.constant.Type;
|
||||
import com.huantansheng.easyphotos.models.album.entity.Album;
|
||||
import com.huantansheng.easyphotos.models.album.entity.AlbumItem;
|
||||
import com.huantansheng.easyphotos.models.album.entity.Photo;
|
||||
import com.huantansheng.easyphotos.result.Result;
|
||||
import com.huantansheng.easyphotos.setting.Setting;
|
||||
import com.huantansheng.easyphotos.utils.String.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 专辑模型
|
||||
* Created by huan on 2017/10/20.
|
||||
* <p>
|
||||
* Modified by Eagle on 2018/08/31.
|
||||
* 修改内容:将AlbumModel的实例化与数据查询分开
|
||||
*/
|
||||
public class AlbumModel {
|
||||
private static final String TAG = "AlbumModel";
|
||||
public static AlbumModel instance;
|
||||
public Album album;
|
||||
private String[] projections;
|
||||
|
||||
private AlbumModel() {
|
||||
album = new Album();
|
||||
}
|
||||
|
||||
public static AlbumModel getInstance() {
|
||||
if (null == instance) {
|
||||
synchronized (AlbumModel.class) {
|
||||
if (null == instance) {
|
||||
instance = new AlbumModel();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 专辑查询
|
||||
*
|
||||
* @param context 调用查询方法的context
|
||||
* @param callBack 查询完成后的回调
|
||||
*/
|
||||
public volatile boolean canRun = true;
|
||||
|
||||
public void query(Context context, final CallBack callBack) {
|
||||
final Context appCxt = context.getApplicationContext();
|
||||
|
||||
/*******************************官方库的源码start*******************************/
|
||||
// if (PermissionChecker.checkSelfPermission(context,
|
||||
// Manifest.permission.READ_EXTERNAL_STORAGE) != PermissionChecker
|
||||
// .PERMISSION_GRANTED) {
|
||||
// if (null != callBack) callBack.onAlbumWorkedCallBack();
|
||||
// return;
|
||||
// }
|
||||
/*******************************官方库的源码end*******************************/
|
||||
|
||||
|
||||
/*********************************自定义添加适配Android13相册权限start*************************/
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
if (PermissionChecker.checkSelfPermission(context,
|
||||
Manifest.permission.READ_MEDIA_IMAGES) != PermissionChecker.PERMISSION_GRANTED) {
|
||||
if (null != callBack)
|
||||
callBack.onAlbumWorkedCallBack();
|
||||
return;
|
||||
}
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
if (PermissionChecker.checkSelfPermission(context,
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE) != PermissionChecker.PERMISSION_GRANTED) {
|
||||
if (null != callBack)
|
||||
callBack.onAlbumWorkedCallBack();
|
||||
return;
|
||||
}
|
||||
}
|
||||
/*********************************自定义添加适配Android13相册权限end*************************/
|
||||
|
||||
canRun = true;
|
||||
new Thread(() -> {
|
||||
initAlbum(appCxt);
|
||||
if (null != callBack)
|
||||
callBack.onAlbumWorkedCallBack();
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void stopQuery() {
|
||||
canRun = false;
|
||||
}
|
||||
|
||||
private synchronized void initAlbum(Context context) {
|
||||
if (!album.isEmpty()) {
|
||||
Log.e(TAG, "已经预加载过相册");
|
||||
return;
|
||||
}
|
||||
album.clear();
|
||||
// long now = System.currentTimeMillis();
|
||||
if (Setting.selectedPhotos.size() > Setting.count) {
|
||||
throw new RuntimeException("AlbumBuilder: 默认勾选的图片张数不能大于设置的选择数!" + "|默认勾选图片张数:" + Setting.selectedPhotos.size() + "|设置的选择数:" + Setting.count);
|
||||
}
|
||||
boolean canReadWidth =
|
||||
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN;
|
||||
// boolean isQ = android.os.Build.VERSION.SDK_INT == Build.VERSION_CODES.Q;
|
||||
final String sortOrder = MediaStore.Files.FileColumns.DATE_MODIFIED + " DESC";
|
||||
|
||||
Uri contentUri;
|
||||
String selection = null;
|
||||
String[] selectionAllArgs = null;
|
||||
|
||||
if (Setting.isOnlyVideo()) {
|
||||
contentUri = MediaStore.Video.Media.getContentUri("external");
|
||||
|
||||
} else if (!Setting.showVideo) {
|
||||
contentUri = MediaStore.Images.Media.getContentUri("external");
|
||||
|
||||
} else {
|
||||
contentUri = MediaStore.Files.getContentUri("external");
|
||||
selection =
|
||||
"(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?" + " OR " + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)";
|
||||
selectionAllArgs =
|
||||
new String[]{String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
|
||||
String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO)};
|
||||
}
|
||||
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
|
||||
|
||||
List<String> projectionList = new ArrayList<>();
|
||||
projectionList.add(MediaStore.Files.FileColumns._ID);
|
||||
// if (isQ) {
|
||||
// projectionList.add(MediaStore.MediaColumns.RELATIVE_PATH);
|
||||
// }else {
|
||||
projectionList.add(MediaStore.MediaColumns.DATA);
|
||||
// }
|
||||
projectionList.add(MediaStore.MediaColumns.DISPLAY_NAME);
|
||||
projectionList.add(MediaStore.MediaColumns.DATE_MODIFIED);
|
||||
projectionList.add(MediaStore.MediaColumns.MIME_TYPE);
|
||||
projectionList.add(MediaStore.MediaColumns.SIZE);
|
||||
projectionList.add(MediaStore.MediaColumns.BUCKET_DISPLAY_NAME);
|
||||
|
||||
if (!Setting.useWidth) {
|
||||
if (Setting.minWidth != 1 && Setting.minHeight != 1)
|
||||
Setting.useWidth = true;
|
||||
}
|
||||
if (canReadWidth) {
|
||||
if (Setting.useWidth) {
|
||||
projectionList.add(MediaStore.MediaColumns.WIDTH);
|
||||
projectionList.add(MediaStore.MediaColumns.HEIGHT);
|
||||
if (!Setting.isOnlyVideo())
|
||||
projectionList.add(MediaStore.MediaColumns.ORIENTATION);
|
||||
}
|
||||
}
|
||||
|
||||
if (Setting.showVideo) {
|
||||
projectionList.add(MediaStore.MediaColumns.DURATION);
|
||||
}
|
||||
|
||||
projections = projectionList.toArray(new String[0]);
|
||||
|
||||
Cursor cursor = contentResolver.query(contentUri, projections, selection,
|
||||
selectionAllArgs, sortOrder);
|
||||
if (cursor == null) {
|
||||
// Log.d(TAG, "call: " + "Empty photos");
|
||||
} else if (cursor.moveToFirst()) {
|
||||
String albumItem_all_name = getAllAlbumName(context);
|
||||
String albumItem_video_name =
|
||||
context.getString(R.string.selector_folder_video_easy_photos);
|
||||
|
||||
int albumNameCol = cursor.getColumnIndex(MediaStore.MediaColumns.BUCKET_DISPLAY_NAME);
|
||||
int durationCol = cursor.getColumnIndex(MediaStore.MediaColumns.DURATION);
|
||||
int WidthCol = 0;
|
||||
int HeightCol = 0;
|
||||
int orientationCol = -1;
|
||||
if (canReadWidth && Setting.useWidth) {
|
||||
WidthCol = cursor.getColumnIndex(MediaStore.MediaColumns.WIDTH);
|
||||
HeightCol = cursor.getColumnIndex(MediaStore.MediaColumns.HEIGHT);
|
||||
orientationCol = cursor.getColumnIndex(MediaStore.MediaColumns.ORIENTATION);
|
||||
}
|
||||
boolean hasTime = durationCol > 0;
|
||||
|
||||
do {
|
||||
long id = cursor.getLong(0);
|
||||
String path = cursor.getString(1);
|
||||
String name = cursor.getString(2);
|
||||
long dateTime = cursor.getLong(3);
|
||||
String type = cursor.getString(4);
|
||||
long size = cursor.getLong(5);
|
||||
long duration = 0;
|
||||
|
||||
|
||||
if (TextUtils.isEmpty(path) || TextUtils.isEmpty(type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (size < Setting.minSize) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean isVideo = type.contains(Type.VIDEO);// 是否是视频
|
||||
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int orientation = 0;
|
||||
if (isVideo) {
|
||||
if (hasTime)
|
||||
duration = cursor.getLong(durationCol);
|
||||
if (duration <= Setting.videoMinSecond || duration >= Setting.videoMaxSecond) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (orientationCol != -1) {
|
||||
orientation = cursor.getInt(orientationCol);
|
||||
}
|
||||
if (!Setting.showGif) {
|
||||
if (path.endsWith(Type.GIF) || type.endsWith(Type.GIF)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (Setting.useWidth) {
|
||||
if (canReadWidth) {
|
||||
width = cursor.getInt(WidthCol);
|
||||
height = cursor.getInt(HeightCol);
|
||||
}
|
||||
if (width == 0 || height == 0) {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFile(path, options);
|
||||
width = options.outWidth;
|
||||
height = options.outHeight;
|
||||
}
|
||||
|
||||
if (orientation == 90 || orientation == 270) {
|
||||
int temp = width;
|
||||
width = height;
|
||||
height = temp;
|
||||
}
|
||||
|
||||
if (width < Setting.minWidth || height < Setting.minHeight) {
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Uri uri = ContentUris.withAppendedId(isVideo ?
|
||||
MediaStore.Video.Media.getContentUri("external") :
|
||||
MediaStore.Images.Media.getContentUri("external"), id);
|
||||
|
||||
//某些机型,特定情况下三方应用或用户操作删除媒体文件时,没有通知媒体库,导致媒体库表中还有其数据,但真实文件已经不存在
|
||||
File file = new File(path);
|
||||
if (!file.isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Photo imageItem = new Photo(name, uri, path, dateTime, width, height, orientation
|
||||
, size, duration, type);
|
||||
if (!Setting.selectedPhotos.isEmpty()) {
|
||||
int selectSize = Setting.selectedPhotos.size();
|
||||
for (int i = 0; i < selectSize; i++) {
|
||||
Photo selectedPhoto = Setting.selectedPhotos.get(i);
|
||||
if (path.equals(selectedPhoto.path)) {
|
||||
imageItem.selectedOriginal = Setting.selectedOriginal;
|
||||
Result.addPhoto(imageItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化“全部”专辑
|
||||
if (album.isEmpty()) {
|
||||
// 用第一个图片作为专辑的封面
|
||||
album.addAlbumItem(albumItem_all_name, "", path, uri);
|
||||
}
|
||||
// 把图片全部放进“全部”专辑
|
||||
album.getAlbumItem(albumItem_all_name).addImageItem(imageItem);
|
||||
|
||||
if (Setting.showVideo && isVideo && !albumItem_video_name.equals(albumItem_all_name)) {
|
||||
album.addAlbumItem(albumItem_video_name, "", path, uri);
|
||||
album.getAlbumItem(albumItem_video_name).addImageItem(imageItem);
|
||||
}
|
||||
|
||||
// 添加当前图片的专辑到专辑模型实体中
|
||||
String albumName;
|
||||
String folderPath;
|
||||
if (albumNameCol > 0) {
|
||||
albumName = cursor.getString(albumNameCol);
|
||||
folderPath = albumName;
|
||||
} else {
|
||||
File parentFile = new File(path).getParentFile();
|
||||
if (null == parentFile) {
|
||||
continue;
|
||||
}
|
||||
folderPath = parentFile.getAbsolutePath();
|
||||
albumName = StringUtils.getLastPathSegment(folderPath);
|
||||
}
|
||||
|
||||
album.addAlbumItem(albumName, folderPath, path, uri);
|
||||
album.getAlbumItem(albumName).addImageItem(imageItem);
|
||||
} while (cursor.moveToNext() && canRun);
|
||||
cursor.close();
|
||||
if (!Setting.selectedPhotos.isEmpty() && Setting.isSequentialSelectedPhotos) {
|
||||
int selectSize = Setting.selectedPhotos.size();
|
||||
int photoSize = Result.photos.size();
|
||||
ArrayList<Photo> tempList = new ArrayList<>(photoSize);
|
||||
for (int i = 0; i < selectSize; i++) {
|
||||
for (int j = 0; j < photoSize; j++) {
|
||||
if (Result.photos.get(j).path.equals(Setting.selectedPhotos.get(i).path)) {
|
||||
tempList.add(i, Result.photos.get(j));
|
||||
}
|
||||
}
|
||||
}
|
||||
Result.photos = tempList;
|
||||
}
|
||||
}
|
||||
// Log.d(TAG, "initAlbum: " + (System.currentTimeMillis() - now));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全部专辑名
|
||||
*
|
||||
* @return 专辑名
|
||||
*/
|
||||
public String getAllAlbumName(Context context) {
|
||||
String albumItem_all_name =
|
||||
context.getString(R.string.selector_folder_all_video_photo_easy_photos);
|
||||
if (Setting.isOnlyVideo()) {
|
||||
albumItem_all_name = context.getString(R.string.selector_folder_video_easy_photos);
|
||||
} else if (!Setting.showVideo) {
|
||||
//不显示视频
|
||||
albumItem_all_name = context.getString(R.string.selector_folder_all_easy_photos);
|
||||
}
|
||||
return albumItem_all_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前专辑项目的图片集
|
||||
*
|
||||
* @return 当前专辑项目的图片集
|
||||
*/
|
||||
public ArrayList<Photo> getCurrAlbumItemPhotos(int currAlbumItemIndex) {
|
||||
return album.getAlbumItem(currAlbumItemIndex).photos;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取专辑项目集
|
||||
*
|
||||
* @return 专辑项目集
|
||||
*/
|
||||
public ArrayList<AlbumItem> getAlbumItems() {
|
||||
return album.albumItems;
|
||||
}
|
||||
|
||||
public interface CallBack {
|
||||
void onAlbumWorkedCallBack();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取projections
|
||||
*/
|
||||
public String[] getProjections() {
|
||||
if (null == projections || projections.length == 0) {
|
||||
if (Setting.useWidth) {
|
||||
projections = new String[]{MediaStore.Files.FileColumns._ID,
|
||||
MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME,
|
||||
MediaStore.MediaColumns.DATE_MODIFIED, MediaStore.MediaColumns.MIME_TYPE,
|
||||
MediaStore.MediaColumns.SIZE, MediaStore.MediaColumns.BUCKET_DISPLAY_NAME
|
||||
, MediaStore.MediaColumns.WIDTH, MediaStore.MediaColumns.HEIGHT,
|
||||
MediaStore.MediaColumns.ORIENTATION};
|
||||
} else {
|
||||
projections = new String[]{MediaStore.Files.FileColumns._ID,
|
||||
MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME,
|
||||
MediaStore.MediaColumns.DATE_MODIFIED, MediaStore.MediaColumns.MIME_TYPE,
|
||||
MediaStore.MediaColumns.SIZE, MediaStore.MediaColumns.BUCKET_DISPLAY_NAME};
|
||||
}
|
||||
}
|
||||
return projections;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.huantansheng.easyphotos.models.album.entity;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* 专辑模型实体类
|
||||
* Created by huan on 2017/10/20.
|
||||
*/
|
||||
|
||||
public class Album {
|
||||
public ArrayList<AlbumItem> albumItems;
|
||||
private LinkedHashMap<String, AlbumItem> hasAlbumItems;//用于记录专辑项目
|
||||
|
||||
public Album() {
|
||||
albumItems = new ArrayList<>();
|
||||
hasAlbumItems = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
private void addAlbumItem(AlbumItem albumItem) {
|
||||
this.hasAlbumItems.put(albumItem.name, albumItem);
|
||||
this.albumItems.add(albumItem);
|
||||
}
|
||||
|
||||
public void addAlbumItem(String name, String folderPath, String coverImagePath, Uri coverImageUri) {
|
||||
if (null == hasAlbumItems.get(name)) {
|
||||
addAlbumItem(new AlbumItem(name, folderPath, coverImagePath,coverImageUri));
|
||||
}
|
||||
}
|
||||
|
||||
public AlbumItem getAlbumItem(String name) {
|
||||
return hasAlbumItems.get(name);
|
||||
}
|
||||
|
||||
public AlbumItem getAlbumItem(int currIndex) {
|
||||
return albumItems.get(currIndex);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return albumItems.isEmpty();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
albumItems.clear();
|
||||
hasAlbumItems.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.huantansheng.easyphotos.models.album.entity;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* 专辑项目实体类
|
||||
* Created by huan on 2017/10/20.
|
||||
*/
|
||||
|
||||
public class AlbumItem {
|
||||
public String name;
|
||||
public String folderPath;
|
||||
public String coverImagePath;
|
||||
public Uri coverImageUri;
|
||||
public ArrayList<Photo> photos;
|
||||
|
||||
AlbumItem(String name, String folderPath, String coverImagePath, Uri coverImageUri) {
|
||||
this.name = name;
|
||||
this.folderPath = folderPath;
|
||||
this.coverImagePath = coverImagePath;
|
||||
this.coverImageUri = coverImageUri;
|
||||
this.photos = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void addImageItem(Photo imageItem) {
|
||||
this.photos.add(imageItem);
|
||||
}
|
||||
|
||||
public void addImageItem(int index, Photo imageItem) {
|
||||
this.photos.add(index, imageItem);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.huantansheng.easyphotos.models.album.entity;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* 图片item实体类
|
||||
* Created by huan on 2017/10/20.
|
||||
*/
|
||||
|
||||
public class Photo implements Parcelable {
|
||||
private static final String TAG = "Photo";
|
||||
public Uri uri;//图片Uri
|
||||
public String name;//图片名称
|
||||
public String path;//图片全路径
|
||||
public String type;//图片类型
|
||||
public int width;//图片宽度
|
||||
public int height;//图片高度
|
||||
public int orientation;//图片旋转角度
|
||||
public long size;//图片文件大小,单位:Bytes
|
||||
public long duration;//视频时长,单位:毫秒
|
||||
public long time;//图片拍摄的时间戳,单位:毫秒
|
||||
public boolean selected;//是否被选中,内部使用,无需关心
|
||||
public boolean selectedOriginal;//用户选择时是否选择了原图选项
|
||||
|
||||
public Photo(String name, Uri uri, String path, long time, int width, int height,int orientation, long size, long duration, String type) {
|
||||
this.name = name;
|
||||
this.uri = uri;
|
||||
this.path = path;
|
||||
this.time = time;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.orientation = orientation;
|
||||
this.type = type;
|
||||
this.size = size;
|
||||
this.duration = duration;
|
||||
this.selected = false;
|
||||
this.selectedOriginal = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
try {
|
||||
Photo other = (Photo) o;
|
||||
return this.path.equalsIgnoreCase(other.path);
|
||||
} catch (ClassCastException e) {
|
||||
Log.e(TAG, "equals: " + Log.getStackTraceString(e));
|
||||
}
|
||||
return super.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Photo{" +
|
||||
"name='" + name + '\'' +
|
||||
", uri='" + uri.toString() + '\'' +
|
||||
", path='" + path + '\'' +
|
||||
", time=" + time + '\'' +
|
||||
", minWidth=" + width + '\'' +
|
||||
", minHeight=" + height +
|
||||
", orientation=" + orientation +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeParcelable(this.uri, flags);
|
||||
dest.writeString(this.name);
|
||||
dest.writeString(this.path);
|
||||
dest.writeString(this.type);
|
||||
dest.writeInt(this.width);
|
||||
dest.writeInt(this.height);
|
||||
dest.writeInt(this.orientation);
|
||||
dest.writeLong(this.size);
|
||||
dest.writeLong(this.duration);
|
||||
dest.writeLong(this.time);
|
||||
dest.writeByte(this.selected ? (byte) 1 : (byte) 0);
|
||||
dest.writeByte(this.selectedOriginal ? (byte) 1 : (byte) 0);
|
||||
}
|
||||
|
||||
protected Photo(Parcel in) {
|
||||
this.uri = in.readParcelable(Uri.class.getClassLoader());
|
||||
this.name = in.readString();
|
||||
this.path = in.readString();
|
||||
this.type = in.readString();
|
||||
this.width = in.readInt();
|
||||
this.height = in.readInt();
|
||||
this.orientation = in.readInt();
|
||||
this.size = in.readLong();
|
||||
this.duration = in.readLong();
|
||||
this.time = in.readLong();
|
||||
this.selected = in.readByte() != 0;
|
||||
this.selectedOriginal = in.readByte() != 0;
|
||||
}
|
||||
|
||||
public static final Creator<Photo> CREATOR = new Creator<Photo>() {
|
||||
@Override
|
||||
public Photo createFromParcel(Parcel source) {
|
||||
return new Photo(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Photo[] newArray(int size) {
|
||||
return new Photo[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle;
|
||||
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.RectF;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
public interface Area {
|
||||
float left();
|
||||
|
||||
float top();
|
||||
|
||||
float right();
|
||||
|
||||
float bottom();
|
||||
|
||||
float centerX();
|
||||
|
||||
float centerY();
|
||||
|
||||
float width();
|
||||
|
||||
float height();
|
||||
|
||||
PointF getCenterPoint();
|
||||
|
||||
boolean contains(PointF point);
|
||||
|
||||
boolean contains(float x, float y);
|
||||
|
||||
boolean contains(Line line);
|
||||
|
||||
Path getAreaPath();
|
||||
|
||||
RectF getAreaRect();
|
||||
|
||||
List<Line> getLines();
|
||||
|
||||
PointF[] getHandleBarPoints(Line line);
|
||||
|
||||
float radian();
|
||||
|
||||
void setRadian(float radian);
|
||||
|
||||
float getPaddingLeft();
|
||||
|
||||
float getPaddingTop();
|
||||
|
||||
float getPaddingRight();
|
||||
|
||||
float getPaddingBottom();
|
||||
|
||||
void setPadding(float padding);
|
||||
|
||||
void setPadding(float paddingLeft, float paddingTop, float paddingRight, float paddingBottom);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,353 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import com.huantansheng.easyphotos.R;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
public class DegreeSeekBar extends View {
|
||||
private static final String TAG = "DegreeSeekBar";
|
||||
private Paint mTextPaint;
|
||||
private Paint mCirclePaint;
|
||||
private Paint.FontMetricsInt mFontMetrics;
|
||||
private int mBaseLine;
|
||||
private float[] mTextWidths;
|
||||
|
||||
private final Rect mCanvasClipBounds = new Rect();
|
||||
|
||||
private ScrollingListener mScrollingListener;
|
||||
private float mLastTouchedPosition;
|
||||
|
||||
private Paint mPointPaint;
|
||||
private float mPointMargin;
|
||||
|
||||
private boolean mScrollStarted;
|
||||
private int mTotalScrollDistance;
|
||||
|
||||
private Path mIndicatorPath = new Path();
|
||||
|
||||
private int mCurrentDegrees = 0;
|
||||
private int mPointCount = 51;
|
||||
|
||||
private int mPointColor;
|
||||
private int mTextColor;
|
||||
private int mCenterTextColor;
|
||||
|
||||
//阻尼系数的倒数
|
||||
private float mDragFactor = 2.1f;
|
||||
|
||||
private int mMinReachableDegrees = -45;
|
||||
private int mMaxReachableDegrees = 45;
|
||||
|
||||
private String suffix = "";
|
||||
|
||||
public DegreeSeekBar(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public DegreeSeekBar(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public DegreeSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public DegreeSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
mPointColor = ContextCompat.getColor(getContext(), R.color.easy_photos_fg_primary);
|
||||
mTextColor = ContextCompat.getColor(getContext(), R.color.easy_photos_fg_primary);
|
||||
mCenterTextColor = ContextCompat.getColor(getContext(), R.color.easy_photos_fg_accent);
|
||||
|
||||
mPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mPointPaint.setStyle(Paint.Style.STROKE);
|
||||
mPointPaint.setColor(mPointColor);
|
||||
mPointPaint.setStrokeWidth(2);
|
||||
|
||||
mTextPaint = new Paint();
|
||||
mTextPaint.setColor(mTextColor);
|
||||
mTextPaint.setStyle(Paint.Style.STROKE);
|
||||
mTextPaint.setAntiAlias(true);
|
||||
mTextPaint.setTextSize(24f);
|
||||
mTextPaint.setTextAlign(Paint.Align.LEFT);
|
||||
mTextPaint.setAlpha(100);
|
||||
|
||||
mFontMetrics = mTextPaint.getFontMetricsInt();
|
||||
|
||||
mTextWidths = new float[1];
|
||||
mTextPaint.getTextWidths("0", mTextWidths);
|
||||
|
||||
mCirclePaint = new Paint();
|
||||
mCirclePaint.setStyle(Paint.Style.FILL);
|
||||
mCirclePaint.setAlpha(255);
|
||||
mCirclePaint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
mPointMargin = (float) w / mPointCount;
|
||||
|
||||
mBaseLine = (h - mFontMetrics.bottom + mFontMetrics.top) / 2 - mFontMetrics.top;
|
||||
|
||||
mIndicatorPath.moveTo(w / 2, h / 2 + mFontMetrics.top / 2 - 18);
|
||||
mIndicatorPath.rLineTo(-8, -8);
|
||||
mIndicatorPath.rLineTo(16, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mLastTouchedPosition = event.getX();
|
||||
if (!mScrollStarted) {
|
||||
mScrollStarted = true;
|
||||
if (mScrollingListener != null) {
|
||||
mScrollingListener.onScrollStart();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
mScrollStarted = false;
|
||||
if (mScrollingListener != null) {
|
||||
mScrollingListener.onScrollEnd();
|
||||
}
|
||||
invalidate();
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
float distance = event.getX() - mLastTouchedPosition;
|
||||
if (mCurrentDegrees >= mMaxReachableDegrees && distance < 0) {
|
||||
mCurrentDegrees = mMaxReachableDegrees;
|
||||
invalidate();
|
||||
break;
|
||||
}
|
||||
if (mCurrentDegrees <= mMinReachableDegrees && distance > 0) {
|
||||
mCurrentDegrees = mMinReachableDegrees;
|
||||
invalidate();
|
||||
break;
|
||||
}
|
||||
if (distance != 0) {
|
||||
onScrollEvent(event, distance);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
canvas.getClipBounds(mCanvasClipBounds);
|
||||
|
||||
int zeroIndex = mPointCount / 2 + (0 - mCurrentDegrees) / 2;
|
||||
mPointPaint.setColor(mPointColor);
|
||||
for (int i = 0; i < mPointCount; i++) {
|
||||
|
||||
if (i > zeroIndex - Math.abs(mMinReachableDegrees) / 2
|
||||
&& i < zeroIndex + Math.abs(mMaxReachableDegrees) / 2
|
||||
&& mScrollStarted) {
|
||||
mPointPaint.setAlpha(255);
|
||||
} else {
|
||||
mPointPaint.setAlpha(100);
|
||||
}
|
||||
|
||||
if (i > mPointCount / 2 - 8
|
||||
&& i < mPointCount / 2 + 8
|
||||
&& i > zeroIndex - Math.abs(mMinReachableDegrees) / 2
|
||||
&& i < zeroIndex + Math.abs(mMaxReachableDegrees) / 2) {
|
||||
if (mScrollStarted) {
|
||||
mPointPaint.setAlpha(Math.abs(mPointCount / 2 - i) * 255 / 8);
|
||||
} else {
|
||||
mPointPaint.setAlpha(Math.abs(mPointCount / 2 - i) * 100 / 8);
|
||||
}
|
||||
}
|
||||
|
||||
canvas.drawPoint(mCanvasClipBounds.centerX() + (i - mPointCount / 2) * mPointMargin,
|
||||
mCanvasClipBounds.centerY(), mPointPaint);
|
||||
|
||||
if (mCurrentDegrees != 0 && i == zeroIndex) {
|
||||
if (mScrollStarted) {
|
||||
mTextPaint.setAlpha(255);
|
||||
} else {
|
||||
mTextPaint.setAlpha(192);
|
||||
}
|
||||
mPointPaint.setStrokeWidth(4);
|
||||
canvas.drawPoint((mCanvasClipBounds.centerX() + (i - mPointCount / 2) * mPointMargin),
|
||||
mCanvasClipBounds.centerY(), mPointPaint);
|
||||
mPointPaint.setStrokeWidth(2);
|
||||
mTextPaint.setAlpha(100);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = -180; i <= 180; i += 15) {
|
||||
if (i >= mMinReachableDegrees && i <= mMaxReachableDegrees) {
|
||||
drawDegreeText(i, canvas, true);
|
||||
} else {
|
||||
drawDegreeText(i, canvas, false);
|
||||
}
|
||||
}
|
||||
|
||||
mTextPaint.setTextSize(28f);
|
||||
mTextPaint.setAlpha(255);
|
||||
mTextPaint.setColor(mCenterTextColor);
|
||||
|
||||
if (mCurrentDegrees >= 10) {
|
||||
canvas.drawText(mCurrentDegrees + suffix, getWidth() / 2 - mTextWidths[0], mBaseLine,
|
||||
mTextPaint);
|
||||
} else if (mCurrentDegrees <= -10) {
|
||||
canvas.drawText(mCurrentDegrees + suffix, getWidth() / 2 - mTextWidths[0] / 2 * 3, mBaseLine,
|
||||
mTextPaint);
|
||||
} else if (mCurrentDegrees < 0) {
|
||||
canvas.drawText(mCurrentDegrees + suffix, getWidth() / 2 - mTextWidths[0], mBaseLine,
|
||||
mTextPaint);
|
||||
} else {
|
||||
canvas.drawText(mCurrentDegrees + suffix, getWidth() / 2 - mTextWidths[0] / 2, mBaseLine,
|
||||
mTextPaint);
|
||||
}
|
||||
|
||||
mTextPaint.setAlpha(100);
|
||||
mTextPaint.setTextSize(24f);
|
||||
mTextPaint.setColor(mTextColor);
|
||||
//画中心三角
|
||||
mCirclePaint.setColor(mCenterTextColor);
|
||||
canvas.drawPath(mIndicatorPath, mCirclePaint);
|
||||
mCirclePaint.setColor(mCenterTextColor);
|
||||
}
|
||||
|
||||
private void drawDegreeText(int degrees, Canvas canvas, boolean canReach) {
|
||||
if (canReach) {
|
||||
if (mScrollStarted) {
|
||||
mTextPaint.setAlpha(Math.min(255, Math.abs(degrees - mCurrentDegrees) * 255 / 15));
|
||||
if (Math.abs(degrees - mCurrentDegrees) <= 7) {
|
||||
mTextPaint.setAlpha(0);
|
||||
}
|
||||
} else {
|
||||
mTextPaint.setAlpha(100);
|
||||
if (Math.abs(degrees - mCurrentDegrees) <= 7) {
|
||||
mTextPaint.setAlpha(0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mTextPaint.setAlpha(100);
|
||||
}
|
||||
if (degrees == 0) {
|
||||
if (Math.abs(mCurrentDegrees) >= 15 && !mScrollStarted) {
|
||||
mTextPaint.setAlpha(180);
|
||||
}
|
||||
canvas.drawText("0°",
|
||||
getWidth() / 2 - mTextWidths[0] / 2 - mCurrentDegrees / 2 * mPointMargin,
|
||||
getHeight() / 2 - 10, mTextPaint);
|
||||
} else {
|
||||
canvas.drawText(degrees + suffix, getWidth() / 2 + mPointMargin * degrees / 2
|
||||
- mTextWidths[0] / 2 * 3
|
||||
- mCurrentDegrees / 2 * mPointMargin, getHeight() / 2 - 10, mTextPaint);
|
||||
}
|
||||
}
|
||||
|
||||
private void onScrollEvent(MotionEvent event, float distance) {
|
||||
mTotalScrollDistance -= distance;
|
||||
postInvalidate();
|
||||
mLastTouchedPosition = event.getX();
|
||||
mCurrentDegrees = (int) ((mTotalScrollDistance * mDragFactor) / mPointMargin);
|
||||
if (mScrollingListener != null) {
|
||||
mScrollingListener.onScroll(mCurrentDegrees);
|
||||
}
|
||||
}
|
||||
|
||||
public void setDegreeRange(int min, int max) {
|
||||
if (min > max) {
|
||||
Log.e(TAG, "setDegreeRange: error, max must greater than min");
|
||||
} else {
|
||||
mMinReachableDegrees = min;
|
||||
mMaxReachableDegrees = max;
|
||||
|
||||
if (mCurrentDegrees > mMaxReachableDegrees || mCurrentDegrees < mMinReachableDegrees) {
|
||||
mCurrentDegrees = (mMinReachableDegrees + mMaxReachableDegrees) / 2;
|
||||
}
|
||||
mTotalScrollDistance = (int) (mCurrentDegrees * mPointMargin / mDragFactor);
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void setCurrentDegrees(int degrees) {
|
||||
if (degrees <= mMaxReachableDegrees && degrees >= mMinReachableDegrees) {
|
||||
mCurrentDegrees = degrees;
|
||||
mTotalScrollDistance = (int) (degrees * mPointMargin / mDragFactor);
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void setScrollingListener(ScrollingListener scrollingListener) {
|
||||
mScrollingListener = scrollingListener;
|
||||
}
|
||||
|
||||
public int getPointColor() {
|
||||
return mPointColor;
|
||||
}
|
||||
|
||||
public void setPointColor(int pointColor) {
|
||||
mPointColor = pointColor;
|
||||
mPointPaint.setColor(mPointColor);
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public int getTextColor() {
|
||||
return mTextColor;
|
||||
}
|
||||
|
||||
public void setTextColor(int textColor) {
|
||||
mTextColor = textColor;
|
||||
mTextPaint.setColor(textColor);
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public int getCenterTextColor() {
|
||||
return mCenterTextColor;
|
||||
}
|
||||
|
||||
public void setCenterTextColor(int centerTextColor) {
|
||||
mCenterTextColor = centerTextColor;
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public float getDragFactor() {
|
||||
return mDragFactor;
|
||||
}
|
||||
|
||||
public void setDragFactor(float dragFactor) {
|
||||
mDragFactor = dragFactor;
|
||||
}
|
||||
|
||||
public void setSuffix(String suffix) {
|
||||
this.suffix = suffix;
|
||||
}
|
||||
|
||||
public interface ScrollingListener {
|
||||
|
||||
void onScrollStart();
|
||||
|
||||
void onScroll(int currentDegrees);
|
||||
|
||||
void onScrollEnd();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle;
|
||||
|
||||
import android.graphics.PointF;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
public interface Line {
|
||||
enum Direction {
|
||||
HORIZONTAL, VERTICAL
|
||||
}
|
||||
|
||||
float length();
|
||||
|
||||
PointF startPoint();
|
||||
|
||||
PointF endPoint();
|
||||
|
||||
Line lowerLine();
|
||||
|
||||
Line upperLine();
|
||||
|
||||
Line attachStartLine();
|
||||
|
||||
Line attachEndLine();
|
||||
|
||||
void setLowerLine(Line lowerLine);
|
||||
|
||||
void setUpperLine(Line upperLine);
|
||||
|
||||
Direction direction();
|
||||
|
||||
float slope();
|
||||
|
||||
boolean contains(float x, float y, float extra);
|
||||
|
||||
void prepareMove();
|
||||
|
||||
boolean move(float offset, float extra);
|
||||
|
||||
void update(float layoutWidth, float layoutHeight);
|
||||
|
||||
float minX();
|
||||
|
||||
float maxX();
|
||||
|
||||
float minY();
|
||||
|
||||
float maxY();
|
||||
|
||||
void offset(float x, float y);
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle;
|
||||
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static java.lang.Math.round;
|
||||
|
||||
/**
|
||||
* some useful matrix operation methods
|
||||
*
|
||||
* @author wupanjie
|
||||
*/
|
||||
public class MatrixUtils {
|
||||
private MatrixUtils() {
|
||||
//no instance
|
||||
}
|
||||
|
||||
private static final float[] sMatrixValues = new float[9];
|
||||
private static final Matrix sTempMatrix = new Matrix();
|
||||
|
||||
/**
|
||||
* This method calculates scale value for given Matrix object.
|
||||
*/
|
||||
public static float getMatrixScale(Matrix matrix) {
|
||||
return (float) Math.sqrt(Math.pow(getMatrixValue(matrix, Matrix.MSCALE_X), 2) + Math.pow(
|
||||
getMatrixValue(matrix, Matrix.MSKEW_Y), 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method calculates rotation angle for given Matrix object.
|
||||
*/
|
||||
public static float getMatrixAngle(Matrix matrix) {
|
||||
return (float) -(Math.atan2(getMatrixValue(matrix, Matrix.MSKEW_X),
|
||||
getMatrixValue(matrix, Matrix.MSCALE_X)) * (180 / Math.PI));
|
||||
}
|
||||
|
||||
public static float getMatrixValue(Matrix matrix, int valueIndex) {
|
||||
matrix.getValues(sMatrixValues);
|
||||
return sMatrixValues[valueIndex];
|
||||
}
|
||||
|
||||
public static float getMinMatrixScale(PuzzlePiece piece) {
|
||||
if (piece != null) {
|
||||
|
||||
sTempMatrix.reset();
|
||||
sTempMatrix.setRotate(-piece.getMatrixAngle());
|
||||
|
||||
float[] unrotatedCropBoundsCorners = getCornersFromRect(piece.getArea().getAreaRect());
|
||||
|
||||
sTempMatrix.mapPoints(unrotatedCropBoundsCorners);
|
||||
|
||||
RectF unrotatedCropRect = trapToRect(unrotatedCropBoundsCorners);
|
||||
|
||||
return Math.max(unrotatedCropRect.width() / piece.getWidth(),
|
||||
unrotatedCropRect.height() / piece.getHeight());
|
||||
}
|
||||
|
||||
return 1f;
|
||||
}
|
||||
|
||||
//判断剪裁框是否在图片内
|
||||
static boolean judgeIsImageContainsBorder(PuzzlePiece piece, float rotateDegrees) {
|
||||
sTempMatrix.reset();
|
||||
sTempMatrix.setRotate(-rotateDegrees);
|
||||
float[] unrotatedWrapperCorner = new float[8];
|
||||
float[] unrotateBorderCorner = new float[8];
|
||||
sTempMatrix.mapPoints(unrotatedWrapperCorner, piece.getCurrentDrawablePoints());
|
||||
sTempMatrix.mapPoints(unrotateBorderCorner, getCornersFromRect(piece.getArea().getAreaRect()));
|
||||
|
||||
return trapToRect(unrotatedWrapperCorner).contains(trapToRect(unrotateBorderCorner));
|
||||
}
|
||||
|
||||
static float[] calculateImageIndents(PuzzlePiece piece) {
|
||||
if (piece == null) return new float[] { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
|
||||
sTempMatrix.reset();
|
||||
sTempMatrix.setRotate(-piece.getMatrixAngle());
|
||||
|
||||
final float[] currentImageCorners = piece.getCurrentDrawablePoints();
|
||||
final float[] unrotatedImageCorners =
|
||||
Arrays.copyOf(currentImageCorners, currentImageCorners.length);
|
||||
final float[] unrotatedCropBoundsCorners = getCornersFromRect(piece.getArea().getAreaRect());
|
||||
|
||||
sTempMatrix.mapPoints(unrotatedImageCorners);
|
||||
sTempMatrix.mapPoints(unrotatedCropBoundsCorners);
|
||||
|
||||
RectF unrotatedImageRect = trapToRect(unrotatedImageCorners);
|
||||
RectF unrotatedCropRect = trapToRect(unrotatedCropBoundsCorners);
|
||||
|
||||
float deltaLeft = unrotatedImageRect.left - unrotatedCropRect.left;
|
||||
float deltaTop = unrotatedImageRect.top - unrotatedCropRect.top;
|
||||
float deltaRight = unrotatedImageRect.right - unrotatedCropRect.right;
|
||||
float deltaBottom = unrotatedImageRect.bottom - unrotatedCropRect.bottom;
|
||||
|
||||
float indents[] = new float[4];
|
||||
|
||||
indents[0] = (deltaLeft > 0) ? deltaLeft : 0;
|
||||
indents[1] = (deltaTop > 0) ? deltaTop : 0;
|
||||
indents[2] = (deltaRight < 0) ? deltaRight : 0;
|
||||
indents[3] = (deltaBottom < 0) ? deltaBottom : 0;
|
||||
|
||||
sTempMatrix.reset();
|
||||
sTempMatrix.setRotate(piece.getMatrixAngle());
|
||||
sTempMatrix.mapPoints(indents);
|
||||
|
||||
return indents;
|
||||
}
|
||||
|
||||
//计算包含给出点的最小矩形
|
||||
public static RectF trapToRect(float[] array) {
|
||||
RectF r = new RectF(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY,
|
||||
Float.NEGATIVE_INFINITY);
|
||||
int length = array.length;
|
||||
for (int i = 1; i < length; i += 2) {
|
||||
float x = round(array[i - 1] * 10) / 10.f;
|
||||
float y = round(array[i] * 10) / 10.f;
|
||||
r.left = (x < r.left) ? x : r.left;
|
||||
r.top = (y < r.top) ? y : r.top;
|
||||
r.right = (x > r.right) ? x : r.right;
|
||||
r.bottom = (y > r.bottom) ? y : r.bottom;
|
||||
}
|
||||
r.sort();
|
||||
return r;
|
||||
}
|
||||
|
||||
public static float[] getCornersFromRect(RectF r) {
|
||||
return new float[] {
|
||||
r.left, r.top, r.right, r.top, r.right, r.bottom, r.left, r.bottom
|
||||
};
|
||||
}
|
||||
|
||||
public static Matrix generateMatrix(PuzzlePiece piece, float extra) {
|
||||
return generateMatrix(piece.getArea(), piece.getDrawable(), extra);
|
||||
}
|
||||
|
||||
public static Matrix generateMatrix(Area area, Drawable drawable, float extraSize) {
|
||||
return generateCenterCropMatrix(area, drawable.getIntrinsicWidth(),
|
||||
drawable.getIntrinsicHeight(), extraSize);
|
||||
}
|
||||
|
||||
private static Matrix generateCenterCropMatrix(Area area, int width, int height,
|
||||
float extraSize) {
|
||||
final RectF rectF = area.getAreaRect();
|
||||
|
||||
Matrix matrix = new Matrix();
|
||||
|
||||
float offsetX = rectF.centerX() - width / 2;
|
||||
float offsetY = rectF.centerY() - height / 2;
|
||||
|
||||
matrix.postTranslate(offsetX, offsetY);
|
||||
|
||||
float scale;
|
||||
|
||||
if (width * rectF.height() > rectF.width() * height) {
|
||||
scale = (rectF.height() + extraSize) / height;
|
||||
} else {
|
||||
scale = (rectF.width() + extraSize) / width;
|
||||
}
|
||||
|
||||
matrix.postScale(scale, scale, rectF.centerX(), rectF.centerY());
|
||||
|
||||
return matrix;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle;
|
||||
|
||||
import android.graphics.RectF;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
public interface PuzzleLayout {
|
||||
void setOuterBounds(RectF bounds);
|
||||
|
||||
void layout();
|
||||
|
||||
int getAreaCount();
|
||||
|
||||
List<Line> getOuterLines();
|
||||
|
||||
List<Line> getLines();
|
||||
|
||||
Area getOuterArea();
|
||||
|
||||
void update();
|
||||
|
||||
void reset();
|
||||
|
||||
Area getArea(int position);
|
||||
|
||||
float width();
|
||||
|
||||
float height();
|
||||
|
||||
void setPadding(float padding);
|
||||
|
||||
float getPadding();
|
||||
|
||||
float getRadian();
|
||||
|
||||
void setRadian(float radian);
|
||||
|
||||
Info generateInfo();
|
||||
|
||||
void setColor(int color);
|
||||
|
||||
int getColor();
|
||||
|
||||
class Info {
|
||||
public static final int TYPE_STRAIGHT = 0;
|
||||
public static final int TYPE_SLANT = 1;
|
||||
|
||||
public int type;
|
||||
public ArrayList<Step> steps;
|
||||
public ArrayList<LineInfo> lineInfos;
|
||||
public float padding;
|
||||
public float radian;
|
||||
public int color;
|
||||
}
|
||||
|
||||
class Step {
|
||||
public static final int ADD_LINE = 0;
|
||||
public static final int ADD_CROSS = 1;
|
||||
public static final int CUT_EQUAL_PART_ONE = 2;
|
||||
public static final int CUT_EQUAL_PART_TWO = 3;
|
||||
public static final int CUT_SPIRAL = 4;
|
||||
|
||||
public int type;
|
||||
public int direction;
|
||||
public int position;
|
||||
public int part;
|
||||
public int hSize;
|
||||
public int vSize;
|
||||
}
|
||||
|
||||
class LineInfo {
|
||||
public float startX;
|
||||
public float startY;
|
||||
public float endX;
|
||||
public float endY;
|
||||
|
||||
public LineInfo(Line line){
|
||||
startX = line.startPoint().x;
|
||||
startY = line.startPoint().y;
|
||||
endX = line.endPoint().x;
|
||||
endY = line.endPoint().y;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,432 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Xfermode;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
|
||||
import static com.huantansheng.easyphotos.models.puzzle.MatrixUtils.calculateImageIndents;
|
||||
import static com.huantansheng.easyphotos.models.puzzle.MatrixUtils.getMinMatrixScale;
|
||||
import static com.huantansheng.easyphotos.models.puzzle.MatrixUtils.judgeIsImageContainsBorder;
|
||||
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess") public class PuzzlePiece {
|
||||
private static Xfermode SRC_IN = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
|
||||
|
||||
private Drawable drawable;
|
||||
private Matrix matrix;
|
||||
private Matrix previousMatrix;
|
||||
private Area area;
|
||||
private Rect drawableBounds;
|
||||
private float[] drawablePoints;
|
||||
private float[] mappedDrawablePoints;
|
||||
|
||||
private float previousMoveX;
|
||||
private float previousMoveY;
|
||||
|
||||
private final RectF mappedBounds;
|
||||
private final PointF centerPoint;
|
||||
private final PointF mappedCenterPoint;
|
||||
|
||||
private ValueAnimator animator;
|
||||
private int duration = 300;
|
||||
private Matrix tempMatrix;
|
||||
|
||||
PuzzlePiece(Drawable drawable, Area area, Matrix matrix) {
|
||||
this.drawable = drawable;
|
||||
this.area = area;
|
||||
this.matrix = matrix;
|
||||
this.previousMatrix = new Matrix();
|
||||
this.drawableBounds = new Rect(0, 0, getWidth(), getHeight());
|
||||
this.drawablePoints = new float[] {
|
||||
0f, 0f, getWidth(), 0f, getWidth(), getHeight(), 0f, getHeight()
|
||||
};
|
||||
this.mappedDrawablePoints = new float[8];
|
||||
|
||||
this.mappedBounds = new RectF();
|
||||
this.centerPoint = new PointF(area.centerX(), area.centerY());
|
||||
this.mappedCenterPoint = new PointF();
|
||||
|
||||
this.animator = ValueAnimator.ofFloat(0f, 1f);
|
||||
this.animator.setInterpolator(new DecelerateInterpolator());
|
||||
|
||||
this.tempMatrix = new Matrix();
|
||||
}
|
||||
|
||||
void draw(Canvas canvas) {
|
||||
draw(canvas, 255, true);
|
||||
}
|
||||
|
||||
void draw(Canvas canvas, int alpha) {
|
||||
draw(canvas, alpha, false);
|
||||
}
|
||||
|
||||
private void draw(Canvas canvas, int alpha, boolean needClip) {
|
||||
if (drawable instanceof BitmapDrawable){
|
||||
int saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
|
||||
|
||||
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
|
||||
Paint paint= ((BitmapDrawable) drawable).getPaint();
|
||||
|
||||
paint.setColor(Color.WHITE);
|
||||
paint.setAlpha(alpha);
|
||||
if (needClip) {
|
||||
canvas.drawPath(area.getAreaPath(), paint);
|
||||
paint.setXfermode(SRC_IN);
|
||||
}
|
||||
canvas.drawBitmap(bitmap,matrix,paint);
|
||||
paint.setXfermode(null);
|
||||
|
||||
canvas.restoreToCount(saved);
|
||||
}else {
|
||||
canvas.save();
|
||||
if (needClip) {
|
||||
canvas.clipPath(area.getAreaPath());
|
||||
}
|
||||
canvas.concat(matrix);
|
||||
drawable.setBounds(drawableBounds);
|
||||
drawable.setAlpha(alpha);
|
||||
drawable.draw(canvas);
|
||||
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
|
||||
public Area getArea() {
|
||||
return area;
|
||||
}
|
||||
|
||||
public void setDrawable(Drawable drawable) {
|
||||
this.drawable = drawable;
|
||||
this.drawableBounds = new Rect(0, 0, getWidth(), getHeight());
|
||||
this.drawablePoints = new float[] {
|
||||
0f, 0f, getWidth(), 0f, getWidth(), getHeight(), 0f, getHeight()
|
||||
};
|
||||
}
|
||||
|
||||
public Drawable getDrawable() {
|
||||
return drawable;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return drawable.getIntrinsicWidth();
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return drawable.getIntrinsicHeight();
|
||||
}
|
||||
|
||||
public boolean contains(float x, float y) {
|
||||
return area.contains(x, y);
|
||||
}
|
||||
|
||||
public boolean contains(Line line) {
|
||||
return area.contains(line);
|
||||
}
|
||||
|
||||
public Rect getDrawableBounds() {
|
||||
return drawableBounds;
|
||||
}
|
||||
|
||||
void setPreviousMoveX(float previousMoveX) {
|
||||
this.previousMoveX = previousMoveX;
|
||||
}
|
||||
|
||||
void setPreviousMoveY(float previousMoveY) {
|
||||
this.previousMoveY = previousMoveY;
|
||||
}
|
||||
|
||||
private RectF getCurrentDrawableBounds() {
|
||||
matrix.mapRect(mappedBounds, new RectF(drawableBounds));
|
||||
return mappedBounds;
|
||||
}
|
||||
|
||||
private PointF getCurrentDrawableCenterPoint() {
|
||||
getCurrentDrawableBounds();
|
||||
mappedCenterPoint.x = mappedBounds.centerX();
|
||||
mappedCenterPoint.y = mappedBounds.centerY();
|
||||
return mappedCenterPoint;
|
||||
}
|
||||
|
||||
public PointF getAreaCenterPoint() {
|
||||
centerPoint.x = area.centerX();
|
||||
centerPoint.y = area.centerY();
|
||||
return centerPoint;
|
||||
}
|
||||
|
||||
private float getMatrixScale() {
|
||||
return MatrixUtils.getMatrixScale(matrix);
|
||||
}
|
||||
|
||||
float getMatrixAngle() {
|
||||
return MatrixUtils.getMatrixAngle(matrix);
|
||||
}
|
||||
|
||||
float[] getCurrentDrawablePoints() {
|
||||
matrix.mapPoints(mappedDrawablePoints, drawablePoints);
|
||||
return mappedDrawablePoints;
|
||||
}
|
||||
|
||||
boolean isFilledArea() {
|
||||
RectF bounds = getCurrentDrawableBounds();
|
||||
return !(bounds.left > area.left()
|
||||
|| bounds.top > area.top()
|
||||
|| bounds.right < area.right()
|
||||
|| bounds.bottom < area.bottom());
|
||||
}
|
||||
|
||||
boolean canFilledArea() {
|
||||
float scale = MatrixUtils.getMatrixScale(matrix);
|
||||
float minScale = getMinMatrixScale(this);
|
||||
return scale >= minScale;
|
||||
}
|
||||
|
||||
void record() {
|
||||
previousMatrix.set(matrix);
|
||||
}
|
||||
|
||||
void translate(float offsetX, float offsetY) {
|
||||
matrix.set(previousMatrix);
|
||||
postTranslate(offsetX, offsetY);
|
||||
}
|
||||
|
||||
private void zoom(float scaleX, float scaleY, PointF midPoint) {
|
||||
matrix.set(previousMatrix);
|
||||
postScale(scaleX, scaleY, midPoint);
|
||||
}
|
||||
|
||||
void zoomAndTranslate(float scaleX, float scaleY, PointF midPoint, float offsetX, float offsetY) {
|
||||
matrix.set(previousMatrix);
|
||||
postTranslate(offsetX, offsetY);
|
||||
postScale(scaleX, scaleY, midPoint);
|
||||
}
|
||||
|
||||
void set(Matrix matrix) {
|
||||
this.matrix.set(matrix);
|
||||
moveToFillArea(null);
|
||||
}
|
||||
|
||||
void postTranslate(float x, float y) {
|
||||
this.matrix.postTranslate(x, y);
|
||||
}
|
||||
|
||||
void postScale(float scaleX, float scaleY, PointF midPoint) {
|
||||
this.matrix.postScale(scaleX, scaleY, midPoint.x, midPoint.y);
|
||||
}
|
||||
|
||||
void postFlipVertically() {
|
||||
this.matrix.postScale(1, -1, area.centerX(), area.centerY());
|
||||
}
|
||||
|
||||
void postFlipHorizontally() {
|
||||
this.matrix.postScale(-1, 1, area.centerX(), area.centerY());
|
||||
}
|
||||
|
||||
void postRotate(float degree) {
|
||||
this.matrix.postRotate(degree, area.centerX(), area.centerY());
|
||||
|
||||
float minScale = getMinMatrixScale(this);
|
||||
if (getMatrixScale() < minScale) {
|
||||
final PointF midPoint = new PointF();
|
||||
midPoint.set(getCurrentDrawableCenterPoint());
|
||||
|
||||
postScale(minScale / getMatrixScale(), minScale / getMatrixScale(), midPoint);
|
||||
}
|
||||
|
||||
if (!judgeIsImageContainsBorder(this, getMatrixAngle())) {
|
||||
final float[] imageIndents = calculateImageIndents(this);
|
||||
float deltaX = -(imageIndents[0] + imageIndents[2]);
|
||||
float deltaY = -(imageIndents[1] + imageIndents[3]);
|
||||
|
||||
postTranslate(deltaX, deltaY);
|
||||
}
|
||||
}
|
||||
|
||||
private void animateTranslate(final View view, final float translateX, final float translateY) {
|
||||
animator.end();
|
||||
animator.removeAllUpdateListeners();
|
||||
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
float x = translateX * (float) animation.getAnimatedValue();
|
||||
float y = translateY * (float) animation.getAnimatedValue();
|
||||
|
||||
translate(x, y);
|
||||
view.invalidate();
|
||||
}
|
||||
});
|
||||
animator.setDuration(duration);
|
||||
animator.start();
|
||||
}
|
||||
|
||||
void moveToFillArea(final View view) {
|
||||
if (isFilledArea()) return;
|
||||
record();
|
||||
|
||||
RectF rectF = getCurrentDrawableBounds();
|
||||
float offsetX = 0f;
|
||||
float offsetY = 0f;
|
||||
|
||||
if (rectF.left > area.left()) {
|
||||
offsetX = area.left() - rectF.left;
|
||||
}
|
||||
|
||||
if (rectF.top > area.top()) {
|
||||
offsetY = area.top() - rectF.top;
|
||||
}
|
||||
|
||||
if (rectF.right < area.right()) {
|
||||
offsetX = area.right() - rectF.right;
|
||||
}
|
||||
|
||||
if (rectF.bottom < area.bottom()) {
|
||||
offsetY = area.bottom() - rectF.bottom;
|
||||
}
|
||||
|
||||
if (view == null) {
|
||||
postTranslate(offsetX, offsetY);
|
||||
} else {
|
||||
animateTranslate(view, offsetX, offsetY);
|
||||
}
|
||||
}
|
||||
|
||||
void fillArea(final View view, boolean quick) {
|
||||
if (isFilledArea()) return;
|
||||
record();
|
||||
|
||||
final float startScale = getMatrixScale();
|
||||
final float endScale = getMinMatrixScale(this);
|
||||
|
||||
final PointF midPoint = new PointF();
|
||||
midPoint.set(getCurrentDrawableCenterPoint());
|
||||
|
||||
tempMatrix.set(matrix);
|
||||
tempMatrix.postScale(endScale / startScale, endScale / startScale, midPoint.x, midPoint.y);
|
||||
|
||||
RectF rectF = new RectF(drawableBounds);
|
||||
tempMatrix.mapRect(rectF);
|
||||
|
||||
float offsetX = 0f;
|
||||
float offsetY = 0f;
|
||||
|
||||
if (rectF.left > area.left()) {
|
||||
offsetX = area.left() - rectF.left;
|
||||
}
|
||||
|
||||
if (rectF.top > area.top()) {
|
||||
offsetY = area.top() - rectF.top;
|
||||
}
|
||||
|
||||
if (rectF.right < area.right()) {
|
||||
offsetX = area.right() - rectF.right;
|
||||
}
|
||||
|
||||
if (rectF.bottom < area.bottom()) {
|
||||
offsetY = area.bottom() - rectF.bottom;
|
||||
}
|
||||
|
||||
final float translateX = offsetX;
|
||||
final float translateY = offsetY;
|
||||
|
||||
animator.end();
|
||||
animator.removeAllUpdateListeners();
|
||||
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
float value = (float) animation.getAnimatedValue();
|
||||
float scale = (startScale + (endScale - startScale) * value) / startScale;
|
||||
float x = translateX * value;
|
||||
float y = translateY * value;
|
||||
|
||||
zoom(scale, scale, midPoint);
|
||||
postTranslate(x, y);
|
||||
view.invalidate();
|
||||
}
|
||||
});
|
||||
|
||||
if (quick) {
|
||||
animator.setDuration(0);
|
||||
} else {
|
||||
animator.setDuration(duration);
|
||||
}
|
||||
animator.start();
|
||||
}
|
||||
|
||||
void updateWith(final MotionEvent event, final Line line) {
|
||||
float offsetX = (event.getX() - previousMoveX) / 2;
|
||||
float offsetY = (event.getY() - previousMoveY) / 2;
|
||||
|
||||
if (!canFilledArea()) {
|
||||
final Area area = getArea();
|
||||
float deltaScale = getMinMatrixScale(this) / getMatrixScale();
|
||||
postScale(deltaScale, deltaScale, area.getCenterPoint());
|
||||
record();
|
||||
|
||||
previousMoveX = event.getX();
|
||||
previousMoveY = event.getY();
|
||||
}
|
||||
|
||||
if (line.direction() == Line.Direction.HORIZONTAL) {
|
||||
translate(0, offsetY);
|
||||
} else if (line.direction() == Line.Direction.VERTICAL) {
|
||||
translate(offsetX, 0);
|
||||
}
|
||||
|
||||
final RectF rectF = getCurrentDrawableBounds();
|
||||
final Area area = getArea();
|
||||
float moveY = 0f;
|
||||
|
||||
if (rectF.top > area.top()) {
|
||||
moveY = area.top() - rectF.top;
|
||||
}
|
||||
|
||||
if (rectF.bottom < area.bottom()) {
|
||||
moveY = area.bottom() - rectF.bottom;
|
||||
}
|
||||
|
||||
float moveX = 0f;
|
||||
|
||||
if (rectF.left > area.left()) {
|
||||
moveX = area.left() - rectF.left;
|
||||
}
|
||||
|
||||
if (rectF.right < area.right()) {
|
||||
moveX = area.right() - rectF.right;
|
||||
}
|
||||
|
||||
if (moveX != 0 || moveY != 0) {
|
||||
previousMoveX = event.getX();
|
||||
previousMoveY = event.getY();
|
||||
postTranslate(moveX, moveY);
|
||||
record();
|
||||
}
|
||||
}
|
||||
|
||||
public void setArea(Area area) {
|
||||
this.area = area;
|
||||
}
|
||||
|
||||
boolean isAnimateRunning() {
|
||||
return animator.isRunning();
|
||||
}
|
||||
|
||||
void setAnimateDuration(int duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle;
|
||||
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.template.slant.OneSlantLayout;
|
||||
import com.huantansheng.easyphotos.models.puzzle.template.slant.SlantLayoutHelper;
|
||||
import com.huantansheng.easyphotos.models.puzzle.template.slant.ThreeSlantLayout;
|
||||
import com.huantansheng.easyphotos.models.puzzle.template.slant.TwoSlantLayout;
|
||||
import com.huantansheng.easyphotos.models.puzzle.template.straight.EightStraightLayout;
|
||||
import com.huantansheng.easyphotos.models.puzzle.template.straight.FiveStraightLayout;
|
||||
import com.huantansheng.easyphotos.models.puzzle.template.straight.FourStraightLayout;
|
||||
import com.huantansheng.easyphotos.models.puzzle.template.straight.NineStraightLayout;
|
||||
import com.huantansheng.easyphotos.models.puzzle.template.straight.OneStraightLayout;
|
||||
import com.huantansheng.easyphotos.models.puzzle.template.straight.SevenStraightLayout;
|
||||
import com.huantansheng.easyphotos.models.puzzle.template.straight.SixStraightLayout;
|
||||
import com.huantansheng.easyphotos.models.puzzle.template.straight.StraightLayoutHelper;
|
||||
import com.huantansheng.easyphotos.models.puzzle.template.straight.ThreeStraightLayout;
|
||||
import com.huantansheng.easyphotos.models.puzzle.template.straight.TwoStraightLayout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
public class PuzzleUtils {
|
||||
private static final String TAG = "PuzzleUtils";
|
||||
|
||||
private PuzzleUtils() {
|
||||
//no instance
|
||||
}
|
||||
|
||||
public static PuzzleLayout getPuzzleLayout(int type, int borderSize, int themeId) {
|
||||
if (type == 0) {
|
||||
switch (borderSize) {
|
||||
case 1:
|
||||
return new OneSlantLayout(themeId);
|
||||
case 2:
|
||||
return new TwoSlantLayout(themeId);
|
||||
case 3:
|
||||
return new ThreeSlantLayout(themeId);
|
||||
default:
|
||||
return new OneSlantLayout(themeId);
|
||||
}
|
||||
} else {
|
||||
switch (borderSize) {
|
||||
case 1:
|
||||
return new OneStraightLayout(themeId);
|
||||
case 2:
|
||||
return new TwoStraightLayout(themeId);
|
||||
case 3:
|
||||
return new ThreeStraightLayout(themeId);
|
||||
case 4:
|
||||
return new FourStraightLayout(themeId);
|
||||
case 5:
|
||||
return new FiveStraightLayout(themeId);
|
||||
case 6:
|
||||
return new SixStraightLayout(themeId);
|
||||
case 7:
|
||||
return new SevenStraightLayout(themeId);
|
||||
case 8:
|
||||
return new EightStraightLayout(themeId);
|
||||
case 9:
|
||||
return new NineStraightLayout(themeId);
|
||||
default:
|
||||
return new OneStraightLayout(themeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<PuzzleLayout> getAllPuzzleLayouts() {
|
||||
List<PuzzleLayout> puzzleLayouts = new ArrayList<>();
|
||||
//slant layout
|
||||
puzzleLayouts.addAll(SlantLayoutHelper.getAllThemeLayout(2));
|
||||
puzzleLayouts.addAll(SlantLayoutHelper.getAllThemeLayout(3));
|
||||
|
||||
// straight layout
|
||||
puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(2));
|
||||
puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(3));
|
||||
puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(4));
|
||||
puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(5));
|
||||
puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(6));
|
||||
puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(7));
|
||||
puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(8));
|
||||
puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(9));
|
||||
return puzzleLayouts;
|
||||
}
|
||||
|
||||
public static List<PuzzleLayout> getPuzzleLayouts(int pieceCount) {
|
||||
List<PuzzleLayout> puzzleLayouts = new ArrayList<>();
|
||||
puzzleLayouts.addAll(SlantLayoutHelper.getAllThemeLayout(pieceCount));
|
||||
puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(pieceCount));
|
||||
return puzzleLayouts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,791 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import com.huantansheng.easyphotos.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
public class PuzzleView extends View {
|
||||
private static final String TAG = "SlantPuzzleView";
|
||||
|
||||
private enum ActionMode {
|
||||
NONE,
|
||||
DRAG,
|
||||
ZOOM,
|
||||
MOVE,
|
||||
SWAP
|
||||
}
|
||||
|
||||
private ActionMode currentMode = ActionMode.NONE;
|
||||
|
||||
private List<PuzzlePiece> puzzlePieces = new ArrayList<>();
|
||||
|
||||
private List<PuzzlePiece> needChangePieces = new ArrayList<>();
|
||||
private PuzzleLayout puzzleLayout;
|
||||
|
||||
private RectF bounds;
|
||||
private int lineSize;
|
||||
|
||||
private int duration;
|
||||
private Line handlingLine;
|
||||
|
||||
private PuzzlePiece handlingPiece;
|
||||
private PuzzlePiece replacePiece;
|
||||
private PuzzlePiece previousHandlingPiece;
|
||||
|
||||
private Paint linePaint;
|
||||
private Paint selectedAreaPaint;
|
||||
private Paint handleBarPaint;
|
||||
|
||||
private float downX;
|
||||
private float downY;
|
||||
private float previousDistance;
|
||||
private PointF midPoint;
|
||||
private boolean needDrawLine;
|
||||
|
||||
private boolean needDrawOuterLine;
|
||||
private boolean touchEnable = true;
|
||||
private int lineColor;
|
||||
|
||||
private int selectedLineColor;
|
||||
private int handleBarColor;
|
||||
private float piecePadding;
|
||||
private float pieceRadian;
|
||||
|
||||
private boolean needResetPieceMatrix = true;
|
||||
|
||||
private OnPieceSelectedListener onPieceSelectedListener;
|
||||
|
||||
private Runnable switchToSwapAction = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
currentMode = ActionMode.SWAP;
|
||||
invalidate();
|
||||
}
|
||||
};
|
||||
|
||||
public PuzzleView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public PuzzleView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public PuzzleView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs) {
|
||||
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PuzzleView);
|
||||
lineSize = ta.getInt(R.styleable.PuzzleView_line_size, 4);
|
||||
lineColor = ta.getColor(R.styleable.PuzzleView_line_color,
|
||||
ContextCompat.getColor(getContext(), R.color.easy_photos_fg_primary));
|
||||
selectedLineColor =
|
||||
ta.getColor(R.styleable.PuzzleView_selected_line_color,
|
||||
ContextCompat.getColor(getContext(), R.color.easy_photos_fg_accent));
|
||||
handleBarColor =
|
||||
ta.getColor(R.styleable.PuzzleView_handle_bar_color,
|
||||
ContextCompat.getColor(getContext(), R.color.easy_photos_fg_accent));
|
||||
piecePadding = ta.getDimensionPixelSize(R.styleable.PuzzleView_piece_padding, 0);
|
||||
needDrawLine = ta.getBoolean(R.styleable.PuzzleView_need_draw_line, false);
|
||||
needDrawOuterLine = ta.getBoolean(R.styleable.PuzzleView_need_draw_outer_line, false);
|
||||
duration = ta.getInt(R.styleable.PuzzleView_animation_duration, 300);
|
||||
pieceRadian = ta.getFloat(R.styleable.PuzzleView_radian, 0f);
|
||||
ta.recycle();
|
||||
|
||||
bounds = new RectF();
|
||||
|
||||
// init some paint
|
||||
linePaint = new Paint();
|
||||
linePaint.setAntiAlias(true);
|
||||
linePaint.setColor(lineColor);
|
||||
linePaint.setStrokeWidth(lineSize);
|
||||
linePaint.setStyle(Paint.Style.STROKE);
|
||||
linePaint.setStrokeJoin(Paint.Join.ROUND);
|
||||
linePaint.setStrokeCap(Paint.Cap.SQUARE);
|
||||
|
||||
selectedAreaPaint = new Paint();
|
||||
selectedAreaPaint.setAntiAlias(true);
|
||||
selectedAreaPaint.setStyle(Paint.Style.STROKE);
|
||||
selectedAreaPaint.setStrokeJoin(Paint.Join.ROUND);
|
||||
selectedAreaPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
selectedAreaPaint.setColor(selectedLineColor);
|
||||
selectedAreaPaint.setStrokeWidth(lineSize);
|
||||
|
||||
handleBarPaint = new Paint();
|
||||
handleBarPaint.setAntiAlias(true);
|
||||
handleBarPaint.setStyle(Paint.Style.FILL);
|
||||
handleBarPaint.setColor(handleBarColor);
|
||||
handleBarPaint.setStrokeWidth(lineSize * 3);
|
||||
|
||||
midPoint = new PointF();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
resetPuzzleBounds();
|
||||
|
||||
if (puzzlePieces.size() != 0) {
|
||||
int size = puzzlePieces.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
PuzzlePiece piece = puzzlePieces.get(i);
|
||||
piece.setArea(puzzleLayout.getArea(i));
|
||||
if (needResetPieceMatrix) {
|
||||
piece.set(MatrixUtils.generateMatrix(piece, 0f));
|
||||
} else {
|
||||
piece.fillArea(this, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private void resetPuzzleBounds() {
|
||||
bounds.left = getPaddingLeft();
|
||||
bounds.top = getPaddingTop();
|
||||
bounds.right = getWidth() - getPaddingRight();
|
||||
bounds.bottom = getHeight() - getPaddingBottom();
|
||||
|
||||
if (puzzleLayout != null) {
|
||||
puzzleLayout.reset();
|
||||
puzzleLayout.setOuterBounds(bounds);
|
||||
puzzleLayout.layout();
|
||||
puzzleLayout.setPadding(piecePadding);
|
||||
puzzleLayout.setRadian(pieceRadian);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
if (puzzleLayout == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
linePaint.setStrokeWidth(lineSize);
|
||||
selectedAreaPaint.setStrokeWidth(lineSize);
|
||||
handleBarPaint.setStrokeWidth(lineSize * 3);
|
||||
|
||||
// draw pieces
|
||||
int count = puzzleLayout.getAreaCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (i >= puzzlePieces.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
PuzzlePiece piece = puzzlePieces.get(i);
|
||||
|
||||
if (piece == handlingPiece && currentMode == ActionMode.SWAP) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (puzzlePieces.size() > i) {
|
||||
piece.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
// draw outer bounds
|
||||
if (needDrawOuterLine) {
|
||||
for (Line outerLine : puzzleLayout.getOuterLines()) {
|
||||
drawLine(canvas, outerLine);
|
||||
}
|
||||
}
|
||||
|
||||
// draw slant lines
|
||||
if (needDrawLine) {
|
||||
for (Line line : puzzleLayout.getLines()) {
|
||||
drawLine(canvas, line);
|
||||
}
|
||||
}
|
||||
|
||||
// draw selected area
|
||||
if (handlingPiece != null && currentMode != ActionMode.SWAP) {
|
||||
drawSelectedArea(canvas, handlingPiece);
|
||||
}
|
||||
|
||||
// draw swap piece
|
||||
if (handlingPiece != null && currentMode == ActionMode.SWAP) {
|
||||
handlingPiece.draw(canvas, 128);
|
||||
if (replacePiece != null) {
|
||||
drawSelectedArea(canvas, replacePiece);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void drawSelectedArea(Canvas canvas, PuzzlePiece piece) {
|
||||
final Area area = piece.getArea();
|
||||
// draw select area
|
||||
canvas.drawPath(area.getAreaPath(), selectedAreaPaint);
|
||||
|
||||
// draw handle bar
|
||||
for (Line line : area.getLines()) {
|
||||
if (puzzleLayout.getLines().contains(line)) {
|
||||
PointF[] handleBarPoints = area.getHandleBarPoints(line);
|
||||
canvas.drawLine(handleBarPoints[0].x, handleBarPoints[0].y, handleBarPoints[1].x,
|
||||
handleBarPoints[1].y, handleBarPaint);
|
||||
canvas.drawCircle(handleBarPoints[0].x, handleBarPoints[0].y, lineSize * 3 / 2,
|
||||
handleBarPaint);
|
||||
canvas.drawCircle(handleBarPoints[1].x, handleBarPoints[1].y, lineSize * 3 / 2,
|
||||
handleBarPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void drawLine(Canvas canvas, Line line) {
|
||||
canvas.drawLine(line.startPoint().x, line.startPoint().y, line.endPoint().x,
|
||||
line.endPoint().y,
|
||||
linePaint);
|
||||
}
|
||||
|
||||
public void setPuzzleLayout(PuzzleLayout puzzleLayout) {
|
||||
clearPieces();
|
||||
|
||||
this.puzzleLayout = puzzleLayout;
|
||||
|
||||
this.puzzleLayout.setOuterBounds(bounds);
|
||||
this.puzzleLayout.layout();
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public PuzzleLayout getPuzzleLayout() {
|
||||
return this.puzzleLayout;
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (!touchEnable) {
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
switch (event.getAction() & MotionEvent.ACTION_MASK) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
downX = event.getX();
|
||||
downY = event.getY();
|
||||
|
||||
decideActionMode(event);
|
||||
prepareAction(event);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
previousDistance = calculateDistance(event);
|
||||
calculateMidPoint(event, midPoint);
|
||||
|
||||
decideActionMode(event);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
performAction(event);
|
||||
|
||||
if ((Math.abs(event.getX() - downX) > 10 || Math.abs(event.getY() - downY) > 10)
|
||||
&& currentMode != ActionMode.SWAP) {
|
||||
removeCallbacks(switchToSwapAction);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
finishAction(event);
|
||||
currentMode = ActionMode.NONE;
|
||||
removeCallbacks(switchToSwapAction);
|
||||
break;
|
||||
}
|
||||
|
||||
invalidate();
|
||||
return true;
|
||||
}
|
||||
|
||||
// 决定应该执行什么Action
|
||||
private void decideActionMode(MotionEvent event) {
|
||||
for (PuzzlePiece piece : puzzlePieces) {
|
||||
if (piece.isAnimateRunning()) {
|
||||
currentMode = ActionMode.NONE;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.getPointerCount() == 1) {
|
||||
handlingLine = findHandlingLine();
|
||||
if (handlingLine != null) {
|
||||
currentMode = ActionMode.MOVE;
|
||||
} else {
|
||||
handlingPiece = findHandlingPiece();
|
||||
|
||||
if (handlingPiece != null) {
|
||||
currentMode = ActionMode.DRAG;
|
||||
|
||||
postDelayed(switchToSwapAction, 500);
|
||||
}
|
||||
}
|
||||
} else if (event.getPointerCount() > 1) {
|
||||
if (handlingPiece != null
|
||||
&& handlingPiece.contains(event.getX(1), event.getY(1))
|
||||
&& currentMode == ActionMode.DRAG) {
|
||||
currentMode = ActionMode.ZOOM;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 执行Action前的准备工作
|
||||
@SuppressWarnings("unused")
|
||||
private void prepareAction(MotionEvent event) {
|
||||
switch (currentMode) {
|
||||
case NONE:
|
||||
break;
|
||||
case DRAG:
|
||||
handlingPiece.record();
|
||||
break;
|
||||
case ZOOM:
|
||||
handlingPiece.record();
|
||||
break;
|
||||
case MOVE:
|
||||
handlingLine.prepareMove();
|
||||
needChangePieces.clear();
|
||||
needChangePieces.addAll(findNeedChangedPieces());
|
||||
for (PuzzlePiece piece : needChangePieces) {
|
||||
piece.record();
|
||||
piece.setPreviousMoveX(downX);
|
||||
piece.setPreviousMoveY(downY);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 执行Action
|
||||
private void performAction(MotionEvent event) {
|
||||
switch (currentMode) {
|
||||
case NONE:
|
||||
break;
|
||||
case DRAG:
|
||||
dragPiece(handlingPiece, event);
|
||||
break;
|
||||
case ZOOM:
|
||||
zoomPiece(handlingPiece, event);
|
||||
break;
|
||||
case SWAP:
|
||||
dragPiece(handlingPiece, event);
|
||||
replacePiece = findReplacePiece(event);
|
||||
break;
|
||||
case MOVE:
|
||||
moveLine(handlingLine, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 结束Action
|
||||
private void finishAction(MotionEvent event) {
|
||||
switch (currentMode) {
|
||||
case NONE:
|
||||
break;
|
||||
case DRAG:
|
||||
if (handlingPiece != null && !handlingPiece.isFilledArea()) {
|
||||
handlingPiece.moveToFillArea(this);
|
||||
}
|
||||
|
||||
if (previousHandlingPiece == handlingPiece
|
||||
&& Math.abs(downX - event.getX()) < 3
|
||||
&& Math.abs(downY - event.getY()) < 3) {
|
||||
|
||||
handlingPiece = null;
|
||||
}
|
||||
|
||||
// trigger listener
|
||||
if (onPieceSelectedListener != null) {
|
||||
onPieceSelectedListener.onPieceSelected(handlingPiece,
|
||||
puzzlePieces.indexOf(handlingPiece));
|
||||
}
|
||||
|
||||
previousHandlingPiece = handlingPiece;
|
||||
break;
|
||||
case ZOOM:
|
||||
if (handlingPiece != null && !handlingPiece.isFilledArea()) {
|
||||
if (handlingPiece.canFilledArea()) {
|
||||
handlingPiece.moveToFillArea(this);
|
||||
} else {
|
||||
handlingPiece.fillArea(this, false);
|
||||
}
|
||||
}
|
||||
previousHandlingPiece = handlingPiece;
|
||||
break;
|
||||
case MOVE:
|
||||
break;
|
||||
case SWAP:
|
||||
if (handlingPiece != null && replacePiece != null) {
|
||||
Drawable temp = handlingPiece.getDrawable();
|
||||
|
||||
handlingPiece.setDrawable(replacePiece.getDrawable());
|
||||
replacePiece.setDrawable(temp);
|
||||
|
||||
handlingPiece.fillArea(this, true);
|
||||
replacePiece.fillArea(this, true);
|
||||
|
||||
handlingPiece = null;
|
||||
replacePiece = null;
|
||||
previousHandlingPiece = null;
|
||||
// trigger listener
|
||||
if (onPieceSelectedListener != null) {
|
||||
onPieceSelectedListener.onPieceSelected(null,
|
||||
0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
handlingLine = null;
|
||||
needChangePieces.clear();
|
||||
}
|
||||
|
||||
private void moveLine(Line line, MotionEvent event) {
|
||||
if (line == null || event == null) return;
|
||||
|
||||
boolean needUpdate;
|
||||
if (line.direction() == Line.Direction.HORIZONTAL) {
|
||||
needUpdate = line.move(event.getY() - downY, 80);
|
||||
} else {
|
||||
needUpdate = line.move(event.getX() - downX, 80);
|
||||
}
|
||||
|
||||
if (needUpdate) {
|
||||
puzzleLayout.update();
|
||||
updatePiecesInArea(line, event);
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePiecesInArea(Line line, MotionEvent event) {
|
||||
int size = needChangePieces.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
needChangePieces.get(i).updateWith(event, line);
|
||||
}
|
||||
}
|
||||
|
||||
private void zoomPiece(PuzzlePiece piece, MotionEvent event) {
|
||||
if (piece == null || event == null || event.getPointerCount() < 2) return;
|
||||
float scale = calculateDistance(event) / previousDistance;
|
||||
piece.zoomAndTranslate(scale, scale, midPoint, event.getX() - downX, event.getY() - downY);
|
||||
}
|
||||
|
||||
private void dragPiece(PuzzlePiece piece, MotionEvent event) {
|
||||
if (piece == null || event == null) return;
|
||||
piece.translate(event.getX() - downX, event.getY() - downY);
|
||||
}
|
||||
|
||||
public void replace(Bitmap bitmap) {
|
||||
replace(new BitmapDrawable(getResources(), bitmap));
|
||||
}
|
||||
|
||||
public void replace(final Drawable bitmapDrawable) {
|
||||
post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (handlingPiece == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
handlingPiece.setDrawable(bitmapDrawable);
|
||||
handlingPiece.set(MatrixUtils.generateMatrix(handlingPiece, 0f));
|
||||
|
||||
postInvalidate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void flipVertically() {
|
||||
if (handlingPiece == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
handlingPiece.postFlipVertically();
|
||||
handlingPiece.record();
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void flipHorizontally() {
|
||||
if (handlingPiece == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
handlingPiece.postFlipHorizontally();
|
||||
handlingPiece.record();
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void rotate(float degree) {
|
||||
if (handlingPiece == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
handlingPiece.postRotate(degree);
|
||||
handlingPiece.record();
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private PuzzlePiece findHandlingPiece() {
|
||||
for (PuzzlePiece piece : puzzlePieces) {
|
||||
if (piece.contains(downX, downY)) {
|
||||
return piece;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Line findHandlingLine() {
|
||||
for (Line line : puzzleLayout.getLines()) {
|
||||
if (line.contains(downX, downY, 40)) {
|
||||
return line;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private PuzzlePiece findReplacePiece(MotionEvent event) {
|
||||
for (PuzzlePiece piece : puzzlePieces) {
|
||||
if (piece.contains(event.getX(), event.getY())) {
|
||||
return piece;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<PuzzlePiece> findNeedChangedPieces() {
|
||||
if (handlingLine == null) return new ArrayList<>();
|
||||
|
||||
List<PuzzlePiece> needChanged = new ArrayList<>();
|
||||
|
||||
for (PuzzlePiece piece : puzzlePieces) {
|
||||
if (piece.contains(handlingLine)) {
|
||||
needChanged.add(piece);
|
||||
}
|
||||
}
|
||||
|
||||
return needChanged;
|
||||
}
|
||||
|
||||
private float calculateDistance(MotionEvent event) {
|
||||
float x = event.getX(0) - event.getX(1);
|
||||
float y = event.getY(0) - event.getY(1);
|
||||
|
||||
return (float) Math.sqrt(x * x + y * y);
|
||||
}
|
||||
|
||||
private void calculateMidPoint(MotionEvent event, PointF point) {
|
||||
point.x = (event.getX(0) + event.getX(1)) / 2;
|
||||
point.y = (event.getY(0) + event.getY(1)) / 2;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
clearPieces();
|
||||
if (puzzleLayout != null) {
|
||||
puzzleLayout.reset();
|
||||
}
|
||||
}
|
||||
|
||||
public void clearPieces() {
|
||||
handlingLine = null;
|
||||
handlingPiece = null;
|
||||
replacePiece = null;
|
||||
needChangePieces.clear();
|
||||
puzzlePieces.clear();
|
||||
}
|
||||
|
||||
public void addPieces(List<Bitmap> bitmaps) {
|
||||
for (Bitmap bitmap : bitmaps) {
|
||||
addPiece(bitmap);
|
||||
}
|
||||
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public void addPiece(Bitmap bitmap) {
|
||||
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), bitmap);
|
||||
bitmapDrawable.setAntiAlias(true);
|
||||
bitmapDrawable.setFilterBitmap(true);
|
||||
|
||||
addPiece(bitmapDrawable);
|
||||
}
|
||||
|
||||
public void addPiece(Drawable drawable) {
|
||||
int position = puzzlePieces.size();
|
||||
|
||||
if (position >= puzzleLayout.getAreaCount()) {
|
||||
Log.e(TAG, "addPiece: can not add more. the current puzzle layout can contains "
|
||||
+ puzzleLayout.getAreaCount()
|
||||
+ " puzzle piece.");
|
||||
return;
|
||||
}
|
||||
|
||||
final Area area = puzzleLayout.getArea(position);
|
||||
area.setPadding(piecePadding);
|
||||
|
||||
PuzzlePiece piece = new PuzzlePiece(drawable, area, new Matrix());
|
||||
|
||||
final Matrix matrix = MatrixUtils.generateMatrix(area, drawable, 0f);
|
||||
piece.set(matrix);
|
||||
|
||||
piece.setAnimateDuration(duration);
|
||||
|
||||
puzzlePieces.add(piece);
|
||||
|
||||
setPiecePadding(piecePadding);
|
||||
setPieceRadian(pieceRadian);
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setAnimateDuration(int duration) {
|
||||
this.duration = duration;
|
||||
for (PuzzlePiece piece : puzzlePieces) {
|
||||
piece.setAnimateDuration(duration);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isNeedDrawLine() {
|
||||
return needDrawLine;
|
||||
}
|
||||
|
||||
public void setNeedDrawLine(boolean needDrawLine) {
|
||||
this.needDrawLine = needDrawLine;
|
||||
handlingPiece = null;
|
||||
previousHandlingPiece = null;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public boolean isNeedDrawOuterLine() {
|
||||
return needDrawOuterLine;
|
||||
}
|
||||
|
||||
public void setNeedDrawOuterLine(boolean needDrawOuterLine) {
|
||||
this.needDrawOuterLine = needDrawOuterLine;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public int getLineColor() {
|
||||
return lineColor;
|
||||
}
|
||||
|
||||
public void setLineColor(int lineColor) {
|
||||
this.lineColor = lineColor;
|
||||
this.linePaint.setColor(lineColor);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public int getLineSize() {
|
||||
return lineSize;
|
||||
}
|
||||
|
||||
public void setLineSize(int lineSize) {
|
||||
this.lineSize = lineSize;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public int getSelectedLineColor() {
|
||||
return selectedLineColor;
|
||||
}
|
||||
|
||||
public void setSelectedLineColor(int selectedLineColor) {
|
||||
this.selectedLineColor = selectedLineColor;
|
||||
this.selectedAreaPaint.setColor(selectedLineColor);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public int getHandleBarColor() {
|
||||
return handleBarColor;
|
||||
}
|
||||
|
||||
public void setHandleBarColor(int handleBarColor) {
|
||||
this.handleBarColor = handleBarColor;
|
||||
this.handleBarPaint.setColor(handleBarColor);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public boolean isTouchEnable() {
|
||||
return touchEnable;
|
||||
}
|
||||
|
||||
public void setTouchEnable(boolean touchEnable) {
|
||||
this.touchEnable = touchEnable;
|
||||
}
|
||||
|
||||
public void clearHandling() {
|
||||
handlingPiece = null;
|
||||
handlingLine = null;
|
||||
replacePiece = null;
|
||||
previousHandlingPiece = null;
|
||||
needChangePieces.clear();
|
||||
}
|
||||
|
||||
public void setPiecePadding(float padding) {
|
||||
this.piecePadding = padding;
|
||||
if (puzzleLayout != null) {
|
||||
puzzleLayout.setPadding(padding);
|
||||
}
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setPieceRadian(float radian) {
|
||||
this.pieceRadian = radian;
|
||||
if (puzzleLayout != null) {
|
||||
puzzleLayout.setRadian(radian);
|
||||
}
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBackgroundColor(int color) {
|
||||
super.setBackgroundColor(color);
|
||||
if (puzzleLayout != null) {
|
||||
puzzleLayout.setColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
public void setNeedResetPieceMatrix(boolean needResetPieceMatrix) {
|
||||
this.needResetPieceMatrix = needResetPieceMatrix;
|
||||
}
|
||||
|
||||
public float getPiecePadding() {
|
||||
return piecePadding;
|
||||
}
|
||||
|
||||
public float getPieceRadian() {
|
||||
return pieceRadian;
|
||||
}
|
||||
|
||||
public void setOnPieceSelectedListener(OnPieceSelectedListener onPieceSelectedListener) {
|
||||
this.onPieceSelectedListener = onPieceSelectedListener;
|
||||
}
|
||||
|
||||
public interface OnPieceSelectedListener {
|
||||
void onPieceSelected(PuzzlePiece piece, int position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
public class SquarePuzzleView extends PuzzleView {
|
||||
public SquarePuzzleView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public SquarePuzzleView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public SquarePuzzleView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
|
||||
int width = getMeasuredWidth();
|
||||
int height = getMeasuredHeight();
|
||||
int length = width > height ? height : width;
|
||||
|
||||
setMeasuredDimension(length, length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.slant;
|
||||
|
||||
import android.graphics.PointF;
|
||||
|
||||
/**
|
||||
* 两条线的交点
|
||||
*
|
||||
* @author wupanjie
|
||||
*/
|
||||
class CrossoverPointF extends PointF {
|
||||
SlantLine horizontal;
|
||||
SlantLine vertical;
|
||||
|
||||
CrossoverPointF() {
|
||||
|
||||
}
|
||||
|
||||
CrossoverPointF(float x, float y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
CrossoverPointF(SlantLine horizontal, SlantLine vertical) {
|
||||
this.horizontal = horizontal;
|
||||
this.vertical = vertical;
|
||||
}
|
||||
|
||||
void update() {
|
||||
if (horizontal == null || vertical == null){
|
||||
return;
|
||||
}
|
||||
SlantUtils.intersectionOfLines(this, horizontal, vertical);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.slant;
|
||||
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.RectF;
|
||||
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.Area;
|
||||
import com.huantansheng.easyphotos.models.puzzle.Line;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.distance;
|
||||
import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.getPoint;
|
||||
import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.intersectionOfLines;
|
||||
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
class SlantArea implements Area {
|
||||
SlantLine lineLeft;
|
||||
SlantLine lineTop;
|
||||
SlantLine lineRight;
|
||||
SlantLine lineBottom;
|
||||
|
||||
CrossoverPointF leftTop;
|
||||
CrossoverPointF leftBottom;
|
||||
CrossoverPointF rightTop;
|
||||
CrossoverPointF rightBottom;
|
||||
|
||||
private PointF tempPoint;
|
||||
|
||||
private float paddingLeft;
|
||||
private float paddingTop;
|
||||
private float paddingRight;
|
||||
private float paddingBottom;
|
||||
private float radian;
|
||||
|
||||
private Path areaPath = new Path();
|
||||
private RectF areaRect = new RectF();
|
||||
private PointF[] handleBarPoints = new PointF[2];
|
||||
|
||||
SlantArea() {
|
||||
handleBarPoints[0] = new PointF();
|
||||
handleBarPoints[1] = new PointF();
|
||||
|
||||
leftTop = new CrossoverPointF();
|
||||
leftBottom = new CrossoverPointF();
|
||||
rightTop = new CrossoverPointF();
|
||||
rightBottom = new CrossoverPointF();
|
||||
|
||||
tempPoint = new PointF();
|
||||
}
|
||||
|
||||
SlantArea(SlantArea src) {
|
||||
this();
|
||||
this.lineLeft = src.lineLeft;
|
||||
this.lineTop = src.lineTop;
|
||||
this.lineRight = src.lineRight;
|
||||
this.lineBottom = src.lineBottom;
|
||||
|
||||
this.leftTop = src.leftTop;
|
||||
this.leftBottom = src.leftBottom;
|
||||
this.rightTop = src.rightTop;
|
||||
this.rightBottom = src.rightBottom;
|
||||
|
||||
updateCornerPoints();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float left() {
|
||||
return Math.min(leftTop.x, leftBottom.x) + paddingLeft;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float top() {
|
||||
return Math.min(leftTop.y, rightTop.y) + paddingTop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float right() {
|
||||
return Math.max(rightTop.x, rightBottom.x) - paddingRight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float bottom() {
|
||||
return Math.max(leftBottom.y, rightBottom.y) - paddingBottom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float centerX() {
|
||||
return (left() + right()) / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float centerY() {
|
||||
return (top() + bottom()) / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float width() {
|
||||
return right() - left();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float height() {
|
||||
return bottom() - top();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PointF getCenterPoint() {
|
||||
return new PointF(centerX(), centerY());
|
||||
}
|
||||
|
||||
public Path getAreaPath() {
|
||||
areaPath.reset();
|
||||
|
||||
if (radian > 0) {
|
||||
float tempRatio = radian / distance(leftTop, leftBottom);
|
||||
getPoint(tempPoint, leftTop, leftBottom, Line.Direction.VERTICAL, tempRatio);
|
||||
tempPoint.offset(paddingLeft, paddingTop);
|
||||
areaPath.moveTo(tempPoint.x, tempPoint.y);
|
||||
|
||||
tempRatio = radian / distance(leftTop, rightTop);
|
||||
getPoint(tempPoint, leftTop, rightTop, Line.Direction.HORIZONTAL, tempRatio);
|
||||
tempPoint.offset(paddingLeft, paddingTop);
|
||||
areaPath.quadTo(leftTop.x + paddingLeft, leftTop.y + paddingTop, tempPoint.x, tempPoint.y);
|
||||
|
||||
tempRatio = 1 - tempRatio;
|
||||
getPoint(tempPoint, leftTop, rightTop, Line.Direction.HORIZONTAL, tempRatio);
|
||||
tempPoint.offset(-paddingRight, paddingTop);
|
||||
areaPath.lineTo(tempPoint.x, tempPoint.y);
|
||||
|
||||
tempRatio = radian / distance(rightTop, rightBottom);
|
||||
getPoint(tempPoint, rightTop, rightBottom, Line.Direction.VERTICAL, tempRatio);
|
||||
tempPoint.offset(-paddingRight, paddingTop);
|
||||
areaPath.quadTo(rightTop.x - paddingLeft, rightTop.y + paddingTop, tempPoint.x, tempPoint.y);
|
||||
|
||||
tempRatio = 1 - tempRatio;
|
||||
getPoint(tempPoint, rightTop, rightBottom, Line.Direction.VERTICAL, tempRatio);
|
||||
tempPoint.offset(-paddingRight, -paddingBottom);
|
||||
areaPath.lineTo(tempPoint.x, tempPoint.y);
|
||||
|
||||
tempRatio = 1 - radian / distance(leftBottom, rightBottom);
|
||||
getPoint(tempPoint, leftBottom, rightBottom, Line.Direction.HORIZONTAL, tempRatio);
|
||||
tempPoint.offset(-paddingRight, -paddingBottom);
|
||||
areaPath.quadTo(rightBottom.x - paddingRight, rightBottom.y - paddingTop, tempPoint.x, tempPoint.y);
|
||||
|
||||
tempRatio = 1 - tempRatio;
|
||||
getPoint(tempPoint, leftBottom, rightBottom, Line.Direction.HORIZONTAL, tempRatio);
|
||||
tempPoint.offset(paddingLeft, -paddingBottom);
|
||||
areaPath.lineTo(tempPoint.x, tempPoint.y);
|
||||
|
||||
tempRatio = 1 - radian / distance(leftTop, leftBottom);
|
||||
getPoint(tempPoint, leftTop, leftBottom, Line.Direction.VERTICAL, tempRatio);
|
||||
tempPoint.offset(paddingLeft, -paddingBottom);
|
||||
areaPath.quadTo(leftBottom.x + paddingLeft, leftBottom.y - paddingBottom, tempPoint.x, tempPoint.y);
|
||||
|
||||
tempRatio = 1 - tempRatio;
|
||||
getPoint(tempPoint, leftTop, leftBottom, Line.Direction.VERTICAL, tempRatio);
|
||||
tempPoint.offset(paddingLeft, paddingTop);
|
||||
areaPath.lineTo(tempPoint.x, tempPoint.y);
|
||||
} else {
|
||||
areaPath.moveTo(leftTop.x + paddingLeft, leftTop.y + paddingTop);
|
||||
areaPath.lineTo(rightTop.x - paddingRight, rightTop.y + paddingTop);
|
||||
areaPath.lineTo(rightBottom.x - paddingRight, rightBottom.y - paddingBottom);
|
||||
areaPath.lineTo(leftBottom.x + paddingLeft, leftBottom.y - paddingBottom);
|
||||
areaPath.lineTo(leftTop.x + paddingLeft, leftTop.y + paddingTop);
|
||||
}
|
||||
return areaPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RectF getAreaRect() {
|
||||
areaRect.set(left(), top(), right(), bottom());
|
||||
return areaRect;
|
||||
}
|
||||
|
||||
public boolean contains(float x, float y) {
|
||||
return SlantUtils.contains(this, x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Line line) {
|
||||
return lineLeft == line || lineTop == line || lineRight == line || lineBottom == line;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(PointF point) {
|
||||
return contains(point.x, point.y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Line> getLines() {
|
||||
return Arrays.asList((Line) lineLeft, lineTop, lineRight, lineBottom);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PointF[] getHandleBarPoints(Line line) {
|
||||
if (line == lineLeft) {
|
||||
getPoint(handleBarPoints[0], leftTop, leftBottom, line.direction(), 0.25f);
|
||||
getPoint(handleBarPoints[1], leftTop, leftBottom, line.direction(), 0.75f);
|
||||
handleBarPoints[0].offset(paddingLeft, 0);
|
||||
handleBarPoints[1].offset(paddingLeft, 0);
|
||||
} else if (line == lineTop) {
|
||||
getPoint(handleBarPoints[0], leftTop, rightTop, line.direction(), 0.25f);
|
||||
getPoint(handleBarPoints[1], leftTop, rightTop, line.direction(), 0.75f);
|
||||
handleBarPoints[0].offset(0, paddingTop);
|
||||
handleBarPoints[1].offset(0, paddingTop);
|
||||
} else if (line == lineRight) {
|
||||
getPoint(handleBarPoints[0], rightTop, rightBottom, line.direction(), 0.25f);
|
||||
getPoint(handleBarPoints[1], rightTop, rightBottom, line.direction(), 0.75f);
|
||||
handleBarPoints[0].offset(-paddingRight, 0);
|
||||
handleBarPoints[1].offset(-paddingRight, 0);
|
||||
} else if (line == lineBottom) {
|
||||
getPoint(handleBarPoints[0], leftBottom, rightBottom, line.direction(), 0.25f);
|
||||
getPoint(handleBarPoints[1], leftBottom, rightBottom, line.direction(), 0.75f);
|
||||
handleBarPoints[0].offset(0, -paddingBottom);
|
||||
handleBarPoints[1].offset(0, -paddingBottom);
|
||||
}
|
||||
return handleBarPoints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float radian() {
|
||||
return radian;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRadian(float radian) {
|
||||
this.radian = radian;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPaddingLeft() {
|
||||
return paddingLeft;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPaddingTop() {
|
||||
return paddingTop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPaddingRight() {
|
||||
return paddingRight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPaddingBottom() {
|
||||
return paddingBottom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPadding(float padding) {
|
||||
setPadding(padding, padding, padding, padding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPadding(float paddingLeft, float paddingTop, float paddingRight, float paddingBottom) {
|
||||
this.paddingLeft = paddingLeft;
|
||||
this.paddingTop = paddingTop;
|
||||
this.paddingRight = paddingRight;
|
||||
this.paddingBottom = paddingBottom;
|
||||
}
|
||||
|
||||
void updateCornerPoints() {
|
||||
intersectionOfLines(leftTop, lineLeft, lineTop);
|
||||
intersectionOfLines(leftBottom, lineLeft, lineBottom);
|
||||
intersectionOfLines(rightTop, lineRight, lineTop);
|
||||
intersectionOfLines(rightBottom, lineRight, lineBottom);
|
||||
}
|
||||
|
||||
static class AreaComparator implements Comparator<SlantArea> {
|
||||
|
||||
@Override
|
||||
public int compare(SlantArea one, SlantArea two) {
|
||||
if (one.leftTop.y < two.leftTop.y) {
|
||||
return -1;
|
||||
} else if (one.leftTop.y == two.leftTop.y) {
|
||||
if (one.leftTop.x < two.leftTop.x) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.slant;
|
||||
|
||||
import android.graphics.PointF;
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.Line;
|
||||
|
||||
import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.intersectionOfLines;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
import static java.lang.Math.pow;
|
||||
import static java.lang.Math.sqrt;
|
||||
|
||||
/**
|
||||
* 分为两种斜线,横谢线和竖线线
|
||||
* 横斜线-->start为左边的点,end为右边的点
|
||||
* 竖斜线-->start为上面的点,end为下面的点
|
||||
*
|
||||
* @author wupanjie
|
||||
*/
|
||||
class SlantLine implements Line {
|
||||
CrossoverPointF start;
|
||||
CrossoverPointF end;
|
||||
|
||||
// 移动前的点
|
||||
private PointF previousStart = new PointF();
|
||||
private PointF previousEnd = new PointF();
|
||||
|
||||
public final Line.Direction direction;
|
||||
|
||||
SlantLine attachLineStart;
|
||||
SlantLine attachLineEnd;
|
||||
|
||||
Line upperLine;
|
||||
Line lowerLine;
|
||||
|
||||
SlantLine(Line.Direction direction) {
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
SlantLine(CrossoverPointF start, CrossoverPointF end, Line.Direction direction) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
public float length() {
|
||||
return (float) sqrt(pow(end.x - start.x, 2) + pow(end.y - start.y, 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PointF startPoint() {
|
||||
return start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PointF endPoint() {
|
||||
return end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Line lowerLine() {
|
||||
return lowerLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Line upperLine() {
|
||||
return upperLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Line attachStartLine() {
|
||||
return attachLineStart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Line attachEndLine() {
|
||||
return attachLineEnd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLowerLine(Line lowerLine) {
|
||||
this.lowerLine = lowerLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUpperLine(Line upperLine) {
|
||||
this.upperLine = upperLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Direction direction() {
|
||||
return direction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float slope() {
|
||||
return SlantUtils.calculateSlope(this);
|
||||
}
|
||||
|
||||
public boolean contains(float x, float y, float extra) {
|
||||
return SlantUtils.contains(this, x, y, extra);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean move(float offset, float extra) {
|
||||
if (direction == Line.Direction.HORIZONTAL) {
|
||||
if (previousStart.y + offset < lowerLine.maxY() + extra
|
||||
|| previousStart.y + offset > upperLine.minY() - extra
|
||||
|| previousEnd.y + offset < lowerLine.maxY() + extra
|
||||
|| previousEnd.y + offset > upperLine.minY() - extra) {
|
||||
return false;
|
||||
}
|
||||
|
||||
start.y = previousStart.y + offset;
|
||||
end.y = previousEnd.y + offset;
|
||||
} else {
|
||||
if (previousStart.x + offset < lowerLine.maxX() + extra
|
||||
|| previousStart.x + offset > upperLine.minX() - extra
|
||||
|| previousEnd.x + offset < lowerLine.maxX() + extra
|
||||
|| previousEnd.x + offset > upperLine.minX() - extra) {
|
||||
return false;
|
||||
}
|
||||
|
||||
start.x = previousStart.x + offset;
|
||||
end.x = previousEnd.x + offset;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareMove() {
|
||||
previousStart.set(start);
|
||||
previousEnd.set(end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(float layoutWidth, float layoutHeight) {
|
||||
intersectionOfLines(start, this, attachLineStart);
|
||||
intersectionOfLines(end, this, attachLineEnd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float minX() {
|
||||
return min(start.x, end.x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxX() {
|
||||
return max(start.x, end.x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float minY() {
|
||||
return min(start.y, end.y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxY() {
|
||||
return max(start.y, end.y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offset(float x, float y) {
|
||||
start.offset(x, y);
|
||||
end.offset(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "start --> " + start.toString() + ",end --> " + end.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.slant;
|
||||
|
||||
import android.graphics.RectF;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.Area;
|
||||
import com.huantansheng.easyphotos.models.puzzle.Line;
|
||||
import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.createLine;
|
||||
import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.cutAreaCross;
|
||||
import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.cutAreaWith;
|
||||
|
||||
|
||||
/**
|
||||
* 斜线布局,外围区域为一矩形
|
||||
*
|
||||
* @author wupanjie
|
||||
*/
|
||||
public abstract class SlantPuzzleLayout implements PuzzleLayout {
|
||||
private RectF bounds;
|
||||
private SlantArea outerArea;
|
||||
|
||||
private List<Line> outerLines = new ArrayList<>(4);
|
||||
private List<SlantArea> areas = new ArrayList<>();
|
||||
private List<Line> lines = new ArrayList<>();
|
||||
|
||||
private float padding;
|
||||
private float radian;
|
||||
private int color;
|
||||
|
||||
private Comparator<SlantArea> areaComparator = new SlantArea.AreaComparator();
|
||||
|
||||
private ArrayList<Step> steps = new ArrayList<>();
|
||||
|
||||
protected SlantPuzzleLayout() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOuterBounds(RectF bounds) {
|
||||
reset();
|
||||
|
||||
this.bounds = bounds;
|
||||
|
||||
CrossoverPointF leftTop = new CrossoverPointF(bounds.left, bounds.top);
|
||||
CrossoverPointF rightTop = new CrossoverPointF(bounds.right, bounds.top);
|
||||
CrossoverPointF leftBottom = new CrossoverPointF(bounds.left, bounds.bottom);
|
||||
CrossoverPointF rightBottom = new CrossoverPointF(bounds.right, bounds.bottom);
|
||||
|
||||
SlantLine lineLeft = new SlantLine(leftTop, leftBottom, Line.Direction.VERTICAL);
|
||||
SlantLine lineTop = new SlantLine(leftTop, rightTop, Line.Direction.HORIZONTAL);
|
||||
SlantLine lineRight = new SlantLine(rightTop, rightBottom, Line.Direction.VERTICAL);
|
||||
SlantLine lineBottom = new SlantLine(leftBottom, rightBottom, Line.Direction.HORIZONTAL);
|
||||
|
||||
outerLines.clear();
|
||||
|
||||
outerLines.add(lineLeft);
|
||||
outerLines.add(lineTop);
|
||||
outerLines.add(lineRight);
|
||||
outerLines.add(lineBottom);
|
||||
|
||||
outerArea = new SlantArea();
|
||||
outerArea.lineLeft = lineLeft;
|
||||
outerArea.lineTop = lineTop;
|
||||
outerArea.lineRight = lineRight;
|
||||
outerArea.lineBottom = lineBottom;
|
||||
|
||||
outerArea.updateCornerPoints();
|
||||
|
||||
areas.clear();
|
||||
areas.add(outerArea);
|
||||
}
|
||||
|
||||
public abstract void layout();
|
||||
|
||||
private void updateLineLimit() {
|
||||
int size = lines.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
Line line = lines.get(i);
|
||||
updateUpperLine(line);
|
||||
updateLowerLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateLowerLine(final Line line) {
|
||||
int size = lines.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
Line l = lines.get(i);
|
||||
if (l.direction() != line.direction()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (l.attachStartLine() != line.attachStartLine()
|
||||
|| l.attachEndLine() != line.attachEndLine()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (l.direction() == Line.Direction.HORIZONTAL) {
|
||||
if (l.minY() > line.lowerLine().maxY() && l.maxY() < line.minY()) {
|
||||
line.setLowerLine(l);
|
||||
}
|
||||
} else {
|
||||
if (l.minX() > line.lowerLine().maxX() && l.maxX() < line.minX()) {
|
||||
line.setLowerLine(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUpperLine(final Line line) {
|
||||
int size = lines.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
Line l = lines.get(i);
|
||||
if (l.direction() != line.direction()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (l.attachStartLine() != line.attachStartLine()
|
||||
|| l.attachEndLine() != line.attachEndLine()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (l.direction() == Line.Direction.HORIZONTAL) {
|
||||
if (l.maxY() < line.upperLine().minY() && l.minY() > line.maxY()) {
|
||||
line.setUpperLine(l);
|
||||
}
|
||||
} else {
|
||||
if (l.maxX() < line.upperLine().minX() && l.minX() > line.maxX()) {
|
||||
line.setUpperLine(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAreaCount() {
|
||||
return areas.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
lines.clear();
|
||||
areas.clear();
|
||||
areas.add(outerArea);
|
||||
steps.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
int size = lines.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
lines.get(i).update(width(), height());
|
||||
}
|
||||
int areasSize = areas.size();
|
||||
for (int i = 0; i < areasSize; i++) {
|
||||
areas.get(i).updateCornerPoints();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float width() {
|
||||
return outerArea == null ? 0 : outerArea.width();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float height() {
|
||||
return outerArea == null ? 0 : outerArea.height();
|
||||
}
|
||||
|
||||
private void sortAreas() {
|
||||
Collections.sort(areas, areaComparator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Line> getOuterLines() {
|
||||
return outerLines;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Area getOuterArea() {
|
||||
return outerArea;
|
||||
}
|
||||
|
||||
public List<SlantArea> getAreas() {
|
||||
return areas;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SlantArea getArea(int position) {
|
||||
return areas.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Line> getLines() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPadding(float padding) {
|
||||
this.padding = padding;
|
||||
for (Area area : areas) {
|
||||
area.setPadding(padding);
|
||||
}
|
||||
|
||||
outerArea.lineLeft.startPoint().set(bounds.left + padding, bounds.top + padding);
|
||||
outerArea.lineLeft.endPoint().set(bounds.left + padding, bounds.bottom - padding);
|
||||
|
||||
outerArea.lineRight.startPoint().set(bounds.right - padding, bounds.top + padding);
|
||||
outerArea.lineRight.endPoint().set(bounds.right - padding, bounds.bottom - padding);
|
||||
|
||||
outerArea.updateCornerPoints();
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPadding() {
|
||||
return padding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getRadian() {
|
||||
return radian;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRadian(float radian) {
|
||||
this.radian = radian;
|
||||
for (Area area : areas) {
|
||||
area.setRadian(radian);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColor(int color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
protected List<SlantArea> addLine(int position, Line.Direction direction, float ratio) {
|
||||
return addLine(position, direction, ratio, ratio);
|
||||
}
|
||||
|
||||
protected List<SlantArea> addLine(int position, Line.Direction direction, float startRatio,
|
||||
float endRatio) {
|
||||
SlantArea area = areas.get(position);
|
||||
areas.remove(area);
|
||||
SlantLine line = createLine(area, direction, startRatio, endRatio);
|
||||
lines.add(line);
|
||||
|
||||
List<SlantArea> increasedAreas = cutAreaWith(area, line);
|
||||
|
||||
areas.addAll(increasedAreas);
|
||||
|
||||
updateLineLimit();
|
||||
sortAreas();
|
||||
|
||||
Step step = new Step();
|
||||
step.type = Step.ADD_LINE;
|
||||
step.direction = direction == Line.Direction.HORIZONTAL ? 0 : 1;
|
||||
step.position = position;
|
||||
steps.add(step);
|
||||
|
||||
return increasedAreas;
|
||||
}
|
||||
|
||||
protected void addCross(int position, float startRatio1, float endRatio1,
|
||||
float startRatio2, float endRatio2) {
|
||||
SlantArea area = areas.get(position);
|
||||
areas.remove(area);
|
||||
|
||||
SlantLine horizontal = createLine(area, Line.Direction.HORIZONTAL, startRatio1, endRatio1);
|
||||
SlantLine vertical = createLine(area, Line.Direction.VERTICAL, startRatio2, endRatio2);
|
||||
lines.add(horizontal);
|
||||
lines.add(vertical);
|
||||
|
||||
List<SlantArea> increasedAreas = cutAreaCross(area, horizontal, vertical);
|
||||
|
||||
areas.addAll(increasedAreas);
|
||||
sortAreas();
|
||||
|
||||
Step step = new Step();
|
||||
step.type = Step.ADD_CROSS;
|
||||
step.position = position;
|
||||
steps.add(step);
|
||||
}
|
||||
|
||||
protected void cutArea(int position, int hSize, int vSize) {
|
||||
SlantArea area = areas.get(position);
|
||||
areas.remove(area);
|
||||
|
||||
Pair<List<SlantLine>, List<SlantArea>> spilt =
|
||||
cutAreaWith(area, hSize, vSize);
|
||||
|
||||
lines.addAll(spilt.first);
|
||||
areas.addAll(spilt.second);
|
||||
|
||||
updateLineLimit();
|
||||
sortAreas();
|
||||
|
||||
Step step = new Step();
|
||||
step.type = Step.CUT_EQUAL_PART_ONE;
|
||||
step.position = position;
|
||||
step.hSize = hSize;
|
||||
step.vSize = vSize;
|
||||
steps.add(step);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Info generateInfo() {
|
||||
Info info = new Info();
|
||||
info.type = Info.TYPE_SLANT;
|
||||
info.padding = padding;
|
||||
info.radian = radian;
|
||||
info.color = color;
|
||||
info.steps = steps;
|
||||
ArrayList<LineInfo> lineInfos = new ArrayList<>();
|
||||
for (Line line : lines) {
|
||||
LineInfo lineInfo = new LineInfo(line);
|
||||
lineInfos.add(lineInfo);
|
||||
}
|
||||
info.lineInfos = lineInfos;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,482 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.slant;
|
||||
|
||||
import android.graphics.PointF;
|
||||
import android.util.Pair;
|
||||
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.Line;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
class SlantUtils {
|
||||
private static final PointF A = new PointF();
|
||||
private static final PointF B = new PointF();
|
||||
private static final PointF C = new PointF();
|
||||
private static final PointF D = new PointF();
|
||||
private static final PointF AB = new PointF();
|
||||
private static final PointF AM = new PointF();
|
||||
private static final PointF BC = new PointF();
|
||||
private static final PointF BM = new PointF();
|
||||
private static final PointF CD = new PointF();
|
||||
private static final PointF CM = new PointF();
|
||||
private static final PointF DA = new PointF();
|
||||
private static final PointF DM = new PointF();
|
||||
|
||||
private SlantUtils() {
|
||||
//no instance
|
||||
}
|
||||
|
||||
static float distance(PointF one, PointF two) {
|
||||
return (float) Math.sqrt(Math.pow(two.x - one.x, 2) + Math.pow(two.y - one.y, 2));
|
||||
}
|
||||
|
||||
static List<SlantArea> cutAreaWith(SlantArea area, SlantLine line) {
|
||||
List<SlantArea> areas = new ArrayList<>();
|
||||
SlantArea area1 = new SlantArea(area);
|
||||
SlantArea area2 = new SlantArea(area);
|
||||
|
||||
if (line.direction == Line.Direction.HORIZONTAL) {
|
||||
area1.lineBottom = line;
|
||||
area1.leftBottom = line.start;
|
||||
area1.rightBottom = line.end;
|
||||
|
||||
area2.lineTop = line;
|
||||
area2.leftTop = line.start;
|
||||
area2.rightTop = line.end;
|
||||
} else {
|
||||
area1.lineRight = line;
|
||||
area1.rightTop = line.start;
|
||||
area1.rightBottom = line.end;
|
||||
|
||||
area2.lineLeft = line;
|
||||
area2.leftTop = line.start;
|
||||
area2.leftBottom = line.end;
|
||||
}
|
||||
|
||||
areas.add(area1);
|
||||
areas.add(area2);
|
||||
|
||||
return areas;
|
||||
}
|
||||
|
||||
static SlantLine createLine(SlantArea area, Line.Direction direction, float startratio,
|
||||
float endratio) {
|
||||
SlantLine line = new SlantLine(direction);
|
||||
|
||||
if (direction == Line.Direction.HORIZONTAL) {
|
||||
line.start = getPoint(area.leftTop, area.leftBottom, Line.Direction.VERTICAL, startratio);
|
||||
line.end = getPoint(area.rightTop, area.rightBottom, Line.Direction.VERTICAL, endratio);
|
||||
|
||||
line.attachLineStart = area.lineLeft;
|
||||
line.attachLineEnd = area.lineRight;
|
||||
|
||||
line.upperLine = area.lineBottom;
|
||||
line.lowerLine = area.lineTop;
|
||||
} else {
|
||||
line.start = getPoint(area.leftTop, area.rightTop, Line.Direction.HORIZONTAL, startratio);
|
||||
line.end = getPoint(area.leftBottom, area.rightBottom, Line.Direction.HORIZONTAL, endratio);
|
||||
|
||||
line.attachLineStart = area.lineTop;
|
||||
line.attachLineEnd = area.lineBottom;
|
||||
|
||||
line.upperLine = area.lineRight;
|
||||
line.lowerLine = area.lineLeft;
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
static Pair<List<SlantLine>, List<SlantArea>> cutAreaWith(final SlantArea area,
|
||||
final int horizontalSize, final int verticalSize) {
|
||||
List<SlantArea> areaList = new ArrayList<>();
|
||||
List<SlantLine> horizontalLines = new ArrayList<>(horizontalSize);
|
||||
|
||||
SlantArea restArea = new SlantArea(area);
|
||||
for (int i = horizontalSize + 1; i > 1; i--) {
|
||||
SlantLine horizontalLine =
|
||||
createLine(restArea, Line.Direction.HORIZONTAL, (float) (i - 1) / i - 0.025f, (float) (i - 1) / i + 0.025f);
|
||||
horizontalLines.add(horizontalLine);
|
||||
restArea.lineBottom = horizontalLine;
|
||||
restArea.leftBottom = horizontalLine.start;
|
||||
restArea.rightBottom = horizontalLine.end;
|
||||
}
|
||||
List<SlantLine> verticalLines = new ArrayList<>();
|
||||
|
||||
restArea = new SlantArea(area);
|
||||
for (int i = verticalSize + 1; i > 1; i--) {
|
||||
SlantLine verticalLine =
|
||||
createLine(restArea, Line.Direction.VERTICAL, (float) (i - 1) / i + 0.025f, (float) (i - 1) / i - 0.025f);
|
||||
verticalLines.add(verticalLine);
|
||||
SlantArea spiltArea = new SlantArea(restArea);
|
||||
spiltArea.lineLeft = verticalLine;
|
||||
spiltArea.leftTop = verticalLine.start;
|
||||
spiltArea.leftBottom = verticalLine.end;
|
||||
|
||||
int size = horizontalLines.size();
|
||||
for (int j = 0; j <= size; j++) {
|
||||
SlantArea blockArea = new SlantArea(spiltArea);
|
||||
if (j == 0) {
|
||||
blockArea.lineTop = horizontalLines.get(j);
|
||||
} else if (j == size) {
|
||||
blockArea.lineBottom = horizontalLines.get(j - 1);
|
||||
|
||||
CrossoverPointF leftBottom =
|
||||
new CrossoverPointF(blockArea.lineBottom, blockArea.lineLeft);
|
||||
intersectionOfLines(leftBottom, blockArea.lineBottom, blockArea.lineLeft);
|
||||
CrossoverPointF rightBottom =
|
||||
new CrossoverPointF(blockArea.lineBottom, blockArea.lineRight);
|
||||
intersectionOfLines(rightBottom, blockArea.lineBottom, blockArea.lineRight);
|
||||
blockArea.leftBottom = leftBottom;
|
||||
blockArea.rightBottom = rightBottom;
|
||||
} else {
|
||||
blockArea.lineTop = horizontalLines.get(j);
|
||||
blockArea.lineBottom = horizontalLines.get(j - 1);
|
||||
}
|
||||
CrossoverPointF leftTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineLeft);
|
||||
intersectionOfLines(leftTop, blockArea.lineTop, blockArea.lineLeft);
|
||||
CrossoverPointF rightTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineRight);
|
||||
intersectionOfLines(rightTop, blockArea.lineTop, blockArea.lineRight);
|
||||
blockArea.leftTop = leftTop;
|
||||
blockArea.rightTop = rightTop;
|
||||
areaList.add(blockArea);
|
||||
}
|
||||
restArea.lineRight = verticalLine;
|
||||
restArea.rightTop = verticalLine.start;
|
||||
restArea.rightBottom = verticalLine.end;
|
||||
}
|
||||
int size = horizontalLines.size();
|
||||
for (int j = 0; j <= size; j++) {
|
||||
SlantArea blockArea = new SlantArea(restArea);
|
||||
if (j == 0) {
|
||||
blockArea.lineTop = horizontalLines.get(j);
|
||||
} else if (j == size) {
|
||||
blockArea.lineBottom = horizontalLines.get(j - 1);
|
||||
CrossoverPointF leftBottom = new CrossoverPointF(blockArea.lineBottom, blockArea.lineLeft);
|
||||
intersectionOfLines(leftBottom, blockArea.lineBottom, blockArea.lineLeft);
|
||||
CrossoverPointF rightBottom =
|
||||
new CrossoverPointF(blockArea.lineBottom, blockArea.lineRight);
|
||||
intersectionOfLines(rightBottom, blockArea.lineBottom, blockArea.lineRight);
|
||||
blockArea.leftBottom = leftBottom;
|
||||
blockArea.rightBottom = rightBottom;
|
||||
} else {
|
||||
blockArea.lineTop = horizontalLines.get(j);
|
||||
blockArea.lineBottom = horizontalLines.get(j - 1);
|
||||
}
|
||||
CrossoverPointF leftTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineLeft);
|
||||
intersectionOfLines(leftTop, blockArea.lineTop, blockArea.lineLeft);
|
||||
CrossoverPointF rightTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineRight);
|
||||
intersectionOfLines(rightTop, blockArea.lineTop, blockArea.lineRight);
|
||||
blockArea.leftTop = leftTop;
|
||||
blockArea.rightTop = rightTop;
|
||||
areaList.add(blockArea);
|
||||
}
|
||||
|
||||
List<SlantLine> lines = new ArrayList<>();
|
||||
lines.addAll(horizontalLines);
|
||||
lines.addAll(verticalLines);
|
||||
return new Pair<>(lines, areaList);
|
||||
}
|
||||
|
||||
static List<SlantArea> cutAreaCross(final SlantArea area, final SlantLine horizontal,
|
||||
final SlantLine vertical) {
|
||||
List<SlantArea> list = new ArrayList<>();
|
||||
|
||||
CrossoverPointF crossoverPoint = new CrossoverPointF(horizontal, vertical);
|
||||
intersectionOfLines(crossoverPoint, horizontal, vertical);
|
||||
|
||||
SlantArea one = new SlantArea(area);
|
||||
one.lineBottom = horizontal;
|
||||
one.lineRight = vertical;
|
||||
|
||||
one.rightTop = vertical.start;
|
||||
one.rightBottom = crossoverPoint;
|
||||
one.leftBottom = horizontal.start;
|
||||
list.add(one);
|
||||
|
||||
SlantArea two = new SlantArea(area);
|
||||
two.lineBottom = horizontal;
|
||||
two.lineLeft = vertical;
|
||||
|
||||
two.leftTop = vertical.start;
|
||||
two.rightBottom = horizontal.end;
|
||||
two.leftBottom = crossoverPoint;
|
||||
list.add(two);
|
||||
|
||||
SlantArea three = new SlantArea(area);
|
||||
three.lineTop = horizontal;
|
||||
three.lineRight = vertical;
|
||||
|
||||
three.leftTop = horizontal.start;
|
||||
three.rightTop = crossoverPoint;
|
||||
three.rightBottom = vertical.end;
|
||||
list.add(three);
|
||||
|
||||
SlantArea four = new SlantArea(area);
|
||||
four.lineTop = horizontal;
|
||||
four.lineLeft = vertical;
|
||||
|
||||
four.leftTop = crossoverPoint;
|
||||
four.rightTop = horizontal.end;
|
||||
four.leftBottom = vertical.end;
|
||||
list.add(four);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static CrossoverPointF getPoint(final PointF start, final PointF end,
|
||||
final Line.Direction direction, float ratio) {
|
||||
CrossoverPointF point = new CrossoverPointF();
|
||||
getPoint(point, start, end, direction, ratio);
|
||||
return point;
|
||||
}
|
||||
|
||||
static void getPoint(final PointF dst, final PointF start, final PointF end,
|
||||
final Line.Direction direction, float ratio) {
|
||||
float deltaY = Math.abs(start.y - end.y);
|
||||
float deltaX = Math.abs(start.x - end.x);
|
||||
float maxY = Math.max(start.y, end.y);
|
||||
float minY = Math.min(start.y, end.y);
|
||||
float maxX = Math.max(start.x, end.x);
|
||||
float minX = Math.min(start.x, end.x);
|
||||
if (direction == Line.Direction.HORIZONTAL) {
|
||||
dst.x = minX + deltaX * ratio;
|
||||
if (start.y < end.y) {
|
||||
dst.y = minY + ratio * deltaY;
|
||||
} else {
|
||||
dst.y = maxY - ratio * deltaY;
|
||||
}
|
||||
} else {
|
||||
dst.y = minY + deltaY * ratio;
|
||||
if (start.x < end.x) {
|
||||
dst.x = minX + ratio * deltaX;
|
||||
} else {
|
||||
dst.x = maxX - ratio * deltaX;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 叉乘
|
||||
private static float crossProduct(final PointF a, final PointF b) {
|
||||
return a.x * b.y - b.x * a.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断一个斜线区域是否包含(x,y)点
|
||||
*
|
||||
* @param area 斜线区域
|
||||
* @param x x
|
||||
* @param y y
|
||||
* @return 是否包含
|
||||
*/
|
||||
static boolean contains(SlantArea area, float x, float y) {
|
||||
AB.x = area.rightTop.x - area.leftTop.x;
|
||||
AB.y = area.rightTop.y - area.leftTop.y;
|
||||
|
||||
AM.x = x - area.leftTop.x;
|
||||
AM.y = y - area.leftTop.y;
|
||||
|
||||
BC.x = area.rightBottom.x - area.rightTop.x;
|
||||
BC.y = area.rightBottom.y - area.rightTop.y;
|
||||
|
||||
BM.x = x - area.rightTop.x;
|
||||
BM.y = y - area.rightTop.y;
|
||||
|
||||
CD.x = area.leftBottom.x - area.rightBottom.x;
|
||||
CD.y = area.leftBottom.y - area.rightBottom.y;
|
||||
|
||||
CM.x = x - area.rightBottom.x;
|
||||
CM.y = y - area.rightBottom.y;
|
||||
|
||||
DA.x = area.leftTop.x - area.leftBottom.x;
|
||||
DA.y = area.leftTop.y - area.leftBottom.y;
|
||||
|
||||
DM.x = x - area.leftBottom.x;
|
||||
DM.y = y - area.leftBottom.y;
|
||||
|
||||
return crossProduct(AB, AM) > 0
|
||||
&& crossProduct(BC, BM) > 0
|
||||
&& crossProduct(CD, CM) > 0
|
||||
&& crossProduct(DA, DM) > 0;
|
||||
}
|
||||
|
||||
static boolean contains(SlantLine line, float x, float y, float extra) {
|
||||
PointF start = line.start;
|
||||
PointF end = line.end;
|
||||
if (line.direction == Line.Direction.VERTICAL) {
|
||||
A.x = start.x - extra;
|
||||
A.y = start.y;
|
||||
B.x = start.x + extra;
|
||||
B.y = start.y;
|
||||
C.x = end.x + extra;
|
||||
C.y = end.y;
|
||||
D.x = end.x - extra;
|
||||
D.y = end.y;
|
||||
} else {
|
||||
A.x = start.x;
|
||||
A.y = start.y - extra;
|
||||
B.x = end.x;
|
||||
B.y = end.y - extra;
|
||||
C.x = end.x;
|
||||
C.y = end.y + extra;
|
||||
D.x = start.x;
|
||||
D.y = start.y + extra;
|
||||
}
|
||||
|
||||
AB.x = B.x - A.x;
|
||||
AB.y = B.y - A.y;
|
||||
|
||||
AM.x = x - A.x;
|
||||
AM.y = y - A.y;
|
||||
|
||||
BC.x = C.x - B.x;
|
||||
BC.y = C.y - B.y;
|
||||
|
||||
BM.x = x - B.x;
|
||||
BM.y = y - B.y;
|
||||
|
||||
CD.x = D.x - C.x;
|
||||
CD.y = D.y - C.y;
|
||||
|
||||
CM.x = x - C.x;
|
||||
CM.y = y - C.y;
|
||||
|
||||
DA.x = A.x - D.x;
|
||||
DA.y = A.y - D.y;
|
||||
|
||||
DM.x = x - D.x;
|
||||
DM.y = y - D.y;
|
||||
|
||||
return crossProduct(AB, AM) > 0
|
||||
&& crossProduct(BC, BM) > 0
|
||||
&& crossProduct(CD, CM) > 0
|
||||
&& crossProduct(DA, DM) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算两线的交点
|
||||
*
|
||||
* @param dst 计算出的交点
|
||||
* @param lineOne 线一
|
||||
* @param lineTwo 线二
|
||||
*/
|
||||
static void intersectionOfLines(final CrossoverPointF dst, final SlantLine lineOne,
|
||||
final SlantLine lineTwo) {
|
||||
dst.horizontal = lineOne;
|
||||
dst.vertical = lineTwo;
|
||||
if (isParallel(lineOne, lineTwo)) {
|
||||
dst.set(0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isHorizontalLine(lineOne) && isVerticalLine(lineTwo)) {
|
||||
dst.set(lineTwo.start.x, lineOne.start.y);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isVerticalLine(lineOne) && isHorizontalLine(lineTwo)) {
|
||||
dst.set(lineOne.start.x, lineTwo.start.y);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isHorizontalLine(lineOne) && !isVerticalLine(lineTwo)) {
|
||||
float k = calculateSlope(lineTwo);
|
||||
float b = calculateVerticalIntercept(lineTwo);
|
||||
|
||||
dst.y = lineOne.start.y;
|
||||
dst.x = (dst.y - b) / k;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isVerticalLine(lineOne) && !isHorizontalLine(lineTwo)) {
|
||||
float k = calculateSlope(lineTwo);
|
||||
float b = calculateVerticalIntercept(lineTwo);
|
||||
|
||||
dst.x = lineOne.start.x;
|
||||
dst.y = k * dst.x + b;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isHorizontalLine(lineTwo) && !isVerticalLine(lineOne)) {
|
||||
float k = calculateSlope(lineOne);
|
||||
float b = calculateVerticalIntercept(lineOne);
|
||||
|
||||
dst.y = lineTwo.start.y;
|
||||
dst.x = (dst.y - b) / k;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isVerticalLine(lineTwo) && !isHorizontalLine(lineOne)) {
|
||||
float k = calculateSlope(lineOne);
|
||||
float b = calculateVerticalIntercept(lineOne);
|
||||
|
||||
dst.x = lineTwo.start.x;
|
||||
dst.y = k * dst.x + b;
|
||||
return;
|
||||
}
|
||||
|
||||
final float k1 = calculateSlope(lineOne);
|
||||
final float b1 = calculateVerticalIntercept(lineOne);
|
||||
|
||||
final float k2 = calculateSlope(lineTwo);
|
||||
final float b2 = calculateVerticalIntercept(lineTwo);
|
||||
|
||||
dst.x = (b2 - b1) / (k1 - k2);
|
||||
dst.y = dst.x * k1 + b1;
|
||||
}
|
||||
|
||||
private static boolean isHorizontalLine(SlantLine line) {
|
||||
return line.start.y == line.end.y;
|
||||
}
|
||||
|
||||
private static boolean isVerticalLine(SlantLine line) {
|
||||
return line.start.x == line.end.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两条线是否平行
|
||||
*
|
||||
* @param lineOne 第一条
|
||||
* @param lineTwo 第二条
|
||||
* @return 是否平行
|
||||
*/
|
||||
private static boolean isParallel(final SlantLine lineOne, final SlantLine lineTwo) {
|
||||
return calculateSlope(lineOne) == calculateSlope(lineTwo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算线的斜率
|
||||
*
|
||||
* @param line 线
|
||||
* @return 线的斜率
|
||||
*/
|
||||
static float calculateSlope(final SlantLine line) {
|
||||
if (isHorizontalLine(line)) {
|
||||
return 0f;
|
||||
} else if (isVerticalLine(line)) {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
} else {
|
||||
return (line.start.y - line.end.y) / (line.start.x - line.end.x);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算纵截距
|
||||
*
|
||||
* @param line 线
|
||||
* @return 纵截距
|
||||
*/
|
||||
private static float calculateVerticalIntercept(final SlantLine line) {
|
||||
if (isHorizontalLine(line)) {
|
||||
return line.start.y;
|
||||
} else if (isVerticalLine(line)) {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
} else {
|
||||
float k = calculateSlope(line);
|
||||
return line.start.y - k * line.start.x;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.straight;
|
||||
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.RectF;
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.Area;
|
||||
import com.huantansheng.easyphotos.models.puzzle.Line;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
class StraightArea implements Area {
|
||||
StraightLine lineLeft;
|
||||
StraightLine lineTop;
|
||||
StraightLine lineRight;
|
||||
StraightLine lineBottom;
|
||||
|
||||
private Path areaPath = new Path();
|
||||
private RectF areaRect = new RectF();
|
||||
private PointF[] handleBarPoints = new PointF[2];
|
||||
|
||||
private float paddingLeft;
|
||||
private float paddingTop;
|
||||
private float paddingRight;
|
||||
private float paddingBottom;
|
||||
private float radian;
|
||||
|
||||
StraightArea(){
|
||||
handleBarPoints[0] = new PointF();
|
||||
handleBarPoints[1] = new PointF();
|
||||
}
|
||||
|
||||
StraightArea(RectF baseRect) {
|
||||
this();
|
||||
setBaseRect(baseRect);
|
||||
}
|
||||
|
||||
private void setBaseRect(RectF baseRect) {
|
||||
PointF one = new PointF(baseRect.left, baseRect.top);
|
||||
PointF two = new PointF(baseRect.right, baseRect.top);
|
||||
PointF three = new PointF(baseRect.left, baseRect.bottom);
|
||||
PointF four = new PointF(baseRect.right, baseRect.bottom);
|
||||
|
||||
lineLeft = new StraightLine(one, three);
|
||||
lineTop = new StraightLine(one, two);
|
||||
lineRight = new StraightLine(two, four);
|
||||
lineBottom = new StraightLine(three, four);
|
||||
}
|
||||
|
||||
StraightArea(StraightArea src) {
|
||||
this.lineLeft = src.lineLeft;
|
||||
this.lineTop = src.lineTop;
|
||||
this.lineRight = src.lineRight;
|
||||
this.lineBottom = src.lineBottom;
|
||||
|
||||
handleBarPoints[0] = new PointF();
|
||||
handleBarPoints[1] = new PointF();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float left() {
|
||||
return lineLeft.minX() + paddingLeft;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float top() {
|
||||
return lineTop.minY() + paddingTop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float right() {
|
||||
return lineRight.maxX() - paddingRight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float bottom() {
|
||||
return lineBottom.maxY() - paddingBottom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float centerX() {
|
||||
return (left() + right()) / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float centerY() {
|
||||
return (top() + bottom()) / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float width() {
|
||||
return right() - left();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float height() {
|
||||
return bottom() - top();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PointF getCenterPoint() {
|
||||
return new PointF(centerX(), centerY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(PointF point) {
|
||||
return contains(point.x, point.y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(float x, float y) {
|
||||
return getAreaRect().contains(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Line line) {
|
||||
return lineLeft == line || lineTop == line || lineRight == line || lineBottom == line;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getAreaPath() {
|
||||
areaPath.reset();
|
||||
areaPath.addRoundRect(getAreaRect(),radian,radian, Path.Direction.CCW);
|
||||
//areaPath.addRect(getAreaRect(), Path.Direction.CCW);
|
||||
return areaPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RectF getAreaRect() {
|
||||
areaRect.set(left(), top(), right(), bottom());
|
||||
return areaRect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Line> getLines() {
|
||||
return Arrays.asList((Line) lineLeft, lineTop, lineRight, lineBottom);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PointF[] getHandleBarPoints(Line line) {
|
||||
if (line == lineLeft) {
|
||||
handleBarPoints[0].x = left();
|
||||
handleBarPoints[0].y = top() + height() / 4;
|
||||
handleBarPoints[1].x = left();
|
||||
handleBarPoints[1].y = top() + height() / 4 * 3;
|
||||
} else if (line == lineTop) {
|
||||
handleBarPoints[0].x = left() + width() / 4;
|
||||
handleBarPoints[0].y = top();
|
||||
handleBarPoints[1].x = left() + width() / 4 * 3;
|
||||
handleBarPoints[1].y = top();
|
||||
} else if (line == lineRight) {
|
||||
handleBarPoints[0].x = right();
|
||||
handleBarPoints[0].y = top() + height() / 4;
|
||||
handleBarPoints[1].x = right();
|
||||
handleBarPoints[1].y = top() + height() / 4 * 3;
|
||||
} else if (line == lineBottom) {
|
||||
handleBarPoints[0].x = left() + width() / 4;
|
||||
handleBarPoints[0].y = bottom();
|
||||
handleBarPoints[1].x = left() + width() / 4 * 3;
|
||||
handleBarPoints[1].y = bottom();
|
||||
}
|
||||
return handleBarPoints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float radian() {
|
||||
return radian;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRadian(float radian) {
|
||||
this.radian = radian;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPaddingLeft() {
|
||||
return paddingLeft;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPaddingTop() {
|
||||
return paddingTop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPaddingRight() {
|
||||
return paddingRight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPaddingBottom() {
|
||||
return paddingBottom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPadding(float padding) {
|
||||
setPadding(padding, padding, padding, padding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPadding(float paddingLeft, float paddingTop, float paddingRight,
|
||||
float paddingBottom) {
|
||||
this.paddingLeft = paddingLeft;
|
||||
this.paddingTop = paddingTop;
|
||||
this.paddingRight = paddingRight;
|
||||
this.paddingBottom = paddingBottom;
|
||||
}
|
||||
|
||||
static class AreaComparator implements Comparator<StraightArea> {
|
||||
@Override
|
||||
public int compare(StraightArea lhs, StraightArea rhs) {
|
||||
if (lhs.top() < rhs.top()) {
|
||||
return -1;
|
||||
} else if (lhs.top() == rhs.top()) {
|
||||
if (lhs.left() < rhs.left()) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.straight;
|
||||
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.RectF;
|
||||
import android.util.Log;
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.Line;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
class StraightLine implements Line {
|
||||
private PointF start;
|
||||
private PointF end;
|
||||
|
||||
private PointF previousStart = new PointF();
|
||||
private PointF previousEnd = new PointF();
|
||||
|
||||
public Line.Direction direction = Direction.HORIZONTAL;
|
||||
|
||||
StraightLine attachLineStart;
|
||||
StraightLine attachLineEnd;
|
||||
|
||||
private Line upperLine;
|
||||
private Line lowerLine;
|
||||
|
||||
private RectF bounds = new RectF();
|
||||
|
||||
StraightLine(PointF start, PointF end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
|
||||
if (start.x == end.x) {
|
||||
direction = Line.Direction.VERTICAL;
|
||||
} else if (start.y == end.y) {
|
||||
direction = Line.Direction.HORIZONTAL;
|
||||
} else {
|
||||
Log.d("StraightLine", "StraightLine: current only support two direction");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float length() {
|
||||
return (float) Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PointF startPoint() {
|
||||
return start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PointF endPoint() {
|
||||
return end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Line lowerLine() {
|
||||
return lowerLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Line upperLine() {
|
||||
return upperLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Line attachStartLine() {
|
||||
return attachLineStart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Line attachEndLine() {
|
||||
return attachLineEnd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLowerLine(Line lowerLine) {
|
||||
this.lowerLine = lowerLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUpperLine(Line upperLine) {
|
||||
this.upperLine = upperLine;
|
||||
}
|
||||
|
||||
void setAttachLineStart(StraightLine attachLineStart) {
|
||||
this.attachLineStart = attachLineStart;
|
||||
}
|
||||
|
||||
void setAttachLineEnd(StraightLine attachLineEnd) {
|
||||
this.attachLineEnd = attachLineEnd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Direction direction() {
|
||||
return direction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float slope() {
|
||||
return direction == Direction.HORIZONTAL ? 0 : Float.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(float x, float y, float extra) {
|
||||
if (direction == Line.Direction.HORIZONTAL) {
|
||||
bounds.left = start.x;
|
||||
bounds.right = end.x;
|
||||
bounds.top = start.y - extra / 2;
|
||||
bounds.bottom = start.y + extra / 2;
|
||||
} else if (direction == Line.Direction.VERTICAL) {
|
||||
bounds.top = start.y;
|
||||
bounds.bottom = end.y;
|
||||
bounds.left = start.x - extra / 2;
|
||||
bounds.right = start.x + extra / 2;
|
||||
}
|
||||
|
||||
return bounds.contains(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareMove() {
|
||||
previousStart.set(start);
|
||||
previousEnd.set(end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean move(float offset, float extra) {
|
||||
if (direction == Line.Direction.HORIZONTAL) {
|
||||
if (previousStart.y + offset < lowerLine.maxY() + extra
|
||||
|| previousStart.y + offset > upperLine.minY() - extra
|
||||
|| previousEnd.y + offset < lowerLine.maxY() + extra
|
||||
|| previousEnd.y + offset > upperLine.minY() - extra) {
|
||||
return false;
|
||||
}
|
||||
|
||||
start.y = previousStart.y + offset;
|
||||
end.y = previousEnd.y + offset;
|
||||
} else {
|
||||
if (previousStart.x + offset < lowerLine.maxX() + extra
|
||||
|| previousStart.x + offset > upperLine.minX() - extra
|
||||
|| previousEnd.x + offset < lowerLine.maxX() + extra
|
||||
|| previousEnd.x + offset > upperLine.minX() - extra) {
|
||||
return false;
|
||||
}
|
||||
|
||||
start.x = previousStart.x + offset;
|
||||
end.x = previousEnd.x + offset;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(float layoutWidth, float layoutHeight) {
|
||||
if (direction == Line.Direction.HORIZONTAL) {
|
||||
if (attachLineStart != null) {
|
||||
start.x = attachLineStart.getPosition();
|
||||
}
|
||||
if (attachLineEnd != null) {
|
||||
end.x = attachLineEnd.getPosition();
|
||||
}
|
||||
} else if (direction == Line.Direction.VERTICAL) {
|
||||
if (attachLineStart != null) {
|
||||
start.y = attachLineStart.getPosition();
|
||||
}
|
||||
if (attachLineEnd != null) {
|
||||
end.y = attachLineEnd.getPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float getPosition() {
|
||||
if (direction == Line.Direction.HORIZONTAL) {
|
||||
return start.y;
|
||||
} else {
|
||||
return start.x;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float minX() {
|
||||
return min(start.x, end.x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxX() {
|
||||
return max(start.x, end.x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float minY() {
|
||||
return min(start.y, end.y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxY() {
|
||||
return max(start.y, end.y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offset(float x, float y) {
|
||||
start.offset(x, y);
|
||||
end.offset(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "start --> " + start.toString() + ",end --> " + end.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.straight;
|
||||
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.RectF;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.Area;
|
||||
import com.huantansheng.easyphotos.models.puzzle.Line;
|
||||
import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import static com.huantansheng.easyphotos.models.puzzle.straight.StraightUtils.createLine;
|
||||
import static com.huantansheng.easyphotos.models.puzzle.straight.StraightUtils.cutAreaCross;
|
||||
import static com.huantansheng.easyphotos.models.puzzle.straight.StraightUtils.cutAreaSpiral;
|
||||
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
public abstract class StraightPuzzleLayout implements PuzzleLayout {
|
||||
private RectF bounds;
|
||||
private StraightArea outerArea;
|
||||
|
||||
private List<StraightArea> areas = new ArrayList<>();
|
||||
private List<Line> lines = new ArrayList<>();
|
||||
private List<Line> outerLines = new ArrayList<>(4);
|
||||
|
||||
private float padding;
|
||||
private float radian;
|
||||
private int color;
|
||||
|
||||
private Comparator<StraightArea> areaComparator = new StraightArea.AreaComparator();
|
||||
|
||||
private ArrayList<Step> steps = new ArrayList<>();
|
||||
|
||||
protected StraightPuzzleLayout() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOuterBounds(RectF bounds) {
|
||||
reset();
|
||||
|
||||
this.bounds = bounds;
|
||||
|
||||
PointF one = new PointF(bounds.left, bounds.top);
|
||||
PointF two = new PointF(bounds.right, bounds.top);
|
||||
PointF three = new PointF(bounds.left, bounds.bottom);
|
||||
PointF four = new PointF(bounds.right, bounds.bottom);
|
||||
|
||||
StraightLine lineLeft = new StraightLine(one, three);
|
||||
StraightLine lineTop = new StraightLine(one, two);
|
||||
StraightLine lineRight = new StraightLine(two, four);
|
||||
StraightLine lineBottom = new StraightLine(three, four);
|
||||
|
||||
outerLines.clear();
|
||||
|
||||
outerLines.add(lineLeft);
|
||||
outerLines.add(lineTop);
|
||||
outerLines.add(lineRight);
|
||||
outerLines.add(lineBottom);
|
||||
|
||||
outerArea = new StraightArea();
|
||||
outerArea.lineLeft = lineLeft;
|
||||
outerArea.lineTop = lineTop;
|
||||
outerArea.lineRight = lineRight;
|
||||
outerArea.lineBottom = lineBottom;
|
||||
|
||||
areas.clear();
|
||||
areas.add(outerArea);
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void layout();
|
||||
|
||||
@Override
|
||||
public int getAreaCount() {
|
||||
return areas.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Line> getOuterLines() {
|
||||
return outerLines;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Line> getLines() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
for (Line line : lines) {
|
||||
line.update(width(), height());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float width() {
|
||||
return outerArea == null ? 0 : outerArea.width();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float height() {
|
||||
return outerArea == null ? 0 : outerArea.height();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
lines.clear();
|
||||
areas.clear();
|
||||
areas.add(outerArea);
|
||||
steps.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Area getArea(int position) {
|
||||
return areas.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
public StraightArea getOuterArea() {
|
||||
return outerArea;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPadding(float padding) {
|
||||
this.padding = padding;
|
||||
|
||||
for (Area area : areas) {
|
||||
area.setPadding(padding);
|
||||
}
|
||||
|
||||
outerArea.lineLeft.startPoint().set(bounds.left + padding, bounds.top + padding);
|
||||
outerArea.lineLeft.endPoint().set(bounds.left + padding, bounds.bottom - padding);
|
||||
|
||||
outerArea.lineRight.startPoint().set(bounds.right - padding, bounds.top + padding);
|
||||
outerArea.lineRight.endPoint().set(bounds.right - padding, bounds.bottom - padding);
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPadding() {
|
||||
return padding;
|
||||
}
|
||||
|
||||
protected void addLine(int position, Line.Direction direction, float ratio) {
|
||||
StraightArea area = areas.get(position);
|
||||
addLine(area, direction, ratio);
|
||||
|
||||
Step step = new Step();
|
||||
step.type = Step.ADD_LINE;
|
||||
step.direction = direction == Line.Direction.HORIZONTAL ? 0 : 1;
|
||||
step.position = position;
|
||||
steps.add(step);
|
||||
}
|
||||
|
||||
private List<StraightArea> addLine(StraightArea area, Line.Direction direction, float ratio) {
|
||||
areas.remove(area);
|
||||
StraightLine line = createLine(area, direction, ratio);
|
||||
lines.add(line);
|
||||
|
||||
List<StraightArea> increasedArea = StraightUtils.cutArea(area, line);
|
||||
areas.addAll(increasedArea);
|
||||
|
||||
updateLineLimit();
|
||||
sortAreas();
|
||||
|
||||
return increasedArea;
|
||||
}
|
||||
|
||||
protected void cutAreaEqualPart(int position, int part, Line.Direction direction) {
|
||||
StraightArea temp = areas.get(position);
|
||||
for (int i = part; i > 1; i--) {
|
||||
temp = addLine(temp, direction, (float) (i - 1) / i).get(0);
|
||||
}
|
||||
|
||||
Step step = new Step();
|
||||
step.type = Step.CUT_EQUAL_PART_TWO;
|
||||
step.part = part;
|
||||
step.position = position;
|
||||
step.direction = direction == Line.Direction.HORIZONTAL ? 0 : 1;
|
||||
steps.add(step);
|
||||
}
|
||||
|
||||
protected void addCross(int position, float ratio) {
|
||||
addCross(position, ratio, ratio);
|
||||
}
|
||||
|
||||
protected void addCross(int position, float horizontalRatio, float verticalRatio) {
|
||||
StraightArea area = areas.get(position);
|
||||
areas.remove(area);
|
||||
StraightLine horizontal = createLine(area, Line.Direction.HORIZONTAL, horizontalRatio);
|
||||
StraightLine vertical = createLine(area, Line.Direction.VERTICAL, verticalRatio);
|
||||
lines.add(horizontal);
|
||||
lines.add(vertical);
|
||||
|
||||
List<StraightArea> newAreas = cutAreaCross(area, horizontal, vertical);
|
||||
areas.addAll(newAreas);
|
||||
|
||||
updateLineLimit();
|
||||
sortAreas();
|
||||
|
||||
Step step = new Step();
|
||||
step.type = Step.ADD_CROSS;
|
||||
step.position = position;
|
||||
steps.add(step);
|
||||
}
|
||||
|
||||
protected void cutAreaEqualPart(int position, int hSize, int vSize) {
|
||||
StraightArea area = areas.get(position);
|
||||
areas.remove(area);
|
||||
Pair<List<StraightLine>, List<StraightArea>> increased =
|
||||
StraightUtils.cutArea(area, hSize, vSize);
|
||||
List<StraightLine> newLines = increased.first;
|
||||
List<StraightArea> newAreas = increased.second;
|
||||
|
||||
lines.addAll(newLines);
|
||||
areas.addAll(newAreas);
|
||||
|
||||
updateLineLimit();
|
||||
sortAreas();
|
||||
|
||||
Step step = new Step();
|
||||
step.type = Step.CUT_EQUAL_PART_ONE;
|
||||
step.position = position;
|
||||
step.hSize = hSize;
|
||||
step.vSize = vSize;
|
||||
steps.add(step);
|
||||
}
|
||||
|
||||
protected void cutSpiral(int position) {
|
||||
StraightArea area = areas.get(position);
|
||||
areas.remove(area);
|
||||
Pair<List<StraightLine>, List<StraightArea>> spilt = cutAreaSpiral(area);
|
||||
|
||||
lines.addAll(spilt.first);
|
||||
areas.addAll(spilt.second);
|
||||
|
||||
updateLineLimit();
|
||||
sortAreas();
|
||||
|
||||
Step step = new Step();
|
||||
step.type = Step.CUT_SPIRAL;
|
||||
step.position = position;
|
||||
steps.add(step);
|
||||
}
|
||||
|
||||
private void sortAreas() {
|
||||
Collections.sort(areas, areaComparator);
|
||||
}
|
||||
|
||||
private void updateLineLimit() {
|
||||
int size = lines.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
Line line = lines.get(i);
|
||||
updateUpperLine(line);
|
||||
updateLowerLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateLowerLine(final Line line) {
|
||||
int size = lines.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
Line l = lines.get(i);
|
||||
if (l == line) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (l.direction() != line.direction()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (l.direction() == Line.Direction.HORIZONTAL) {
|
||||
if (l.maxX() <= line.minX() || line.maxX() <= l.minX()) continue;
|
||||
if (l.minY() > line.lowerLine().maxY() && l.maxY() < line.minY()) {
|
||||
line.setLowerLine(l);
|
||||
}
|
||||
} else {
|
||||
if (l.maxY() <= line.minY() || line.maxY() <= l.minY()) continue;
|
||||
if (l.minX() > line.lowerLine().maxX() && l.maxX() < line.minX()) {
|
||||
line.setLowerLine(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUpperLine(final Line line) {
|
||||
int size = lines.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
Line l = lines.get(i);
|
||||
if (l == line) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (l.direction() != line.direction()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (l.direction() == Line.Direction.HORIZONTAL) {
|
||||
if (l.maxX() <= line.minX() || line.maxX() <= l.minX()) continue;
|
||||
if (l.maxY() < line.upperLine().minY() && l.minY() > line.maxY()) {
|
||||
line.setUpperLine(l);
|
||||
}
|
||||
} else {
|
||||
if (l.maxY() <= line.minY() || line.maxY() <= l.minY()) continue;
|
||||
if (l.maxX() < line.upperLine().minX() && l.minX() > line.maxX()) {
|
||||
line.setUpperLine(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getRadian() {
|
||||
return radian;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRadian(float radian) {
|
||||
this.radian = radian;
|
||||
for (Area area : areas) {
|
||||
area.setRadian(radian);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColor(int color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Info generateInfo() {
|
||||
Info info = new Info();
|
||||
info.type = Info.TYPE_STRAIGHT;
|
||||
info.padding = padding;
|
||||
info.radian = radian;
|
||||
info.color = color;
|
||||
info.steps = steps;
|
||||
ArrayList<LineInfo> lineInfos = new ArrayList<>();
|
||||
for (Line line : lines) {
|
||||
LineInfo lineInfo = new LineInfo(line);
|
||||
lineInfos.add(lineInfo);
|
||||
}
|
||||
info.lineInfos = lineInfos;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.straight;
|
||||
|
||||
import android.graphics.PointF;
|
||||
import android.util.Pair;
|
||||
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.Line;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
class StraightUtils {
|
||||
static StraightLine createLine(final StraightArea area, final Line.Direction direction,
|
||||
final float ratio) {
|
||||
PointF one = new PointF();
|
||||
PointF two = new PointF();
|
||||
if (direction == Line.Direction.HORIZONTAL) {
|
||||
one.x = area.left();
|
||||
one.y = area.height() * ratio + area.top();
|
||||
two.x = area.right();
|
||||
two.y = area.height() * ratio + area.top();
|
||||
} else if (direction == Line.Direction.VERTICAL) {
|
||||
one.x = area.width() * ratio + area.left();
|
||||
one.y = area.top();
|
||||
two.x = area.width() * ratio + area.left();
|
||||
two.y = area.bottom();
|
||||
}
|
||||
|
||||
StraightLine line = new StraightLine(one, two);
|
||||
|
||||
if (direction == Line.Direction.HORIZONTAL) {
|
||||
line.attachLineStart = area.lineLeft;
|
||||
line.attachLineEnd = area.lineRight;
|
||||
|
||||
line.setUpperLine(area.lineBottom);
|
||||
line.setLowerLine(area.lineTop);
|
||||
} else if (direction == Line.Direction.VERTICAL) {
|
||||
line.attachLineStart = area.lineTop;
|
||||
line.attachLineEnd = area.lineBottom;
|
||||
|
||||
line.setUpperLine(area.lineRight);
|
||||
line.setLowerLine(area.lineLeft);
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
static List<StraightArea> cutArea(final StraightArea area, final StraightLine line) {
|
||||
List<StraightArea> list = new ArrayList<>();
|
||||
if (line.direction() == Line.Direction.HORIZONTAL) {
|
||||
StraightArea one = new StraightArea(area);
|
||||
one.lineBottom = line;
|
||||
list.add(one);
|
||||
|
||||
StraightArea two = new StraightArea(area);
|
||||
two.lineTop = line;
|
||||
list.add(two);
|
||||
} else if (line.direction() == Line.Direction.VERTICAL) {
|
||||
StraightArea one = new StraightArea(area);
|
||||
one.lineRight = line;
|
||||
list.add(one);
|
||||
|
||||
StraightArea two = new StraightArea(area);
|
||||
two.lineLeft = line;
|
||||
list.add(two);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
static Pair<List<StraightLine>, List<StraightArea>> cutArea(final StraightArea area,
|
||||
final int horizontalSize,
|
||||
final int verticalSize) {
|
||||
List<StraightArea> areaList = new ArrayList<>();
|
||||
List<StraightLine> horizontalLines = new ArrayList<>(horizontalSize);
|
||||
|
||||
StraightArea restArea = new StraightArea(area);
|
||||
for (int i = horizontalSize + 1; i > 1; i--) {
|
||||
StraightLine horizontalLine =
|
||||
createLine(restArea, Line.Direction.HORIZONTAL, (float) (i - 1) / i);
|
||||
horizontalLines.add(horizontalLine);
|
||||
restArea.lineBottom = horizontalLine;
|
||||
}
|
||||
List<StraightLine> verticalLines = new ArrayList<>();
|
||||
|
||||
restArea = new StraightArea(area);
|
||||
for (int i = verticalSize + 1; i > 1; i--) {
|
||||
StraightLine verticalLine =
|
||||
createLine(restArea, Line.Direction.VERTICAL, (float) (i - 1) / i);
|
||||
verticalLines.add(verticalLine);
|
||||
StraightArea spiltArea = new StraightArea(restArea);
|
||||
spiltArea.lineLeft = verticalLine;
|
||||
int size = horizontalLines.size();
|
||||
for (int j = 0; j <= size; j++) {
|
||||
StraightArea blockArea = new StraightArea(spiltArea);
|
||||
if (j == 0) {
|
||||
blockArea.lineTop = horizontalLines.get(j);
|
||||
} else if (j == size) {
|
||||
blockArea.lineBottom = horizontalLines.get(j - 1);
|
||||
} else {
|
||||
blockArea.lineTop = horizontalLines.get(j);
|
||||
blockArea.lineBottom = horizontalLines.get(j - 1);
|
||||
}
|
||||
areaList.add(blockArea);
|
||||
}
|
||||
restArea.lineRight = verticalLine;
|
||||
}
|
||||
int size = horizontalLines.size();
|
||||
for (int j = 0; j <= size; j++) {
|
||||
StraightArea blockArea = new StraightArea(restArea);
|
||||
if (j == 0) {
|
||||
blockArea.lineTop = horizontalLines.get(j);
|
||||
} else if (j == horizontalLines.size()) {
|
||||
blockArea.lineBottom = horizontalLines.get(j - 1);
|
||||
} else {
|
||||
blockArea.lineTop = horizontalLines.get(j);
|
||||
blockArea.lineBottom = horizontalLines.get(j - 1);
|
||||
}
|
||||
areaList.add(blockArea);
|
||||
}
|
||||
|
||||
List<StraightLine> lines = new ArrayList<>();
|
||||
lines.addAll(horizontalLines);
|
||||
lines.addAll(verticalLines);
|
||||
return new Pair<>(lines, areaList);
|
||||
}
|
||||
|
||||
static List<StraightArea> cutAreaCross(final StraightArea area, final StraightLine horizontal,
|
||||
final StraightLine vertical) {
|
||||
List<StraightArea> list = new ArrayList<>();
|
||||
|
||||
StraightArea one = new StraightArea(area);
|
||||
one.lineBottom = horizontal;
|
||||
one.lineRight = vertical;
|
||||
list.add(one);
|
||||
|
||||
StraightArea two = new StraightArea(area);
|
||||
two.lineBottom = horizontal;
|
||||
two.lineLeft = vertical;
|
||||
list.add(two);
|
||||
|
||||
StraightArea three = new StraightArea(area);
|
||||
three.lineTop = horizontal;
|
||||
three.lineRight = vertical;
|
||||
list.add(three);
|
||||
|
||||
StraightArea four = new StraightArea(area);
|
||||
four.lineTop = horizontal;
|
||||
four.lineLeft = vertical;
|
||||
list.add(four);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
static Pair<List<StraightLine>, List<StraightArea>> cutAreaSpiral(final StraightArea area) {
|
||||
List<StraightLine> lines = new ArrayList<>();
|
||||
List<StraightArea> areas = new ArrayList<>();
|
||||
|
||||
float width = area.width();
|
||||
float height = area.height();
|
||||
|
||||
float left = area.left();
|
||||
float top = area.top();
|
||||
|
||||
PointF one = new PointF(left, top + height / 3);
|
||||
PointF two = new PointF(left + width / 3 * 2, top);
|
||||
PointF three = new PointF(left + width, top + height / 3 * 2);
|
||||
PointF four = new PointF(left + width / 3, top + height);
|
||||
PointF five = new PointF(left + width / 3, top + height / 3);
|
||||
PointF six = new PointF(left + width / 3 * 2, top + height / 3);
|
||||
PointF seven = new PointF(left + width / 3 * 2, top + height / 3 * 2);
|
||||
PointF eight = new PointF(left + width / 3, top + height / 3 * 2);
|
||||
|
||||
StraightLine l1 = new StraightLine(one, six);
|
||||
StraightLine l2 = new StraightLine(two, seven);
|
||||
StraightLine l3 = new StraightLine(eight, three);
|
||||
StraightLine l4 = new StraightLine(five, four);
|
||||
|
||||
l1.setAttachLineStart(area.lineLeft);
|
||||
l1.setAttachLineEnd(l2);
|
||||
l1.setLowerLine(area.lineTop);
|
||||
l1.setUpperLine(l3);
|
||||
|
||||
l2.setAttachLineStart(area.lineTop);
|
||||
l2.setAttachLineEnd(l3);
|
||||
l2.setLowerLine(l4);
|
||||
l2.setUpperLine(area.lineRight);
|
||||
|
||||
l3.setAttachLineStart(l4);
|
||||
l3.setAttachLineEnd(area.lineRight);
|
||||
l3.setLowerLine(l1);
|
||||
l3.setUpperLine(area.lineBottom);
|
||||
|
||||
l4.setAttachLineStart(l1);
|
||||
l4.setAttachLineEnd(area.lineBottom);
|
||||
l4.setLowerLine(area.lineLeft);
|
||||
l4.setUpperLine(l2);
|
||||
|
||||
lines.add(l1);
|
||||
lines.add(l2);
|
||||
lines.add(l3);
|
||||
lines.add(l4);
|
||||
|
||||
StraightArea b1 = new StraightArea(area);
|
||||
b1.lineRight = l2;
|
||||
b1.lineBottom = l1;
|
||||
areas.add(b1);
|
||||
|
||||
StraightArea b2 = new StraightArea(area);
|
||||
b2.lineLeft = l2;
|
||||
b2.lineBottom = l3;
|
||||
areas.add(b2);
|
||||
|
||||
StraightArea b3 = new StraightArea(area);
|
||||
b3.lineRight = l4;
|
||||
b3.lineTop = l1;
|
||||
areas.add(b3);
|
||||
|
||||
StraightArea b4 = new StraightArea(area);
|
||||
b4.lineTop = l1;
|
||||
b4.lineRight = l2;
|
||||
b4.lineLeft = l4;
|
||||
b4.lineBottom = l3;
|
||||
areas.add(b4);
|
||||
|
||||
StraightArea b5 = new StraightArea(area);
|
||||
b5.lineLeft = l4;
|
||||
b5.lineTop = l3;
|
||||
areas.add(b5);
|
||||
|
||||
return new Pair<>(lines, areas);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.template.slant;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.slant.SlantPuzzleLayout;
|
||||
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
|
||||
public abstract class NumberSlantLayout extends SlantPuzzleLayout {
|
||||
|
||||
static final String TAG = "NumberSlantLayout";
|
||||
protected int theme;
|
||||
|
||||
public NumberSlantLayout(int theme) {
|
||||
if (theme >= getThemeCount()) {
|
||||
Log.e(TAG, "NumberSlantLayout: the most theme count is "
|
||||
+ getThemeCount()
|
||||
+ " ,you should let theme from 0 to "
|
||||
+ (getThemeCount() - 1)
|
||||
+ " .");
|
||||
}
|
||||
this.theme = theme;
|
||||
}
|
||||
|
||||
public abstract int getThemeCount();
|
||||
|
||||
public int getTheme() {
|
||||
return theme;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.template.slant;
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.Line;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
|
||||
public class OneSlantLayout extends NumberSlantLayout {
|
||||
public OneSlantLayout(int theme) {
|
||||
super(theme);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThemeCount() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layout() {
|
||||
switch (theme) {
|
||||
case 0:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 0.56f, 0.44f);
|
||||
break;
|
||||
case 1:
|
||||
addLine(0, Line.Direction.VERTICAL, 0.56f, 0.44f);
|
||||
break;
|
||||
case 2:
|
||||
addCross(0, 0.56f, 0.44f, 0.56f, 0.44f);
|
||||
break;
|
||||
case 3:
|
||||
cutArea(0, 1, 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.template.slant;
|
||||
|
||||
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
public class SlantLayoutHelper {
|
||||
private SlantLayoutHelper() {
|
||||
|
||||
}
|
||||
|
||||
public static List<PuzzleLayout> getAllThemeLayout(int pieceCount) {
|
||||
List<PuzzleLayout> puzzleLayouts = new ArrayList<>();
|
||||
switch (pieceCount) {
|
||||
case 1:
|
||||
for (int i = 0; i < 4; i++) {
|
||||
puzzleLayouts.add(new OneSlantLayout(i));
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
for (int i = 0; i < 2; i++) {
|
||||
puzzleLayouts.add(new TwoSlantLayout(i));
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
for (int i = 0; i < 6; i++) {
|
||||
puzzleLayouts.add(new ThreeSlantLayout(i));
|
||||
}
|
||||
break;
|
||||
//case 4:
|
||||
// for (int i = 0; i < 8; i++) {
|
||||
// puzzleLayouts.add(new FourStraightLayout(i));
|
||||
// }
|
||||
// break;
|
||||
//case 5:
|
||||
// for (int i = 0; i < 17; i++) {
|
||||
// puzzleLayouts.add(new FiveStraightLayout(i));
|
||||
// }
|
||||
// break;
|
||||
//case 6:
|
||||
// for (int i = 0; i < 12; i++) {
|
||||
// puzzleLayouts.add(new SixStraightLayout(i));
|
||||
// }
|
||||
// break;
|
||||
//case 7:
|
||||
// for (int i = 0; i < 9; i++) {
|
||||
// puzzleLayouts.add(new SevenStraightLayout(i));
|
||||
// }
|
||||
// break;
|
||||
//case 8:
|
||||
// for (int i = 0; i < 11; i++) {
|
||||
// puzzleLayouts.add(new EightStraightLayout(i));
|
||||
// }
|
||||
// break;
|
||||
//case 9:
|
||||
// for (int i = 0; i < 8; i++) {
|
||||
// puzzleLayouts.add(new NineStraightLayout(i));
|
||||
// }
|
||||
// break;
|
||||
}
|
||||
|
||||
return puzzleLayouts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.template.slant;
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.Line;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
|
||||
public class ThreeSlantLayout extends NumberSlantLayout {
|
||||
public ThreeSlantLayout(int theme) {
|
||||
super(theme);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThemeCount() {
|
||||
return 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layout() {
|
||||
switch (theme) {
|
||||
case 0:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 0.5f);
|
||||
addLine(0, Line.Direction.VERTICAL, 0.56f, 0.44f);
|
||||
break;
|
||||
case 1:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 0.5f);
|
||||
addLine(1, Line.Direction.VERTICAL, 0.56f, 0.44f);
|
||||
break;
|
||||
case 2:
|
||||
addLine(0, Line.Direction.VERTICAL, 0.5f);
|
||||
addLine(0, Line.Direction.HORIZONTAL, 0.56f, 0.44f);
|
||||
break;
|
||||
case 3:
|
||||
addLine(0, Line.Direction.VERTICAL, 0.5f);
|
||||
addLine(1, Line.Direction.HORIZONTAL, 0.56f, 0.44f);
|
||||
break;
|
||||
case 4:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 0.44f, 0.56f);
|
||||
addLine(0, Line.Direction.VERTICAL, 0.56f, 0.44f);
|
||||
break;
|
||||
case 5:
|
||||
addLine(0, Line.Direction.VERTICAL, 0.56f, 0.44f);
|
||||
addLine(1, Line.Direction.HORIZONTAL, 0.44f, 0.56f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.template.slant;
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.Line;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
|
||||
public class TwoSlantLayout extends NumberSlantLayout {
|
||||
public TwoSlantLayout(int theme) {
|
||||
super(theme);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThemeCount() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layout() {
|
||||
switch (theme) {
|
||||
case 0:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 0.56f, 0.44f);
|
||||
break;
|
||||
case 1:
|
||||
addLine(0, Line.Direction.VERTICAL, 0.56f, 0.44f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.template.straight;
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.Line;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
public class EightStraightLayout extends NumberStraightLayout {
|
||||
public EightStraightLayout(int theme) {
|
||||
super(theme);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThemeCount() {
|
||||
return 11;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layout() {
|
||||
switch (theme) {
|
||||
case 0:
|
||||
cutAreaEqualPart(0, 3, 1);
|
||||
break;
|
||||
case 1:
|
||||
cutAreaEqualPart(0, 1, 3);
|
||||
break;
|
||||
case 2:
|
||||
cutAreaEqualPart(0, 4, Line.Direction.VERTICAL);
|
||||
addLine(3, Line.Direction.HORIZONTAL, 4f / 5);
|
||||
addLine(2, Line.Direction.HORIZONTAL, 3f / 5);
|
||||
addLine(1, Line.Direction.HORIZONTAL, 2f / 5);
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 5);
|
||||
break;
|
||||
case 3:
|
||||
cutAreaEqualPart(0, 4, Line.Direction.HORIZONTAL);
|
||||
addLine(3, Line.Direction.VERTICAL, 4f / 5);
|
||||
addLine(2, Line.Direction.VERTICAL, 3f / 5);
|
||||
addLine(1, Line.Direction.VERTICAL, 2f / 5);
|
||||
addLine(0, Line.Direction.VERTICAL, 1f / 5);
|
||||
break;
|
||||
case 4:
|
||||
cutAreaEqualPart(0, 4, Line.Direction.VERTICAL);
|
||||
addLine(3, Line.Direction.HORIZONTAL, 1f / 5);
|
||||
addLine(2, Line.Direction.HORIZONTAL, 2f / 5);
|
||||
addLine(1, Line.Direction.HORIZONTAL, 3f / 5);
|
||||
addLine(0, Line.Direction.HORIZONTAL, 4f / 5);
|
||||
break;
|
||||
case 5:
|
||||
cutAreaEqualPart(0, 4, Line.Direction.HORIZONTAL);
|
||||
addLine(3, Line.Direction.VERTICAL, 1f / 5);
|
||||
addLine(2, Line.Direction.VERTICAL, 2f / 5);
|
||||
addLine(1, Line.Direction.VERTICAL, 3f / 5);
|
||||
addLine(0, Line.Direction.VERTICAL, 4f / 5);
|
||||
break;
|
||||
case 6:
|
||||
cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
|
||||
cutAreaEqualPart(2, 3, Line.Direction.VERTICAL);
|
||||
cutAreaEqualPart(1, 2, Line.Direction.VERTICAL);
|
||||
cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
|
||||
break;
|
||||
case 7:
|
||||
cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
|
||||
cutAreaEqualPart(2, 3, Line.Direction.HORIZONTAL);
|
||||
cutAreaEqualPart(1, 2, Line.Direction.HORIZONTAL);
|
||||
cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
|
||||
break;
|
||||
case 8:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 4f / 5);
|
||||
cutAreaEqualPart(1, 5, Line.Direction.VERTICAL);
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
addLine(1, Line.Direction.VERTICAL, 1f / 2);
|
||||
break;
|
||||
case 9:
|
||||
cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
|
||||
cutAreaEqualPart(2, 2, Line.Direction.VERTICAL);
|
||||
cutAreaEqualPart(1, 3, Line.Direction.VERTICAL);
|
||||
addLine(0, Line.Direction.VERTICAL, 3f / 4);
|
||||
addLine(0, Line.Direction.VERTICAL, 1f / 3);
|
||||
break;
|
||||
case 10:
|
||||
cutAreaEqualPart(0, 2, 1);
|
||||
addLine(5, Line.Direction.VERTICAL, 1f / 2);
|
||||
addLine(4, Line.Direction.VERTICAL, 1f / 2);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.template.straight;
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.Line;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
public class FiveStraightLayout extends NumberStraightLayout {
|
||||
|
||||
public FiveStraightLayout(int theme) {
|
||||
super(theme);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThemeCount() {
|
||||
return 15;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layout() {
|
||||
switch (theme) {
|
||||
case 0:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 2f / 5);
|
||||
addLine(0, Line.Direction.VERTICAL, 1f / 2);
|
||||
cutAreaEqualPart(2, 3, Line.Direction.VERTICAL);
|
||||
break;
|
||||
case 1:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 3f / 5);
|
||||
cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
|
||||
addLine(3, Line.Direction.VERTICAL, 1f / 2);
|
||||
break;
|
||||
case 2:
|
||||
addLine(0, Line.Direction.VERTICAL, 2f / 5);
|
||||
cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
|
||||
addLine(1, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
break;
|
||||
case 3:
|
||||
addLine(0, Line.Direction.VERTICAL, 2f / 5);
|
||||
cutAreaEqualPart(1, 3, Line.Direction.HORIZONTAL);
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
break;
|
||||
case 4:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 3f / 4);
|
||||
cutAreaEqualPart(1, 4, Line.Direction.VERTICAL);
|
||||
break;
|
||||
case 5:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 4);
|
||||
cutAreaEqualPart(0, 4, Line.Direction.VERTICAL);
|
||||
break;
|
||||
case 6:
|
||||
addLine(0, Line.Direction.VERTICAL, 3f / 4);
|
||||
cutAreaEqualPart(1, 4, Line.Direction.HORIZONTAL);
|
||||
break;
|
||||
case 7:
|
||||
addLine(0, Line.Direction.VERTICAL, 1f / 4);
|
||||
cutAreaEqualPart(0, 4, Line.Direction.HORIZONTAL);
|
||||
break;
|
||||
case 8:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 4);
|
||||
addLine(1, Line.Direction.HORIZONTAL, 2f / 3);
|
||||
addLine(0, Line.Direction.VERTICAL, 1f / 2);
|
||||
addLine(3, Line.Direction.VERTICAL, 1f / 2);
|
||||
break;
|
||||
case 9:
|
||||
addLine(0, Line.Direction.VERTICAL, 1f / 4);
|
||||
addLine(1, Line.Direction.VERTICAL, 2f / 3);
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
addLine(2, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
break;
|
||||
case 10:
|
||||
addCross(0, 1f / 3);
|
||||
addLine(2, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
break;
|
||||
case 11:
|
||||
addCross(0, 2f / 3);
|
||||
addLine(1, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
break;
|
||||
case 12:
|
||||
addCross(0, 1f / 3, 2f / 3);
|
||||
addLine(3, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
break;
|
||||
case 13:
|
||||
addCross(0, 2f / 3, 1f / 3);
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
break;
|
||||
case 14:
|
||||
cutSpiral(0);
|
||||
break;
|
||||
default:
|
||||
cutAreaEqualPart(0, 5, Line.Direction.HORIZONTAL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.template.straight;
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.Line;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
public class FourStraightLayout extends NumberStraightLayout {
|
||||
private static final String TAG = "FourStraightLayout";
|
||||
|
||||
public FourStraightLayout(int theme) {
|
||||
super(theme);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThemeCount() {
|
||||
return 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layout() {
|
||||
switch (theme) {
|
||||
case 0:
|
||||
addCross(0, 1f / 2);
|
||||
break;
|
||||
case 1:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 3);
|
||||
cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
|
||||
break;
|
||||
case 2:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 2f / 3);
|
||||
cutAreaEqualPart(1, 3, Line.Direction.VERTICAL);
|
||||
break;
|
||||
case 3:
|
||||
addLine(0, Line.Direction.VERTICAL, 1f / 3);
|
||||
cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
|
||||
break;
|
||||
case 4:
|
||||
addLine(0, Line.Direction.VERTICAL, 2f / 3);
|
||||
cutAreaEqualPart(1, 3, Line.Direction.HORIZONTAL);
|
||||
break;
|
||||
case 5:
|
||||
addLine(0, Line.Direction.VERTICAL, 1f / 2);
|
||||
addLine(1, Line.Direction.HORIZONTAL, 2f / 3);
|
||||
addLine(1, Line.Direction.HORIZONTAL, 1f / 3);
|
||||
break;
|
||||
default:
|
||||
cutAreaEqualPart(0, 4, Line.Direction.HORIZONTAL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.template.straight;
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.Line;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
public class NineStraightLayout extends NumberStraightLayout {
|
||||
public NineStraightLayout(int theme) {
|
||||
super(theme);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThemeCount() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layout() {
|
||||
switch (theme) {
|
||||
case 0:
|
||||
cutAreaEqualPart(0, 2, 2);
|
||||
break;
|
||||
case 1:
|
||||
addLine(0, Line.Direction.VERTICAL, 3f / 4);
|
||||
addLine(0, Line.Direction.VERTICAL, 1f / 3);
|
||||
cutAreaEqualPart(2, 4, Line.Direction.HORIZONTAL);
|
||||
cutAreaEqualPart(0, 4, Line.Direction.HORIZONTAL);
|
||||
break;
|
||||
case 2:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 3f / 4);
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 3);
|
||||
cutAreaEqualPart(2, 4, Line.Direction.VERTICAL);
|
||||
cutAreaEqualPart(0, 4, Line.Direction.VERTICAL);
|
||||
break;
|
||||
case 3:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 3f / 4);
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 3);
|
||||
cutAreaEqualPart(2, 3, Line.Direction.VERTICAL);
|
||||
addLine(1, Line.Direction.VERTICAL, 3f / 4);
|
||||
addLine(1, Line.Direction.VERTICAL, 1f / 3);
|
||||
cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
|
||||
break;
|
||||
case 4:
|
||||
addLine(0, Line.Direction.VERTICAL, 3f / 4);
|
||||
addLine(0, Line.Direction.VERTICAL, 1f / 3);
|
||||
cutAreaEqualPart(2, 3, Line.Direction.HORIZONTAL);
|
||||
addLine(1, Line.Direction.HORIZONTAL, 3f / 4);
|
||||
addLine(1, Line.Direction.HORIZONTAL, 1f / 3);
|
||||
cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
|
||||
break;
|
||||
case 5:
|
||||
cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
|
||||
addLine(2, Line.Direction.HORIZONTAL, 3f / 4);
|
||||
addLine(2, Line.Direction.HORIZONTAL, 1f / 3);
|
||||
cutAreaEqualPart(1, 3, Line.Direction.HORIZONTAL);
|
||||
addLine(0, Line.Direction.HORIZONTAL, 3f / 4);
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 3);
|
||||
break;
|
||||
case 6:
|
||||
cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
|
||||
addLine(2, Line.Direction.VERTICAL, 3f / 4);
|
||||
addLine(2, Line.Direction.VERTICAL, 1f / 3);
|
||||
cutAreaEqualPart(1, 3, Line.Direction.VERTICAL);
|
||||
addLine(0, Line.Direction.VERTICAL, 3f / 4);
|
||||
addLine(0, Line.Direction.VERTICAL, 1f / 3);
|
||||
break;
|
||||
case 7:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
cutAreaEqualPart(1, 1, 3);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.template.straight;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.straight.StraightPuzzleLayout;
|
||||
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
public abstract class NumberStraightLayout extends StraightPuzzleLayout {
|
||||
static final String TAG = "NumberStraightLayout";
|
||||
protected int theme;
|
||||
|
||||
public NumberStraightLayout(int theme) {
|
||||
if (theme >= getThemeCount()) {
|
||||
Log.e(TAG, "NumberStraightLayout: the most theme count is "
|
||||
+ getThemeCount()
|
||||
+ " ,you should let theme from 0 to "
|
||||
+ (getThemeCount() - 1)
|
||||
+ " .");
|
||||
}
|
||||
this.theme = theme;
|
||||
}
|
||||
|
||||
public abstract int getThemeCount();
|
||||
|
||||
public int getTheme() {
|
||||
return theme;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.template.straight;
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.Line;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
public class OneStraightLayout extends NumberStraightLayout {
|
||||
|
||||
public OneStraightLayout(int theme) {
|
||||
super(theme);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThemeCount() {
|
||||
return 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layout() {
|
||||
switch (theme) {
|
||||
case 0:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
break;
|
||||
case 1:
|
||||
addLine(0, Line.Direction.VERTICAL, 1f / 2);
|
||||
break;
|
||||
case 2:
|
||||
addCross(0, 1f / 2);
|
||||
break;
|
||||
case 3:
|
||||
cutAreaEqualPart(0, 2, 1);
|
||||
break;
|
||||
case 4:
|
||||
cutAreaEqualPart(0, 1, 2);
|
||||
break;
|
||||
case 5:
|
||||
cutAreaEqualPart(0, 2, 2);
|
||||
break;
|
||||
default:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.template.straight;
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.Line;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
public class SevenStraightLayout extends NumberStraightLayout {
|
||||
public SevenStraightLayout(int theme) {
|
||||
super(theme);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThemeCount() {
|
||||
return 9;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layout() {
|
||||
switch (theme) {
|
||||
case 0:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
cutAreaEqualPart(1, 4, Line.Direction.VERTICAL);
|
||||
cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
|
||||
break;
|
||||
case 1:
|
||||
addLine(0, Line.Direction.VERTICAL, 1f / 2);
|
||||
cutAreaEqualPart(1, 4, Line.Direction.HORIZONTAL);
|
||||
cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
|
||||
break;
|
||||
case 2:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
cutAreaEqualPart(1, 1, 2);
|
||||
break;
|
||||
case 3:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 2f / 3);
|
||||
cutAreaEqualPart(1, 3, Line.Direction.VERTICAL);
|
||||
addCross(0, 1f / 2);
|
||||
break;
|
||||
case 4:
|
||||
cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
|
||||
cutAreaEqualPart(2, 3, Line.Direction.HORIZONTAL);
|
||||
cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
|
||||
break;
|
||||
case 5:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 2f / 3);
|
||||
addLine(1, Line.Direction.VERTICAL, 3f / 4);
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
addLine(1, Line.Direction.VERTICAL, 2f / 5);
|
||||
cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
|
||||
break;
|
||||
case 6:
|
||||
addLine(0, Line.Direction.VERTICAL, 2f / 3);
|
||||
addLine(1, Line.Direction.HORIZONTAL, 3f / 4);
|
||||
addLine(0, Line.Direction.VERTICAL, 1f / 2);
|
||||
addLine(1, Line.Direction.HORIZONTAL, 2f / 5);
|
||||
cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
|
||||
break;
|
||||
case 7:
|
||||
addLine(0, Line.Direction.VERTICAL, 1f / 4);
|
||||
addLine(1, Line.Direction.VERTICAL, 2f / 3);
|
||||
addLine(2, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
addLine(1, Line.Direction.HORIZONTAL, 3f / 4);
|
||||
addLine(1, Line.Direction.HORIZONTAL, 1f / 3);
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
break;
|
||||
case 8:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 4);
|
||||
addLine(1, Line.Direction.HORIZONTAL, 2f / 3);
|
||||
cutAreaEqualPart(2, 3, Line.Direction.VERTICAL);
|
||||
cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.template.straight;
|
||||
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.Line;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
public class SixStraightLayout extends NumberStraightLayout {
|
||||
|
||||
public SixStraightLayout(int theme) {
|
||||
super(theme);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThemeCount() {
|
||||
return 12;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layout() {
|
||||
switch (theme) {
|
||||
case 0:
|
||||
cutAreaEqualPart(0, 2, 1);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
cutAreaEqualPart(0, 1, 2);
|
||||
break;
|
||||
case 2:
|
||||
addCross(0, 2f / 3, 1f / 2);
|
||||
addLine(3, Line.Direction.VERTICAL, 1f / 2);
|
||||
addLine(2, Line.Direction.VERTICAL, 1f / 2);
|
||||
break;
|
||||
case 3:
|
||||
addCross(0, 1f / 2, 2f / 3);
|
||||
addLine(3, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
addLine(1, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
break;
|
||||
case 4:
|
||||
addCross(0, 1f / 2, 1f / 3);
|
||||
addLine(2, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
break;
|
||||
case 5:
|
||||
addCross(0, 1f / 3, 1f / 2);
|
||||
addLine(1, Line.Direction.VERTICAL, 1f / 2);
|
||||
addLine(0, Line.Direction.VERTICAL, 1f / 2);
|
||||
break;
|
||||
case 6:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 4f / 5);
|
||||
cutAreaEqualPart(1, 5, Line.Direction.VERTICAL);
|
||||
break;
|
||||
case 7:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 4);
|
||||
addLine(1, Line.Direction.HORIZONTAL, 2f / 3);
|
||||
addLine(1, Line.Direction.VERTICAL, 1f / 4);
|
||||
addLine(2, Line.Direction.VERTICAL, 2f / 3);
|
||||
addLine(4, Line.Direction.VERTICAL, 1f / 2);
|
||||
break;
|
||||
case 8:
|
||||
addCross(0, 1f / 3);
|
||||
addLine(1, Line.Direction.VERTICAL, 1f / 2);
|
||||
addLine(4, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
break;
|
||||
case 9:
|
||||
addCross(0, 2f / 3, 1f / 3);
|
||||
addLine(3, Line.Direction.VERTICAL, 1f / 2);
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
break;
|
||||
case 10:
|
||||
addCross(0, 2f / 3);
|
||||
addLine(2, Line.Direction.VERTICAL, 1f / 2);
|
||||
addLine(1, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
break;
|
||||
case 11:
|
||||
addCross(0, 1f / 3, 2f / 3);
|
||||
addLine(3, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
addLine(0, Line.Direction.VERTICAL, 1f / 2);
|
||||
break;
|
||||
case 12:
|
||||
addCross(0, 1f / 3);
|
||||
addLine(2, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
addLine(1, Line.Direction.VERTICAL, 1f / 2);
|
||||
break;
|
||||
default:
|
||||
addCross(0, 2f / 3, 1f / 2);
|
||||
addLine(3, Line.Direction.VERTICAL, 1f / 2);
|
||||
addLine(2, Line.Direction.VERTICAL, 1f / 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.template.straight;
|
||||
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
public class StraightLayoutHelper {
|
||||
private StraightLayoutHelper() {
|
||||
|
||||
}
|
||||
|
||||
public static List<PuzzleLayout> getAllThemeLayout(int pieceCount) {
|
||||
List<PuzzleLayout> puzzleLayouts = new ArrayList<>();
|
||||
switch (pieceCount) {
|
||||
case 1:
|
||||
for (int i = 0; i < 6; i++) {
|
||||
puzzleLayouts.add(new OneStraightLayout(i));
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
for (int i = 0; i < 6; i++) {
|
||||
puzzleLayouts.add(new TwoStraightLayout(i));
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
for (int i = 0; i < 6; i++) {
|
||||
puzzleLayouts.add(new ThreeStraightLayout(i));
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
for (int i = 0; i < 8; i++) {
|
||||
puzzleLayouts.add(new FourStraightLayout(i));
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
for (int i = 0; i < 17; i++) {
|
||||
puzzleLayouts.add(new FiveStraightLayout(i));
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
for (int i = 0; i < 12; i++) {
|
||||
puzzleLayouts.add(new SixStraightLayout(i));
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
for (int i = 0; i < 9; i++) {
|
||||
puzzleLayouts.add(new SevenStraightLayout(i));
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
for (int i = 0; i < 11; i++) {
|
||||
puzzleLayouts.add(new EightStraightLayout(i));
|
||||
}
|
||||
break;
|
||||
case 9:
|
||||
for (int i = 0; i < 8; i++) {
|
||||
puzzleLayouts.add(new NineStraightLayout(i));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return puzzleLayouts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.template.straight;
|
||||
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.Line;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
public class ThreeStraightLayout extends NumberStraightLayout {
|
||||
|
||||
public ThreeStraightLayout(int theme) {
|
||||
super(theme);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThemeCount() {
|
||||
return 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layout() {
|
||||
switch (theme) {
|
||||
case 0:
|
||||
cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
|
||||
break;
|
||||
case 1:
|
||||
cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
|
||||
break;
|
||||
case 2:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
addLine(0, Line.Direction.VERTICAL, 1f / 2);
|
||||
break;
|
||||
case 3:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
addLine(1, Line.Direction.VERTICAL, 1f / 2);
|
||||
break;
|
||||
case 4:
|
||||
addLine(0, Line.Direction.VERTICAL, 1f / 2);
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
break;
|
||||
case 5:
|
||||
addLine(0, Line.Direction.VERTICAL, 1f / 2);
|
||||
addLine(1, Line.Direction.HORIZONTAL, 1f / 2);
|
||||
break;
|
||||
default:
|
||||
cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.huantansheng.easyphotos.models.puzzle.template.straight;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.huantansheng.easyphotos.models.puzzle.Line;
|
||||
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
public class TwoStraightLayout extends NumberStraightLayout {
|
||||
private float mRadio = 1f / 2;
|
||||
|
||||
public TwoStraightLayout(int theme) {
|
||||
super(theme);
|
||||
}
|
||||
|
||||
public TwoStraightLayout(float radio, int theme) {
|
||||
super(theme);
|
||||
if (mRadio > 1) {
|
||||
Log.e(TAG, "CrossLayout: the radio can not greater than 1f");
|
||||
mRadio = 1f;
|
||||
}
|
||||
mRadio = radio;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThemeCount() {
|
||||
return 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layout() {
|
||||
switch (theme) {
|
||||
case 0:
|
||||
addLine(0, Line.Direction.HORIZONTAL, mRadio);
|
||||
break;
|
||||
case 1:
|
||||
addLine(0, Line.Direction.VERTICAL, mRadio);
|
||||
break;
|
||||
case 2:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 1f / 3);
|
||||
break;
|
||||
case 3:
|
||||
addLine(0, Line.Direction.HORIZONTAL, 2f / 3);
|
||||
break;
|
||||
case 4:
|
||||
addLine(0, Line.Direction.VERTICAL, 1f / 3);
|
||||
break;
|
||||
case 5:
|
||||
addLine(0, Line.Direction.VERTICAL, 2f / 3);
|
||||
break;
|
||||
default:
|
||||
addLine(0, Line.Direction.HORIZONTAL, mRadio);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package com.huantansheng.easyphotos.models.sticker;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.huantansheng.easyphotos.EasyPhotos;
|
||||
import com.huantansheng.easyphotos.models.sticker.cache.StickerCache;
|
||||
import com.huantansheng.easyphotos.models.sticker.entity.TextStickerData;
|
||||
import com.huantansheng.easyphotos.models.sticker.view.BitmapSticker;
|
||||
import com.huantansheng.easyphotos.models.sticker.view.TextSticker;
|
||||
import com.huantansheng.easyphotos.models.sticker.listener.OnStickerClickListener;
|
||||
import com.huantansheng.easyphotos.models.sticker.view.EditFragment;
|
||||
import com.huantansheng.easyphotos.utils.bitmap.BitmapUtils;
|
||||
import com.huantansheng.easyphotos.utils.bitmap.SaveBitmapCallBack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 贴图view的管理器,用于module与外部解耦
|
||||
* Created by huan on 2017/7/24.
|
||||
*/
|
||||
|
||||
public class StickerModel {
|
||||
public static final ArrayList<TextStickerData> textDataList = new ArrayList<>();
|
||||
|
||||
public List<BitmapSticker> bitmapStickers;
|
||||
public List<TextSticker> textStickers;
|
||||
public BitmapSticker currBitmapSticker;
|
||||
public TextSticker currTextSticker;
|
||||
|
||||
|
||||
public StickerModel() {
|
||||
super();
|
||||
this.bitmapStickers = new ArrayList<>();
|
||||
this.textStickers = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void addBitmapSticker(Context cxt, String imagePath, int imageResourceId, ViewGroup rootgroup) {
|
||||
|
||||
if (bitmapStickers.size() > 0 && !bitmapStickers.get(bitmapStickers.size() - 1).isChecked) {
|
||||
bitmapStickers.get(bitmapStickers.size() - 1).delete();
|
||||
}
|
||||
final BitmapSticker sticker = new BitmapSticker(cxt, imagePath, imageResourceId, rootgroup.getWidth() / 2, rootgroup.getHeight() / 2);
|
||||
sticker.setOnStickerClickListener(new OnStickerClickListener() {
|
||||
@Override
|
||||
public void onDelete() {
|
||||
bitmapStickers.remove(sticker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEditor() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTop() {
|
||||
bitmapStickers.remove(sticker);
|
||||
bitmapStickers.add(sticker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUsing() {
|
||||
if (currBitmapSticker != null && currBitmapSticker != sticker) {
|
||||
currBitmapSticker.setUsing(false);
|
||||
currBitmapSticker = sticker;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (currBitmapSticker != null) {
|
||||
currBitmapSticker.setUsing(false);
|
||||
}
|
||||
rootgroup.addView(sticker);
|
||||
currBitmapSticker = sticker;
|
||||
bitmapStickers.add(sticker);
|
||||
}
|
||||
|
||||
|
||||
public void addTextSticker(final Context cxt, final FragmentManager fragmentManager, String text, ViewGroup rootgroup) {
|
||||
|
||||
if (textStickers.size() > 0 && !textStickers.get(textStickers.size() - 1).isChecked) {
|
||||
textStickers.get(textStickers.size() - 1).delete();
|
||||
}
|
||||
final TextSticker sticker = new TextSticker(cxt, text, rootgroup.getWidth() / 2, rootgroup.getHeight() / 2);
|
||||
sticker.setOnStickerClickListener(new OnStickerClickListener() {
|
||||
@Override
|
||||
public void onDelete() {
|
||||
textStickers.remove(sticker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEditor() {
|
||||
EditFragment.show(fragmentManager, sticker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTop() {
|
||||
textStickers.remove(sticker);
|
||||
textStickers.add(sticker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUsing() {
|
||||
if (currTextSticker != null && currTextSticker != sticker) {
|
||||
currTextSticker.setUsing(false);
|
||||
currTextSticker = sticker;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (currBitmapSticker != null) {
|
||||
currBitmapSticker.setUsing(false);
|
||||
}
|
||||
rootgroup.addView(sticker);
|
||||
currTextSticker = sticker;
|
||||
textStickers.add(sticker);
|
||||
}
|
||||
|
||||
public void save(Activity act, ViewGroup stickerGroup, View imageGroup, int imageWidth, int imageHeight, final String dirPath, final String namePrefix, final boolean notifyMedia, final SaveBitmapCallBack callBack) {
|
||||
|
||||
if (null != this.currBitmapSticker && this.currBitmapSticker.isUsing()) {
|
||||
this.currBitmapSticker.setUsing(false);
|
||||
}
|
||||
if (null != this.currTextSticker && this.currTextSticker.isUsing()) {
|
||||
this.currTextSticker.setUsing(false);
|
||||
}
|
||||
|
||||
for (BitmapSticker bs : bitmapStickers) {
|
||||
if (bs.isUsing()) {
|
||||
bs.setUsing(false);
|
||||
}
|
||||
}
|
||||
|
||||
for (TextSticker ts : textStickers) {
|
||||
if (ts.isUsing()) {
|
||||
ts.setUsing(false);
|
||||
}
|
||||
}
|
||||
|
||||
Bitmap srcBitmap = Bitmap.createBitmap(stickerGroup.getWidth(), stickerGroup.getHeight(), Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(srcBitmap);
|
||||
stickerGroup.draw(canvas);
|
||||
|
||||
Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap, imageGroup.getLeft(), imageGroup.getTop(), imageGroup.getWidth(), imageGroup.getHeight());
|
||||
BitmapUtils.recycle(srcBitmap);
|
||||
Bitmap saveBitmap = null;
|
||||
if (imageGroup.getWidth() > imageWidth || imageGroup.getHeight() > imageHeight) {
|
||||
saveBitmap = Bitmap.createScaledBitmap(cropBitmap, imageWidth, imageHeight, true);
|
||||
BitmapUtils.recycle(cropBitmap);
|
||||
} else {
|
||||
saveBitmap = cropBitmap;
|
||||
}
|
||||
|
||||
EasyPhotos.saveBitmapToDir(act, dirPath, namePrefix, saveBitmap, notifyMedia, callBack);
|
||||
|
||||
}
|
||||
|
||||
public void setCanvasSize(final Bitmap b, final ViewGroup imageGroup) {
|
||||
if (imageGroup.getMeasuredWidth() == 0) {
|
||||
imageGroup.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setSize(b, imageGroup);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setSize(b, imageGroup);
|
||||
}
|
||||
}
|
||||
|
||||
private void setSize(Bitmap b, ViewGroup v) {
|
||||
int bW = b.getWidth();
|
||||
int bH = b.getHeight();
|
||||
|
||||
int vW = v.getMeasuredWidth();
|
||||
int vH = v.getMeasuredHeight();
|
||||
|
||||
float scalW = (float) vW / (float) bW;
|
||||
float scalH = (float) vH / (float) bH;
|
||||
|
||||
ViewGroup.LayoutParams params = v.getLayoutParams();
|
||||
//如果图片小于viewGroup的宽高则把viewgroup设置为图片宽高
|
||||
// if (bW < vW && bH < vH) {
|
||||
// params.width = bW;
|
||||
// params.height = bH;
|
||||
// v.setLayoutParams(params);
|
||||
// return;
|
||||
// }
|
||||
if (bW >= bH) {
|
||||
params.width = vW;
|
||||
params.height = (int) (scalW * bH);
|
||||
} else {
|
||||
params.width = (int) (scalH * bW);
|
||||
params.height = vH;
|
||||
}
|
||||
if (params.width > vW) {
|
||||
float tempScaleW = (float) vW / (float) params.width;
|
||||
params.width = vW;
|
||||
params.height = (int) (params.height * tempScaleW);
|
||||
}
|
||||
if (params.height > vH) {
|
||||
float tempScaleH = (float) vH / (float) params.height;
|
||||
params.height = vH;
|
||||
params.width = (int) (params.width * tempScaleH);
|
||||
}
|
||||
v.setLayoutParams(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放资源
|
||||
*/
|
||||
public void release() {
|
||||
StickerCache.get().clear();
|
||||
}
|
||||
|
||||
}
|
||||
114
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/cache/StickerCache.java
vendored
Normal file
114
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/cache/StickerCache.java
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
package com.huantansheng.easyphotos.models.sticker.cache;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Matrix;
|
||||
import androidx.annotation.IdRes;
|
||||
|
||||
import com.huantansheng.easyphotos.EasyPhotos;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* 贴纸的图片缓存器
|
||||
* Created by huan on 2017/12/8.
|
||||
*/
|
||||
|
||||
public class StickerCache {
|
||||
private static StickerCache instance = null;
|
||||
|
||||
public static StickerCache get() {
|
||||
if (null == instance) {
|
||||
synchronized (StickerCache.class) {
|
||||
if (null == instance) {
|
||||
instance = new StickerCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private LinkedHashMap<String, Bitmap> srcBitmapCache = null;
|
||||
private LinkedHashMap<String, Bitmap> mirrorBitmapCache = null;
|
||||
private LinkedHashMap<String, Integer> bitmapUsedCount = null;
|
||||
|
||||
private StickerCache() {
|
||||
srcBitmapCache = new LinkedHashMap<>();
|
||||
mirrorBitmapCache = new LinkedHashMap<>();
|
||||
bitmapUsedCount = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
public Bitmap getSrcBitmap(String path) {
|
||||
Bitmap bitmap = srcBitmapCache.get(path);
|
||||
if (null == bitmap) {
|
||||
bitmap = BitmapFactory.decodeFile(path);
|
||||
srcBitmapCache.put(path, bitmap);
|
||||
bitmapUsedCount.put(path, 0);
|
||||
convertMirror(path, bitmap);
|
||||
}
|
||||
|
||||
int count = bitmapUsedCount.get(path);
|
||||
bitmapUsedCount.put(path, ++count);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public Bitmap getSrcBitmap(Resources resources, @IdRes int resId) {
|
||||
String path = String.valueOf(resId);
|
||||
Bitmap bitmap = srcBitmapCache.get(path);
|
||||
if (null == bitmap) {
|
||||
bitmap = BitmapFactory.decodeResource(resources, resId);
|
||||
srcBitmapCache.put(path, bitmap);
|
||||
bitmapUsedCount.put(path, 0);
|
||||
convertMirror(path, bitmap);
|
||||
}
|
||||
|
||||
int count = bitmapUsedCount.get(path);
|
||||
bitmapUsedCount.put(path, ++count);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public Bitmap getMirrorBitmap(String key) {
|
||||
return mirrorBitmapCache.get(key);
|
||||
}
|
||||
|
||||
|
||||
public void clear() {
|
||||
for (String key : srcBitmapCache.keySet()) {
|
||||
recycle(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void recycle(String key) {
|
||||
if (!srcBitmapCache.containsKey(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int count = bitmapUsedCount.get(key);
|
||||
if (count > 1) {
|
||||
count--;
|
||||
bitmapUsedCount.put(key, count);
|
||||
return;
|
||||
}
|
||||
|
||||
EasyPhotos.recycle(srcBitmapCache.get(key), mirrorBitmapCache.get(key));
|
||||
removeKey(key);
|
||||
}
|
||||
|
||||
private void convertMirror(String key, Bitmap a) {
|
||||
int w = a.getWidth();
|
||||
int h = a.getHeight();
|
||||
|
||||
Matrix m = new Matrix();
|
||||
m.postScale(-1, 1); //镜像水平翻转
|
||||
Bitmap mirrorBitmap = Bitmap.createBitmap(a, 0, 0, w, h, m, true);
|
||||
mirrorBitmapCache.put(key, mirrorBitmap);
|
||||
}
|
||||
|
||||
|
||||
private void removeKey(String key) {
|
||||
srcBitmapCache.remove(key);
|
||||
mirrorBitmapCache.remove(key);
|
||||
bitmapUsedCount.remove(key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.huantansheng.easyphotos.models.sticker.entity;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* 文字贴纸数据类
|
||||
* Created by huan on 2017/12/14.
|
||||
*/
|
||||
|
||||
public class TextStickerData implements Parcelable {
|
||||
|
||||
public String stickerName;//文字贴纸的名字
|
||||
public String stickerValue;//文字贴纸的文字内容
|
||||
|
||||
public TextStickerData(String stickerName, String stickerValue) {
|
||||
this.stickerName = stickerName;
|
||||
this.stickerValue = stickerValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(this.stickerName);
|
||||
dest.writeString(this.stickerValue);
|
||||
}
|
||||
|
||||
protected TextStickerData(Parcel in) {
|
||||
this.stickerName = in.readString();
|
||||
this.stickerValue = in.readString();
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<TextStickerData> CREATOR = new Parcelable.Creator<TextStickerData>() {
|
||||
@Override
|
||||
public TextStickerData createFromParcel(Parcel source) {
|
||||
return new TextStickerData(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextStickerData[] newArray(int size) {
|
||||
return new TextStickerData[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.huantansheng.easyphotos.models.sticker.listener;
|
||||
|
||||
/**
|
||||
* 贴纸的点击监听
|
||||
* Created by huan on 2017/12/12.
|
||||
*/
|
||||
|
||||
public interface OnStickerClickListener {
|
||||
void onDelete();
|
||||
|
||||
void onEditor();
|
||||
|
||||
void onTop();
|
||||
|
||||
void onUsing();
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
package com.huantansheng.easyphotos.models.sticker.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Region;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import android.text.TextUtils;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import com.huantansheng.easyphotos.R;
|
||||
import com.huantansheng.easyphotos.models.sticker.cache.StickerCache;
|
||||
import com.huantansheng.easyphotos.models.sticker.listener.OnStickerClickListener;
|
||||
|
||||
|
||||
/**
|
||||
* 自定义贴图view
|
||||
* Created by huan on 2017/7/24.
|
||||
*/
|
||||
|
||||
public class BitmapSticker extends View {
|
||||
|
||||
public boolean isChecked = false;
|
||||
private Bitmap image;
|
||||
private Bitmap mirrorImage;
|
||||
private Bitmap srcImage;
|
||||
private boolean mirror = true;
|
||||
private int imageWidth;
|
||||
private int imageHeight;
|
||||
private Bitmap btDelete;
|
||||
private Bitmap btScale;
|
||||
private Bitmap btMirror;
|
||||
// private Bitmap btRotate;
|
||||
private int btSize;
|
||||
private Matrix mMatrix;
|
||||
private float[] srcPs, dstPs;
|
||||
private Paint mPaint;
|
||||
private Paint framePaint;
|
||||
private boolean isUsing = true;
|
||||
private float downX1;
|
||||
private float downY1;
|
||||
private float downX2;
|
||||
private float downY2;
|
||||
private ClickType clickType;
|
||||
private boolean isOut = false;
|
||||
private GestureDetector gestureDetector;
|
||||
private float lastDegree;
|
||||
private float lastDoubleDegress;
|
||||
private OnStickerClickListener listener;
|
||||
private int startX, startY;
|
||||
private Path path;
|
||||
|
||||
private String cacheKey;
|
||||
|
||||
public BitmapSticker(Context context, String bitmapPath, int bitmapResourceId, int ViewGroupCenterX, int ViewGroupCenterY) {
|
||||
super(context);
|
||||
|
||||
if (TextUtils.isEmpty(bitmapPath)) {
|
||||
image = StickerCache.get().getSrcBitmap(getResources(), bitmapResourceId);
|
||||
cacheKey = String.valueOf(bitmapResourceId);
|
||||
} else {
|
||||
image = StickerCache.get().getSrcBitmap(bitmapPath);
|
||||
cacheKey = bitmapPath;
|
||||
}
|
||||
path = new Path();
|
||||
srcImage = image;
|
||||
mirrorImage = StickerCache.get().getMirrorBitmap(cacheKey);
|
||||
|
||||
this.imageWidth = image.getWidth();
|
||||
this.imageHeight = image.getHeight();
|
||||
this.startX = ViewGroupCenterX - imageWidth / 2;
|
||||
if (this.startX < 100) {
|
||||
this.startX = ViewGroupCenterX / 2;
|
||||
}
|
||||
this.startY = ViewGroupCenterY - imageHeight / 2;
|
||||
if (this.startY < 100) {
|
||||
this.startY = ViewGroupCenterY / 2;
|
||||
}
|
||||
initButtons();
|
||||
initPs();
|
||||
initPaints();
|
||||
initMatrix();
|
||||
initCanvasPosition();
|
||||
lastDegree = computeDegree(new Point(imageWidth, imageHeight), new Point(imageWidth / 2, imageHeight / 2));
|
||||
lastDoubleDegress = 1000;
|
||||
gestureDetector = new GestureDetector(context, new StickerGestureListener());
|
||||
|
||||
}
|
||||
|
||||
public void setOnStickerClickListener(OnStickerClickListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private void initCanvasPosition() {
|
||||
mMatrix.postTranslate(startX, startY);
|
||||
mMatrix.mapPoints(dstPs, srcPs);
|
||||
}
|
||||
|
||||
private void initMatrix() {
|
||||
mMatrix = new Matrix();
|
||||
}
|
||||
|
||||
private void initPaints() {
|
||||
mPaint = new Paint();
|
||||
mPaint.setAntiAlias(true);
|
||||
mPaint.setDither(true);
|
||||
mPaint.setFilterBitmap(true);
|
||||
|
||||
framePaint = new Paint();
|
||||
framePaint.setAntiAlias(true);
|
||||
framePaint.setStrokeWidth(1);
|
||||
framePaint.setColor(ContextCompat.getColor(getContext(), android.R.color.white));
|
||||
}
|
||||
|
||||
private void initPs() {
|
||||
srcPs = new float[]{0, 0, imageWidth, 0, imageWidth, imageHeight, 0, imageHeight, imageWidth / 2, imageHeight / 2};
|
||||
dstPs = srcPs.clone();
|
||||
}
|
||||
|
||||
private void initButtons() {
|
||||
btDelete = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_delete_easy_photos);
|
||||
btMirror = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_mirror_easy_photos);
|
||||
btScale = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_controller_easy_photos);
|
||||
// btRotate = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_rotate);
|
||||
btSize = btDelete.getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
canvas.drawBitmap(image, mMatrix, mPaint);
|
||||
if (isUsing)
|
||||
drawOthers(canvas);
|
||||
}
|
||||
|
||||
private void drawOthers(Canvas canvas) {
|
||||
path.reset();
|
||||
path.moveTo(dstPs[0], dstPs[1]);
|
||||
path.lineTo(dstPs[2], dstPs[3]);
|
||||
path.lineTo(dstPs[4], dstPs[5]);
|
||||
path.lineTo(dstPs[6], dstPs[7]);
|
||||
path.lineTo(dstPs[0], dstPs[1]);
|
||||
for (int i = 0; i < 7; i += 2) {
|
||||
if (i == 6) {
|
||||
canvas.drawLine(dstPs[i], dstPs[i + 1], dstPs[0], dstPs[1], framePaint);
|
||||
break;
|
||||
}
|
||||
canvas.drawLine(dstPs[i], dstPs[i + 1], dstPs[i + 2], dstPs[i + 3], framePaint);
|
||||
}
|
||||
|
||||
canvas.drawBitmap(btDelete, dstPs[2] - btSize / 2, dstPs[3] - btSize / 2, mPaint);
|
||||
canvas.drawBitmap(btMirror, dstPs[0] - btSize / 2, dstPs[1] - btSize / 2, mPaint);
|
||||
canvas.drawBitmap(btScale, dstPs[4] - btSize / 2, dstPs[5] - btSize / 2, mPaint);
|
||||
// canvas.drawBitmap(btRotate, dstPs[6] - btSize / 2, dstPs[7] - btSize / 2, mPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
|
||||
gestureDetector.onTouchEvent(event);
|
||||
if (MotionEvent.ACTION_UP == event.getAction() || MotionEvent.ACTION_POINTER_UP == event.getAction() || MotionEvent.ACTION_POINTER_1_UP == event.getAction() || MotionEvent.ACTION_POINTER_2_UP == event.getAction()) {
|
||||
setDoubleDownPoints(0, 0, 0, 0);
|
||||
lastDoubleDegress = 1000;
|
||||
lastDegree = computeDegree(new Point((int) dstPs[4], (int) dstPs[5]), new Point((int) dstPs[8], (int) dstPs[9]));
|
||||
}
|
||||
|
||||
return !isOut;
|
||||
}
|
||||
|
||||
private void setDoubleDownPoints(float x1, float y1, float x2, float y2) {
|
||||
downX1 = x1;
|
||||
downY1 = y1;
|
||||
downX2 = x2;
|
||||
downY2 = y2;
|
||||
}
|
||||
|
||||
private void calculateClickType(int x, int y) {
|
||||
RectF rectF = new RectF(x - btSize / 2 - 40, y - btSize / 2 - 40, x + btSize / 2 + 40, y + btSize / 2 + 40);
|
||||
if (rectF.contains(dstPs[2] - 20, dstPs[3])) {
|
||||
clickType = ClickType.DELETE;
|
||||
} else if (rectF.contains(dstPs[0], dstPs[1])) {
|
||||
clickType = ClickType.MIRROR;
|
||||
} else if (rectF.contains(dstPs[4] + 20, dstPs[5])) {
|
||||
clickType = ClickType.SCALE;
|
||||
} else if (rectF.contains(dstPs[6] - 20, dstPs[7])) {
|
||||
clickType = ClickType.IMAGE;
|
||||
} else {
|
||||
|
||||
RectF bounds = new RectF();
|
||||
path.computeBounds(bounds, true);
|
||||
Region region = new Region();
|
||||
region.setPath(path, new Region((int)bounds.left, (int)bounds.top,(int)bounds.right, (int)bounds.bottom));
|
||||
|
||||
if (region.contains(x, y)) {
|
||||
if (isOut) {
|
||||
isOut = false;
|
||||
}
|
||||
if (!isUsing) {
|
||||
isUsing = true;
|
||||
listener.onUsing();
|
||||
postInvalidate();
|
||||
}
|
||||
clickType = ClickType.IMAGE;
|
||||
} else {
|
||||
if (isUsing) {
|
||||
isUsing = false;
|
||||
postInvalidate();
|
||||
}
|
||||
if (!isOut) {
|
||||
isOut = true;
|
||||
}
|
||||
clickType = ClickType.OUT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
if (null == listener) {
|
||||
throw new NullPointerException("OnStickerClickListener listener is null");
|
||||
}
|
||||
setVisibility(GONE);
|
||||
StickerCache.get().recycle(cacheKey);
|
||||
listener.onDelete();
|
||||
}
|
||||
|
||||
private void mirror() {
|
||||
if (mirror) {
|
||||
image = mirrorImage;
|
||||
} else {
|
||||
image = srcImage;
|
||||
}
|
||||
mirror = !mirror;
|
||||
matrixMap();
|
||||
}
|
||||
|
||||
private void top() {
|
||||
bringToFront();//置顶
|
||||
invalidate();
|
||||
listener.onTop();
|
||||
}
|
||||
|
||||
private void move(float distansX, float distansY) {
|
||||
mMatrix.postTranslate(distansX, distansY);
|
||||
matrixMap();
|
||||
}
|
||||
|
||||
private void controller(MotionEvent event) {
|
||||
scale(event);
|
||||
rotate(event);
|
||||
}
|
||||
|
||||
private void scale(MotionEvent event) {
|
||||
float originalX1;
|
||||
float originalY1;
|
||||
float originalX2;
|
||||
float originalY2;
|
||||
float moveX1;
|
||||
float moveY1;
|
||||
float moveX2;
|
||||
float moveY2;
|
||||
if (event.getPointerCount() == 2) {
|
||||
originalX2 = downX2;
|
||||
originalY2 = downY2;
|
||||
originalX1 = downX1;
|
||||
originalY1 = downY1;
|
||||
|
||||
moveX2 = event.getX(1);
|
||||
moveY2 = event.getY(1);
|
||||
moveX1 = event.getX(0);
|
||||
moveY1 = event.getY(0);
|
||||
|
||||
} else {
|
||||
originalX2 = dstPs[4];
|
||||
originalY2 = dstPs[5];
|
||||
originalX1 = dstPs[0];
|
||||
originalY1 = dstPs[1];
|
||||
|
||||
moveX2 = event.getX();
|
||||
moveY2 = event.getY();
|
||||
moveX1 = originalX1;
|
||||
moveY1 = originalY1;
|
||||
}
|
||||
|
||||
float temp1 = getDistanceOfTwoPoints(originalX2, originalY2, originalX1, originalY1);
|
||||
float temp2 = getDistanceOfTwoPoints(moveX2, moveY2, moveX1, moveY1);
|
||||
|
||||
float scalValue = temp2 / temp1;
|
||||
|
||||
if (getScaleValue() < (float) 0.3 && scalValue < (float) 1) {
|
||||
return;
|
||||
}
|
||||
mMatrix.postScale(scalValue, scalValue, dstPs[8], dstPs[9]);
|
||||
matrixMap();
|
||||
if (event.getPointerCount() == 2) {
|
||||
setDoubleDownPoints(moveX1, moveY1, moveX2, moveY2);
|
||||
}
|
||||
}
|
||||
|
||||
private void rotate(MotionEvent event) {
|
||||
if (event.getPointerCount() == 2) {
|
||||
float preDegree = computeDegree(new Point((int) event.getX(0), (int) event.getY(0)), new Point((int) event.getX(1), (int) event.getY(1)));
|
||||
if (lastDoubleDegress == 1000) {
|
||||
lastDoubleDegress = preDegree;
|
||||
}
|
||||
mMatrix.postRotate(preDegree - lastDoubleDegress, dstPs[8], dstPs[9]);
|
||||
matrixMap();
|
||||
lastDoubleDegress = preDegree;
|
||||
|
||||
} else {
|
||||
float preDegree = computeDegree(new Point((int) event.getX(), (int) event.getY()), new Point((int) dstPs[8], (int) dstPs[9]));
|
||||
mMatrix.postRotate(preDegree - lastDegree, dstPs[8], dstPs[9]);
|
||||
matrixMap();
|
||||
lastDegree = preDegree;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取饰品缩放比例(与原图相比)
|
||||
public float getScaleValue() {
|
||||
float preDistance = (srcPs[8] - srcPs[0]) * (srcPs[8] - srcPs[0]) + (srcPs[9] - srcPs[1]) * (srcPs[9] - srcPs[1]);
|
||||
float lastDistance = (dstPs[8] - dstPs[0]) * (dstPs[8] - dstPs[0]) + (dstPs[9] - dstPs[1]) * (dstPs[9] - dstPs[1]);
|
||||
float scaleValue = (float) Math.sqrt(lastDistance / preDistance);
|
||||
return scaleValue;
|
||||
}
|
||||
|
||||
private void matrixMap() {
|
||||
mMatrix.mapPoints(dstPs, srcPs);
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public void setUsing(boolean isUsing) {
|
||||
this.isUsing = isUsing;
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public boolean isUsing() {
|
||||
return this.isUsing;
|
||||
}
|
||||
|
||||
private float getDistanceOfTwoPoints(float x1, float y1, float x2, float y2) {
|
||||
return (float) (Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)));
|
||||
}
|
||||
|
||||
public float computeDegree(Point p1, Point p2) {
|
||||
float tran_x = p1.x - p2.x;
|
||||
float tran_y = p1.y - p2.y;
|
||||
float degree = 0.0f;
|
||||
float angle = (float) (Math.asin(tran_x / Math.sqrt(tran_x * tran_x + tran_y * tran_y)) * 180 / Math.PI);
|
||||
if (!Float.isNaN(angle)) {
|
||||
if (tran_x >= 0 && tran_y <= 0) {//第一象限
|
||||
degree = angle;
|
||||
} else if (tran_x <= 0 && tran_y <= 0) {//第二象限
|
||||
degree = angle;
|
||||
} else if (tran_x <= 0 && tran_y >= 0) {//第三象限
|
||||
degree = -180 - angle;
|
||||
} else if (tran_x >= 0 && tran_y >= 0) {//第四象限
|
||||
degree = 180 - angle;
|
||||
}
|
||||
}
|
||||
return degree;
|
||||
}
|
||||
|
||||
private enum ClickType {
|
||||
DELETE, MIRROR, SCALE, ROTATE, IMAGE, OUT
|
||||
}
|
||||
|
||||
private class StickerGestureListener extends GestureDetector.SimpleOnGestureListener {
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||
|
||||
switch (clickType) {
|
||||
case DELETE:
|
||||
delete();
|
||||
break;
|
||||
case MIRROR:
|
||||
mirror();
|
||||
break;
|
||||
case SCALE:
|
||||
break;
|
||||
case ROTATE:
|
||||
break;
|
||||
case IMAGE:
|
||||
break;
|
||||
case OUT:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||
// Log.d("scroll", "onScroll: " + distanceX);
|
||||
switch (clickType) {
|
||||
case DELETE:
|
||||
break;
|
||||
case MIRROR:
|
||||
break;
|
||||
case SCALE:
|
||||
if (e2.getPointerCount() > 1) break;
|
||||
controller(e2);
|
||||
break;
|
||||
case ROTATE:
|
||||
// rotate(e2);
|
||||
break;
|
||||
case IMAGE:
|
||||
if (e2.getPointerCount() == 2) {
|
||||
if (downX1 + downY1 + downX2 + downY2 == 0) {
|
||||
setDoubleDownPoints(e2.getX(0), e2.getY(0), e2.getX(1), e2.getY(1));
|
||||
}
|
||||
controller(e2);
|
||||
} else if (e2.getPointerCount() == 1) {
|
||||
move(-distanceX, -distanceY);
|
||||
}
|
||||
break;
|
||||
case OUT:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e) {
|
||||
isChecked = true;
|
||||
calculateClickType((int) e.getX(), (int) e.getY());
|
||||
if (clickType == ClickType.IMAGE) {
|
||||
top();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package com.huantansheng.easyphotos.models.sticker.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.huantansheng.easyphotos.R;
|
||||
|
||||
/**
|
||||
* 文字贴纸,编辑界面
|
||||
* Created by huan on 2017/12/13.
|
||||
*/
|
||||
|
||||
public class EditFragment extends DialogFragment implements View.OnClickListener {
|
||||
|
||||
private TextView tvSample;
|
||||
private EditText et;
|
||||
private SeekBar seekBar;
|
||||
|
||||
private TextSticker textSticker = null;
|
||||
|
||||
private InputMethodManager inputMethodManager;
|
||||
|
||||
public static EditFragment show(FragmentManager fm, TextSticker sticker) {
|
||||
EditFragment editFragment = new EditFragment();
|
||||
editFragment.textSticker = sticker;
|
||||
editFragment.show(fm, "edit");
|
||||
return editFragment;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
|
||||
getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
View rootView = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_text_sticker_easy_photos, container);
|
||||
|
||||
tvSample = (TextView) rootView.findViewById(R.id.tv_sample);
|
||||
et = (EditText) rootView.findViewById(R.id.et);
|
||||
seekBar = (SeekBar) rootView.findViewById(R.id.m_seek_bar);
|
||||
|
||||
l(rootView, R.id.iv_red, R.id.iv_orange, R.id.iv_yellow, R.id.iv_green, R.id.iv_cyan, R.id.iv_blue, R.id.iv_purple, R.id.iv_black, R.id.iv_gray, R.id.iv_white, R.id.tv_done, R.id.iv_clear);
|
||||
|
||||
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
|
||||
if (b) {
|
||||
setTextAlpha(i);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
et.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
tvSample.setText(editable.toString());
|
||||
if (null != textSticker) {
|
||||
textSticker.resetText(editable.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
return rootView;
|
||||
}
|
||||
|
||||
private void l(View view, @IdRes int... resIds) {
|
||||
for (int resId : resIds) {
|
||||
view.findViewById(resId).setOnClickListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
bindingSticker();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
Window dialogWindow = getDialog().getWindow();
|
||||
if (null != dialogWindow) {
|
||||
WindowManager.LayoutParams attrs = dialogWindow.getAttributes();
|
||||
attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
|
||||
dialogWindow.setAttributes(attrs);
|
||||
dialogWindow.requestFeature(Window.FEATURE_NO_TITLE);
|
||||
}
|
||||
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
if (null != dialogWindow) {
|
||||
dialogWindow.setBackgroundDrawable(new ColorDrawable(0x00000000));
|
||||
dialogWindow.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT);
|
||||
dialogWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void bindingSticker() {
|
||||
String text = textSticker.getText();
|
||||
tvSample.setText(text);
|
||||
et.setText(text);
|
||||
et.setSelection(text.length());
|
||||
int alpha = textSticker.getTextAlpha();
|
||||
float alphaF = (float) alpha / (float) 255;
|
||||
seekBar.setProgress(alpha);
|
||||
tvSample.setTextColor(textSticker.getTextColor());
|
||||
tvSample.setAlpha(alphaF);
|
||||
inputMethodManager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (null != inputMethodManager) {
|
||||
inputMethodManager.showSoftInput(et, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
int id = view.getId();
|
||||
if (R.id.iv_red == id) {
|
||||
setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_red_easy_photos));
|
||||
|
||||
} else if (R.id.iv_orange == id) {
|
||||
setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_orange_easy_photos));
|
||||
|
||||
} else if (R.id.iv_yellow == id) {
|
||||
setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_yellow_easy_photos));
|
||||
|
||||
} else if (R.id.iv_green == id) {
|
||||
setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_green_easy_photos));
|
||||
|
||||
} else if (R.id.iv_cyan == id) {
|
||||
setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_cyan_easy_photos));
|
||||
|
||||
} else if (R.id.iv_blue == id) {
|
||||
setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_blue_easy_photos));
|
||||
|
||||
} else if (R.id.iv_purple == id) {
|
||||
setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_purple_easy_photos));
|
||||
|
||||
} else if (R.id.iv_black == id) {
|
||||
setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_black_easy_photos));
|
||||
|
||||
} else if (R.id.iv_gray == id) {
|
||||
setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_gray_easy_photos));
|
||||
|
||||
} else if (R.id.iv_white == id) {
|
||||
setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_white_easy_photos));
|
||||
|
||||
} else if (R.id.tv_done == id) {
|
||||
dismiss();
|
||||
} else if (R.id.iv_clear == id) {
|
||||
et.setText(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void setTextColor(int color) {
|
||||
tvSample.setTextColor(color);
|
||||
textSticker.setTextColor(color);
|
||||
}
|
||||
|
||||
private void setTextAlpha(int alpha) {
|
||||
tvSample.setAlpha((float) alpha / (float) 225);
|
||||
textSticker.setTextAlpha(alpha);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,553 @@
|
||||
package com.huantansheng.easyphotos.models.sticker.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Region;
|
||||
import android.graphics.Typeface;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import android.text.Layout;
|
||||
import android.text.StaticLayout;
|
||||
import android.text.TextPaint;
|
||||
import android.text.TextUtils;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import com.huantansheng.easyphotos.EasyPhotos;
|
||||
import com.huantansheng.easyphotos.R;
|
||||
import com.huantansheng.easyphotos.models.sticker.listener.OnStickerClickListener;
|
||||
|
||||
|
||||
/**
|
||||
* 自定义贴图view
|
||||
* Created by huan on 2017/7/24.
|
||||
*/
|
||||
|
||||
public class TextSticker extends View {
|
||||
|
||||
public boolean isChecked = false;
|
||||
private String text;
|
||||
private float textWidth;
|
||||
private float textHeight;
|
||||
private Bitmap bitmap;
|
||||
private Bitmap btDelete;
|
||||
private Bitmap btController;
|
||||
// private Bitmap btEditor;
|
||||
// private Bitmap btRotate;
|
||||
private int btSize;
|
||||
private Matrix mMatrix;
|
||||
private float[] srcPs, dstPs;
|
||||
private TextPaint textPaint;
|
||||
private Paint bitmapPaint;
|
||||
private Paint framePaint;
|
||||
private boolean isUsing = true;
|
||||
private float downX1;
|
||||
private float downY1;
|
||||
private float downX2;
|
||||
private float downY2;
|
||||
private ClickType clickType;
|
||||
private boolean isOut = false;
|
||||
private GestureDetector gestureDetector;
|
||||
private float lastDegree;
|
||||
private float lastDoubleDegress;
|
||||
private OnStickerClickListener listener;
|
||||
private int startX, startY;
|
||||
private int minWidth = 300, minHeight = 100;
|
||||
private float minScale;
|
||||
private StaticLayout textLayout;
|
||||
private int textLayoutWidth;
|
||||
|
||||
private Canvas bitmapConvas;
|
||||
|
||||
private Path path;
|
||||
|
||||
public TextSticker(Context context, String text, int viewGroupCenterX, int viewGroupCenterY) {
|
||||
super(context);
|
||||
|
||||
this.text = text;
|
||||
if (TextUtils.isEmpty(this.text)) {
|
||||
this.text = context.getString(R.string.text_sticker_hint_easy_photos);
|
||||
}
|
||||
path = new Path();
|
||||
textLayoutWidth = getResources().getDisplayMetrics().widthPixels / 2;
|
||||
initButtons();
|
||||
initPaints();
|
||||
resetSize();
|
||||
initStartPoint(viewGroupCenterX, viewGroupCenterY);
|
||||
initPs();
|
||||
resetBitmap();
|
||||
initMatrix();
|
||||
initCanvasPosition();
|
||||
lastDegree = computeDegree(new Point((int) textWidth, (int) textHeight), new Point((int) textWidth / 2, (int) textHeight / 2));
|
||||
lastDoubleDegress = 1000;
|
||||
gestureDetector = new GestureDetector(context, new StickerGestureListener());
|
||||
|
||||
}
|
||||
|
||||
private void resetBitmap() {
|
||||
EasyPhotos.recycle(bitmap);
|
||||
bitmap = Bitmap.createBitmap((int) textWidth, (int) textHeight, Bitmap.Config.ARGB_4444);
|
||||
bitmapConvas = new Canvas(bitmap);
|
||||
// bitmapConvas.drawText(text, 0, Math.abs(textPaint.getFontMetrics().top), textPaint);
|
||||
textLayout.draw(bitmapConvas);
|
||||
}
|
||||
|
||||
private void initStartPoint(int viewGroupCenterX, int viewGroupCenterY) {
|
||||
this.startX = viewGroupCenterX - (int) textWidth / 2;
|
||||
if (this.startX < 100) {
|
||||
this.startX = viewGroupCenterX / 2;
|
||||
}
|
||||
this.startY = viewGroupCenterY - (int) textHeight / 2;
|
||||
if (this.startY < 100) {
|
||||
this.startY = viewGroupCenterY / 2;
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnStickerClickListener(OnStickerClickListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private void initCanvasPosition() {
|
||||
mMatrix.postTranslate(startX, startY);
|
||||
mMatrix.mapPoints(dstPs, srcPs);
|
||||
}
|
||||
|
||||
private void initMatrix() {
|
||||
mMatrix = new Matrix();
|
||||
}
|
||||
|
||||
private void initPaints() {
|
||||
textPaint = new TextPaint();
|
||||
textPaint.setAntiAlias(true);
|
||||
textPaint.setDither(true);
|
||||
textPaint.setFilterBitmap(true);
|
||||
textPaint.setTypeface(Typeface.DEFAULT_BOLD);
|
||||
textPaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.sticker_text_size_easy_photos));
|
||||
textPaint.setColor(Color.WHITE);
|
||||
|
||||
|
||||
bitmapPaint = new Paint();
|
||||
bitmapPaint.setAntiAlias(true);
|
||||
bitmapPaint.setDither(true);
|
||||
bitmapPaint.setFilterBitmap(true);
|
||||
|
||||
framePaint = new Paint();
|
||||
framePaint.setAntiAlias(true);
|
||||
bitmapPaint.setDither(true);
|
||||
bitmapPaint.setFilterBitmap(true);
|
||||
framePaint.setStrokeWidth(1);
|
||||
framePaint.setColor(ContextCompat.getColor(getContext(), android.R.color.white));
|
||||
}
|
||||
|
||||
|
||||
private void resetSize() {
|
||||
textLayout = new StaticLayout(text, textPaint, textLayoutWidth,
|
||||
Layout.Alignment.ALIGN_CENTER, 1.0F, 0.0F, true);
|
||||
textWidth = minWidth;
|
||||
textHeight = minHeight;
|
||||
if (textWidth < textLayout.getWidth()) {
|
||||
textWidth = textLayout.getWidth();
|
||||
}
|
||||
if (textHeight < textLayout.getHeight()) {
|
||||
textHeight = textLayout.getHeight();
|
||||
}
|
||||
minScale = minWidth / textWidth;
|
||||
|
||||
}
|
||||
|
||||
private void initPs() {
|
||||
srcPs = new float[]{0, 0, textWidth, 0, textWidth, textHeight, 0, textHeight, textWidth / 2, textHeight / 2};
|
||||
dstPs = srcPs.clone();
|
||||
}
|
||||
|
||||
private void initButtons() {
|
||||
btDelete = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_delete_easy_photos);
|
||||
// btEditor = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_editor_easy_photos);
|
||||
btController = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_controller_easy_photos);
|
||||
// btRotate = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_rotate);
|
||||
btSize = btDelete.getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
canvas.drawBitmap(bitmap, mMatrix, bitmapPaint);
|
||||
if (isUsing)
|
||||
drawOthers(canvas);
|
||||
}
|
||||
|
||||
private void drawOthers(Canvas canvas) {
|
||||
path.reset();
|
||||
path.moveTo(dstPs[0], dstPs[1]);
|
||||
path.lineTo(dstPs[2], dstPs[3]);
|
||||
path.lineTo(dstPs[4], dstPs[5]);
|
||||
path.lineTo(dstPs[6], dstPs[7]);
|
||||
path.lineTo(dstPs[0], dstPs[1]);
|
||||
for (int i = 0; i < 7; i += 2) {
|
||||
if (i == 6) {
|
||||
canvas.drawLine(dstPs[i], dstPs[i + 1], dstPs[0], dstPs[1], framePaint);
|
||||
break;
|
||||
}
|
||||
canvas.drawLine(dstPs[i], dstPs[i + 1], dstPs[i + 2], dstPs[i + 3], framePaint);
|
||||
}
|
||||
|
||||
|
||||
canvas.drawBitmap(btDelete, dstPs[2] - btSize / 2, dstPs[3] - btSize / 2, bitmapPaint);
|
||||
// canvas.drawBitmap(btEditor, dstPs[0] - btSize / 2, dstPs[1] - btSize / 2, bitmapPaint);
|
||||
canvas.drawBitmap(btController, dstPs[4] - btSize / 2, dstPs[5] - btSize / 2, bitmapPaint);
|
||||
// canvas.drawBitmap(btRotate, dstPs[6] - btSize / 2, dstPs[7] - btSize / 2, bitmapPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
|
||||
gestureDetector.onTouchEvent(event);
|
||||
if (MotionEvent.ACTION_UP == event.getAction() || MotionEvent.ACTION_POINTER_UP == event.getAction() || MotionEvent.ACTION_POINTER_1_UP == event.getAction() || MotionEvent.ACTION_POINTER_2_UP == event.getAction()) {
|
||||
setDoubleDownPoints(0, 0, 0, 0);
|
||||
lastDoubleDegress = 1000;
|
||||
lastDegree = computeDegree(new Point((int) dstPs[4], (int) dstPs[5]), new Point((int) dstPs[8], (int) dstPs[9]));
|
||||
}
|
||||
|
||||
return !isOut;
|
||||
}
|
||||
|
||||
private void setDoubleDownPoints(float x1, float y1, float x2, float y2) {
|
||||
downX1 = x1;
|
||||
downY1 = y1;
|
||||
downX2 = x2;
|
||||
downY2 = y2;
|
||||
}
|
||||
|
||||
private void calculateClickType(int x, int y) {
|
||||
RectF rectF = new RectF(x - btSize / 2 - 40, y - btSize / 2 - 40, x + btSize / 2 + 40, y + btSize / 2 + 40);
|
||||
|
||||
Rect rect = new Rect();
|
||||
|
||||
|
||||
if (rectF.contains(dstPs[2] - 20, dstPs[3])) {
|
||||
clickType = ClickType.DELETE;
|
||||
}
|
||||
// else if (rectF.contains(dstPs[0], dstPs[1])) {
|
||||
// clickType = ClickType.EDITOR;
|
||||
// }
|
||||
else if (rectF.contains(dstPs[4] + 20, dstPs[5])) {
|
||||
clickType = ClickType.SCALE;
|
||||
}
|
||||
// else if (rectF.contains(dstPs[6] - 20, dstPs[7])) {
|
||||
// clickType = ClickType.IMAGE;
|
||||
// }
|
||||
else {
|
||||
RectF bounds = new RectF();
|
||||
path.computeBounds(bounds, true);
|
||||
Region region = new Region();
|
||||
region.setPath(path, new Region((int) bounds.left, (int) bounds.top, (int) bounds.right, (int) bounds.bottom));
|
||||
|
||||
if (region.contains(x, y)) {
|
||||
if (isOut) {
|
||||
isOut = false;
|
||||
}
|
||||
if (!isUsing) {
|
||||
isUsing = true;
|
||||
listener.onUsing();
|
||||
postInvalidate();
|
||||
}
|
||||
clickType = ClickType.IMAGE;
|
||||
} else {
|
||||
if (isUsing) {
|
||||
isUsing = false;
|
||||
postInvalidate();
|
||||
}
|
||||
if (!isOut) {
|
||||
isOut = true;
|
||||
}
|
||||
clickType = ClickType.OUT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
if (null == listener) {
|
||||
throw new NullPointerException("OnStickerClickListener listener is null");
|
||||
}
|
||||
setVisibility(GONE);
|
||||
EasyPhotos.recycle(bitmap);
|
||||
listener.onDelete();
|
||||
}
|
||||
|
||||
private void editor() {
|
||||
listener.onEditor();
|
||||
}
|
||||
|
||||
private void top() {
|
||||
bringToFront();//置顶
|
||||
invalidate();
|
||||
listener.onTop();
|
||||
}
|
||||
|
||||
private void move(float distansX, float distansY) {
|
||||
mMatrix.postTranslate(distansX, distansY);
|
||||
matrixMap();
|
||||
}
|
||||
|
||||
public void moveTo(float x, float y) {
|
||||
move(x - dstPs[8], y - dstPs[1]);
|
||||
}
|
||||
|
||||
private void controller(MotionEvent event) {
|
||||
scale(event);
|
||||
rotate(event);
|
||||
}
|
||||
|
||||
private void scale(MotionEvent event) {
|
||||
float originalX1;
|
||||
float originalY1;
|
||||
float originalX2;
|
||||
float originalY2;
|
||||
float moveX1;
|
||||
float moveY1;
|
||||
float moveX2;
|
||||
float moveY2;
|
||||
if (event.getPointerCount() == 2) {
|
||||
originalX2 = downX2;
|
||||
originalY2 = downY2;
|
||||
originalX1 = downX1;
|
||||
originalY1 = downY1;
|
||||
|
||||
moveX2 = event.getX(1);
|
||||
moveY2 = event.getY(1);
|
||||
moveX1 = event.getX(0);
|
||||
moveY1 = event.getY(0);
|
||||
|
||||
} else {
|
||||
originalX2 = dstPs[4];
|
||||
originalY2 = dstPs[5];
|
||||
originalX1 = dstPs[0];
|
||||
originalY1 = dstPs[1];
|
||||
|
||||
moveX2 = event.getX();
|
||||
moveY2 = event.getY();
|
||||
moveX1 = originalX1;
|
||||
moveY1 = originalY1;
|
||||
}
|
||||
|
||||
float temp1 = getDistanceOfTwoPoints(originalX2, originalY2, originalX1, originalY1);
|
||||
float temp2 = getDistanceOfTwoPoints(moveX2, moveY2, moveX1, moveY1);
|
||||
|
||||
float scalValue = temp2 / temp1;
|
||||
|
||||
if (getScaleValue() < minScale && scalValue < (float) 1) {
|
||||
return;
|
||||
}
|
||||
mMatrix.postScale(scalValue, scalValue, dstPs[8], dstPs[9]);
|
||||
matrixMap();
|
||||
if (event.getPointerCount() == 2) {
|
||||
setDoubleDownPoints(moveX1, moveY1, moveX2, moveY2);
|
||||
}
|
||||
}
|
||||
|
||||
private void rotate(MotionEvent event) {
|
||||
if (event.getPointerCount() == 2) {
|
||||
float preDegree = computeDegree(new Point((int) event.getX(0), (int) event.getY(0)), new Point((int) event.getX(1), (int) event.getY(1)));
|
||||
if (lastDoubleDegress == 1000) {
|
||||
lastDoubleDegress = preDegree;
|
||||
}
|
||||
mMatrix.postRotate(preDegree - lastDoubleDegress, dstPs[8], dstPs[9]);
|
||||
matrixMap();
|
||||
lastDoubleDegress = preDegree;
|
||||
|
||||
} else {
|
||||
float preDegree = computeDegree(new Point((int) event.getX(), (int) event.getY()), new Point((int) dstPs[8], (int) dstPs[9]));
|
||||
mMatrix.postRotate(preDegree - lastDegree, dstPs[8], dstPs[9]);
|
||||
matrixMap();
|
||||
lastDegree = preDegree;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取饰品缩放比例(与原图相比)
|
||||
public float getScaleValue() {
|
||||
float preDistance = (srcPs[8] - srcPs[0]) * (srcPs[8] - srcPs[0]) + (srcPs[9] - srcPs[1]) * (srcPs[9] - srcPs[1]);
|
||||
float lastDistance = (dstPs[8] - dstPs[0]) * (dstPs[8] - dstPs[0]) + (dstPs[9] - dstPs[1]) * (dstPs[9] - dstPs[1]);
|
||||
float scaleValue = (float) Math.sqrt(lastDistance / preDistance);
|
||||
return scaleValue;
|
||||
}
|
||||
|
||||
private void matrixMap() {
|
||||
mMatrix.mapPoints(dstPs, srcPs);
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public void setUsing(boolean isUsing) {
|
||||
this.isUsing = isUsing;
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
private float getDistanceOfTwoPoints(float x1, float y1, float x2, float y2) {
|
||||
return (float) (Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)));
|
||||
}
|
||||
|
||||
public float computeDegree(Point p1, Point p2) {
|
||||
float tran_x = p1.x - p2.x;
|
||||
float tran_y = p1.y - p2.y;
|
||||
float degree = 0.0f;
|
||||
float angle = (float) (Math.asin(tran_x / Math.sqrt(tran_x * tran_x + tran_y * tran_y)) * 180 / Math.PI);
|
||||
if (!Float.isNaN(angle)) {
|
||||
if (tran_x >= 0 && tran_y <= 0) {//第一象限
|
||||
degree = angle;
|
||||
} else if (tran_x <= 0 && tran_y <= 0) {//第二象限
|
||||
degree = angle;
|
||||
} else if (tran_x <= 0 && tran_y >= 0) {//第三象限
|
||||
degree = -180 - angle;
|
||||
} else if (tran_x >= 0 && tran_y >= 0) {//第四象限
|
||||
degree = 180 - angle;
|
||||
}
|
||||
}
|
||||
return degree;
|
||||
}
|
||||
|
||||
private enum ClickType {
|
||||
DELETE, EDITOR, SCALE, ROTATE, IMAGE, OUT
|
||||
}
|
||||
|
||||
private class StickerGestureListener extends GestureDetector.SimpleOnGestureListener {
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||
|
||||
switch (clickType) {
|
||||
case DELETE:
|
||||
delete();
|
||||
break;
|
||||
case EDITOR:
|
||||
// editor();
|
||||
break;
|
||||
case SCALE:
|
||||
break;
|
||||
case ROTATE:
|
||||
break;
|
||||
case IMAGE:
|
||||
// editor();
|
||||
break;
|
||||
case OUT:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTap(MotionEvent e) {
|
||||
switch (clickType) {
|
||||
case DELETE:
|
||||
break;
|
||||
case EDITOR:
|
||||
break;
|
||||
case SCALE:
|
||||
break;
|
||||
case ROTATE:
|
||||
break;
|
||||
case IMAGE:
|
||||
editor();
|
||||
break;
|
||||
case OUT:
|
||||
break;
|
||||
}
|
||||
return super.onDoubleTap(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||
switch (clickType) {
|
||||
case DELETE:
|
||||
break;
|
||||
case EDITOR:
|
||||
break;
|
||||
case SCALE:
|
||||
if (e2.getPointerCount() > 1) break;
|
||||
controller(e2);
|
||||
break;
|
||||
case ROTATE:
|
||||
// rotate(e2);
|
||||
break;
|
||||
case IMAGE:
|
||||
if (e2.getPointerCount() == 2) {
|
||||
if (downX1 + downY1 + downX2 + downY2 == 0) {
|
||||
setDoubleDownPoints(e2.getX(0), e2.getY(0), e2.getX(1), e2.getY(1));
|
||||
}
|
||||
controller(e2);
|
||||
} else if (e2.getPointerCount() == 1) {
|
||||
move(-distanceX, -distanceY);
|
||||
}
|
||||
break;
|
||||
case OUT:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e) {
|
||||
isChecked = true;
|
||||
calculateClickType((int) e.getX(), (int) e.getY());
|
||||
if (clickType == ClickType.IMAGE) {
|
||||
top();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void resetText(String text) {
|
||||
this.text = text;
|
||||
resetSize();
|
||||
resetPoints();
|
||||
resetBitmap();
|
||||
matrixMap();
|
||||
}
|
||||
|
||||
private void resetPoints() {
|
||||
srcPs = new float[]{0, 0, textWidth, 0, textWidth, textHeight, 0, textHeight, textWidth / 2, textHeight / 2};
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return this.text;
|
||||
}
|
||||
|
||||
public void setTextColor(int color) {
|
||||
textPaint.setColor(color);
|
||||
resetSize();
|
||||
resetPoints();
|
||||
resetBitmap();
|
||||
matrixMap();
|
||||
}
|
||||
|
||||
public int getTextColor() {
|
||||
return textPaint.getColor();
|
||||
}
|
||||
|
||||
public void setTextAlpha(int alpha) {
|
||||
textPaint.setAlpha(alpha);
|
||||
resetSize();
|
||||
resetPoints();
|
||||
resetBitmap();
|
||||
matrixMap();
|
||||
}
|
||||
|
||||
public int getTextAlpha() {
|
||||
return textPaint.getAlpha();
|
||||
}
|
||||
|
||||
public boolean isUsing() {
|
||||
return this.isUsing;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package com.huantansheng.easyphotos.result;
|
||||
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
||||
import com.huantansheng.easyphotos.constant.Type;
|
||||
import com.huantansheng.easyphotos.models.album.entity.Photo;
|
||||
import com.huantansheng.easyphotos.setting.Setting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* 存储的返回图片集
|
||||
* Created by huan on 2017/10/24.
|
||||
*/
|
||||
|
||||
public class Result {
|
||||
public static ArrayList<Photo> photos = new ArrayList<>();
|
||||
public static final int ADD_SUCCESS = 0;
|
||||
public static final int PICTURE_OUT = -1;
|
||||
public static final int VIDEO_OUT = -2;
|
||||
public static final int SINGLE_TYPE = -3;
|
||||
|
||||
/**
|
||||
* @return 0:添加成功 -2:超过视频选择数 -1:超过图片选择数
|
||||
*/
|
||||
public static int addPhoto(Photo photo) {
|
||||
if (photos.isEmpty()) {
|
||||
photo.selected = true;
|
||||
photos.add(photo);
|
||||
return ADD_SUCCESS;
|
||||
}
|
||||
if (Setting.complexSelector) {
|
||||
if (Setting.complexSingleType) {
|
||||
if (photos.get(0).type.contains(Type.VIDEO)) {
|
||||
if (!photo.type.contains(Type.VIDEO)) {
|
||||
return SINGLE_TYPE;
|
||||
}
|
||||
}
|
||||
if (!photos.get(0).type.contains(Type.VIDEO)) {
|
||||
if (photo.type.contains(Type.VIDEO)) {
|
||||
return SINGLE_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
int number = getVideoNumber();
|
||||
if (photo.type.contains(Type.VIDEO) && number >= Setting.complexVideoCount) {
|
||||
return VIDEO_OUT;
|
||||
}
|
||||
number = photos.size() - number;
|
||||
if ((!photo.type.contains(Type.VIDEO)) && number >= Setting.complexPictureCount) {
|
||||
return PICTURE_OUT;
|
||||
}
|
||||
}
|
||||
photo.selected = true;
|
||||
photos.add(photo);
|
||||
return ADD_SUCCESS;
|
||||
}
|
||||
|
||||
public static void removePhoto(Photo photo) {
|
||||
photo.selected = false;
|
||||
photos.remove(photo);
|
||||
}
|
||||
|
||||
public static void removePhoto(int photoIndex) {
|
||||
removePhoto(photos.get(photoIndex));
|
||||
}
|
||||
|
||||
public static void removeAll() {
|
||||
int size = photos.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
removePhoto(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static int getVideoNumber() {
|
||||
int count = 0;
|
||||
for (Photo p : photos) {
|
||||
if (p.type.contains(Type.VIDEO)) {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public static void processOriginal() {
|
||||
boolean isIceApi = Build.VERSION.SDK_INT == Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1;
|
||||
if (Setting.showOriginalMenu) {
|
||||
if (Setting.originalMenuUsable) {
|
||||
for (Photo photo : photos) {
|
||||
photo.selectedOriginal = Setting.selectedOriginal;
|
||||
if (isIceApi && photo.width == 0) {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFile(photo.path, options);
|
||||
photo.width = options.outWidth;
|
||||
photo.height = options.outHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
photos.clear();
|
||||
}
|
||||
|
||||
public static boolean isEmpty() {
|
||||
return photos.isEmpty();
|
||||
}
|
||||
|
||||
public static int count() {
|
||||
return photos.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取选择器应该显示的数字
|
||||
*
|
||||
* @param photo 当前图片
|
||||
* @return 选择器应该显示的数字
|
||||
*/
|
||||
public static String getSelectorNumber(Photo photo) {
|
||||
return String.valueOf(photos.indexOf(photo) + 1);
|
||||
}
|
||||
|
||||
public static String getPhotoPath(int position) {
|
||||
return photos.get(position).path;
|
||||
}
|
||||
|
||||
public static Uri getPhotoUri(int position) {
|
||||
return photos.get(position).uri;
|
||||
}
|
||||
|
||||
public static String getPhotoType(int position) {
|
||||
return photos.get(position).type;
|
||||
}
|
||||
|
||||
public static long getPhotoDuration(int position) {
|
||||
return photos.get(position).duration;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.huantansheng.easyphotos.setting;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import android.view.View;
|
||||
|
||||
import com.huantansheng.easyphotos.constant.Type;
|
||||
import com.huantansheng.easyphotos.engine.ImageEngine;
|
||||
import com.huantansheng.easyphotos.models.album.entity.Photo;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* EasyPhotos的设置值
|
||||
* Created by huan on 2017/10/24.
|
||||
*/
|
||||
|
||||
public class Setting {
|
||||
public static int minWidth = 1;
|
||||
public static int minHeight = 1;
|
||||
public static long minSize = 1;
|
||||
public static int count = 1;
|
||||
|
||||
public static WeakReference<View> photosAdView = null;
|
||||
public static WeakReference<View> albumItemsAdView = null;
|
||||
public static boolean photoAdIsOk = false;
|
||||
public static boolean albumItemsAdIsOk = false;
|
||||
public static boolean useWidth = false;
|
||||
public static ArrayList<Photo> selectedPhotos = new ArrayList<>();
|
||||
public static boolean showOriginalMenu = false;
|
||||
public static boolean originalMenuUsable = false;
|
||||
public static String originalMenuUnusableHint = "";
|
||||
public static boolean selectedOriginal = false;
|
||||
public static String fileProviderAuthority = null;
|
||||
public static boolean isShowCamera = false;
|
||||
public static int cameraLocation = 1;
|
||||
public static boolean onlyStartCamera = false;
|
||||
public static boolean showPuzzleMenu = true;
|
||||
public static List<String> filterTypes = new ArrayList<>();
|
||||
public static boolean showGif = false;
|
||||
public static boolean showVideo = false;
|
||||
public static boolean showCleanMenu = true;
|
||||
public static long videoMinSecond = 0L;
|
||||
public static long videoMaxSecond = Long.MAX_VALUE;
|
||||
public static ImageEngine imageEngine = null;
|
||||
|
||||
public static final int LIST_FIRST = 0;
|
||||
public static final int BOTTOM_RIGHT = 1;
|
||||
|
||||
public static boolean complexSelector = false;
|
||||
public static boolean complexSingleType = false;
|
||||
public static int complexVideoCount = 0;
|
||||
public static int complexPictureCount = 0;
|
||||
//当传入已选中图片时,是否按照之前选中的顺序排序
|
||||
public static boolean isSequentialSelectedPhotos = true;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(value = {LIST_FIRST, BOTTOM_RIGHT})
|
||||
public @interface Location {
|
||||
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
minWidth = 1;
|
||||
minHeight = 1;
|
||||
minSize = 1;
|
||||
count = 1;
|
||||
|
||||
photosAdView = null;
|
||||
albumItemsAdView = null;
|
||||
photoAdIsOk = false;
|
||||
albumItemsAdIsOk = false;
|
||||
selectedPhotos.clear();
|
||||
showOriginalMenu = false;
|
||||
originalMenuUsable = false;
|
||||
originalMenuUnusableHint = "";
|
||||
selectedOriginal = false;
|
||||
cameraLocation = BOTTOM_RIGHT;
|
||||
isShowCamera = false;
|
||||
onlyStartCamera = false;
|
||||
showPuzzleMenu = true;
|
||||
filterTypes = new ArrayList<>();
|
||||
showGif = false;
|
||||
showVideo = false;
|
||||
showCleanMenu = true;
|
||||
videoMinSecond = 0L;
|
||||
videoMaxSecond = Long.MAX_VALUE;
|
||||
complexSelector = false;
|
||||
complexSingleType = false;
|
||||
complexVideoCount = 0;
|
||||
complexPictureCount = 0;
|
||||
isSequentialSelectedPhotos = true;
|
||||
}
|
||||
|
||||
public static boolean isFilter(String type) {
|
||||
type = type.toLowerCase();
|
||||
for (String filterType : Setting.filterTypes) {
|
||||
if (type.contains(filterType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isOnlyVideo() {
|
||||
return filterTypes.size() == 1 && filterTypes.get(0).equals(Type.VIDEO);
|
||||
}
|
||||
|
||||
public static boolean hasPhotosAd() {
|
||||
return photosAdView != null && photosAdView.get() != null;
|
||||
}
|
||||
|
||||
public static boolean hasAlbumItemsAd() {
|
||||
return albumItemsAdView != null && albumItemsAdView.get() != null;
|
||||
}
|
||||
|
||||
public static boolean isBottomRightCamera() {
|
||||
return cameraLocation == BOTTOM_RIGHT;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,503 @@
|
||||
package com.huantansheng.easyphotos.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.ScaleAnimation;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.PagerSnapHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.huantansheng.easyphotos.R;
|
||||
import com.huantansheng.easyphotos.constant.Code;
|
||||
import com.huantansheng.easyphotos.constant.Key;
|
||||
import com.huantansheng.easyphotos.constant.Type;
|
||||
import com.huantansheng.easyphotos.models.album.AlbumModel;
|
||||
import com.huantansheng.easyphotos.models.album.entity.Photo;
|
||||
import com.huantansheng.easyphotos.result.Result;
|
||||
import com.huantansheng.easyphotos.setting.Setting;
|
||||
import com.huantansheng.easyphotos.ui.adapter.PreviewPhotosAdapter;
|
||||
import com.huantansheng.easyphotos.ui.widget.PressedTextView;
|
||||
import com.huantansheng.easyphotos.utils.Color.ColorUtils;
|
||||
import com.huantansheng.easyphotos.utils.system.SystemUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* 预览页
|
||||
*/
|
||||
public class PreviewActivity extends AppCompatActivity implements PreviewPhotosAdapter.OnClickListener, View.OnClickListener, PreviewFragment.OnPreviewFragmentClickListener {
|
||||
|
||||
public static void start(Activity act, int albumItemIndex, int currIndex) {
|
||||
Intent intent = new Intent(act, PreviewActivity.class);
|
||||
intent.putExtra(Key.PREVIEW_ALBUM_ITEM_INDEX, albumItemIndex);
|
||||
intent.putExtra(Key.PREVIEW_PHOTO_INDEX, currIndex);
|
||||
act.startActivityForResult(intent, Code.REQUEST_PREVIEW_ACTIVITY);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 一些旧设备在UI小部件更新之间需要一个小延迟
|
||||
* and a change of the status and navigation bar.
|
||||
*/
|
||||
private static final int UI_ANIMATION_DELAY = 300;
|
||||
private final Handler mHideHandler = new Handler();
|
||||
private final Runnable mHidePart2Runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
SystemUtils.getInstance().systemUiHide(PreviewActivity.this, decorView);
|
||||
}
|
||||
};
|
||||
private RelativeLayout mBottomBar;
|
||||
private FrameLayout mToolBar;
|
||||
private final Runnable mShowPart2Runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// 延迟显示UI元素
|
||||
mBottomBar.setVisibility(View.VISIBLE);
|
||||
mToolBar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
};
|
||||
private boolean mVisible;
|
||||
View decorView;
|
||||
private TextView tvOriginal, tvNumber;
|
||||
private PressedTextView tvDone;
|
||||
private ImageView ivSelector;
|
||||
private RecyclerView rvPhotos;
|
||||
private PreviewPhotosAdapter adapter;
|
||||
private PagerSnapHelper snapHelper;
|
||||
private LinearLayoutManager lm;
|
||||
private int index;
|
||||
private ArrayList<Photo> photos = new ArrayList<>();
|
||||
private int resultCode = RESULT_CANCELED;
|
||||
private int lastPosition = 0;//记录recyclerView最后一次角标位置,用于判断是否转换了item
|
||||
private boolean isSingle = Setting.count == 1;
|
||||
private boolean unable = Result.count() == Setting.count;
|
||||
|
||||
private FrameLayout flFragment;
|
||||
private PreviewFragment previewFragment;
|
||||
private int statusColor;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
decorView = getWindow().getDecorView();
|
||||
SystemUtils.getInstance().systemUiInit(this, decorView);
|
||||
|
||||
setContentView(R.layout.activity_preview_easy_photos);
|
||||
|
||||
hideActionBar();
|
||||
adaptationStatusBar();
|
||||
if (null == AlbumModel.instance) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
initData();
|
||||
initView();
|
||||
}
|
||||
|
||||
private void adaptationStatusBar() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
statusColor = ContextCompat.getColor(this, R.color.easy_photos_status_bar);
|
||||
if (ColorUtils.isWhiteColor(statusColor)) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void hideActionBar() {
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.hide();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void initData() {
|
||||
Intent intent = getIntent();
|
||||
int albumItemIndex = intent.getIntExtra(Key.PREVIEW_ALBUM_ITEM_INDEX, 0);
|
||||
photos.clear();
|
||||
|
||||
if (albumItemIndex == -1) {
|
||||
photos.addAll(Result.photos);
|
||||
} else {
|
||||
photos.addAll(AlbumModel.instance.getCurrAlbumItemPhotos(albumItemIndex));
|
||||
}
|
||||
index = intent.getIntExtra(Key.PREVIEW_PHOTO_INDEX, 0);
|
||||
|
||||
lastPosition = index;
|
||||
mVisible = true;
|
||||
}
|
||||
|
||||
private void toggle() {
|
||||
if (mVisible) {
|
||||
hide();
|
||||
} else {
|
||||
show();
|
||||
}
|
||||
}
|
||||
|
||||
private void hide() {
|
||||
// Hide UI first
|
||||
AlphaAnimation hideAnimation = new AlphaAnimation(1.0f, 0.0f);
|
||||
hideAnimation.setAnimationListener(new Animation.AnimationListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animation animation) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
mBottomBar.setVisibility(View.GONE);
|
||||
mToolBar.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animation animation) {
|
||||
|
||||
}
|
||||
});
|
||||
hideAnimation.setDuration(UI_ANIMATION_DELAY);
|
||||
mBottomBar.startAnimation(hideAnimation);
|
||||
mToolBar.startAnimation(hideAnimation);
|
||||
mVisible = false;
|
||||
|
||||
// Schedule a runnable to remove the status and navigation bar after a delay
|
||||
mHideHandler.removeCallbacks(mShowPart2Runnable);
|
||||
|
||||
mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void show() {
|
||||
// Show the system bar
|
||||
if (Build.VERSION.SDK_INT >= 16) {
|
||||
SystemUtils.getInstance().systemUiShow(this, decorView);
|
||||
}
|
||||
|
||||
mVisible = true;
|
||||
|
||||
// Schedule a runnable to display UI elements after a delay
|
||||
mHideHandler.removeCallbacks(mHidePart2Runnable);
|
||||
mHideHandler.post(mShowPart2Runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPhotoClick() {
|
||||
toggle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPhotoScaleChanged() {
|
||||
if (mVisible) hide();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
doBack();
|
||||
}
|
||||
|
||||
private void doBack() {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(Key.PREVIEW_CLICK_DONE, false);
|
||||
setResult(resultCode, intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
setClick(R.id.iv_back, R.id.tv_edit, R.id.tv_selector);
|
||||
|
||||
mToolBar = (FrameLayout) findViewById(R.id.m_top_bar_layout);
|
||||
if (!SystemUtils.getInstance().hasNavigationBar(this)) {
|
||||
FrameLayout mRootView = (FrameLayout) findViewById(R.id.m_root_view);
|
||||
mRootView.setFitsSystemWindows(true);
|
||||
mToolBar.setPadding(0, SystemUtils.getInstance().getStatusBarHeight(this), 0, 0);
|
||||
if (ColorUtils.isWhiteColor(statusColor)) {
|
||||
SystemUtils.getInstance().setStatusDark(this, true);
|
||||
}
|
||||
}
|
||||
mBottomBar = (RelativeLayout) findViewById(R.id.m_bottom_bar);
|
||||
ivSelector = (ImageView) findViewById(R.id.iv_selector);
|
||||
tvNumber = (TextView) findViewById(R.id.tv_number);
|
||||
tvDone = (PressedTextView) findViewById(R.id.tv_done);
|
||||
tvOriginal = (TextView) findViewById(R.id.tv_original);
|
||||
flFragment = (FrameLayout) findViewById(R.id.fl_fragment);
|
||||
previewFragment =
|
||||
(PreviewFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_preview);
|
||||
if (Setting.showOriginalMenu) {
|
||||
processOriginalMenu();
|
||||
} else {
|
||||
tvOriginal.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
setClick(tvOriginal, tvDone, ivSelector);
|
||||
|
||||
initRecyclerView();
|
||||
shouldShowMenuDone();
|
||||
}
|
||||
|
||||
private void initRecyclerView() {
|
||||
rvPhotos = (RecyclerView) findViewById(R.id.rv_photos);
|
||||
adapter = new PreviewPhotosAdapter(this, photos, this);
|
||||
lm = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
|
||||
rvPhotos.setLayoutManager(lm);
|
||||
rvPhotos.setAdapter(adapter);
|
||||
rvPhotos.scrollToPosition(index);
|
||||
toggleSelector();
|
||||
snapHelper = new PagerSnapHelper();
|
||||
snapHelper.attachToRecyclerView(rvPhotos);
|
||||
rvPhotos.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||
super.onScrollStateChanged(recyclerView, newState);
|
||||
|
||||
View view = snapHelper.findSnapView(lm);
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
int position = lm.getPosition(view);
|
||||
if (lastPosition == position) {
|
||||
return;
|
||||
}
|
||||
lastPosition = position;
|
||||
previewFragment.setSelectedPosition(-1);
|
||||
tvNumber.setText(getString(R.string.preview_current_number_easy_photos,
|
||||
lastPosition + 1, photos.size()));
|
||||
toggleSelector();
|
||||
}
|
||||
});
|
||||
tvNumber.setText(getString(R.string.preview_current_number_easy_photos, index + 1,
|
||||
photos.size()));
|
||||
}
|
||||
|
||||
private boolean clickDone = false;
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int id = v.getId();
|
||||
if (R.id.iv_back == id) {
|
||||
doBack();
|
||||
} else if (R.id.tv_selector == id) {
|
||||
updateSelector();
|
||||
} else if (R.id.iv_selector == id) {
|
||||
updateSelector();
|
||||
} else if (R.id.tv_original == id) {
|
||||
if (!Setting.originalMenuUsable) {
|
||||
Toast.makeText(getApplicationContext(), Setting.originalMenuUnusableHint, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
Setting.selectedOriginal = !Setting.selectedOriginal;
|
||||
processOriginalMenu();
|
||||
} else if (R.id.tv_done == id) {
|
||||
if (clickDone) return;
|
||||
clickDone = true;
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(Key.PREVIEW_CLICK_DONE, true);
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
}
|
||||
// else if (R.id.m_bottom_bar == id) {
|
||||
//
|
||||
// } else if (R.id.tv_edit == id) {
|
||||
//
|
||||
// }
|
||||
}
|
||||
|
||||
private void processOriginalMenu() {
|
||||
if (Setting.selectedOriginal) {
|
||||
tvOriginal.setTextColor(ContextCompat.getColor(this, R.color.easy_photos_fg_accent));
|
||||
} else {
|
||||
if (Setting.originalMenuUsable) {
|
||||
tvOriginal.setTextColor(ContextCompat.getColor(this,
|
||||
R.color.easy_photos_fg_primary));
|
||||
} else {
|
||||
tvOriginal.setTextColor(ContextCompat.getColor(this,
|
||||
R.color.easy_photos_fg_primary_dark));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleSelector() {
|
||||
if (photos.get(lastPosition).selected) {
|
||||
ivSelector.setImageResource(R.drawable.ic_selector_true_easy_photos);
|
||||
if (!Result.isEmpty()) {
|
||||
int count = Result.count();
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (photos.get(lastPosition).path.equals(Result.getPhotoPath(i))) {
|
||||
previewFragment.setSelectedPosition(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ivSelector.setImageResource(R.drawable.ic_selector_easy_photos);
|
||||
}
|
||||
previewFragment.notifyDataSetChanged();
|
||||
shouldShowMenuDone();
|
||||
}
|
||||
|
||||
private void updateSelector() {
|
||||
resultCode = RESULT_OK;
|
||||
Photo item = photos.get(lastPosition);
|
||||
if (isSingle) {
|
||||
singleSelector(item);
|
||||
return;
|
||||
}
|
||||
if (unable) {
|
||||
if (item.selected) {
|
||||
Result.removePhoto(item);
|
||||
if (unable) {
|
||||
unable = false;
|
||||
}
|
||||
toggleSelector();
|
||||
return;
|
||||
}
|
||||
if (Setting.isOnlyVideo()) {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.selector_reach_max_video_hint_easy_photos
|
||||
, Setting.count), Toast.LENGTH_SHORT).show();
|
||||
|
||||
} else if (Setting.showVideo) {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.selector_reach_max_hint_easy_photos,
|
||||
Setting.count), Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.selector_reach_max_image_hint_easy_photos,
|
||||
Setting.count), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
item.selected = !item.selected;
|
||||
if (item.selected) {
|
||||
int res = Result.addPhoto(item);
|
||||
if (res != 0) {
|
||||
item.selected = false;
|
||||
switch (res) {
|
||||
case Result.PICTURE_OUT:
|
||||
Toast.makeText(getApplicationContext(),
|
||||
getString(R.string.selector_reach_max_image_hint_easy_photos,
|
||||
Setting.complexPictureCount), Toast.LENGTH_SHORT).show();
|
||||
break;
|
||||
case Result.VIDEO_OUT:
|
||||
Toast.makeText(getApplicationContext(),
|
||||
getString(R.string.selector_reach_max_video_hint_easy_photos,
|
||||
Setting.complexVideoCount), Toast.LENGTH_SHORT).show();
|
||||
break;
|
||||
case Result.SINGLE_TYPE:
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.selector_single_type_hint_easy_photos), Toast.LENGTH_SHORT).show();
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (Result.count() == Setting.count) {
|
||||
unable = true;
|
||||
}
|
||||
} else {
|
||||
Result.removePhoto(item);
|
||||
previewFragment.setSelectedPosition(-1);
|
||||
if (unable) {
|
||||
unable = false;
|
||||
}
|
||||
}
|
||||
toggleSelector();
|
||||
}
|
||||
|
||||
private void singleSelector(Photo photo) {
|
||||
if (!Result.isEmpty()) {
|
||||
if (Result.getPhotoPath(0).equals(photo.path)) {
|
||||
Result.removePhoto(photo);
|
||||
} else {
|
||||
Result.removePhoto(0);
|
||||
Result.addPhoto(photo);
|
||||
}
|
||||
} else {
|
||||
Result.addPhoto(photo);
|
||||
}
|
||||
toggleSelector();
|
||||
}
|
||||
|
||||
private void shouldShowMenuDone() {
|
||||
if (Result.isEmpty()) {
|
||||
if (View.VISIBLE == tvDone.getVisibility()) {
|
||||
ScaleAnimation scaleHide = new ScaleAnimation(1f, 0f, 1f, 0f);
|
||||
scaleHide.setDuration(200);
|
||||
tvDone.startAnimation(scaleHide);
|
||||
}
|
||||
tvDone.setVisibility(View.GONE);
|
||||
flFragment.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (View.GONE == tvDone.getVisibility()) {
|
||||
ScaleAnimation scaleShow = new ScaleAnimation(0f, 1f, 0f, 1f);
|
||||
scaleShow.setDuration(200);
|
||||
tvDone.startAnimation(scaleShow);
|
||||
}
|
||||
flFragment.setVisibility(View.VISIBLE);
|
||||
tvDone.setVisibility(View.VISIBLE);
|
||||
|
||||
if (Result.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Setting.complexSelector) {
|
||||
if (Setting.complexSingleType) {
|
||||
if (Result.getPhotoType(0).contains(Type.VIDEO)) {
|
||||
tvDone.setText(getString(R.string.selector_action_done_easy_photos, Result.count(),
|
||||
Setting.complexVideoCount));
|
||||
return;
|
||||
}
|
||||
tvDone.setText(getString(R.string.selector_action_done_easy_photos, Result.count(),
|
||||
Setting.complexPictureCount));
|
||||
return;
|
||||
}
|
||||
}
|
||||
tvDone.setText(getString(R.string.selector_action_done_easy_photos, Result.count(),
|
||||
Setting.count));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreviewPhotoClick(int position) {
|
||||
String path = Result.getPhotoPath(position);
|
||||
int size = photos.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (TextUtils.equals(path, photos.get(i).path)) {
|
||||
rvPhotos.scrollToPosition(i);
|
||||
lastPosition = i;
|
||||
tvNumber.setText(getString(R.string.preview_current_number_easy_photos,
|
||||
lastPosition + 1, photos.size()));
|
||||
previewFragment.setSelectedPosition(position);
|
||||
toggleSelector();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setClick(@IdRes int... ids) {
|
||||
for (int id : ids) {
|
||||
findViewById(id).setOnClickListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void setClick(View... views) {
|
||||
for (View v : views) {
|
||||
v.setOnClickListener(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.huantansheng.easyphotos.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.huantansheng.easyphotos.R;
|
||||
import com.huantansheng.easyphotos.ui.adapter.PreviewPhotosFragmentAdapter;
|
||||
|
||||
|
||||
public class PreviewFragment extends Fragment implements PreviewPhotosFragmentAdapter.OnClickListener {
|
||||
|
||||
private OnPreviewFragmentClickListener mListener;
|
||||
|
||||
private RecyclerView rvPhotos;
|
||||
private PreviewPhotosFragmentAdapter adapter;
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_preview_easy_photos, container, false);
|
||||
rvPhotos = (RecyclerView) rootView.findViewById(R.id.rv_preview_selected_photos);
|
||||
adapter = new PreviewPhotosFragmentAdapter(getActivity(), this);
|
||||
rvPhotos.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false));
|
||||
rvPhotos.setAdapter(adapter);
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
if (context instanceof OnPreviewFragmentClickListener) {
|
||||
mListener = (OnPreviewFragmentClickListener) context;
|
||||
} else {
|
||||
throw new RuntimeException(context.toString()
|
||||
+ " must implement OnPreviewFragmentClickListener");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPhotoClick(int position) {
|
||||
mListener.onPreviewPhotoClick(position);
|
||||
}
|
||||
|
||||
|
||||
public interface OnPreviewFragmentClickListener {
|
||||
void onPreviewPhotoClick(int position);
|
||||
}
|
||||
|
||||
public void notifyDataSetChanged() {
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setSelectedPosition(int position) {
|
||||
adapter.setChecked(position);
|
||||
if (position != -1) {
|
||||
rvPhotos.smoothScrollToPosition(position);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,668 @@
|
||||
package com.huantansheng.easyphotos.ui;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.huantansheng.easyphotos.EasyPhotos;
|
||||
import com.huantansheng.easyphotos.R;
|
||||
import com.huantansheng.easyphotos.constant.Code;
|
||||
import com.huantansheng.easyphotos.constant.Key;
|
||||
import com.huantansheng.easyphotos.engine.ImageEngine;
|
||||
import com.huantansheng.easyphotos.models.album.entity.Photo;
|
||||
import com.huantansheng.easyphotos.models.puzzle.Area;
|
||||
import com.huantansheng.easyphotos.models.puzzle.DegreeSeekBar;
|
||||
import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout;
|
||||
import com.huantansheng.easyphotos.models.puzzle.PuzzlePiece;
|
||||
import com.huantansheng.easyphotos.models.puzzle.PuzzleUtils;
|
||||
import com.huantansheng.easyphotos.models.puzzle.PuzzleView;
|
||||
import com.huantansheng.easyphotos.models.sticker.StickerModel;
|
||||
import com.huantansheng.easyphotos.setting.Setting;
|
||||
import com.huantansheng.easyphotos.ui.adapter.PuzzleAdapter;
|
||||
import com.huantansheng.easyphotos.ui.adapter.TextStickerAdapter;
|
||||
import com.huantansheng.easyphotos.utils.bitmap.SaveBitmapCallBack;
|
||||
import com.huantansheng.easyphotos.utils.media.DurationUtils;
|
||||
import com.huantansheng.easyphotos.utils.permission.PermissionUtil;
|
||||
import com.huantansheng.easyphotos.utils.settings.SettingsUtils;
|
||||
import com.huantansheng.easyphotos.utils.uri.UriUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 拼图界面
|
||||
* Created by huan on 2017/12/4.
|
||||
*/
|
||||
|
||||
public class PuzzleActivity extends AppCompatActivity implements View.OnClickListener,
|
||||
PuzzleAdapter.OnItemClickListener, TextStickerAdapter.OnItemClickListener {
|
||||
|
||||
private static WeakReference<Class<? extends Activity>> toClass;
|
||||
|
||||
|
||||
public static void startWithPhotos(Activity act, ArrayList<Photo> photos,
|
||||
String puzzleSaveDirPath, String puzzleSaveNamePrefix,
|
||||
int requestCode, boolean replaceCustom,
|
||||
@NonNull ImageEngine imageEngine) {
|
||||
if (null != toClass) {
|
||||
toClass.clear();
|
||||
toClass = null;
|
||||
}
|
||||
if (Setting.imageEngine != imageEngine) {
|
||||
Setting.imageEngine = imageEngine;
|
||||
}
|
||||
Intent intent = new Intent(act, PuzzleActivity.class);
|
||||
intent.putExtra(Key.PUZZLE_FILE_IS_PHOTO, true);
|
||||
intent.putParcelableArrayListExtra(Key.PUZZLE_FILES, photos);
|
||||
intent.putExtra(Key.PUZZLE_SAVE_DIR, puzzleSaveDirPath);
|
||||
intent.putExtra(Key.PUZZLE_SAVE_NAME_PREFIX, puzzleSaveNamePrefix);
|
||||
if (replaceCustom) {
|
||||
toClass = new WeakReference<Class<? extends Activity>>(act.getClass());
|
||||
}
|
||||
act.startActivityForResult(intent, requestCode);
|
||||
}
|
||||
|
||||
public static void startWithPhotos(Fragment fragment, ArrayList<Photo> photos,
|
||||
String puzzleSaveDirPath, String puzzleSaveNamePrefix,
|
||||
int requestCode, boolean replaceCustom,
|
||||
@NonNull ImageEngine imageEngine) {
|
||||
if (null != toClass) {
|
||||
toClass.clear();
|
||||
toClass = null;
|
||||
}
|
||||
if (Setting.imageEngine != imageEngine) {
|
||||
Setting.imageEngine = imageEngine;
|
||||
}
|
||||
Intent intent = new Intent(fragment.getActivity(), PuzzleActivity.class);
|
||||
intent.putExtra(Key.PUZZLE_FILE_IS_PHOTO, true);
|
||||
intent.putParcelableArrayListExtra(Key.PUZZLE_FILES, photos);
|
||||
intent.putExtra(Key.PUZZLE_SAVE_DIR, puzzleSaveDirPath);
|
||||
intent.putExtra(Key.PUZZLE_SAVE_NAME_PREFIX, puzzleSaveNamePrefix);
|
||||
if (replaceCustom) {
|
||||
toClass =
|
||||
new WeakReference<Class<? extends Activity>>(fragment.getActivity().getClass());
|
||||
}
|
||||
fragment.startActivityForResult(intent, requestCode);
|
||||
}
|
||||
|
||||
public static void startWithPhotos(androidx.fragment.app.Fragment fragmentV,
|
||||
ArrayList<Photo> photos, String puzzleSaveDirPath,
|
||||
String puzzleSaveNamePrefix, int requestCode,
|
||||
boolean replaceCustom, @NonNull ImageEngine imageEngine) {
|
||||
if (null != toClass) {
|
||||
toClass.clear();
|
||||
toClass = null;
|
||||
}
|
||||
if (Setting.imageEngine != imageEngine) {
|
||||
Setting.imageEngine = imageEngine;
|
||||
}
|
||||
Intent intent = new Intent(fragmentV.getActivity(), PuzzleActivity.class);
|
||||
intent.putExtra(Key.PUZZLE_FILE_IS_PHOTO, true);
|
||||
intent.putParcelableArrayListExtra(Key.PUZZLE_FILES, photos);
|
||||
intent.putExtra(Key.PUZZLE_SAVE_DIR, puzzleSaveDirPath);
|
||||
intent.putExtra(Key.PUZZLE_SAVE_NAME_PREFIX, puzzleSaveNamePrefix);
|
||||
if (replaceCustom) {
|
||||
if (fragmentV.getActivity() != null) {
|
||||
toClass =
|
||||
new WeakReference<Class<? extends Activity>>(fragmentV.getActivity().getClass());
|
||||
}
|
||||
}
|
||||
fragmentV.startActivityForResult(intent, requestCode);
|
||||
}
|
||||
|
||||
ArrayList<Photo> photos = null;
|
||||
|
||||
ArrayList<Bitmap> bitmaps = new ArrayList<>();
|
||||
String saveDirPath, saveNamePrefix;
|
||||
|
||||
private PuzzleView puzzleView;
|
||||
private RecyclerView rvPuzzleTemplet;
|
||||
private PuzzleAdapter puzzleAdapter;
|
||||
private ProgressBar progressBar;
|
||||
private int fileCount = 0;
|
||||
|
||||
private LinearLayout llMenu;
|
||||
private DegreeSeekBar degreeSeekBar;
|
||||
private ArrayList<ImageView> ivMenus = new ArrayList<>();
|
||||
|
||||
private ArrayList<Integer> degrees = new ArrayList<>();
|
||||
private int degreeIndex = -1;
|
||||
private int controlFlag;
|
||||
private static final int FLAG_CONTROL_PADDING = 0;
|
||||
private static final int FLAG_CONTROL_CORNER = 1;
|
||||
private static final int FLAG_CONTROL_ROTATE = 2;
|
||||
|
||||
private int deviceWidth = 0;
|
||||
private int deviceHeight = 0;
|
||||
|
||||
private TextView tvTemplate, tvTextSticker;
|
||||
private RelativeLayout mRootView, mBottomLayout;
|
||||
private TextStickerAdapter textStickerAdapter;
|
||||
|
||||
private StickerModel stickerModel;
|
||||
|
||||
FloatingActionButton fab;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
WindowManager.LayoutParams attrs = getWindow().getAttributes();
|
||||
attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
|
||||
getWindow().setAttributes(attrs);
|
||||
setContentView(R.layout.activity_puzzle_easy_photos);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.hide();
|
||||
}
|
||||
if (null == Setting.imageEngine) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
initData();
|
||||
initView();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
initIvMenu();
|
||||
initPuzzleView();
|
||||
initRecyclerView();
|
||||
progressBar = findViewById(R.id.progress);
|
||||
setClick(R.id.tv_back, R.id.tv_done);
|
||||
}
|
||||
|
||||
private void initIvMenu() {
|
||||
fab = (FloatingActionButton) findViewById(R.id.fab);
|
||||
|
||||
tvTemplate = (TextView) findViewById(R.id.tv_template);
|
||||
tvTextSticker = (TextView) findViewById(R.id.tv_text_sticker);
|
||||
mRootView = (RelativeLayout) findViewById(R.id.m_root_view);
|
||||
mBottomLayout = (RelativeLayout) findViewById(R.id.m_bottom_layout);
|
||||
|
||||
llMenu = (LinearLayout) findViewById(R.id.ll_menu);
|
||||
ImageView ivRotate = (ImageView) findViewById(R.id.iv_rotate);
|
||||
ImageView ivCorner = (ImageView) findViewById(R.id.iv_corner);
|
||||
ImageView ivPadding = (ImageView) findViewById(R.id.iv_padding);
|
||||
|
||||
setClick(R.id.iv_replace, R.id.iv_mirror, R.id.iv_flip);
|
||||
setClick(ivRotate, ivCorner, ivPadding, fab, tvTextSticker, tvTemplate);
|
||||
|
||||
ivMenus.add(ivRotate);
|
||||
ivMenus.add(ivCorner);
|
||||
ivMenus.add(ivPadding);
|
||||
|
||||
degreeSeekBar = (DegreeSeekBar) findViewById(R.id.degree_seek_bar);
|
||||
degreeSeekBar.setScrollingListener(new DegreeSeekBar.ScrollingListener() {
|
||||
@Override
|
||||
public void onScrollStart() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScroll(int currentDegrees) {
|
||||
switch (controlFlag) {
|
||||
case FLAG_CONTROL_PADDING:
|
||||
puzzleView.setPiecePadding(currentDegrees);
|
||||
break;
|
||||
case FLAG_CONTROL_CORNER:
|
||||
if (currentDegrees < 0) {
|
||||
currentDegrees = 0;
|
||||
}
|
||||
puzzleView.setPieceRadian(currentDegrees);
|
||||
break;
|
||||
case FLAG_CONTROL_ROTATE:
|
||||
puzzleView.rotate(currentDegrees - degrees.get(degreeIndex));
|
||||
degrees.remove(degreeIndex);
|
||||
degrees.add(degreeIndex, currentDegrees);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollEnd() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initRecyclerView() {
|
||||
rvPuzzleTemplet = (RecyclerView) findViewById(R.id.rv_puzzle_template);
|
||||
puzzleAdapter = new PuzzleAdapter();
|
||||
puzzleAdapter.setOnItemClickListener(this);
|
||||
rvPuzzleTemplet.setLayoutManager(new LinearLayoutManager(this,
|
||||
LinearLayoutManager.HORIZONTAL, false));
|
||||
rvPuzzleTemplet.setAdapter(puzzleAdapter);
|
||||
puzzleAdapter.refreshData(PuzzleUtils.getPuzzleLayouts(fileCount));
|
||||
|
||||
textStickerAdapter = new TextStickerAdapter(this, this);
|
||||
}
|
||||
|
||||
private void initPuzzleView() {
|
||||
int themeType = fileCount > 3 ? 1 : 0;
|
||||
puzzleView = (PuzzleView) findViewById(R.id.puzzle_view);
|
||||
puzzleView.setPuzzleLayout(PuzzleUtils.getPuzzleLayout(themeType, fileCount, 0));
|
||||
puzzleView.setOnPieceSelectedListener(new PuzzleView.OnPieceSelectedListener() {
|
||||
@Override
|
||||
public void onPieceSelected(PuzzlePiece piece, int position) {
|
||||
|
||||
if (null == piece) {
|
||||
toggleIvMenu(R.id.iv_replace);
|
||||
llMenu.setVisibility(View.GONE);
|
||||
degreeSeekBar.setVisibility(View.GONE);
|
||||
degreeIndex = -1;
|
||||
controlFlag = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (degreeIndex != position) {
|
||||
controlFlag = -1;
|
||||
toggleIvMenu(R.id.iv_replace);
|
||||
degreeSeekBar.setVisibility(View.GONE);
|
||||
}
|
||||
llMenu.setVisibility(View.VISIBLE);
|
||||
degreeIndex = position;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadPhoto() {
|
||||
puzzleView.addPieces(bitmaps);
|
||||
}
|
||||
|
||||
private void initData() {
|
||||
stickerModel = new StickerModel();
|
||||
deviceWidth = getResources().getDisplayMetrics().widthPixels;
|
||||
deviceHeight = getResources().getDisplayMetrics().heightPixels;
|
||||
Intent intent = getIntent();
|
||||
saveDirPath = intent.getStringExtra(Key.PUZZLE_SAVE_DIR);
|
||||
saveNamePrefix = intent.getStringExtra(Key.PUZZLE_SAVE_NAME_PREFIX);
|
||||
|
||||
photos = intent.getParcelableArrayListExtra(Key.PUZZLE_FILES);
|
||||
fileCount = photos.size() > 9 ? 9 : photos.size();
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i = 0; i < fileCount; i++) {
|
||||
Bitmap bitmap = getScaleBitmap(photos.get(i).path, photos.get(i).uri);
|
||||
bitmaps.add(bitmap);
|
||||
degrees.add(0);
|
||||
}
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
puzzleView.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
loadPhoto();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}).start();
|
||||
|
||||
|
||||
}
|
||||
|
||||
private Bitmap getScaleBitmap(String path, Uri uri) {
|
||||
Bitmap bitmap = null;
|
||||
|
||||
try {
|
||||
bitmap = Setting.imageEngine.getCacheBitmap(this, uri, deviceWidth / 2,
|
||||
deviceHeight / 2);
|
||||
} catch (Exception e) {
|
||||
bitmap = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(path),
|
||||
deviceWidth / 2, deviceHeight / 2, true);
|
||||
}
|
||||
|
||||
if (bitmap == null) {
|
||||
bitmap = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(path),
|
||||
deviceWidth / 2, deviceHeight / 2, true);
|
||||
}
|
||||
return bitmap;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
|
||||
int id = view.getId();
|
||||
|
||||
if (R.id.tv_back == id) {
|
||||
finish();
|
||||
} else if (R.id.tv_done == id) {
|
||||
if (PermissionUtil.checkAndRequestPermissionsInActivity(this, getNeedPermissions())) {
|
||||
savePhoto();
|
||||
}
|
||||
|
||||
} else if (R.id.iv_replace == id) {
|
||||
controlFlag = -1;
|
||||
degreeSeekBar.setVisibility(View.GONE);
|
||||
toggleIvMenu(R.id.iv_replace);
|
||||
if (null == toClass) {
|
||||
EasyPhotos.createAlbum(this, true, false, Setting.imageEngine).setCount(1).start(91);
|
||||
} else {
|
||||
Intent intent = new Intent(this, toClass.get());
|
||||
startActivityForResult(intent, 91);
|
||||
}
|
||||
} else if (R.id.iv_rotate == id) {
|
||||
if (controlFlag == FLAG_CONTROL_ROTATE) {
|
||||
if (degrees.get(degreeIndex) % 90 != 0) {
|
||||
puzzleView.rotate(-degrees.get(degreeIndex));
|
||||
degrees.remove(degreeIndex);
|
||||
degrees.add(degreeIndex, 0);
|
||||
degreeSeekBar.setCurrentDegrees(0);
|
||||
return;
|
||||
}
|
||||
puzzleView.rotate(90);
|
||||
int degree = degrees.get(degreeIndex) + 90;
|
||||
if (degree == 360 || degree == -360) {
|
||||
degree = 0;
|
||||
}
|
||||
degrees.remove(degreeIndex);
|
||||
degrees.add(degreeIndex, degree);
|
||||
degreeSeekBar.setCurrentDegrees(degrees.get(degreeIndex));
|
||||
return;
|
||||
}
|
||||
handleSeekBar(FLAG_CONTROL_ROTATE, -360, 360, degrees.get(degreeIndex));
|
||||
toggleIvMenu(R.id.iv_rotate);
|
||||
} else if (R.id.iv_mirror == id) {
|
||||
degreeSeekBar.setVisibility(View.GONE);
|
||||
controlFlag = -1;
|
||||
toggleIvMenu(R.id.iv_mirror);
|
||||
puzzleView.flipHorizontally();
|
||||
} else if (R.id.iv_flip == id) {
|
||||
controlFlag = -1;
|
||||
degreeSeekBar.setVisibility(View.GONE);
|
||||
toggleIvMenu(R.id.iv_flip);
|
||||
puzzleView.flipVertically();
|
||||
} else if (R.id.iv_corner == id) {
|
||||
handleSeekBar(FLAG_CONTROL_CORNER, 0, 1000, puzzleView.getPieceRadian());
|
||||
toggleIvMenu(R.id.iv_corner);
|
||||
} else if (R.id.iv_padding == id) {
|
||||
handleSeekBar(FLAG_CONTROL_PADDING, 0, 100, puzzleView.getPiecePadding());
|
||||
toggleIvMenu(R.id.iv_padding);
|
||||
} else if (R.id.tv_template == id) {
|
||||
tvTemplate.setTextColor(ContextCompat.getColor(this, R.color.easy_photos_fg_accent));
|
||||
tvTextSticker.setTextColor(ContextCompat.getColor(this,
|
||||
R.color.easy_photos_fg_primary));
|
||||
|
||||
rvPuzzleTemplet.setAdapter(puzzleAdapter);
|
||||
|
||||
} else if (R.id.tv_text_sticker == id) {
|
||||
tvTextSticker.setTextColor(ContextCompat.getColor(this, R.color.easy_photos_fg_accent));
|
||||
tvTemplate.setTextColor(ContextCompat.getColor(this, R.color.easy_photos_fg_primary));
|
||||
|
||||
rvPuzzleTemplet.setAdapter(textStickerAdapter);
|
||||
} else if (R.id.fab == id) {
|
||||
processBottomLayout();
|
||||
}
|
||||
}
|
||||
|
||||
private void processBottomLayout() {
|
||||
if (View.VISIBLE == mBottomLayout.getVisibility()) {
|
||||
mBottomLayout.setVisibility(View.GONE);
|
||||
fab.setImageResource(R.drawable.ic_arrow_up_easy_photos);
|
||||
} else {
|
||||
mBottomLayout.setVisibility(View.VISIBLE);
|
||||
fab.setImageResource(R.drawable.ic_arrow_down_easy_photos);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSeekBar(int controlFlag, int rangeStart, int rangeEnd, float degrees) {
|
||||
this.controlFlag = controlFlag;
|
||||
degreeSeekBar.setVisibility(View.VISIBLE);
|
||||
degreeSeekBar.setDegreeRange(rangeStart, rangeEnd);
|
||||
degreeSeekBar.setCurrentDegrees((int) degrees);
|
||||
}
|
||||
|
||||
private void savePhoto() {
|
||||
mBottomLayout.setVisibility(View.GONE);
|
||||
fab.setVisibility(View.GONE);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
findViewById(R.id.tv_done).setVisibility(View.INVISIBLE);
|
||||
findViewById(R.id.progress_frame).setVisibility(View.VISIBLE);
|
||||
|
||||
puzzleView.clearHandling();
|
||||
puzzleView.invalidate();
|
||||
|
||||
stickerModel.save(this, mRootView, puzzleView, puzzleView.getWidth(),
|
||||
puzzleView.getHeight(), saveDirPath, saveNamePrefix, true,
|
||||
new SaveBitmapCallBack() {
|
||||
@Override
|
||||
public void onSuccess(File file) {
|
||||
Intent intent = new Intent();
|
||||
|
||||
Photo photo = new Photo(file.getName(), UriUtils.getUri(PuzzleActivity.this,
|
||||
file), file.getAbsolutePath(), file.lastModified() / 1000,
|
||||
puzzleView.getWidth(), puzzleView.getHeight(), 0, file.length(),
|
||||
DurationUtils.getDuration(file.getAbsolutePath()), "image/png");
|
||||
intent.putExtra(EasyPhotos.RESULT_PHOTOS, photo);
|
||||
setResult(RESULT_OK, intent);
|
||||
PuzzleActivity.this.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIOFailed(IOException exception) {
|
||||
exception.printStackTrace();
|
||||
setResult(RESULT_OK);
|
||||
PuzzleActivity.this.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateDirFailed() {
|
||||
setResult(RESULT_OK);
|
||||
PuzzleActivity.this.finish();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private void toggleIvMenu(@IdRes int resId) {
|
||||
int size = ivMenus.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
ImageView ivMenu = ivMenus.get(i);
|
||||
if (ivMenu.getId() == resId) {
|
||||
ivMenu.setColorFilter(ContextCompat.getColor(this, R.color.easy_photos_fg_accent));
|
||||
} else {
|
||||
ivMenu.clearColorFilter();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(int themeType, int themeId) {
|
||||
puzzleView.setPuzzleLayout(PuzzleUtils.getPuzzleLayout(themeType, fileCount, themeId));
|
||||
loadPhoto();
|
||||
resetDegress();
|
||||
|
||||
}
|
||||
|
||||
private void resetDegress() {
|
||||
llMenu.setVisibility(View.GONE);
|
||||
degreeSeekBar.setVisibility(View.GONE);
|
||||
degreeIndex = -1;
|
||||
int size = degrees.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
degrees.remove(i);
|
||||
degrees.add(i, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (null != toClass) {
|
||||
toClass.clear();
|
||||
toClass = null;
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == Code.REQUEST_SETTING_APP_DETAILS) {
|
||||
if (PermissionUtil.checkAndRequestPermissionsInActivity(this, getNeedPermissions())) {
|
||||
savePhoto();
|
||||
}
|
||||
return;
|
||||
}
|
||||
switch (resultCode) {
|
||||
case RESULT_OK:
|
||||
if (degreeIndex != -1) {
|
||||
degrees.remove(degreeIndex);
|
||||
degrees.add(degreeIndex, 0);
|
||||
}
|
||||
|
||||
String tempPath = "";
|
||||
Uri tempUri = null;
|
||||
|
||||
ArrayList<Photo> photos =
|
||||
data.getParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS);
|
||||
Photo photo = photos.get(0);
|
||||
tempPath = photo.path;
|
||||
tempUri = photo.uri;
|
||||
|
||||
|
||||
final String path = tempPath;
|
||||
final Uri uri = tempUri;
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final Bitmap bitmap = getScaleBitmap(path, uri);
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
puzzleView.replace(bitmap);
|
||||
}
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
|
||||
break;
|
||||
case RESULT_CANCELED:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected String[] getNeedPermissions() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
return new String[]{Manifest.permission.CAMERA,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE};
|
||||
}
|
||||
return new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
|
||||
@NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
PermissionUtil.onPermissionResult(this, permissions, grantResults,
|
||||
new PermissionUtil.PermissionCallBack() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
savePhoto();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShouldShow() {
|
||||
Snackbar.make(rvPuzzleTemplet, R.string.permissions_again_easy_photos,
|
||||
Snackbar.LENGTH_INDEFINITE).setAction("go",
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (PermissionUtil.checkAndRequestPermissionsInActivity(PuzzleActivity.this, getNeedPermissions())) {
|
||||
savePhoto();
|
||||
}
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailed() {
|
||||
Snackbar.make(rvPuzzleTemplet, R.string.permissions_die_easy_photos,
|
||||
Snackbar.LENGTH_INDEFINITE).setAction("go",
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
SettingsUtils.startMyApplicationDetailsForResult(PuzzleActivity.this,
|
||||
getPackageName());
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(String stickerValue) {
|
||||
if (stickerValue.equals("-1")) {
|
||||
|
||||
PuzzleLayout puzzleLayout = puzzleView.getPuzzleLayout();
|
||||
int size = puzzleLayout.getAreaCount();
|
||||
for (int i = 0; i < size; i++) {
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
||||
String date = format.format(photos.get(i).time);
|
||||
stickerModel.addTextSticker(this, getSupportFragmentManager(), date, mRootView);
|
||||
stickerModel.currTextSticker.isChecked = true;
|
||||
Area area = puzzleLayout.getArea(i);
|
||||
stickerModel.currTextSticker.moveTo(area.centerX(), area.centerY());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
stickerModel.addTextSticker(this, getSupportFragmentManager(), stickerValue, mRootView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (View.VISIBLE == mBottomLayout.getVisibility()) {
|
||||
processBottomLayout();
|
||||
return;
|
||||
}
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
private void setClick(@IdRes int... ids) {
|
||||
for (int id : ids) {
|
||||
findViewById(id).setOnClickListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void setClick(View... views) {
|
||||
for (View v : views) {
|
||||
v.setOnClickListener(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
package com.huantansheng.easyphotos.ui;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator;
|
||||
import android.view.View;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.view.animation.AccelerateInterpolator;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.huantansheng.easyphotos.R;
|
||||
import com.huantansheng.easyphotos.constant.Code;
|
||||
import com.huantansheng.easyphotos.models.album.AlbumModel;
|
||||
import com.huantansheng.easyphotos.models.album.entity.Photo;
|
||||
import com.huantansheng.easyphotos.setting.Setting;
|
||||
import com.huantansheng.easyphotos.ui.adapter.AlbumItemsAdapter;
|
||||
import com.huantansheng.easyphotos.ui.adapter.PuzzleSelectorAdapter;
|
||||
import com.huantansheng.easyphotos.ui.adapter.PuzzleSelectorPreviewAdapter;
|
||||
import com.huantansheng.easyphotos.ui.widget.PressedTextView;
|
||||
import com.huantansheng.easyphotos.utils.Color.ColorUtils;
|
||||
import com.huantansheng.easyphotos.utils.system.SystemUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class PuzzleSelectorActivity extends AppCompatActivity implements View.OnClickListener, AlbumItemsAdapter.OnClickListener, PuzzleSelectorAdapter.OnClickListener, PuzzleSelectorPreviewAdapter.OnClickListener {
|
||||
|
||||
public static void start(Activity activity) {
|
||||
Intent intent = new Intent(activity, PuzzleSelectorActivity.class);
|
||||
activity.startActivityForResult(intent, Code.REQUEST_PUZZLE_SELECTOR);
|
||||
}
|
||||
|
||||
private AlbumModel albumModel;
|
||||
|
||||
private AnimatorSet setShow;
|
||||
private AnimatorSet setHide;
|
||||
|
||||
private RelativeLayout rootViewAlbumItems, rootSelectorView;
|
||||
private RecyclerView rvAlbumItems;
|
||||
private AlbumItemsAdapter albumItemsAdapter;
|
||||
private PressedTextView tvAlbumItems;
|
||||
|
||||
private ArrayList<Photo> photoList = new ArrayList<>();
|
||||
private PuzzleSelectorAdapter photosAdapter;
|
||||
private RecyclerView rvPhotos, rvPreview;
|
||||
private PuzzleSelectorPreviewAdapter previewAdapter;
|
||||
private ArrayList<Photo> selectedPhotos = new ArrayList<>();
|
||||
|
||||
private PressedTextView tvDone;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_puzzle_selector_easy_photos);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
int statusColor = getWindow().getStatusBarColor();
|
||||
if(statusColor == Color.TRANSPARENT){
|
||||
statusColor = ContextCompat.getColor(this, R.color.easy_photos_status_bar);
|
||||
}
|
||||
if (ColorUtils.isWhiteColor(statusColor)) {
|
||||
SystemUtils.getInstance().setStatusDark(this, true);
|
||||
}
|
||||
}
|
||||
albumModel = AlbumModel.getInstance();
|
||||
// albumModel.query(this, null);
|
||||
if (null == albumModel||albumModel.getAlbumItems().isEmpty()) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
initView();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
setClick(R.id.iv_back);
|
||||
tvAlbumItems = (PressedTextView) findViewById(R.id.tv_album_items);
|
||||
tvAlbumItems.setText(albumModel.getAlbumItems().get(0).name);
|
||||
rootSelectorView = (RelativeLayout) findViewById(R.id.m_selector_root);
|
||||
tvDone = (PressedTextView) findViewById(R.id.tv_done);
|
||||
tvDone.setOnClickListener(this);
|
||||
tvAlbumItems.setOnClickListener(this);
|
||||
initAlbumItems();
|
||||
initPhotos();
|
||||
initPreview();
|
||||
}
|
||||
|
||||
private void initPreview() {
|
||||
rvPreview = (RecyclerView) findViewById(R.id.rv_preview_selected_photos);
|
||||
LinearLayoutManager lm = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
|
||||
previewAdapter = new PuzzleSelectorPreviewAdapter(this, selectedPhotos, this);
|
||||
rvPreview.setLayoutManager(lm);
|
||||
rvPreview.setAdapter(previewAdapter);
|
||||
}
|
||||
|
||||
private void initPhotos() {
|
||||
rvPhotos = (RecyclerView) findViewById(R.id.rv_photos);
|
||||
((SimpleItemAnimator) rvPhotos.getItemAnimator()).setSupportsChangeAnimations(false);//去除item更新的闪光
|
||||
|
||||
photoList.addAll(albumModel.getCurrAlbumItemPhotos(0));
|
||||
photosAdapter = new PuzzleSelectorAdapter(this, photoList, this);
|
||||
|
||||
int columns = getResources().getInteger(R.integer.photos_columns_easy_photos);
|
||||
|
||||
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, columns);
|
||||
rvPhotos.setLayoutManager(gridLayoutManager);
|
||||
rvPhotos.setAdapter(photosAdapter);
|
||||
}
|
||||
|
||||
private void initAlbumItems() {
|
||||
rootViewAlbumItems = (RelativeLayout) findViewById(R.id.root_view_album_items);
|
||||
rootViewAlbumItems.setOnClickListener(this);
|
||||
setClick(R.id.iv_album_items);
|
||||
rvAlbumItems = (RecyclerView) findViewById(R.id.rv_album_items);
|
||||
LinearLayoutManager lm = new LinearLayoutManager(this);
|
||||
ArrayList<Object> list = new ArrayList<Object>(albumModel.getAlbumItems());
|
||||
albumItemsAdapter = new AlbumItemsAdapter(this, list, 0, this);
|
||||
rvAlbumItems.setLayoutManager(lm);
|
||||
rvAlbumItems.setAdapter(albumItemsAdapter);
|
||||
}
|
||||
|
||||
private void setClick(@IdRes int... ids) {
|
||||
for (int id : ids) {
|
||||
findViewById(id).setOnClickListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
int id = view.getId();
|
||||
if (R.id.iv_back == id) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
} else if (R.id.tv_album_items == id || R.id.iv_album_items == id) {
|
||||
showAlbumItems(View.GONE == rootViewAlbumItems.getVisibility());
|
||||
} else if (R.id.root_view_album_items == id) {
|
||||
showAlbumItems(false);
|
||||
} else if (R.id.tv_done == id) {
|
||||
PuzzleActivity.startWithPhotos(this, selectedPhotos, Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + getString(R.string.app_name), "IMG", Code.REQUEST_PUZZLE, false, Setting.imageEngine);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void showAlbumItems(boolean isShow) {
|
||||
if (null == setShow) {
|
||||
newAnimators();
|
||||
}
|
||||
if (isShow) {
|
||||
rootViewAlbumItems.setVisibility(View.VISIBLE);
|
||||
setShow.start();
|
||||
} else {
|
||||
setHide.start();
|
||||
}
|
||||
}
|
||||
|
||||
private void newAnimators() {
|
||||
newHideAnim();
|
||||
newShowAnim();
|
||||
}
|
||||
|
||||
private void newShowAnim() {
|
||||
ObjectAnimator translationShow = ObjectAnimator.ofFloat(rvAlbumItems, "translationY", rootSelectorView.getTop(), 0);
|
||||
ObjectAnimator alphaShow = ObjectAnimator.ofFloat(rootViewAlbumItems, "alpha", 0.0f, 1.0f);
|
||||
translationShow.setDuration(300);
|
||||
setShow = new AnimatorSet();
|
||||
setShow.setInterpolator(new AccelerateDecelerateInterpolator());
|
||||
setShow.play(translationShow).with(alphaShow);
|
||||
}
|
||||
|
||||
private void newHideAnim() {
|
||||
ObjectAnimator translationHide = ObjectAnimator.ofFloat(rvAlbumItems, "translationY", 0, rootSelectorView.getTop());
|
||||
ObjectAnimator alphaHide = ObjectAnimator.ofFloat(rootViewAlbumItems, "alpha", 1.0f, 0.0f);
|
||||
translationHide.setDuration(200);
|
||||
setHide = new AnimatorSet();
|
||||
setHide.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
super.onAnimationEnd(animation);
|
||||
rootViewAlbumItems.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
setHide.setInterpolator(new AccelerateInterpolator());
|
||||
setHide.play(translationHide).with(alphaHide);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAlbumItemClick(int position, int realPosition) {
|
||||
updatePhotos(realPosition);
|
||||
showAlbumItems(false);
|
||||
tvAlbumItems.setText(albumModel.getAlbumItems().get(realPosition).name);
|
||||
}
|
||||
|
||||
private void updatePhotos(int currAlbumItemIndex) {
|
||||
photoList.clear();
|
||||
photoList.addAll(albumModel.getCurrAlbumItemPhotos(currAlbumItemIndex));
|
||||
|
||||
photosAdapter.notifyDataSetChanged();
|
||||
rvPhotos.scrollToPosition(0);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
|
||||
if (null != rootViewAlbumItems && rootViewAlbumItems.getVisibility() == View.VISIBLE) {
|
||||
showAlbumItems(false);
|
||||
return;
|
||||
}
|
||||
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPhotoClick(int position) {
|
||||
if (selectedPhotos.size() > 8) {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.selector_reach_max_image_hint_easy_photos, 9), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
selectedPhotos.add(photoList.get(position));
|
||||
previewAdapter.notifyDataSetChanged();
|
||||
rvPreview.smoothScrollToPosition(selectedPhotos.size() - 1);
|
||||
|
||||
tvDone.setText(getString(R.string.selector_action_done_easy_photos, selectedPhotos.size(), 9));
|
||||
if (selectedPhotos.size() > 1) {
|
||||
tvDone.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteClick(int position) {
|
||||
selectedPhotos.remove(position);
|
||||
previewAdapter.notifyDataSetChanged();
|
||||
tvDone.setText(getString(R.string.selector_action_done_easy_photos, selectedPhotos.size(), 9));
|
||||
if (selectedPhotos.size() < 2) {
|
||||
tvDone.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (resultCode == RESULT_OK) {
|
||||
if (requestCode == Code.REQUEST_PUZZLE) {
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package com.huantansheng.easyphotos.ui.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.huantansheng.easyphotos.R;
|
||||
import com.huantansheng.easyphotos.models.ad.AdViewHolder;
|
||||
import com.huantansheng.easyphotos.models.album.entity.AlbumItem;
|
||||
import com.huantansheng.easyphotos.setting.Setting;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* 媒体列表适配器
|
||||
* Created by huan on 2017/10/23.
|
||||
*/
|
||||
|
||||
public class AlbumItemsAdapter extends RecyclerView.Adapter {
|
||||
private static final int TYPE_AD = 0;
|
||||
private static final int TYPE_ALBUM_ITEMS = 1;
|
||||
|
||||
private ArrayList<Object> dataList;
|
||||
private LayoutInflater mInflater;
|
||||
private int selectedPosition;
|
||||
private OnClickListener listener;
|
||||
private int adPosition = 0;
|
||||
private int padding = 0;
|
||||
|
||||
private boolean clearAd = false;
|
||||
|
||||
public interface OnClickListener {
|
||||
void onAlbumItemClick(int position, int realPosition);
|
||||
}
|
||||
|
||||
|
||||
public AlbumItemsAdapter(Context cxt, ArrayList<Object> list, int selectedPosition, OnClickListener listener) {
|
||||
this.dataList = list;
|
||||
this.mInflater = LayoutInflater.from(cxt);
|
||||
this.listener = listener;
|
||||
this.selectedPosition = selectedPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
switch (viewType) {
|
||||
case TYPE_AD:
|
||||
return new AdViewHolder(mInflater.inflate(R.layout.item_ad_easy_photos, parent, false));
|
||||
default:
|
||||
return new AlbumItemsViewHolder(mInflater.inflate(R.layout.item_dialog_album_items_easy_photos, parent, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
||||
final int p = position;
|
||||
if (holder instanceof AlbumItemsViewHolder) {
|
||||
if (padding == 0) {
|
||||
padding = ((AlbumItemsViewHolder) holder).mRoot.getPaddingLeft();
|
||||
}
|
||||
if (p == getItemCount() - 1) {
|
||||
((AlbumItemsViewHolder) holder).mRoot.setPadding(padding, padding, padding, padding);
|
||||
} else {
|
||||
((AlbumItemsViewHolder) holder).mRoot.setPadding(padding, padding, padding, 0);
|
||||
}
|
||||
AlbumItem item = (AlbumItem) dataList.get(p);
|
||||
Setting.imageEngine.loadPhoto(((AlbumItemsViewHolder) holder).ivAlbumCover.getContext(), item.coverImageUri, ((AlbumItemsViewHolder) holder).ivAlbumCover);
|
||||
((AlbumItemsViewHolder) holder).tvAlbumName.setText(item.name);
|
||||
((AlbumItemsViewHolder) holder).tvAlbumPhotosCount.setText(String.valueOf(item.photos.size()));
|
||||
if (selectedPosition == p) {
|
||||
((AlbumItemsViewHolder) holder).ivSelected.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
((AlbumItemsViewHolder) holder).ivSelected.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
int realPosition = p;
|
||||
if (Setting.hasAlbumItemsAd()) {
|
||||
if (p > adPosition) {
|
||||
realPosition--;
|
||||
}
|
||||
}
|
||||
int tempSelected = selectedPosition;
|
||||
selectedPosition = p;
|
||||
notifyItemChanged(tempSelected);
|
||||
notifyItemChanged(p);
|
||||
listener.onAlbumItemClick(p, realPosition);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (holder instanceof AdViewHolder) {
|
||||
if (clearAd) {
|
||||
((AdViewHolder) holder).adFrame.removeAllViews();
|
||||
((AdViewHolder) holder).adFrame.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
adPosition = p;
|
||||
if (!Setting.albumItemsAdIsOk) {
|
||||
((AdViewHolder) holder).adFrame.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
WeakReference weakReference = (WeakReference) dataList.get(p);
|
||||
|
||||
if (null != weakReference) {
|
||||
View adView = (View) weakReference.get();
|
||||
if (null != adView) {
|
||||
if (null != adView.getParent()) {
|
||||
if (adView.getParent() instanceof FrameLayout) {
|
||||
((FrameLayout) adView.getParent()).removeAllViews();
|
||||
}
|
||||
}
|
||||
((AdViewHolder) holder).adFrame.setVisibility(View.VISIBLE);
|
||||
((AdViewHolder) holder).adFrame.removeAllViews();
|
||||
((AdViewHolder) holder).adFrame.addView(adView);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clearAd() {
|
||||
clearAd = true;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setSelectedPosition(int position) {
|
||||
int realPosition = position;
|
||||
if (Setting.hasAlbumItemsAd()) {
|
||||
if (position > adPosition) {
|
||||
realPosition--;
|
||||
}
|
||||
}
|
||||
int tempSelected = selectedPosition;
|
||||
selectedPosition = position;
|
||||
notifyItemChanged(tempSelected);
|
||||
notifyItemChanged(position);
|
||||
listener.onAlbumItemClick(position, realPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return dataList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
Object item = dataList.get(position);
|
||||
if (null == item || item instanceof WeakReference) {
|
||||
return TYPE_AD;
|
||||
} else {
|
||||
return TYPE_ALBUM_ITEMS;
|
||||
}
|
||||
}
|
||||
|
||||
public class AlbumItemsViewHolder extends RecyclerView.ViewHolder {
|
||||
ImageView ivAlbumCover;
|
||||
TextView tvAlbumName;
|
||||
TextView tvAlbumPhotosCount;
|
||||
ImageView ivSelected;
|
||||
ConstraintLayout mRoot;
|
||||
|
||||
AlbumItemsViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
this.ivAlbumCover = (ImageView) itemView.findViewById(R.id.iv_album_cover);
|
||||
this.tvAlbumName = (TextView) itemView.findViewById(R.id.tv_album_name);
|
||||
this.tvAlbumPhotosCount = (TextView) itemView.findViewById(R.id.tv_album_photos_count);
|
||||
this.ivSelected = (ImageView) itemView.findViewById(R.id.iv_selected);
|
||||
this.mRoot = (ConstraintLayout) itemView.findViewById(R.id.m_root_view);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
package com.huantansheng.easyphotos.ui.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.huantansheng.easyphotos.R;
|
||||
import com.huantansheng.easyphotos.constant.Type;
|
||||
import com.huantansheng.easyphotos.models.ad.AdViewHolder;
|
||||
import com.huantansheng.easyphotos.models.album.entity.Photo;
|
||||
import com.huantansheng.easyphotos.result.Result;
|
||||
import com.huantansheng.easyphotos.setting.Setting;
|
||||
import com.huantansheng.easyphotos.ui.widget.PressedImageView;
|
||||
import com.huantansheng.easyphotos.utils.media.DurationUtils;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* 专辑相册适配器
|
||||
* Created by huan on 2017/10/23.
|
||||
*/
|
||||
public class PhotosAdapter extends RecyclerView.Adapter {
|
||||
private static final int TYPE_AD = 0;
|
||||
private static final int TYPE_CAMERA = 1;
|
||||
private static final int TYPE_ALBUM_ITEMS = 2;
|
||||
|
||||
private ArrayList<Object> dataList;
|
||||
private LayoutInflater mInflater;
|
||||
private OnClickListener listener;
|
||||
private boolean unable, isSingle;
|
||||
private int singlePosition;
|
||||
|
||||
private boolean clearAd = false;
|
||||
|
||||
|
||||
public PhotosAdapter(Context cxt, ArrayList<Object> dataList, OnClickListener listener) {
|
||||
this.dataList = dataList;
|
||||
this.listener = listener;
|
||||
this.mInflater = LayoutInflater.from(cxt);
|
||||
this.unable = Result.count() == Setting.count;
|
||||
this.isSingle = Setting.count == 1;
|
||||
}
|
||||
|
||||
public void change() {
|
||||
unable = Result.count() == Setting.count;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
switch (viewType) {
|
||||
case TYPE_AD:
|
||||
return new AdViewHolder(mInflater.inflate(R.layout.item_ad_easy_photos, parent, false));
|
||||
case TYPE_CAMERA:
|
||||
return new CameraViewHolder(mInflater.inflate(R.layout.item_camera_easy_photos, parent, false));
|
||||
default:
|
||||
return new PhotoViewHolder(mInflater.inflate(R.layout.item_rv_photos_easy_photos, parent, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int position) {
|
||||
final int p = position;
|
||||
if (holder instanceof PhotoViewHolder) {
|
||||
final Photo item = (Photo) dataList.get(p);
|
||||
if (item == null) return;
|
||||
updateSelector(((PhotoViewHolder) holder).tvSelector, item.selected, item, p);
|
||||
String path = item.path;
|
||||
Uri uri = item.uri;
|
||||
String type = item.type;
|
||||
long duration = item.duration;
|
||||
final boolean isGif = path.endsWith(Type.GIF) || type.endsWith(Type.GIF);
|
||||
if (Setting.showGif && isGif) {
|
||||
Setting.imageEngine.loadGifAsBitmap(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto);
|
||||
((PhotoViewHolder) holder).tvType.setText(R.string.gif_easy_photos);
|
||||
((PhotoViewHolder) holder).tvType.setVisibility(View.VISIBLE);
|
||||
((PhotoViewHolder) holder).ivVideo.setVisibility(View.GONE);
|
||||
} else if (Setting.showVideo && type.contains(Type.VIDEO)) {
|
||||
Setting.imageEngine.loadPhoto(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto);
|
||||
((PhotoViewHolder) holder).tvType.setText(DurationUtils.format(duration));
|
||||
((PhotoViewHolder) holder).tvType.setVisibility(View.VISIBLE);
|
||||
((PhotoViewHolder) holder).ivVideo.setVisibility(View.VISIBLE);
|
||||
|
||||
} else {
|
||||
Setting.imageEngine.loadPhoto(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto);
|
||||
((PhotoViewHolder) holder).tvType.setVisibility(View.GONE);
|
||||
((PhotoViewHolder) holder).ivVideo.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
((PhotoViewHolder) holder).vSelector.setVisibility(View.VISIBLE);
|
||||
((PhotoViewHolder) holder).tvSelector.setVisibility(View.VISIBLE);
|
||||
((PhotoViewHolder) holder).ivPhoto.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int realPosition = p;
|
||||
if (Setting.hasPhotosAd()) {
|
||||
realPosition--;
|
||||
}
|
||||
if (Setting.isShowCamera && !Setting.isBottomRightCamera()) {
|
||||
realPosition--;
|
||||
}
|
||||
listener.onPhotoClick(p, realPosition);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
((PhotoViewHolder) holder).vSelector.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (isSingle) {
|
||||
singleSelector(item, p);
|
||||
return;
|
||||
}
|
||||
if (unable) {
|
||||
if (item.selected) {
|
||||
Result.removePhoto(item);
|
||||
if (unable) {
|
||||
unable = false;
|
||||
}
|
||||
listener.onSelectorChanged();
|
||||
notifyDataSetChanged();
|
||||
return;
|
||||
}
|
||||
listener.onSelectorOutOfMax(null);
|
||||
return;
|
||||
}
|
||||
item.selected = !item.selected;
|
||||
if (item.selected) {
|
||||
int res = Result.addPhoto(item);
|
||||
if (res != 0) {
|
||||
listener.onSelectorOutOfMax(res);
|
||||
item.selected = false;
|
||||
return;
|
||||
}
|
||||
((PhotoViewHolder) holder).tvSelector.setBackgroundResource(R.drawable.bg_select_true_easy_photos);
|
||||
((PhotoViewHolder) holder).tvSelector.setText(String.valueOf(Result.count()));
|
||||
if (Result.count() == Setting.count) {
|
||||
unable = true;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
} else {
|
||||
Result.removePhoto(item);
|
||||
if (unable) {
|
||||
unable = false;
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
listener.onSelectorChanged();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (holder instanceof AdViewHolder) {
|
||||
if (clearAd) {
|
||||
((AdViewHolder) holder).adFrame.removeAllViews();
|
||||
((AdViewHolder) holder).adFrame.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
if (!Setting.photoAdIsOk) {
|
||||
((AdViewHolder) holder).adFrame.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
WeakReference weakReference = (WeakReference) dataList.get(p);
|
||||
|
||||
if (null != weakReference) {
|
||||
View adView = (View) weakReference.get();
|
||||
if (null != adView) {
|
||||
if (null != adView.getParent()) {
|
||||
if (adView.getParent() instanceof FrameLayout) {
|
||||
((FrameLayout) adView.getParent()).removeAllViews();
|
||||
}
|
||||
}
|
||||
((AdViewHolder) holder).adFrame.setVisibility(View.VISIBLE);
|
||||
((AdViewHolder) holder).adFrame.removeAllViews();
|
||||
((AdViewHolder) holder).adFrame.addView(adView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (holder instanceof CameraViewHolder) {
|
||||
((CameraViewHolder) holder).flCamera.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onCameraClick();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void clearAd() {
|
||||
clearAd = true;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void singleSelector(Photo photo, int position) {
|
||||
if (!Result.isEmpty()) {
|
||||
if (Result.getPhotoPath(0).equals(photo.path)) {
|
||||
Result.removePhoto(photo);
|
||||
} else {
|
||||
Result.removePhoto(0);
|
||||
Result.addPhoto(photo);
|
||||
notifyItemChanged(singlePosition);
|
||||
}
|
||||
} else {
|
||||
Result.addPhoto(photo);
|
||||
}
|
||||
notifyItemChanged(position);
|
||||
listener.onSelectorChanged();
|
||||
}
|
||||
|
||||
private void updateSelector(TextView tvSelector, boolean selected, Photo photo, int position) {
|
||||
if (selected) {
|
||||
String number = Result.getSelectorNumber(photo);
|
||||
if (number.equals("0")) {
|
||||
tvSelector.setBackgroundResource(R.drawable.bg_select_false_easy_photos);
|
||||
tvSelector.setText(null);
|
||||
return;
|
||||
}
|
||||
tvSelector.setText(number);
|
||||
tvSelector.setBackgroundResource(R.drawable.bg_select_true_easy_photos);
|
||||
if (isSingle) {
|
||||
singlePosition = position;
|
||||
tvSelector.setText("1");
|
||||
}
|
||||
} else {
|
||||
if (unable) {
|
||||
tvSelector.setBackgroundResource(R.drawable.bg_select_false_unable_easy_photos);
|
||||
} else {
|
||||
tvSelector.setBackgroundResource(R.drawable.bg_select_false_easy_photos);
|
||||
}
|
||||
tvSelector.setText(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return dataList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (0 == position) {
|
||||
if (Setting.hasPhotosAd()) {
|
||||
return TYPE_AD;
|
||||
}
|
||||
if (Setting.isShowCamera && !Setting.isBottomRightCamera()) {
|
||||
return TYPE_CAMERA;
|
||||
}
|
||||
}
|
||||
if (1 == position && !Setting.isBottomRightCamera()) {
|
||||
if (Setting.hasPhotosAd() && Setting.isShowCamera) {
|
||||
return TYPE_CAMERA;
|
||||
}
|
||||
}
|
||||
return TYPE_ALBUM_ITEMS;
|
||||
}
|
||||
|
||||
public interface OnClickListener {
|
||||
void onCameraClick();
|
||||
|
||||
void onPhotoClick(int position, int realPosition);
|
||||
|
||||
void onSelectorOutOfMax(@Nullable Integer result);
|
||||
|
||||
void onSelectorChanged();
|
||||
}
|
||||
|
||||
private static class CameraViewHolder extends RecyclerView.ViewHolder {
|
||||
final FrameLayout flCamera;
|
||||
|
||||
CameraViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
this.flCamera = itemView.findViewById(R.id.fl_camera);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PhotoViewHolder extends RecyclerView.ViewHolder {
|
||||
final PressedImageView ivPhoto;
|
||||
final TextView tvSelector;
|
||||
final View vSelector;
|
||||
final TextView tvType;
|
||||
final ImageView ivVideo;
|
||||
|
||||
PhotoViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
this.ivPhoto = itemView.findViewById(R.id.iv_photo);
|
||||
this.tvSelector = itemView.findViewById(R.id.tv_selector);
|
||||
this.vSelector = itemView.findViewById(R.id.v_selector);
|
||||
this.tvType = itemView.findViewById(R.id.tv_type);
|
||||
this.ivVideo = itemView.findViewById(R.id.iv_play);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package com.huantansheng.easyphotos.ui.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.PointF;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.davemorrissey.labs.subscaleview.ImageSource;
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
|
||||
import com.github.chrisbanes.photoview.OnScaleChangedListener;
|
||||
import com.github.chrisbanes.photoview.PhotoView;
|
||||
import com.huantansheng.easyphotos.R;
|
||||
import com.huantansheng.easyphotos.constant.Type;
|
||||
import com.huantansheng.easyphotos.models.album.entity.Photo;
|
||||
import com.huantansheng.easyphotos.setting.Setting;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* 大图预览界面图片集合的适配器
|
||||
* Created by huan on 2017/10/26.
|
||||
*/
|
||||
public class PreviewPhotosAdapter extends RecyclerView.Adapter<PreviewPhotosAdapter.PreviewPhotosViewHolder> {
|
||||
private ArrayList<Photo> photos;
|
||||
private OnClickListener listener;
|
||||
private LayoutInflater inflater;
|
||||
|
||||
public interface OnClickListener {
|
||||
void onPhotoClick();
|
||||
|
||||
void onPhotoScaleChanged();
|
||||
}
|
||||
|
||||
public PreviewPhotosAdapter(Context cxt, ArrayList<Photo> photos, OnClickListener listener) {
|
||||
this.photos = photos;
|
||||
this.inflater = LayoutInflater.from(cxt);
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public PreviewPhotosViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new PreviewPhotosViewHolder(inflater.inflate(R.layout.item_preview_photo_easy_photos, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final PreviewPhotosViewHolder holder, int position) {
|
||||
final Uri uri = photos.get(position).uri;
|
||||
final String path = photos.get(position).path;
|
||||
final String type = photos.get(position).type;
|
||||
final double ratio =
|
||||
(double) photos.get(position).height / (double) photos.get(position).width;
|
||||
|
||||
holder.ivPlay.setVisibility(View.GONE);
|
||||
holder.ivPhotoView.setVisibility(View.GONE);
|
||||
holder.ivLongPhoto.setVisibility(View.GONE);
|
||||
|
||||
if (type.contains(Type.VIDEO)) {
|
||||
holder.ivPhotoView.setVisibility(View.VISIBLE);
|
||||
Setting.imageEngine.loadPhoto(holder.ivPhotoView.getContext(), uri, holder.ivPhotoView);
|
||||
holder.ivPlay.setVisibility(View.VISIBLE);
|
||||
holder.ivPlay.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
toPlayVideo(v, uri, type);
|
||||
}
|
||||
});
|
||||
} else if (path.endsWith(Type.GIF) || type.endsWith(Type.GIF)) {
|
||||
holder.ivPhotoView.setVisibility(View.VISIBLE);
|
||||
Setting.imageEngine.loadGif(holder.ivPhotoView.getContext(), uri, holder.ivPhotoView);
|
||||
} else {
|
||||
if (ratio > 2.3) {
|
||||
holder.ivLongPhoto.setVisibility(View.VISIBLE);
|
||||
holder.ivLongPhoto.setImage(ImageSource.uri(path));
|
||||
} else {
|
||||
holder.ivPhotoView.setVisibility(View.VISIBLE);
|
||||
Setting.imageEngine.loadPhoto(holder.ivPhotoView.getContext(), uri,
|
||||
holder.ivPhotoView);
|
||||
}
|
||||
}
|
||||
|
||||
holder.ivLongPhoto.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onPhotoClick();
|
||||
}
|
||||
});
|
||||
holder.ivPhotoView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onPhotoClick();
|
||||
}
|
||||
});
|
||||
holder.ivLongPhoto.setOnStateChangedListener(new SubsamplingScaleImageView.OnStateChangedListener() {
|
||||
@Override
|
||||
public void onScaleChanged(float newScale, int origin) {
|
||||
listener.onPhotoScaleChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCenterChanged(PointF newCenter, int origin) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
holder.ivPhotoView.setScale(1f);
|
||||
|
||||
holder.ivPhotoView.setOnScaleChangeListener(new OnScaleChangedListener() {
|
||||
@Override
|
||||
public void onScaleChange(float scaleFactor, float focusX, float focusY) {
|
||||
listener.onPhotoScaleChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void toPlayVideo(View v, Uri uri, String type) {
|
||||
Context context = v.getContext();
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
}
|
||||
intent.setDataAndType(uri, type);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
private Uri getUri(Context context, String path, Intent intent) {
|
||||
File file = new File(path);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
return FileProvider.getUriForFile(context, Setting.fileProviderAuthority, file);
|
||||
} else {
|
||||
return Uri.fromFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return photos.size();
|
||||
}
|
||||
|
||||
public class PreviewPhotosViewHolder extends RecyclerView.ViewHolder {
|
||||
public SubsamplingScaleImageView ivLongPhoto;
|
||||
ImageView ivPlay;
|
||||
PhotoView ivPhotoView;
|
||||
|
||||
PreviewPhotosViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ivLongPhoto = itemView.findViewById(R.id.iv_long_photo);
|
||||
ivPhotoView = itemView.findViewById(R.id.iv_photo_view);
|
||||
ivPlay = itemView.findViewById(R.id.iv_play);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.huantansheng.easyphotos.ui.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.huantansheng.easyphotos.R;
|
||||
import com.huantansheng.easyphotos.constant.Type;
|
||||
import com.huantansheng.easyphotos.result.Result;
|
||||
import com.huantansheng.easyphotos.setting.Setting;
|
||||
import com.huantansheng.easyphotos.ui.widget.PressedImageView;
|
||||
import com.huantansheng.easyphotos.utils.media.DurationUtils;
|
||||
|
||||
/**
|
||||
* 预览所有选中图片集合的适配器
|
||||
* Created by huan on 2017/12/1.
|
||||
*/
|
||||
|
||||
public class PreviewPhotosFragmentAdapter extends RecyclerView.Adapter<PreviewPhotosFragmentAdapter.PreviewPhotoVH> {
|
||||
private LayoutInflater inflater;
|
||||
private OnClickListener listener;
|
||||
private int checkedPosition = -1;
|
||||
|
||||
public PreviewPhotosFragmentAdapter(Context context, OnClickListener listener) {
|
||||
this.inflater = LayoutInflater.from(context);
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PreviewPhotoVH onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
return new PreviewPhotoVH(inflater.inflate(R.layout.item_preview_selected_photos_easy_photos, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreviewPhotoVH holder, int position) {
|
||||
final int p = position;
|
||||
String path = Result.getPhotoPath(position);
|
||||
String type = Result.getPhotoType(position);
|
||||
Uri uri = Result.getPhotoUri(position);
|
||||
long duration = Result.getPhotoDuration(position);
|
||||
|
||||
final boolean isGif = path.endsWith(Type.GIF) || type.endsWith(Type.GIF);
|
||||
if (Setting.showGif && isGif) {
|
||||
Setting.imageEngine.loadGifAsBitmap(holder.ivPhoto.getContext(), uri, holder.ivPhoto);
|
||||
holder.tvType.setText(R.string.gif_easy_photos);
|
||||
holder.tvType.setVisibility(View.VISIBLE);
|
||||
} else if (Setting.showVideo && type.contains(Type.VIDEO)) {
|
||||
Setting.imageEngine.loadPhoto(holder.ivPhoto.getContext(), uri, holder.ivPhoto);
|
||||
holder.tvType.setText(DurationUtils.format(duration));
|
||||
holder.tvType.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
Setting.imageEngine.loadPhoto(holder.ivPhoto.getContext(), uri, holder.ivPhoto);
|
||||
holder.tvType.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (checkedPosition == p) {
|
||||
holder.frame.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.frame.setVisibility(View.GONE);
|
||||
}
|
||||
holder.ivPhoto.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
listener.onPhotoClick(p);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return Result.count();
|
||||
}
|
||||
|
||||
public void setChecked(int position) {
|
||||
if (checkedPosition == position) {
|
||||
return;
|
||||
}
|
||||
checkedPosition = position;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
class PreviewPhotoVH extends RecyclerView.ViewHolder {
|
||||
PressedImageView ivPhoto;
|
||||
View frame;
|
||||
TextView tvType;
|
||||
|
||||
public PreviewPhotoVH(View itemView) {
|
||||
super(itemView);
|
||||
ivPhoto = (PressedImageView) itemView.findViewById(R.id.iv_photo);
|
||||
frame = itemView.findViewById(R.id.v_selector);
|
||||
tvType = (TextView) itemView.findViewById(R.id.tv_type);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnClickListener {
|
||||
void onPhotoClick(int position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.huantansheng.easyphotos.ui.adapter;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.huantansheng.easyphotos.R;
|
||||
import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout;
|
||||
import com.huantansheng.easyphotos.models.puzzle.SquarePuzzleView;
|
||||
import com.huantansheng.easyphotos.models.puzzle.template.slant.NumberSlantLayout;
|
||||
import com.huantansheng.easyphotos.models.puzzle.template.straight.NumberStraightLayout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author wupanjie
|
||||
*/
|
||||
public class PuzzleAdapter extends RecyclerView.Adapter<PuzzleAdapter.PuzzleViewHolder> {
|
||||
|
||||
private List<PuzzleLayout> layoutData = new ArrayList<>();
|
||||
private OnItemClickListener onItemClickListener;
|
||||
private int selectedNumber = 0;
|
||||
|
||||
@Override
|
||||
public PuzzleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View itemView =
|
||||
LayoutInflater.from(parent.getContext()).inflate(R.layout.item_puzzle_easy_photos, parent, false);
|
||||
return new PuzzleViewHolder(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PuzzleViewHolder holder, int position) {
|
||||
final PuzzleLayout puzzleLayout = layoutData.get(position);
|
||||
final int p = position;
|
||||
if (selectedNumber == position) {
|
||||
holder.mFrame.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.mFrame.setVisibility(View.GONE);
|
||||
}
|
||||
holder.puzzleView.setNeedDrawLine(true);
|
||||
holder.puzzleView.setNeedDrawOuterLine(true);
|
||||
holder.puzzleView.setTouchEnable(false);
|
||||
|
||||
holder.puzzleView.setPuzzleLayout(puzzleLayout);
|
||||
|
||||
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (selectedNumber == p) {
|
||||
return;
|
||||
}
|
||||
if (onItemClickListener != null) {
|
||||
int themeType = 0;
|
||||
int themeId = 0;
|
||||
if (puzzleLayout instanceof NumberSlantLayout) {
|
||||
themeType = 0;
|
||||
themeId = ((NumberSlantLayout) puzzleLayout).getTheme();
|
||||
} else if (puzzleLayout instanceof NumberStraightLayout) {
|
||||
themeType = 1;
|
||||
themeId = ((NumberStraightLayout) puzzleLayout).getTheme();
|
||||
}
|
||||
selectedNumber = p;
|
||||
onItemClickListener.onItemClick(themeType, themeId);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return layoutData == null ? 0 : layoutData.size();
|
||||
}
|
||||
|
||||
public void refreshData(List<PuzzleLayout> layoutData) {
|
||||
this.layoutData = layoutData;
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
|
||||
this.onItemClickListener = onItemClickListener;
|
||||
}
|
||||
|
||||
public static class PuzzleViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
SquarePuzzleView puzzleView;
|
||||
View mFrame;
|
||||
|
||||
public PuzzleViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
puzzleView = (SquarePuzzleView) itemView.findViewById(R.id.puzzle);
|
||||
mFrame = itemView.findViewById(R.id.m_selector);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnItemClickListener {
|
||||
void onItemClick(int themeType, int themeId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.huantansheng.easyphotos.ui.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.huantansheng.easyphotos.R;
|
||||
import com.huantansheng.easyphotos.constant.Type;
|
||||
import com.huantansheng.easyphotos.models.album.entity.Photo;
|
||||
import com.huantansheng.easyphotos.setting.Setting;
|
||||
import com.huantansheng.easyphotos.utils.media.DurationUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* 拼图相册适配器
|
||||
* Created by huan on 2017/10/23.
|
||||
*/
|
||||
|
||||
public class PuzzleSelectorAdapter extends RecyclerView.Adapter {
|
||||
|
||||
|
||||
private ArrayList<Photo> dataList;
|
||||
private LayoutInflater mInflater;
|
||||
private OnClickListener listener;
|
||||
|
||||
|
||||
public PuzzleSelectorAdapter(Context cxt, ArrayList<Photo> dataList, OnClickListener listener) {
|
||||
this.dataList = dataList;
|
||||
this.listener = listener;
|
||||
this.mInflater = LayoutInflater.from(cxt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
|
||||
return new PhotoViewHolder(mInflater.inflate(R.layout.item_puzzle_selector_easy_photos, parent, false));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
|
||||
|
||||
final int p = position;
|
||||
Photo photo = dataList.get(position);
|
||||
String path = photo.path;
|
||||
String type = photo.type;
|
||||
Uri uri = photo.uri;
|
||||
long duration = photo.duration;
|
||||
final boolean isGif = path.endsWith(Type.GIF) || type.endsWith(Type.GIF);
|
||||
if (Setting.showGif && isGif) {
|
||||
Setting.imageEngine.loadGifAsBitmap(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto);
|
||||
((PhotoViewHolder) holder).tvType.setText(R.string.gif_easy_photos);
|
||||
((PhotoViewHolder) holder).tvType.setVisibility(View.VISIBLE);
|
||||
} else if (Setting.showVideo && type.contains(Type.VIDEO)) {
|
||||
Setting.imageEngine.loadPhoto(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto);
|
||||
((PhotoViewHolder) holder).tvType.setText(DurationUtils.format(duration));
|
||||
((PhotoViewHolder) holder).tvType.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
Setting.imageEngine.loadPhoto(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto);
|
||||
((PhotoViewHolder) holder).tvType.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
((PhotoViewHolder) holder).ivPhoto.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onPhotoClick(p);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return null == dataList ? 0 : dataList.size();
|
||||
}
|
||||
|
||||
|
||||
public interface OnClickListener {
|
||||
void onPhotoClick(int position);
|
||||
}
|
||||
|
||||
public class PhotoViewHolder extends RecyclerView.ViewHolder {
|
||||
ImageView ivPhoto;
|
||||
TextView tvType;
|
||||
|
||||
public PhotoViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
this.ivPhoto = (ImageView) itemView.findViewById(R.id.iv_photo);
|
||||
this.tvType = (TextView) itemView.findViewById(R.id.tv_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.huantansheng.easyphotos.ui.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.huantansheng.easyphotos.R;
|
||||
import com.huantansheng.easyphotos.constant.Type;
|
||||
import com.huantansheng.easyphotos.models.album.entity.Photo;
|
||||
import com.huantansheng.easyphotos.setting.Setting;
|
||||
import com.huantansheng.easyphotos.utils.media.DurationUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* 拼图相册适配器
|
||||
* Created by huan on 2017/10/23.
|
||||
*/
|
||||
|
||||
public class PuzzleSelectorPreviewAdapter extends RecyclerView.Adapter {
|
||||
|
||||
|
||||
private ArrayList<Photo> dataList;
|
||||
private LayoutInflater mInflater;
|
||||
private OnClickListener listener;
|
||||
|
||||
|
||||
public PuzzleSelectorPreviewAdapter(Context cxt, ArrayList<Photo> dataList, OnClickListener listener) {
|
||||
this.dataList = dataList;
|
||||
this.listener = listener;
|
||||
this.mInflater = LayoutInflater.from(cxt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
|
||||
return new PhotoViewHolder(mInflater.inflate(R.layout.item_puzzle_selector_preview_easy_photos, parent, false));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
|
||||
|
||||
final int p = position;
|
||||
Photo photo = dataList.get(position);
|
||||
String path = photo.path;
|
||||
String type = photo.type;
|
||||
Uri uri = photo.uri;
|
||||
long duration = photo.duration;
|
||||
final boolean isGif = path.endsWith(Type.GIF) || type.endsWith(Type.GIF);
|
||||
if (Setting.showGif && isGif) {
|
||||
Setting.imageEngine.loadGifAsBitmap(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto);
|
||||
((PhotoViewHolder) holder).tvType.setText(R.string.gif_easy_photos);
|
||||
((PhotoViewHolder) holder).tvType.setVisibility(View.VISIBLE);
|
||||
} else if (Setting.showVideo && type.contains(Type.VIDEO)) {
|
||||
Setting.imageEngine.loadPhoto(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto);
|
||||
((PhotoViewHolder) holder).tvType.setText(DurationUtils.format(duration));
|
||||
((PhotoViewHolder) holder).tvType.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
Setting.imageEngine.loadPhoto(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto);
|
||||
((PhotoViewHolder) holder).tvType.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
((PhotoViewHolder) holder).ivDelete.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onDeleteClick(p);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return null == dataList ? 0 : dataList.size();
|
||||
}
|
||||
|
||||
|
||||
public interface OnClickListener {
|
||||
void onDeleteClick(int position);
|
||||
}
|
||||
|
||||
public class PhotoViewHolder extends RecyclerView.ViewHolder {
|
||||
ImageView ivPhoto;
|
||||
ImageView ivDelete;
|
||||
TextView tvType;
|
||||
|
||||
public PhotoViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
this.ivPhoto = (ImageView) itemView.findViewById(R.id.iv_photo);
|
||||
this.ivDelete = (ImageView) itemView.findViewById(R.id.iv_delete);
|
||||
this.tvType = (TextView) itemView.findViewById(R.id.tv_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.huantansheng.easyphotos.ui.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.huantansheng.easyphotos.R;
|
||||
import com.huantansheng.easyphotos.models.sticker.StickerModel;
|
||||
import com.huantansheng.easyphotos.models.sticker.entity.TextStickerData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author huan
|
||||
*/
|
||||
public class TextStickerAdapter extends RecyclerView.Adapter<TextStickerAdapter.TextViewHolder> {
|
||||
|
||||
private List<TextStickerData> datas;
|
||||
private OnItemClickListener onItemClickListener;
|
||||
|
||||
public TextStickerAdapter(Context cxt, OnItemClickListener listener) {
|
||||
super();
|
||||
this.onItemClickListener = listener;
|
||||
this.datas = new ArrayList<>();
|
||||
TextStickerData data = new TextStickerData(cxt.getString(R.string.text_sticker_hint_name_easy_photos), cxt.getString(R.string.text_sticker_hint_easy_photos));
|
||||
this.datas.add(0, data);
|
||||
TextStickerData d = new TextStickerData(cxt.getString(R.string.text_sticker_date_easy_photos), "-1");
|
||||
datas.add(d);
|
||||
datas.addAll(StickerModel.textDataList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View itemView =
|
||||
LayoutInflater.from(parent.getContext()).inflate(R.layout.item_text_sticker_easy_photos, parent, false);
|
||||
return new TextViewHolder(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(TextViewHolder holder, int position) {
|
||||
final TextStickerData data = datas.get(position);
|
||||
|
||||
holder.tvSticker.setText(data.stickerName);
|
||||
|
||||
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (onItemClickListener != null) {
|
||||
onItemClickListener.onItemClick(data.stickerValue);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return datas == null ? 0 : datas.size();
|
||||
}
|
||||
|
||||
|
||||
public static class TextViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
TextView tvSticker;
|
||||
|
||||
public TextViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
tvSticker = (TextView) itemView.findViewById(R.id.puzzle);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnItemClickListener {
|
||||
void onItemClick(String stickerValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.huantansheng.easyphotos.ui.dialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.huantansheng.easyphotos.R;
|
||||
|
||||
public class LoadingDialog extends Dialog {
|
||||
|
||||
private LoadingDialog(@NonNull Context context) {
|
||||
super(context);
|
||||
Window window = getWindow();
|
||||
if (null != window) {
|
||||
window.requestFeature(Window.FEATURE_NO_TITLE);
|
||||
}
|
||||
}
|
||||
|
||||
public static LoadingDialog get(Context context) {
|
||||
LoadingDialog loading = new LoadingDialog(context);
|
||||
View view = LayoutInflater.from(context).inflate(R.layout.dialog_loading_easy_photos, null);
|
||||
loading.setContentView(view);
|
||||
loading.setCancelable(false);
|
||||
loading.setCanceledOnTouchOutside(false);
|
||||
return loading;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
Window window = getWindow();
|
||||
if (null != window) {
|
||||
WindowManager.LayoutParams attributes = window.getAttributes();
|
||||
window.setBackgroundDrawableResource(R.color.transparent_easy_photos);
|
||||
window.setGravity(Gravity.CENTER);
|
||||
attributes.width = WindowManager.LayoutParams.MATCH_PARENT;
|
||||
attributes.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
window.setAttributes(attributes);
|
||||
}
|
||||
super.show();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.huantansheng.easyphotos.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
/**
|
||||
* 自带点击效果的imageview
|
||||
* Created by huan on 2017/8/15.
|
||||
*/
|
||||
|
||||
public class PressedImageView extends androidx.appcompat.widget.AppCompatImageView {
|
||||
private float scaleSize;//按压颜色
|
||||
|
||||
public PressedImageView(Context context) {
|
||||
super(context);
|
||||
this.scaleSize = 0.97f;
|
||||
}
|
||||
|
||||
public PressedImageView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
this.scaleSize = 0.97f;
|
||||
|
||||
}
|
||||
|
||||
public PressedImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
this.scaleSize = 0.97f;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPressed(boolean pressed) {
|
||||
super.setPressed(pressed);
|
||||
if (isPressed()) {
|
||||
setScaleX(this.scaleSize);
|
||||
setScaleY(this.scaleSize);
|
||||
} else {
|
||||
setScaleX(1.0f);
|
||||
setScaleY(1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void setScaleSize(float scaleSize) {
|
||||
this.scaleSize = scaleSize;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.huantansheng.easyphotos.ui.widget;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
/**
|
||||
* 带点击状态的textview
|
||||
* Created by huan on 2017/9/15.
|
||||
*/
|
||||
|
||||
public class PressedTextView extends androidx.appcompat.widget.AppCompatTextView {
|
||||
private float pressedScale;
|
||||
private AnimatorSet set;
|
||||
private int pressedFlag;
|
||||
|
||||
public PressedTextView(Context context) {
|
||||
super(context);
|
||||
this.pressedScale = 1.1f;
|
||||
this.pressedFlag = 1;
|
||||
}
|
||||
|
||||
public PressedTextView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
this.pressedScale = 1.1f;
|
||||
this.pressedFlag = 1;
|
||||
}
|
||||
|
||||
public PressedTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
this.pressedScale = 1.1f;
|
||||
this.pressedFlag = 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPressed(boolean pressed) {
|
||||
super.setPressed(pressed);
|
||||
if (isPressed()) {
|
||||
pressedFlag = 1;
|
||||
if (null == set) {
|
||||
set = new AnimatorSet();
|
||||
set.setDuration(5);
|
||||
}
|
||||
if (set.isRunning()) set.cancel();
|
||||
ObjectAnimator pScaleX = ObjectAnimator.ofFloat(this, "scaleX", 1.0f, pressedScale);
|
||||
ObjectAnimator pScaleY = ObjectAnimator.ofFloat(this, "scaleY", 1.0f, pressedScale);
|
||||
set.play(pScaleX).with(pScaleY);
|
||||
set.start();
|
||||
} else {
|
||||
if (pressedFlag != 1) {
|
||||
return;
|
||||
}
|
||||
pressedFlag = 2;
|
||||
if (null == set) {
|
||||
set = new AnimatorSet();
|
||||
set.setDuration(5);
|
||||
}
|
||||
if (set.isRunning()) set.cancel();
|
||||
ObjectAnimator nScaleX = ObjectAnimator.ofFloat(this, "scaleX", pressedScale, 1.0f);
|
||||
ObjectAnimator nScaleY = ObjectAnimator.ofFloat(this, "scaleY", pressedScale, 1.0f);
|
||||
set.play(nScaleX).with(nScaleY);
|
||||
set.start();
|
||||
}
|
||||
}
|
||||
|
||||
public void setPressedScale(float pressedScale) {
|
||||
this.pressedScale = pressedScale;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.huantansheng.easyphotos.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
/**
|
||||
* 图片预览 RecyclerView
|
||||
* Create By lishilin On 2019/3/25
|
||||
*/
|
||||
public class PreviewRecyclerView extends RecyclerView {
|
||||
|
||||
private boolean isLock;// 是否锁住 RecyclerView ,避免和 PhotoView 双指放大缩小操作冲突
|
||||
|
||||
public PreviewRecyclerView(@NonNull Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public PreviewRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public PreviewRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent event) {
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_POINTER_DOWN:// 非第一个触点按下
|
||||
isLock = true;
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:// 最后一个触点抬起
|
||||
isLock = false;
|
||||
break;
|
||||
}
|
||||
if (isLock) {
|
||||
return false;// 不拦截,交给子View处理
|
||||
}
|
||||
return super.onInterceptTouchEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(MotionEvent event) {
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_POINTER_DOWN:// 非第一个触点按下
|
||||
isLock = true;
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:// 最后一个触点抬起
|
||||
isLock = false;
|
||||
break;
|
||||
}
|
||||
return super.dispatchTouchEvent(event);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.huantansheng.easyphotos.utils.Color;
|
||||
|
||||
/**
|
||||
* Created by huan on 2018/1/9.
|
||||
*/
|
||||
|
||||
public class ColorUtils {
|
||||
|
||||
/**
|
||||
* 判断颜色是否偏黑色
|
||||
*
|
||||
* @param color 颜色
|
||||
* @return
|
||||
*/
|
||||
public static boolean isBlackColor(int color) {
|
||||
int grey = toGrey(color);
|
||||
return grey < 50;
|
||||
}
|
||||
|
||||
/**
|
||||
* 颜色转换成灰度值
|
||||
*
|
||||
* @param rgb 颜色
|
||||
* @return 灰度值
|
||||
*/
|
||||
public static int toGrey(int rgb) {
|
||||
int blue = rgb & 0x000000FF;
|
||||
int green = (rgb & 0x0000FF00) >> 8;
|
||||
int red = (rgb & 0x00FF0000) >> 16;
|
||||
return (red * 38 + green * 75 + blue * 15) >> 7;
|
||||
}
|
||||
|
||||
public static boolean isWhiteColor(int color) {
|
||||
int grey = toGrey(color);
|
||||
return grey > 200;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.huantansheng.easyphotos.utils.String;
|
||||
|
||||
/**
|
||||
* 字符串工具类
|
||||
* Created by huan on 2017/10/20.
|
||||
*/
|
||||
public class StringUtils {
|
||||
public static String getLastPathSegment(String content) {
|
||||
if (content == null || content.length() == 0) {
|
||||
return "";
|
||||
}
|
||||
String[] segments = content.split("/");
|
||||
if (segments.length > 0) {
|
||||
return segments[segments.length - 1];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
package com.huantansheng.easyphotos.utils.bitmap;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.TextPaint;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.exifinterface.media.ExifInterface;
|
||||
|
||||
import com.huantansheng.easyphotos.EasyPhotos;
|
||||
import com.huantansheng.easyphotos.models.album.entity.Photo;
|
||||
import com.huantansheng.easyphotos.utils.uri.UriUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* bitmap工具类
|
||||
* Created by huan on 2017/9/4.
|
||||
*/
|
||||
|
||||
public class BitmapUtils {
|
||||
/**
|
||||
* 回收bitmap
|
||||
*
|
||||
* @param bitmap 回收的bitmap
|
||||
*/
|
||||
public static void recycle(Bitmap bitmap) {
|
||||
if (null != bitmap && !bitmap.isRecycled()) {
|
||||
bitmap.recycle();
|
||||
bitmap = null;
|
||||
}
|
||||
System.gc();
|
||||
}
|
||||
|
||||
public static void recycle(Bitmap... bitmaps) {
|
||||
for (Bitmap b : bitmaps) {
|
||||
recycle(b);
|
||||
}
|
||||
}
|
||||
|
||||
public static void recycle(List<Bitmap> bitmaps) {
|
||||
for (Bitmap b : bitmaps) {
|
||||
recycle(b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 给图片添加水印,水印会根据图片宽高自动缩放处理
|
||||
*
|
||||
* @param watermark 水印
|
||||
* @param image 添加水印的图片
|
||||
* @param offsetX 添加水印的X轴偏移量
|
||||
* @param offsetY 添加水印的Y轴偏移量
|
||||
* @param srcWaterMarkImageWidth 水印对应的原图片宽度,即ui制作水印时候参考的图片画布宽度,应该是已知的图片最大宽度
|
||||
* @param addInLeft true 在左下角添加水印,false 在右下角添加水印
|
||||
* @param orientation Bitmap的旋转角度。当useWidth为true时,Photo实体类中会有orientation,若bitmap
|
||||
* 不是用户手机内图片,填0即可。
|
||||
* @return 添加水印后的bitmap
|
||||
*/
|
||||
public static Bitmap addWatermark(Bitmap watermark, Bitmap image, int srcWaterMarkImageWidth,
|
||||
int offsetX, int offsetY, boolean addInLeft, int orientation) {
|
||||
int imageWidth = image.getWidth();
|
||||
int imageHeight = image.getHeight();
|
||||
if (0 == imageWidth || 0 == imageHeight) {
|
||||
throw new RuntimeException("AlbumBuilder: 加水印的原图宽或高不能为0!");
|
||||
}
|
||||
switch (orientation) {
|
||||
case 90:
|
||||
case 270:
|
||||
image = adjustBitmapRotation(image, orientation);
|
||||
int temp = imageHeight;
|
||||
imageHeight = imageWidth;
|
||||
imageWidth = temp;
|
||||
break;
|
||||
case 180:
|
||||
image = adjustBitmapRotation(image, orientation);
|
||||
break;
|
||||
|
||||
}
|
||||
int watermarkWidth = watermark.getWidth();
|
||||
int watermarkHeight = watermark.getHeight();
|
||||
float scale = imageWidth / (float) srcWaterMarkImageWidth;
|
||||
if (scale > 1) scale = 1;
|
||||
else if (scale < 0.4) scale = 0.4f;
|
||||
int scaleWatermarkWidth = (int) (watermarkWidth * scale);
|
||||
int scaleWatermarkHeight = (int) (watermarkHeight * scale);
|
||||
Bitmap scaleWatermark = Bitmap.createScaledBitmap(watermark, scaleWatermarkWidth,
|
||||
scaleWatermarkHeight, true);
|
||||
Canvas canvas = new Canvas(image);
|
||||
Paint paint = new Paint();
|
||||
paint.setAntiAlias(true);
|
||||
if (addInLeft) {
|
||||
canvas.drawBitmap(scaleWatermark, offsetX,
|
||||
imageHeight - scaleWatermarkHeight - offsetY, paint);
|
||||
} else {
|
||||
canvas.drawBitmap(scaleWatermark, imageWidth - offsetX - scaleWatermarkWidth,
|
||||
imageHeight - scaleWatermarkHeight - offsetY, paint);
|
||||
}
|
||||
recycle(scaleWatermark);
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给图片添加带文字和图片的水印,水印会根据图片宽高自动缩放处理
|
||||
*
|
||||
* @param watermark 水印图片
|
||||
* @param image 要加水印的图片
|
||||
* @param srcWaterMarkImageWidth 水印对应的原图片宽度,即ui制作水印时候参考的图片画布宽度,应该是已知的图片最大宽度
|
||||
* @param text 要添加的文字
|
||||
* @param offsetX 添加水印的X轴偏移量
|
||||
* @param offsetY 添加水印的Y轴偏移量
|
||||
* @param addInLeft true 在左下角添加水印,false 在右下角添加水印
|
||||
* @param orientation Bitmap的旋转角度。当useWidth为true时,Photo实体类中会有orientation,若bitmap
|
||||
* 不是用户手机内图片,填0即可。
|
||||
* @return 添加水印后的bitmap
|
||||
*/
|
||||
public static Bitmap addWatermarkWithText(@NonNull Bitmap watermark, Bitmap image,
|
||||
int srcWaterMarkImageWidth, @NonNull String text,
|
||||
int offsetX, int offsetY, boolean addInLeft,
|
||||
int orientation) {
|
||||
float imageWidth = image.getWidth();
|
||||
float imageHeight = image.getHeight();
|
||||
if (0 == imageWidth || 0 == imageHeight) {
|
||||
throw new RuntimeException("AlbumBuilder: 加水印的原图宽或高不能为0!");
|
||||
}
|
||||
switch (orientation) {
|
||||
case 90:
|
||||
case 270:
|
||||
image = adjustBitmapRotation(image, orientation);
|
||||
float temp = imageHeight;
|
||||
imageHeight = imageWidth;
|
||||
imageWidth = temp;
|
||||
break;
|
||||
case 180:
|
||||
image = adjustBitmapRotation(image, orientation);
|
||||
break;
|
||||
|
||||
}
|
||||
float watermarkWidth = watermark.getWidth();
|
||||
float watermarkHeight = watermark.getHeight();
|
||||
float scale = imageWidth / (float) srcWaterMarkImageWidth;
|
||||
if (scale > 1) scale = 1;
|
||||
else if (scale < 0.4) scale = 0.4f;
|
||||
float scaleWatermarkWidth = watermarkWidth * scale;
|
||||
float scaleWatermarkHeight = watermarkHeight * scale;
|
||||
Bitmap scaleWatermark = Bitmap.createScaledBitmap(watermark, (int) scaleWatermarkWidth,
|
||||
(int) scaleWatermarkHeight, true);
|
||||
Canvas canvas = new Canvas(image);
|
||||
|
||||
Paint textPaint = new TextPaint();
|
||||
textPaint.setAntiAlias(true);
|
||||
textPaint.setColor(Color.WHITE);
|
||||
float textsize = (float) (scaleWatermark.getHeight() * 2) / (float) 3;
|
||||
textPaint.setTextSize(textsize);
|
||||
Rect textRect = new Rect();
|
||||
textPaint.getTextBounds(text, 0, text.length(), textRect);
|
||||
if (addInLeft) {
|
||||
canvas.drawText(text, scaleWatermarkWidth + offsetX,
|
||||
imageHeight - textRect.height() - textRect.top - offsetY, textPaint);
|
||||
} else {
|
||||
canvas.drawText(text, imageWidth - offsetX - textRect.width() - textRect.left,
|
||||
imageHeight - textRect.height() - textRect.top - offsetY, textPaint);
|
||||
}
|
||||
|
||||
Paint sacleWatermarkPaint = new Paint();
|
||||
sacleWatermarkPaint.setAntiAlias(true);
|
||||
if (addInLeft) {
|
||||
canvas.drawBitmap(scaleWatermark, offsetX,
|
||||
imageHeight - textRect.height() - offsetY - scaleWatermarkHeight / 6,
|
||||
sacleWatermarkPaint);
|
||||
} else {
|
||||
canvas.drawBitmap(scaleWatermark,
|
||||
imageWidth - textRect.width() - offsetX - scaleWatermarkWidth / 6,
|
||||
imageHeight - textRect.height() - offsetY - scaleWatermarkHeight / 6,
|
||||
sacleWatermarkPaint);
|
||||
}
|
||||
recycle(scaleWatermark);
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 保存Bitmap到指定文件夹
|
||||
*
|
||||
* @param act 上下文
|
||||
* @param dirPath 文件夹全路径
|
||||
* @param bitmap bitmap
|
||||
* @param namePrefix 保存文件的前缀名,文件最终名称格式为:前缀名+自动生成的唯一数字字符+.png
|
||||
* @param notifyMedia 是否更新到媒体库
|
||||
* @param callBack 保存图片后的回调,回调已经处于UI线程
|
||||
*/
|
||||
public static void saveBitmapToDir(final Activity act, final String dirPath,
|
||||
final String namePrefix, final Bitmap bitmap,
|
||||
final boolean notifyMedia,
|
||||
final SaveBitmapCallBack callBack) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
|
||||
//android10+
|
||||
saveBitmapToDirQ(act, dirPath, namePrefix, bitmap, notifyMedia, callBack);
|
||||
return;
|
||||
}
|
||||
|
||||
File dirF = new File(dirPath);
|
||||
if (!dirF.exists() || !dirF.isDirectory()) {
|
||||
if (!dirF.mkdirs()) {
|
||||
act.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callBack.onCreateDirFailed();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
final File writeFile = File.createTempFile(namePrefix, ".png", dirF);
|
||||
|
||||
FileOutputStream fos = null;
|
||||
fos = new FileOutputStream(writeFile);
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
|
||||
fos.flush();
|
||||
fos.close();
|
||||
if (notifyMedia) {
|
||||
EasyPhotos.notifyMedia(act, writeFile);
|
||||
}
|
||||
act.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callBack.onSuccess(writeFile);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (final IOException e) {
|
||||
act.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callBack.onIOFailed(e);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
}
|
||||
|
||||
private static void saveBitmapToDirQ(final Activity act, final String dirPath,
|
||||
final String namePrefix, final Bitmap bitmap,
|
||||
final boolean notifyMedia,
|
||||
final SaveBitmapCallBack callBack) {
|
||||
long dataTake = System.currentTimeMillis();
|
||||
String jpegName = namePrefix + dataTake + ".png";
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(MediaStore.Images.Media.DISPLAY_NAME, jpegName);
|
||||
values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
|
||||
int dirIndex = dirPath.lastIndexOf("/");
|
||||
if (dirIndex == dirPath.length()) {
|
||||
String dirPath2 = dirPath.substring(0, dirIndex - 1);
|
||||
dirIndex = dirPath2.lastIndexOf("/");
|
||||
}
|
||||
String dirName = dirPath.substring(dirIndex + 1);
|
||||
values.put(MediaStore.Images.Media.RELATIVE_PATH, "DCIM/" + dirName);
|
||||
|
||||
Uri external;
|
||||
ContentResolver resolver = act.getContentResolver();
|
||||
String status = Environment.getExternalStorageState();
|
||||
// 判断是否有SD卡,优先使用SD卡存储,当没有SD卡时使用手机存储
|
||||
if (status.equals(Environment.MEDIA_MOUNTED)) {
|
||||
external = MediaStore.Images.Media.getContentUri("external");
|
||||
} else {
|
||||
external = MediaStore.Images.Media.INTERNAL_CONTENT_URI;
|
||||
}
|
||||
|
||||
final Uri insertUri = resolver.insert(external, values);
|
||||
if (insertUri == null) {
|
||||
act.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callBack.onCreateDirFailed();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
OutputStream os;
|
||||
try {
|
||||
os = resolver.openOutputStream(insertUri);
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
|
||||
if (os != null) {
|
||||
os.flush();
|
||||
os.close();
|
||||
}
|
||||
act.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String uriPath = UriUtils.getPathByUri(act, insertUri);
|
||||
if (null == uriPath) {
|
||||
callBack.onCreateDirFailed();
|
||||
} else {
|
||||
callBack.onSuccess(new File(uriPath));
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (final IOException e) {
|
||||
e.printStackTrace();
|
||||
act.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callBack.onIOFailed(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 把View画成Bitmap
|
||||
*
|
||||
* @param view 要处理的View
|
||||
* @return Bitmap
|
||||
*/
|
||||
public static Bitmap createBitmapFromView(View view) {
|
||||
Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
|
||||
Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
view.draw(canvas);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算获取的照片的宽高是否需要交换,如果图片是旋转了90度或270度的,那么就需要交换
|
||||
*
|
||||
* @param photo 需要计算的图片
|
||||
* @return 宽高是否需要交换
|
||||
*/
|
||||
public static Boolean needChangeWidthAndHeight(Photo photo) throws IOException {
|
||||
|
||||
ExifInterface exifInterface = new ExifInterface(photo.path);
|
||||
int exifOrientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,
|
||||
ExifInterface.ORIENTATION_NORMAL);
|
||||
|
||||
// 如果拿到的照片是旋转了90度或270度的,意味着通过MediaStore获取的宽高需要交换
|
||||
return exifOrientation == ExifInterface.ORIENTATION_ROTATE_90 || exifOrientation == ExifInterface.ORIENTATION_ROTATE_270;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过BitmapFactory.Options重设图片的宽高,在通过MediaStore获取的图片宽高为0时使用
|
||||
*
|
||||
* @param photo 需要计算的图片
|
||||
*/
|
||||
public static void calculateLocalImageSizeThroughBitmapOptions(Photo photo) throws IOException {
|
||||
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFile(photo.path, options);
|
||||
|
||||
photo.width = options.outWidth;
|
||||
photo.height = options.outHeight;
|
||||
}
|
||||
|
||||
public static Bitmap adjustBitmapRotation(Bitmap bm, final int orientationDegree) {
|
||||
|
||||
Matrix m = new Matrix();
|
||||
m.setRotate(orientationDegree, (float) bm.getWidth() / 2, (float) bm.getHeight() / 2);
|
||||
float targetX, targetY;
|
||||
if (orientationDegree == 90) {
|
||||
targetX = bm.getHeight();
|
||||
targetY = 0;
|
||||
} else {
|
||||
targetX = bm.getHeight();
|
||||
targetY = bm.getWidth();
|
||||
}
|
||||
|
||||
final float[] values = new float[9];
|
||||
m.getValues(values);
|
||||
|
||||
float x1 = values[Matrix.MTRANS_X];
|
||||
float y1 = values[Matrix.MTRANS_Y];
|
||||
|
||||
m.postTranslate(targetX - x1, targetY - y1);
|
||||
|
||||
Bitmap bm1 = Bitmap.createBitmap(bm.getHeight(), bm.getWidth(), Bitmap.Config.ARGB_8888);
|
||||
|
||||
Paint paint = new Paint();
|
||||
Canvas canvas = new Canvas(bm1);
|
||||
canvas.drawBitmap(bm, m, paint);
|
||||
|
||||
|
||||
return bm1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.huantansheng.easyphotos.utils.bitmap;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 保存图片到本地的回调
|
||||
* Created by huan on 2017/12/6.
|
||||
*/
|
||||
|
||||
public interface SaveBitmapCallBack {
|
||||
void onSuccess(File file);
|
||||
|
||||
void onIOFailed(IOException exception);
|
||||
|
||||
void onCreateDirFailed();
|
||||
}
|
||||
@@ -0,0 +1,633 @@
|
||||
package com.huantansheng.easyphotos.utils.file;
|
||||
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.util.Log;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
|
||||
public class FileUtils {
|
||||
public static final String DOCUMENTS_DIR = "documents";
|
||||
// configured android:authorities in AndroidManifest (https://developer.android
|
||||
// .com/reference/android/support/v4/content/FileProvider)
|
||||
public static final String HIDDEN_PREFIX = ".";
|
||||
/**
|
||||
* TAG for log messages.
|
||||
*/
|
||||
static final String TAG = "FileUtils";
|
||||
private static final boolean DEBUG = false; // Set to true to enable logging
|
||||
/**
|
||||
* File and folder comparator. TODO Expose sorting option method
|
||||
*/
|
||||
|
||||
/**
|
||||
* Folder (directories) filter.
|
||||
*/
|
||||
|
||||
|
||||
private FileUtils() {
|
||||
} //private constructor to enforce Singleton pattern
|
||||
|
||||
/**
|
||||
* Gets the extension of a file name, like ".png" or ".jpg".
|
||||
*
|
||||
* @param uri
|
||||
* @return Extension including the dot("."); "" if there is no extension;
|
||||
* null if uri was null.
|
||||
*/
|
||||
public static String getExtension(String uri) {
|
||||
if (uri == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int dot = uri.lastIndexOf(".");
|
||||
if (dot >= 0) {
|
||||
return uri.substring(dot);
|
||||
} else {
|
||||
// No extension.
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the URI is a local one.
|
||||
*/
|
||||
public static boolean isLocal(String url) {
|
||||
return url != null && !url.startsWith("http://") && !url.startsWith("https://");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if Uri is a MediaStore Uri.
|
||||
* @author paulburke
|
||||
*/
|
||||
public static boolean isMediaUri(Uri uri) {
|
||||
return "media".equalsIgnoreCase(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert File into Uri.
|
||||
*
|
||||
* @param file
|
||||
* @return uri
|
||||
*/
|
||||
public static Uri getUri(File file) {
|
||||
return (file != null) ? Uri.fromFile(file) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path only (without file name).
|
||||
*
|
||||
* @param file
|
||||
* @return
|
||||
*/
|
||||
public static File getPathWithoutFilename(File file) {
|
||||
if (file != null) {
|
||||
if (file.isDirectory()) {
|
||||
// no file to be split off. Return everything
|
||||
return file;
|
||||
} else {
|
||||
String filename = file.getName();
|
||||
String filepath = file.getAbsolutePath();
|
||||
|
||||
// Construct path without file name.
|
||||
String pathwithoutname = filepath.substring(0,
|
||||
filepath.length() - filename.length());
|
||||
if (pathwithoutname.endsWith("/")) {
|
||||
pathwithoutname = pathwithoutname.substring(0, pathwithoutname.length() - 1);
|
||||
}
|
||||
return new File(pathwithoutname);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The MIME type for the given file.
|
||||
*/
|
||||
public static String getMimeType(File file) {
|
||||
|
||||
String extension = getExtension(file.getName());
|
||||
|
||||
if (extension.length() > 0)
|
||||
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.substring(1));
|
||||
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The MIME type for the give Uri.
|
||||
*/
|
||||
public static String getMimeType(Context context, Uri uri) {
|
||||
File file = new File(getPath(context, uri));
|
||||
return getMimeType(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The MIME type for the give String Uri.
|
||||
*/
|
||||
public static String getMimeType(Context context, String url) {
|
||||
String type = context.getContentResolver().getType(Uri.parse(url));
|
||||
if (type == null) {
|
||||
type = "application/octet-stream";
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is local.
|
||||
*/
|
||||
public static boolean isLocalStorageDocument(Context cxt, Uri uri) {
|
||||
return (cxt.getApplicationContext().getPackageName() + ".fileprovider").equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* @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());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is Google Photos.
|
||||
*/
|
||||
public static boolean isGooglePhotosUri(Uri uri) {
|
||||
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = MediaStore.Files.FileColumns.DATA;
|
||||
final String[] projection = {column};
|
||||
|
||||
try {
|
||||
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs
|
||||
, null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
if (DEBUG) DatabaseUtils.dumpCursor(cursor);
|
||||
|
||||
final int column_index = cursor.getColumnIndexOrThrow(column);
|
||||
return cursor.getString(column_index);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (cursor != null) cursor.close();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.<br>
|
||||
* <br>
|
||||
* Callers should check whether the path is local before assuming it
|
||||
* represents a local file.
|
||||
*
|
||||
* @param context The context.
|
||||
* @param uri The Uri to query.
|
||||
* @see #getFile(Context, Uri)
|
||||
*/
|
||||
public static String getPath(final Context context, final Uri uri) {
|
||||
String absolutePath = getLocalPath(context, uri);
|
||||
return absolutePath != null ? absolutePath : uri.toString();
|
||||
}
|
||||
|
||||
private static String getLocalPath(final Context context, final Uri uri) {
|
||||
|
||||
if (DEBUG)
|
||||
Log.d(TAG + " File -",
|
||||
"Authority: " + uri.getAuthority() + ", Fragment: " + uri.getFragment() + ", "
|
||||
+ "Port: " + uri.getPort() + ", Query: " + uri.getQuery() + ", Scheme"
|
||||
+ ": " + uri.getScheme() + ", Host: " + uri.getHost() + ", Segments: "
|
||||
+ uri.getPathSegments().toString());
|
||||
|
||||
|
||||
// DocumentProvider
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, uri)) {
|
||||
// LocalStorageProvider
|
||||
if (isLocalStorageDocument(context, uri)) {
|
||||
// The path is the id
|
||||
return DocumentsContract.getDocumentId(uri);
|
||||
}
|
||||
// ExternalStorageProvider
|
||||
else if (isExternalStorageDocument(uri)) {
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
final String[] split = docId.split(":");
|
||||
final String type = split[0];
|
||||
|
||||
if ("primary".equalsIgnoreCase(type)) {
|
||||
return Environment.getExternalStorageDirectory() + "/" + split[1];
|
||||
} else if ("home".equalsIgnoreCase(type)) {
|
||||
return Environment.getExternalStorageDirectory() + "/documents/" + split[1];
|
||||
}
|
||||
}
|
||||
// DownloadsProvider
|
||||
else if (isDownloadsDocument(uri)) {
|
||||
|
||||
final String id = DocumentsContract.getDocumentId(uri);
|
||||
|
||||
if (id != null && id.startsWith("raw:")) {
|
||||
return id.substring(4);
|
||||
}
|
||||
|
||||
String[] contentUriPrefixesToTry = new String[]{"content://downloads" +
|
||||
"/public_downloads", "content://downloads/my_downloads"};
|
||||
|
||||
for (String contentUriPrefix : contentUriPrefixesToTry) {
|
||||
Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix),
|
||||
Long.valueOf(id));
|
||||
try {
|
||||
String path = getDataColumn(context, contentUri, null, null);
|
||||
if (path != null) {
|
||||
return path;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
// path could not be retrieved using ContentResolver, therefore copy file to
|
||||
// accessible cache using streams
|
||||
String fileName = getFileName(context, uri);
|
||||
File cacheDir = getDocumentCacheDir(context);
|
||||
File file = generateFileName(fileName, cacheDir);
|
||||
String destinationPath = null;
|
||||
if (file != null) {
|
||||
destinationPath = file.getAbsolutePath();
|
||||
saveFileFromUri(context, uri, destinationPath);
|
||||
}
|
||||
|
||||
return destinationPath;
|
||||
}
|
||||
// MediaProvider
|
||||
else if (isMediaDocument(uri)) {
|
||||
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.getContentUri("external");
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
// MediaStore (and general)
|
||||
else if ("content".equalsIgnoreCase(uri.getScheme())) {
|
||||
|
||||
// Return the remote address
|
||||
if (isGooglePhotosUri(uri)) {
|
||||
return uri.getLastPathSegment();
|
||||
}
|
||||
|
||||
return getDataColumn(context, uri, null, null);
|
||||
}
|
||||
// File
|
||||
else if ("file".equalsIgnoreCase(uri.getScheme())) {
|
||||
return uri.getPath();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Uri into File, if possible.
|
||||
*
|
||||
* @return file A local file that the Uri was pointing to, or null if the
|
||||
* Uri is unsupported or pointed to a remote resource.
|
||||
* @author paulburke
|
||||
* @see #getPath(Context, Uri)
|
||||
*/
|
||||
public static File getFile(Context context, Uri uri) {
|
||||
if (uri != null) {
|
||||
String path = getPath(context, uri);
|
||||
if (path != null && isLocal(path)) {
|
||||
return new File(path);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file size in a human-readable string.
|
||||
*
|
||||
* @param size
|
||||
* @return
|
||||
* @author paulburke
|
||||
*/
|
||||
public static String getReadableFileSize(int size) {
|
||||
final int BYTES_IN_KILOBYTES = 1024;
|
||||
final DecimalFormat dec = new DecimalFormat("###.#");
|
||||
final String KILOBYTES = " KB";
|
||||
final String MEGABYTES = " MB";
|
||||
final String GIGABYTES = " GB";
|
||||
float fileSize = 0;
|
||||
String suffix = KILOBYTES;
|
||||
|
||||
if (size > BYTES_IN_KILOBYTES) {
|
||||
fileSize = size / BYTES_IN_KILOBYTES;
|
||||
if (fileSize > BYTES_IN_KILOBYTES) {
|
||||
fileSize = fileSize / BYTES_IN_KILOBYTES;
|
||||
if (fileSize > BYTES_IN_KILOBYTES) {
|
||||
fileSize = fileSize / BYTES_IN_KILOBYTES;
|
||||
suffix = GIGABYTES;
|
||||
} else {
|
||||
suffix = MEGABYTES;
|
||||
}
|
||||
}
|
||||
}
|
||||
return String.valueOf(dec.format(fileSize) + suffix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Intent for selecting content to be used in an Intent Chooser.
|
||||
*
|
||||
* @return The intent for opening a file with Intent.createChooser()
|
||||
*/
|
||||
public static Intent createGetContentIntent() {
|
||||
// Implicitly allow the user to select a particular kind of data
|
||||
final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
// The MIME data type filter
|
||||
intent.setType("*/*");
|
||||
// Only return URIs that can be opened with ContentResolver
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
return intent;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates View intent for given file
|
||||
*
|
||||
* @param file
|
||||
* @return The intent for viewing file
|
||||
*/
|
||||
public static Intent getViewIntent(Context context, File file) {
|
||||
//Uri uri = Uri.fromFile(file);
|
||||
Uri uri = FileProvider.getUriForFile(context,
|
||||
(context.getApplicationContext().getPackageName() + ".fileprovider"), file);
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
String url = file.toString();
|
||||
if (url.contains(".doc") || url.contains(".docx")) {
|
||||
// Word document
|
||||
intent.setDataAndType(uri, "application/msword");
|
||||
} else if (url.contains(".pdf")) {
|
||||
// PDF file
|
||||
intent.setDataAndType(uri, "application/pdf");
|
||||
} else if (url.contains(".ppt") || url.contains(".pptx")) {
|
||||
// Powerpoint file
|
||||
intent.setDataAndType(uri, "application/vnd.ms-powerpoint");
|
||||
} else if (url.contains(".xls") || url.contains(".xlsx")) {
|
||||
// Excel file
|
||||
intent.setDataAndType(uri, "application/vnd.ms-excel");
|
||||
} else if (url.contains(".zip") || url.contains(".rar")) {
|
||||
// WAV audio file
|
||||
intent.setDataAndType(uri, "application/x-wav");
|
||||
} else if (url.contains(".rtf")) {
|
||||
// RTF file
|
||||
intent.setDataAndType(uri, "application/rtf");
|
||||
} else if (url.contains(".wav") || url.contains(".mp3")) {
|
||||
// WAV audio file
|
||||
intent.setDataAndType(uri, "audio/x-wav");
|
||||
} else if (url.contains(".gif")) {
|
||||
// GIF file
|
||||
intent.setDataAndType(uri, "image/gif");
|
||||
} else if (url.contains(".jpg") || url.contains(".jpeg") || url.contains(".png")) {
|
||||
// JPG file
|
||||
intent.setDataAndType(uri, "image/jpeg");
|
||||
} else if (url.contains(".txt")) {
|
||||
// Text file
|
||||
intent.setDataAndType(uri, "text/plain");
|
||||
} else if (url.contains(".3gp") || url.contains(".mpg") || url.contains(".mpeg") || url.contains(".mpe") || url.contains(".mp4") || url.contains(".avi")) {
|
||||
// Video files
|
||||
intent.setDataAndType(uri, "video/*");
|
||||
} else {
|
||||
intent.setDataAndType(uri, "*/*");
|
||||
}
|
||||
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static File getDownloadsDir() {
|
||||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
}
|
||||
|
||||
public static File getDocumentCacheDir(@NonNull Context context) {
|
||||
File dir = new File(context.getCacheDir(), DOCUMENTS_DIR);
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
logDir(context.getCacheDir());
|
||||
logDir(dir);
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
private static void logDir(File dir) {
|
||||
if (!DEBUG) return;
|
||||
Log.d(TAG, "Dir=" + dir);
|
||||
File[] files = dir.listFiles();
|
||||
for (File file : files) {
|
||||
Log.d(TAG, "File=" + file.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File generateFileName(@Nullable String name, File directory) {
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
File file = new File(directory, name);
|
||||
|
||||
if (file.exists()) {
|
||||
String fileName = name;
|
||||
String extension = "";
|
||||
int dotIndex = name.lastIndexOf('.');
|
||||
if (dotIndex > 0) {
|
||||
fileName = name.substring(0, dotIndex);
|
||||
extension = name.substring(dotIndex);
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
|
||||
while (file.exists()) {
|
||||
index++;
|
||||
name = fileName + '(' + index + ')' + extension;
|
||||
file = new File(directory, name);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (!file.createNewFile()) {
|
||||
return null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
logDir(directory);
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
private static void saveFileFromUri(Context context, Uri uri, String destinationPath) {
|
||||
InputStream is = null;
|
||||
BufferedOutputStream bos = null;
|
||||
try {
|
||||
is = context.getContentResolver().openInputStream(uri);
|
||||
bos = new BufferedOutputStream(new FileOutputStream(destinationPath, false));
|
||||
byte[] buf = new byte[1024];
|
||||
is.read(buf);
|
||||
do {
|
||||
bos.write(buf);
|
||||
} while (is.read(buf) != -1);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
if (is != null) is.close();
|
||||
if (bos != null) bos.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] readBytesFromFile(String filePath) {
|
||||
|
||||
FileInputStream fileInputStream = null;
|
||||
byte[] bytesArray = null;
|
||||
|
||||
try {
|
||||
|
||||
File file = new File(filePath);
|
||||
bytesArray = new byte[(int) file.length()];
|
||||
|
||||
//read file into bytes[]
|
||||
fileInputStream = new FileInputStream(file);
|
||||
fileInputStream.read(bytesArray);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (fileInputStream != null) {
|
||||
try {
|
||||
fileInputStream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return bytesArray;
|
||||
|
||||
}
|
||||
|
||||
public static File createTempImageFile(Context context, String fileName) throws IOException {
|
||||
// Create an image file name
|
||||
File storageDir = new File(context.getCacheDir(), DOCUMENTS_DIR);
|
||||
return File.createTempFile(fileName, ".jpg", storageDir);
|
||||
}
|
||||
|
||||
public static String getFileName(@NonNull Context context, Uri uri) {
|
||||
String mimeType = context.getContentResolver().getType(uri);
|
||||
String filename = null;
|
||||
|
||||
if (mimeType == null && context != null) {
|
||||
String path = getPath(context, uri);
|
||||
if (path == null) {
|
||||
filename = getName(uri.toString());
|
||||
} else {
|
||||
File file = new File(path);
|
||||
filename = file.getName();
|
||||
}
|
||||
} else {
|
||||
Cursor returnCursor = context.getContentResolver().query(uri, null, null, null, null);
|
||||
if (returnCursor != null) {
|
||||
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
|
||||
returnCursor.moveToFirst();
|
||||
filename = returnCursor.getString(nameIndex);
|
||||
returnCursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
public static String getName(String filename) {
|
||||
if (filename == null) {
|
||||
return null;
|
||||
}
|
||||
int index = filename.lastIndexOf('/');
|
||||
return filename.substring(index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.huantansheng.easyphotos.utils.media;
|
||||
|
||||
import android.media.MediaMetadataRetriever;
|
||||
import android.text.format.DateUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* DurationUtils
|
||||
* Create By lishilin On 2019/3/25
|
||||
*/
|
||||
public class DurationUtils {
|
||||
|
||||
/**
|
||||
* 获取时长
|
||||
*
|
||||
* @param path path
|
||||
* @return duration
|
||||
*/
|
||||
public static long getDuration(String path) {
|
||||
MediaMetadataRetriever mmr = null;
|
||||
try {
|
||||
mmr = new MediaMetadataRetriever();
|
||||
mmr.setDataSource(path);
|
||||
return Long.parseLong(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
|
||||
} catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
} finally {
|
||||
if (mmr != null) {
|
||||
try {
|
||||
mmr.release();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时长(不足一秒则显示为一秒)
|
||||
*
|
||||
* @param duration duration
|
||||
* @return "MM:SS" or "H:MM:SS"
|
||||
*/
|
||||
public static String format(long duration) {
|
||||
long seconds = duration / 1000;
|
||||
if (seconds == 0) {
|
||||
seconds++;
|
||||
}
|
||||
return DateUtils.formatElapsedTime(seconds);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.huantansheng.easyphotos.utils.media;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.MediaScannerConnection;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 更新媒体库
|
||||
* Created by huan on 2017/8/1.
|
||||
*/
|
||||
|
||||
public class MediaScannerConnectionUtils {
|
||||
|
||||
public static void refresh(Context cxt, String... filePaths) {
|
||||
MediaScannerConnection.scanFile(cxt.getApplicationContext(),
|
||||
filePaths, null,
|
||||
null);
|
||||
}
|
||||
|
||||
|
||||
public static void refresh(Context cxt, File... files) {
|
||||
for (File file : files) {
|
||||
String filePath = file.getAbsolutePath();
|
||||
refresh(cxt, filePath);
|
||||
}
|
||||
}
|
||||
|
||||
public static void refresh(Context cxt, List<String> filePathList) {
|
||||
for (String filePath : filePathList) {
|
||||
refresh(cxt, filePath);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.huantansheng.easyphotos.utils.permission;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.PermissionChecker;
|
||||
|
||||
import com.huantansheng.easyphotos.constant.Code;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 运行时权限工具类
|
||||
* Created by huan on 2017/7/27.
|
||||
*/
|
||||
|
||||
public class PermissionUtil {
|
||||
|
||||
|
||||
public interface PermissionCallBack {
|
||||
void onSuccess();
|
||||
|
||||
void onShouldShow();
|
||||
|
||||
void onFailed();
|
||||
}
|
||||
|
||||
public static boolean checkAndRequestPermissionsInActivity(Activity cxt,
|
||||
String... checkPermissions) {
|
||||
boolean isHas = true;
|
||||
List<String> permissions = new ArrayList<>();
|
||||
int size = checkPermissions.length;
|
||||
for (int i = 0; i < size; i++) {
|
||||
String checkPermission = checkPermissions[i];
|
||||
if (PermissionChecker.checkSelfPermission(cxt, checkPermission) != PermissionChecker.PERMISSION_GRANTED) {
|
||||
isHas = false;
|
||||
permissions.add(checkPermission);
|
||||
}
|
||||
}
|
||||
if (!isHas) {
|
||||
String[] p = permissions.toArray(new String[permissions.size()]);
|
||||
requestPermissionsInActivity(cxt, Code.REQUEST_PERMISSION, p);
|
||||
}
|
||||
return isHas;
|
||||
}
|
||||
|
||||
private static void requestPermissionsInActivity(Activity cxt, int requestCode,
|
||||
String... permissions) {
|
||||
ActivityCompat.requestPermissions(cxt, permissions, requestCode);
|
||||
}
|
||||
|
||||
public static void onPermissionResult(Activity cxt, @NonNull String[] permissions,
|
||||
@NonNull int[] grantResults,
|
||||
PermissionCallBack listener) {
|
||||
int length = grantResults.length;
|
||||
List<Integer> positions = new ArrayList<>();
|
||||
if (length > 0) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
|
||||
positions.add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (positions.size() == 0) {
|
||||
listener.onSuccess();
|
||||
return;
|
||||
}
|
||||
progressNoPermission(cxt, listener, permissions, positions, 0);
|
||||
|
||||
}
|
||||
|
||||
private static void progressNoPermission(Activity cxt, PermissionCallBack listener,
|
||||
String[] permissions, List<Integer> positions, int i) {
|
||||
int index = positions.get(i);
|
||||
if (ActivityCompat.shouldShowRequestPermissionRationale(cxt, permissions[index])) {
|
||||
listener.onShouldShow();
|
||||
return;
|
||||
}
|
||||
if (i < positions.size() - 1) {
|
||||
i++;
|
||||
progressNoPermission(cxt, listener, permissions, positions, i);
|
||||
return;
|
||||
}
|
||||
listener.onFailed();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.huantansheng.easyphotos.utils.result;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
/**
|
||||
* EasyResult
|
||||
*
|
||||
* @author joker
|
||||
* @date 2019/4/9.
|
||||
*/
|
||||
public class EasyResult {
|
||||
private static final String TAG = "com.huantansheng.easyphotos";
|
||||
|
||||
private EasyResult() {
|
||||
|
||||
}
|
||||
|
||||
public static HolderFragment get(FragmentActivity activity) {
|
||||
return new EasyResult().getHolderFragment(activity.getSupportFragmentManager());
|
||||
}
|
||||
|
||||
public static HolderFragment get(Fragment fragment) {
|
||||
return new EasyResult().getHolderFragment(fragment.getChildFragmentManager());
|
||||
}
|
||||
|
||||
private HolderFragment getHolderFragment(FragmentManager fragmentManager) {
|
||||
HolderFragment holderFragment = findHolderFragment(fragmentManager);
|
||||
if (holderFragment == null) {
|
||||
holderFragment = new HolderFragment();
|
||||
fragmentManager
|
||||
.beginTransaction()
|
||||
.add(holderFragment, TAG)
|
||||
.commitAllowingStateLoss();
|
||||
fragmentManager.executePendingTransactions();
|
||||
}
|
||||
return holderFragment;
|
||||
}
|
||||
|
||||
private HolderFragment findHolderFragment(FragmentManager fragmentManager) {
|
||||
return (HolderFragment) fragmentManager.findFragmentByTag(TAG);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.huantansheng.easyphotos.utils.result;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.huantansheng.easyphotos.EasyPhotos;
|
||||
import com.huantansheng.easyphotos.callback.PuzzleCallback;
|
||||
import com.huantansheng.easyphotos.callback.SelectCallback;
|
||||
import com.huantansheng.easyphotos.engine.ImageEngine;
|
||||
import com.huantansheng.easyphotos.models.album.entity.Photo;
|
||||
import com.huantansheng.easyphotos.ui.EasyPhotosActivity;
|
||||
import com.huantansheng.easyphotos.ui.PuzzleActivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* HolderFragment
|
||||
*
|
||||
* @author joker
|
||||
* @date 2019/4/9.
|
||||
*/
|
||||
public class HolderFragment extends Fragment {
|
||||
|
||||
private static final int HOLDER_SELECT_REQUEST_CODE = 0x44;
|
||||
private static final int HOLDER_PUZZLE_REQUEST_CODE = 0x55;
|
||||
private SelectCallback mSelectCallback;
|
||||
private PuzzleCallback mPuzzleCallback;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
}
|
||||
|
||||
public void startEasyPhoto(SelectCallback callback) {
|
||||
mSelectCallback = callback;
|
||||
EasyPhotosActivity.start(this, HOLDER_SELECT_REQUEST_CODE);
|
||||
}
|
||||
|
||||
public void startPuzzleWithPhotos(ArrayList<Photo> photos, String puzzleSaveDirPath, String puzzleSaveNamePrefix, boolean replaceCustom, @NonNull ImageEngine imageEngine, PuzzleCallback callback) {
|
||||
mPuzzleCallback = callback;
|
||||
PuzzleActivity.startWithPhotos(this, photos, puzzleSaveDirPath, puzzleSaveNamePrefix, HOLDER_PUZZLE_REQUEST_CODE, replaceCustom, imageEngine);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (Activity.RESULT_OK == resultCode) {
|
||||
switch (requestCode) {
|
||||
case HOLDER_SELECT_REQUEST_CODE:
|
||||
if (mSelectCallback != null) {
|
||||
ArrayList<Photo> resultPhotos = data.getParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS);
|
||||
boolean selectedOriginal = data.getBooleanExtra(EasyPhotos.RESULT_SELECTED_ORIGINAL, false);
|
||||
mSelectCallback.onResult(resultPhotos, selectedOriginal);
|
||||
}
|
||||
break;
|
||||
case HOLDER_PUZZLE_REQUEST_CODE:
|
||||
if (mPuzzleCallback != null) {
|
||||
Photo puzzlePhoto = data.getParcelableExtra(EasyPhotos.RESULT_PHOTOS);
|
||||
mPuzzleCallback.onResult(puzzlePhoto);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (Activity.RESULT_CANCELED == resultCode) {
|
||||
switch (requestCode) {
|
||||
case HOLDER_SELECT_REQUEST_CODE:
|
||||
if (mSelectCallback != null) {
|
||||
mSelectCallback.onCancel();
|
||||
}
|
||||
break;
|
||||
case HOLDER_PUZZLE_REQUEST_CODE:
|
||||
if (mPuzzleCallback != null) {
|
||||
mPuzzleCallback.onCancel();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.huantansheng.easyphotos.utils.settings;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.provider.Settings;
|
||||
|
||||
import com.huantansheng.easyphotos.constant.Code;
|
||||
|
||||
/**
|
||||
* 系统设置界面启动器
|
||||
* Created by huan on 2017/8/7.
|
||||
*/
|
||||
|
||||
public class SettingsUtils {
|
||||
|
||||
/**
|
||||
* 启动应用详情界面
|
||||
* @param cxt 上下文
|
||||
* @param packageName 应用包名
|
||||
*/
|
||||
public static void startMyApplicationDetailsForResult(Activity cxt, String packageName) {
|
||||
Uri packageUri = Uri.parse("package:" + packageName);
|
||||
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageUri);
|
||||
cxt.startActivityForResult(intent, Code.REQUEST_SETTING_APP_DETAILS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
package com.huantansheng.easyphotos.utils.system;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Created by wangchende on 15-9-7.
|
||||
*/
|
||||
public class MeiZuStatusUtils {
|
||||
private static Method mSetStatusBarColorIcon;
|
||||
private static Method mSetStatusBarDarkIcon;
|
||||
private static Field mStatusBarColorFiled;
|
||||
private static int SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0;
|
||||
|
||||
static {
|
||||
try {
|
||||
mSetStatusBarColorIcon = Activity.class.getMethod("setStatusBarDarkIcon", int.class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
mSetStatusBarDarkIcon = Activity.class.getMethod("setStatusBarDarkIcon", boolean.class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
mStatusBarColorFiled = WindowManager.LayoutParams.class.getField("statusBarColor");
|
||||
} catch (NoSuchFieldException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
Field field = View.class.getField("SYSTEM_UI_FLAG_LIGHT_STATUS_BAR");
|
||||
SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = field.getInt(null);
|
||||
} catch (NoSuchFieldException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断颜色是否偏黑色
|
||||
*
|
||||
* @param color 颜色
|
||||
* @param level 级别
|
||||
* @return
|
||||
*/
|
||||
public static boolean isBlackColor(int color, int level) {
|
||||
int grey = toGrey(color);
|
||||
return grey < level;
|
||||
}
|
||||
|
||||
/**
|
||||
* 颜色转换成灰度值
|
||||
*
|
||||
* @param rgb 颜色
|
||||
* @return 灰度值
|
||||
*/
|
||||
public static int toGrey(int rgb) {
|
||||
int blue = rgb & 0x000000FF;
|
||||
int green = (rgb & 0x0000FF00) >> 8;
|
||||
int red = (rgb & 0x00FF0000) >> 16;
|
||||
return (red * 38 + green * 75 + blue * 15) >> 7;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态栏字体图标颜色
|
||||
*
|
||||
* @param activity 当前activity
|
||||
* @param color 颜色
|
||||
*/
|
||||
public static void setStatusBarDarkIcon(Activity activity, int color) {
|
||||
if (mSetStatusBarColorIcon != null) {
|
||||
try {
|
||||
mSetStatusBarColorIcon.invoke(activity, color);
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
boolean whiteColor = isBlackColor(color, 50);
|
||||
if (mStatusBarColorFiled != null) {
|
||||
setStatusBarDarkIcon(activity, whiteColor, whiteColor);
|
||||
setStatusBarDarkIcon(activity.getWindow(), color);
|
||||
} else {
|
||||
setStatusBarDarkIcon(activity, whiteColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态栏字体图标颜色(只限全屏非activity情况)
|
||||
*
|
||||
* @param window 当前窗口
|
||||
* @param color 颜色
|
||||
*/
|
||||
public static void setStatusBarDarkIcon(Window window, int color) {
|
||||
try {
|
||||
setStatusBarColor(window, color);
|
||||
if (Build.VERSION.SDK_INT > 22) {
|
||||
setStatusBarDarkIcon(window.getDecorView(), true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态栏字体图标颜色
|
||||
*
|
||||
* @param activity 当前activity
|
||||
* @param dark 是否深色 true为深色 false 为白色
|
||||
*/
|
||||
public static void setStatusBarDarkIcon(Activity activity, boolean dark) {
|
||||
setStatusBarDarkIcon(activity, dark, true);
|
||||
}
|
||||
|
||||
private static boolean changeMeizuFlag(WindowManager.LayoutParams winParams, String flagName, boolean on) {
|
||||
try {
|
||||
Field f = winParams.getClass().getDeclaredField(flagName);
|
||||
f.setAccessible(true);
|
||||
int bits = f.getInt(winParams);
|
||||
Field f2 = winParams.getClass().getDeclaredField("meizuFlags");
|
||||
f2.setAccessible(true);
|
||||
int meizuFlags = f2.getInt(winParams);
|
||||
int oldFlags = meizuFlags;
|
||||
if (on) {
|
||||
meizuFlags |= bits;
|
||||
} else {
|
||||
meizuFlags &= ~bits;
|
||||
}
|
||||
if (oldFlags != meizuFlags) {
|
||||
f2.setInt(winParams, meizuFlags);
|
||||
return true;
|
||||
}
|
||||
} catch (NoSuchFieldException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态栏颜色
|
||||
*
|
||||
* @param view
|
||||
* @param dark
|
||||
*/
|
||||
private static void setStatusBarDarkIcon(View view, boolean dark) {
|
||||
int oldVis = view.getSystemUiVisibility();
|
||||
int newVis = oldVis;
|
||||
if (dark) {
|
||||
newVis |= SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
|
||||
} else {
|
||||
newVis &= ~SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
|
||||
}
|
||||
if (newVis != oldVis) {
|
||||
view.setSystemUiVisibility(newVis);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态栏颜色
|
||||
*
|
||||
* @param window
|
||||
* @param color
|
||||
*/
|
||||
private static void setStatusBarColor(Window window, int color) {
|
||||
WindowManager.LayoutParams winParams = window.getAttributes();
|
||||
if (mStatusBarColorFiled != null) {
|
||||
try {
|
||||
int oldColor = mStatusBarColorFiled.getInt(winParams);
|
||||
if (oldColor != color) {
|
||||
mStatusBarColorFiled.set(winParams, color);
|
||||
window.setAttributes(winParams);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态栏字体图标颜色(只限全屏非activity情况)
|
||||
*
|
||||
* @param window 当前窗口
|
||||
* @param dark 是否深色 true为深色 false 为白色
|
||||
*/
|
||||
public static void setStatusBarDarkIcon(Window window, boolean dark) {
|
||||
if (Build.VERSION.SDK_INT < 23) {
|
||||
changeMeizuFlag(window.getAttributes(), "MEIZU_FLAG_DARK_STATUS_BAR_ICON", dark);
|
||||
} else {
|
||||
View decorView = window.getDecorView();
|
||||
if (decorView != null) {
|
||||
setStatusBarDarkIcon(decorView, dark);
|
||||
setStatusBarColor(window, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void setStatusBarDarkIcon(Activity activity, boolean dark, boolean flag) {
|
||||
if (mSetStatusBarDarkIcon != null) {
|
||||
try {
|
||||
mSetStatusBarDarkIcon.invoke(activity, dark);
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
if (flag) {
|
||||
setStatusBarDarkIcon(activity.getWindow(), dark);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
package com.huantansheng.easyphotos.utils.system;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.Display;
|
||||
import android.view.KeyCharacterMap;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 系统工具类
|
||||
* Created by huan on 2017/11/13.
|
||||
*/
|
||||
|
||||
public class SystemUtils {
|
||||
private static SystemUtils instance = null;
|
||||
private Boolean hasNavigation = null;//是否有导航栏
|
||||
|
||||
/**
|
||||
* 私有构造方法
|
||||
*/
|
||||
private SystemUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单例
|
||||
*
|
||||
* @return 单例
|
||||
*/
|
||||
public static SystemUtils getInstance() {
|
||||
if (null == instance) {
|
||||
synchronized (SystemUtils.class) {
|
||||
if (null == instance) {
|
||||
instance = new SystemUtils();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有导航栏
|
||||
*
|
||||
* @return 有或没有
|
||||
*/
|
||||
public boolean hasNavigationBar(Activity activity) {
|
||||
if (null == hasNavigation) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
int windowHeight = activity.getResources().getDisplayMetrics().heightPixels;
|
||||
DisplayMetrics dm = new DisplayMetrics();
|
||||
activity.getWindowManager().getDefaultDisplay().getRealMetrics(dm);
|
||||
int screenHeight = dm.heightPixels;
|
||||
hasNavigation = screenHeight - windowHeight > 0;
|
||||
} else {
|
||||
hasNavigation = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasNavigation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 全屏显示的初始化,在setContentView()方法前调用
|
||||
*
|
||||
* @param decorView getWindow().getDecorView(),不同view也可以
|
||||
*/
|
||||
public void systemUiInit(Activity activity, View decorView) {
|
||||
if (!hasNavigationBar(activity)) {
|
||||
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN);
|
||||
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
|
||||
return;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
|
||||
}
|
||||
}
|
||||
|
||||
private void hideStatusBar(Activity activity) {
|
||||
WindowManager.LayoutParams attrs = activity.getWindow().getAttributes();
|
||||
attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
|
||||
activity.getWindow().setAttributes(attrs);
|
||||
}
|
||||
|
||||
private void showStatusBar(Activity activity) {
|
||||
WindowManager.LayoutParams attrs = activity.getWindow().getAttributes();
|
||||
attrs.flags &= ~WindowManager.LayoutParams.FLAG_FULLSCREEN;
|
||||
activity.getWindow().setAttributes(attrs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 隐藏导航栏和状态栏
|
||||
*
|
||||
* @param activity 上下文
|
||||
* @param decorView getWindow().getDecorView(),不同view也可以
|
||||
*/
|
||||
public void systemUiHide(Activity activity, View decorView) {
|
||||
|
||||
if (!hasNavigationBar(activity)) {
|
||||
hideStatusBar(activity);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
|
||||
return;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示导航栏和状态栏
|
||||
*
|
||||
* @param activity 上下文
|
||||
* @param decorView getWindow().getDecorView(),不同view也可以
|
||||
*/
|
||||
public void systemUiShow(Activity activity, View decorView) {
|
||||
if (!hasNavigationBar(activity)) {
|
||||
showStatusBar(activity);
|
||||
return;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态栏高度
|
||||
*
|
||||
* @param cxt 上下文
|
||||
* @return 状态栏高度,单位PX
|
||||
*/
|
||||
public int getStatusBarHeight(Context cxt) {
|
||||
int statusBarHeight = 0;
|
||||
int resourceId = cxt.getResources().getIdentifier("status_bar_height", "dimen", "android");
|
||||
if (resourceId > 0) {
|
||||
statusBarHeight = cxt.getResources().getDimensionPixelSize(resourceId);
|
||||
} else {
|
||||
statusBarHeight = (int) (23 * (cxt.getResources().getDisplayMetrics().density) + 0.5f);
|
||||
}
|
||||
return statusBarHeight;
|
||||
}
|
||||
|
||||
public void setStatusDark(Activity activity, boolean darkmode) {
|
||||
if (isFlymeV4OrAbove()) {
|
||||
MeiZuStatusUtils.setStatusBarDarkIcon(activity, darkmode);
|
||||
return;
|
||||
}
|
||||
if (isMIUIV6OrAbove()) {
|
||||
setStatusTextBlackMi(activity, darkmode);
|
||||
return;
|
||||
}
|
||||
setStatusTextBlackAndroid(activity, darkmode);
|
||||
}
|
||||
|
||||
private void setStatusTextBlackMi(Activity activity, boolean darkmode) {
|
||||
Class<? extends Window> clazz = activity.getWindow().getClass();
|
||||
try {
|
||||
int darkModeFlag = 0;
|
||||
Class<?> layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
|
||||
Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
|
||||
darkModeFlag = field.getInt(layoutParams);
|
||||
Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
|
||||
extraFlagField.invoke(activity.getWindow(), darkmode ? darkModeFlag : 0, darkModeFlag);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
setStatusTextBlackAndroid(activity, darkmode);
|
||||
}
|
||||
|
||||
|
||||
private void setStatusTextBlackAndroid(Activity activity, boolean darkmode) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
Window window = activity.getWindow();
|
||||
if (darkmode) {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
|
||||
} else {
|
||||
int flag = window.getDecorView().getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
|
||||
window.getDecorView().setSystemUiVisibility(flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean isFlymeV4OrAbove() {
|
||||
String displayId = Build.DISPLAY;
|
||||
if (!TextUtils.isEmpty(displayId) && displayId.contains("Flyme")) {
|
||||
String[] displayIdArray = displayId.split(" ");
|
||||
for (String temp : displayIdArray) {
|
||||
//版本号4以上,形如4.x.
|
||||
if (temp.matches("^[4-9]\\.(\\d+\\.)+\\S*")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//MIUI V6对应的versionCode是4
|
||||
//MIUI V7对应的versionCode是5
|
||||
private boolean isMIUIV6OrAbove() {
|
||||
String miuiVersionCodeStr = getSystemProperty("ro.miui.ui.version.code");
|
||||
if (!TextUtils.isEmpty(miuiVersionCodeStr)) {
|
||||
try {
|
||||
int miuiVersionCode = Integer.parseInt(miuiVersionCodeStr);
|
||||
if (miuiVersionCode >= 4) {
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//Android Api 23以上
|
||||
private boolean isAndroidMOrAbove() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String getSystemProperty(String propName) {
|
||||
String line;
|
||||
BufferedReader input = null;
|
||||
try {
|
||||
Process p = Runtime.getRuntime().exec("getprop " + propName);
|
||||
input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024);
|
||||
line = input.readLine();
|
||||
input.close();
|
||||
} catch (IOException ex) {
|
||||
return null;
|
||||
} finally {
|
||||
if (input != null) {
|
||||
try {
|
||||
input.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package com.huantansheng.easyphotos.utils.uri;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
import com.huantansheng.easyphotos.setting.Setting;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Locale;
|
||||
|
||||
public class UriUtils {
|
||||
public static Uri getUri(Context cxt, File file) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
if (null==Setting.fileProviderAuthority){
|
||||
throw new NullPointerException("Setting.fileProviderAuthority must not be null.");
|
||||
}
|
||||
return FileProvider.getUriForFile(cxt, Setting.fileProviderAuthority, file);
|
||||
} else {
|
||||
return Uri.fromFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
private static final String TAG = "UriUtils";
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public static String getPathByUri(Context context, Uri uri) {
|
||||
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
|
||||
// DocumentProvider
|
||||
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
|
||||
if (isExternalStorageDocument(uri)) {
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
final String[] split = docId.split(":");
|
||||
final String type = split[0];
|
||||
|
||||
if ("primary".equalsIgnoreCase(type)) {
|
||||
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
return Environment.getExternalStorageDirectory() + "/" + split[1];
|
||||
} else {
|
||||
return context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) + "/" + split[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
// DownloadsProvider
|
||||
else if (isDownloadsDocument(uri)) {
|
||||
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);
|
||||
}
|
||||
// MediaProvider
|
||||
else if (isMediaDocument(uri)) {
|
||||
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.getContentUri("external");
|
||||
} else if ("video".equals(type)) {
|
||||
contentUri = MediaStore.Video.Media.getContentUri("external");
|
||||
} else if ("audio".equals(type)) {
|
||||
contentUri = MediaStore.Audio.Media.getContentUri("external");
|
||||
}
|
||||
final String selection = "_id=?";
|
||||
final String[] selectionArgs = new String[]{split[1]};
|
||||
return getDataColumn(context, contentUri, selection, selectionArgs);
|
||||
}
|
||||
}
|
||||
// MediaStore (and general)
|
||||
else if ("content".equalsIgnoreCase(uri.getScheme())) {
|
||||
// Return the remote address
|
||||
if (isGooglePhotosUri(uri)) {
|
||||
return uri.getLastPathSegment();
|
||||
}
|
||||
return getDataColumn(context, uri, null, null);
|
||||
}
|
||||
// File
|
||||
else if ("file".equalsIgnoreCase(uri.getScheme())) {
|
||||
return uri.getPath();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private 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 column_index = cursor.getColumnIndexOrThrow(column);
|
||||
return cursor.getString(column_index);
|
||||
}
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Log.i(TAG, String.format(Locale.getDefault(), "getDataColumn: _data - [%s]", ex.getMessage()));
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is ExternalStorageProvider.
|
||||
*/
|
||||
private 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.
|
||||
*/
|
||||
private 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.
|
||||
*/
|
||||
private static boolean isMediaDocument(Uri uri) {
|
||||
return "com.android.providers.media.documents".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是Google相册的图片,类似于content://com.google.android.apps.photos.content/...
|
||||
**/
|
||||
public static boolean isGooglePhotosUri(Uri uri) {
|
||||
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是Google相册的图片,类似于content://com.google.android.apps.photos.contentprovider/0/1/mediakey:/local%3A821abd2f-9f8c-4931-bbe9-a975d1f5fabc/ORIGINAL/NONE/1075342619
|
||||
**/
|
||||
public static boolean isGooglePlayPhotosUri(Uri uri) {
|
||||
return "com.google.android.apps.photos.contentprovider".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片路径转uri
|
||||
*/
|
||||
public static Uri getUriByPath(Context context, String path) {
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
Uri contentUri = MediaStore.Files.getContentUri("external");
|
||||
Cursor cursor = contentResolver.query(contentUri, new String[]{MediaStore.Files.FileColumns._ID},
|
||||
MediaStore.Files.FileColumns.DATA + "=? ", new String[]{path},
|
||||
null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
|
||||
cursor.close();
|
||||
return Uri.withAppendedPath(contentUri, "" + id);
|
||||
} else {
|
||||
if (new File(path).exists()) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(MediaStore.Images.Media.DATA, path);
|
||||
return contentResolver.insert(contentUri, values);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/album_items_background_easy_photos" />
|
||||
<corners android:topLeftRadius="5dp" android:topRightRadius="5dp"/>
|
||||
</shape>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<solid android:color="@color/easy_photos_bg_dialog_loading" />
|
||||
|
||||
<corners android:radius="100dp" />
|
||||
|
||||
</shape>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<corners android:radius="3dp" />
|
||||
<solid android:color="@color/easy_photos_fg_accent" />
|
||||
|
||||
|
||||
</shape>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<corners android:radius="3dp" />
|
||||
<solid android:color="@color/easy_photos_bar_primary_translation" />
|
||||
|
||||
</shape>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@android:id/background">
|
||||
<shape>
|
||||
<solid android:color="@color/easy_photos_bar_primary" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:id="@android:id/secondaryProgress">
|
||||
<clip>
|
||||
<shape>
|
||||
<solid android:color="@color/easy_photos_bar_primary" />
|
||||
</shape>
|
||||
</clip>
|
||||
</item>
|
||||
<item android:id="@android:id/progress">
|
||||
<clip>
|
||||
<shape>
|
||||
<solid android:color="@color/white_easy_photos" />
|
||||
</shape>
|
||||
</clip>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="50dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/easy_photos_bar_primary" />
|
||||
|
||||
</shape>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="50dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/easy_photos_fg_primary_dark" />
|
||||
|
||||
</shape>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user