将easyPhoto,easyPermission和luban都移到library

This commit is contained in:
wushaocheng
2022-11-17 15:35:31 +08:00
parent ef165b67ae
commit b47b1c5f47
212 changed files with 462 additions and 479 deletions

View File

@@ -18,6 +18,30 @@ android {
dataBinding {
enabled = true
}
viewBinding {
enabled = true
}
sourceSets {
main {
java.srcDirs = [
'src/main/java',
'src/module_easypermission/java',
'src/module_luban/java',
'src/module_easyphoto/java',
]
res.srcDirs = [
'src/main/res',
'src/module_easypermission/res',
'src/module_easyphoto/res',
]
}
}
buildTypes {
release {
@@ -64,7 +88,7 @@ dependencies {
api 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
api 'androidx.lifecycle:lifecycle-extensions:2.2.0'
api 'com.google.android.material:material:1.5.0'
api 'com.google.android.material:material:1.6.1'
api "com.squareup.retrofit2:retrofit:${retrofitVersion}"
api "com.squareup.okhttp3:okhttp:${okhttp3}"
@@ -94,6 +118,10 @@ dependencies {
api 'com.github.getActivity:ToastUtils:10.3'
api 'androidx.legacy:legacy-support-v4:1.0.0'
api 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
api 'com.github.chrisbanes:PhotoView:2.3.0'
}
repositories {
mavenCentral()

View File

@@ -1,3 +1,43 @@
<manifest package="com.yizhuan.xchat_android_library">
<manifest xmlns:tools="http://schemas.android.com/tools"
package="com.yizhuan.xchat_android_library"
xmlns:android="http://schemas.android.com/apk/res/android">
<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"
tools:ignore="ScopedStorage" />
<application>
<activity
android:name=".AppSettingsDialogHolderActivity"
android:exported="false"
android:label=""
android:theme="@style/EasyPermissions.Transparent" />
<activity
android:name=".ui.EasyPhotosActivity"
android:configChanges="screenSize|orientation|keyboardHidden|mcc|mnc|locale|touchscreen|screenLayout|keyboard|navigation|fontScale|uiMode|smallestScreenSize|layoutDirection"
android:exported="false"
android:screenOrientation="portrait"
android:theme="@style/EasyPhotosTheme" />
<activity
android:name=".ui.PreviewActivity"
android:configChanges="screenSize|orientation|keyboardHidden|mcc|mnc|locale|touchscreen|screenLayout|keyboard|navigation|fontScale|uiMode|smallestScreenSize|layoutDirection"
android:exported="false"
android:screenOrientation="portrait"
android:theme="@style/EasyPhotosFullscreenTheme" />
<activity
android:name=".ui.PuzzleActivity"
android:configChanges="screenSize|orientation|keyboardHidden|mcc|mnc|locale|touchscreen|screenLayout|keyboard|navigation|fontScale|uiMode|smallestScreenSize|layoutDirection"
android:exported="false"
android:screenOrientation="portrait"
android:theme="@style/EasyPhotosTheme"
android:windowSoftInputMode="adjustPan" />
<activity
android:name=".ui.PuzzleSelectorActivity"
android:configChanges="screenSize|orientation|keyboardHidden|mcc|mnc|locale|touchscreen|screenLayout|keyboard|navigation|fontScale|uiMode|smallestScreenSize|layoutDirection"
android:exported="false"
android:screenOrientation="portrait"
android:theme="@style/EasyPhotosTheme" />
</application>
</manifest>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#ffffff"/>
<corners android:radius="3dp"/>
</shape>

View File

@@ -1,3 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="color_white">#FFFFFF</color>
</resources>

View File

@@ -1,5 +1,6 @@
<!DOCTYPE resources [<!ENTITY app_name "Peko" >]>
<resources>
<string name="app_name">XChat_Android_Library</string>
<string name="app_name">&app_name;</string>
<string name="str_right_now">剛剛</string>
<string name="str_today">今天</string>

View File

@@ -0,0 +1,29 @@
/*
* Copyright Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.yizhuan.xchat_android_library;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterPermissionGranted {
int value();
}

View File

@@ -0,0 +1,356 @@
package com.yizhuan.xchat_android_library;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.StringRes;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
/**
* Dialog to prompt the user to go to the app's settings screen and enable permissions. If the user
* clicks 'OK' on the dialog, they are sent to the settings screen. The result is returned to the
* Activity via {@see Activity#onActivityResult(int, int, Intent)}.
* <p>
* Use the {@link Builder} to create and display a dialog.
*/
public class AppSettingsDialog implements Parcelable {
private static final String TAG = "EasyPermissions";
public static final int DEFAULT_SETTINGS_REQ_CODE = 16061;
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final Creator<AppSettingsDialog> CREATOR = new Creator<AppSettingsDialog>() {
@Override
public AppSettingsDialog createFromParcel(Parcel in) {
return new AppSettingsDialog(in);
}
@Override
public AppSettingsDialog[] newArray(int size) {
return new AppSettingsDialog[size];
}
};
static final String EXTRA_APP_SETTINGS = "extra_app_settings";
@StyleRes
private final int mThemeResId;
private final String mRationale;
private final String mTitle;
private final String mPositiveButtonText;
private final String mNegativeButtonText;
private final int mRequestCode;
private final int mIntentFlags;
private Object mActivityOrFragment;
private Context mContext;
private AppSettingsDialog(Parcel in) {
mThemeResId = in.readInt();
mRationale = in.readString();
mTitle = in.readString();
mPositiveButtonText = in.readString();
mNegativeButtonText = in.readString();
mRequestCode = in.readInt();
mIntentFlags = in.readInt();
}
private AppSettingsDialog(@NonNull final Object activityOrFragment,
@StyleRes int themeResId,
@Nullable String rationale,
@Nullable String title,
@Nullable String positiveButtonText,
@Nullable String negativeButtonText,
int requestCode,
int intentFlags) {
setActivityOrFragment(activityOrFragment);
mThemeResId = themeResId;
mRationale = rationale;
mTitle = title;
mPositiveButtonText = positiveButtonText;
mNegativeButtonText = negativeButtonText;
mRequestCode = requestCode;
mIntentFlags = intentFlags;
}
static AppSettingsDialog fromIntent(Intent intent, Activity activity) {
AppSettingsDialog dialog = intent.getParcelableExtra(AppSettingsDialog.EXTRA_APP_SETTINGS);
// It's not clear how this could happen, but in the case that it does we should try
// to avoid a runtime crash and just use the default dialog.
// https://github.com/googlesamples/easypermissions/issues/278
if (dialog == null) {
Log.e(TAG, "Intent contains null value for EXTRA_APP_SETTINGS: "
+ "intent=" + intent
+ ", "
+ "extras=" + intent.getExtras());
dialog = new Builder(activity).build();
}
dialog.setActivityOrFragment(activity);
return dialog;
}
private void setActivityOrFragment(Object activityOrFragment) {
mActivityOrFragment = activityOrFragment;
if (activityOrFragment instanceof Activity) {
mContext = (Activity) activityOrFragment;
} else if (activityOrFragment instanceof Fragment) {
mContext = ((Fragment) activityOrFragment).getContext();
} else {
throw new IllegalStateException("Unknown object: " + activityOrFragment);
}
}
private void startForResult(Intent intent) {
if (mActivityOrFragment instanceof Activity) {
((Activity) mActivityOrFragment).startActivityForResult(intent, mRequestCode);
} else if (mActivityOrFragment instanceof Fragment) {
((Fragment) mActivityOrFragment).startActivityForResult(intent, mRequestCode);
}
}
/**
* Display the built dialog.
*/
public void show() {
startForResult(AppSettingsDialogHolderActivity.createShowDialogIntent(mContext, this));
}
/**
* Show the dialog. {@link #show()} is a wrapper to ensure backwards compatibility
*/
AlertDialog showDialog(DialogInterface.OnClickListener positiveListener,
DialogInterface.OnClickListener negativeListener) {
AlertDialog.Builder builder;
if (mThemeResId != -1) {
builder = new AlertDialog.Builder(mContext, mThemeResId);
} else {
builder = new AlertDialog.Builder(mContext);
}
return builder
.setCancelable(false)
.setTitle(mTitle)
.setMessage(mRationale)
.setPositiveButton(mPositiveButtonText, positiveListener)
.setNegativeButton(mNegativeButtonText, negativeListener)
.show();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mThemeResId);
dest.writeString(mRationale);
dest.writeString(mTitle);
dest.writeString(mPositiveButtonText);
dest.writeString(mNegativeButtonText);
dest.writeInt(mRequestCode);
dest.writeInt(mIntentFlags);
}
int getIntentFlags() {
return mIntentFlags;
}
/**
* Builder for an {@link AppSettingsDialog}.
*/
public static class Builder {
private final Object mActivityOrFragment;
private final Context mContext;
@StyleRes
private int mThemeResId = -1;
private String mRationale;
private String mTitle;
private String mPositiveButtonText;
private String mNegativeButtonText;
private int mRequestCode = -1;
private boolean mOpenInNewTask = false;
/**
* Create a new Builder for an {@link AppSettingsDialog}.
*
* @param activity the {@link Activity} in which to display the dialog.
*/
public Builder(@NonNull Activity activity) {
mActivityOrFragment = activity;
mContext = activity;
}
/**
* Create a new Builder for an {@link AppSettingsDialog}.
*
* @param fragment the {@link Fragment} in which to display the dialog.
*/
public Builder(@NonNull Fragment fragment) {
mActivityOrFragment = fragment;
mContext = fragment.getContext();
}
/**
* Set the dialog theme.
*/
@NonNull
public Builder setThemeResId(@StyleRes int themeResId) {
mThemeResId = themeResId;
return this;
}
/**
* Set the title dialog. Default is "Permissions Required".
*/
@NonNull
public Builder setTitle(@Nullable String title) {
mTitle = title;
return this;
}
/**
* Set the title dialog. Default is "Permissions Required".
*/
@NonNull
public Builder setTitle(@StringRes int title) {
mTitle = mContext.getString(title);
return this;
}
/**
* Set the rationale dialog. Default is
* "This app may not work correctly without the requested permissions.
* Open the app settings screen to modify app permissions."
*/
@NonNull
public Builder setRationale(@Nullable String rationale) {
mRationale = rationale;
return this;
}
/**
* Set the rationale dialog. Default is
* "This app may not work correctly without the requested permissions.
* Open the app settings screen to modify app permissions."
*/
@NonNull
public Builder setRationale(@StringRes int rationale) {
mRationale = mContext.getString(rationale);
return this;
}
/**
* Set the positive button text, default is {@link android.R.string#ok}.
*/
@NonNull
public Builder setPositiveButton(@Nullable String text) {
mPositiveButtonText = text;
return this;
}
/**
* Set the positive button text, default is {@link android.R.string#ok}.
*/
@NonNull
public Builder setPositiveButton(@StringRes int textId) {
mPositiveButtonText = mContext.getString(textId);
return this;
}
/**
* Set the negative button text, default is {@link android.R.string#cancel}.
* <p>
* To know if a user cancelled the request, check if your permissions were given with {@link
* EasyPermissions#hasPermissions(Context, String...)} in {@see
* Activity#onActivityResult(int, int, Intent)}. If you still don't have the right
* permissions, then the request was cancelled.
*/
@NonNull
public Builder setNegativeButton(@Nullable String text) {
mNegativeButtonText = text;
return this;
}
/**
* Set the negative button text, default is {@link android.R.string#cancel}.
*/
@NonNull
public Builder setNegativeButton(@StringRes int textId) {
mNegativeButtonText = mContext.getString(textId);
return this;
}
/**
* Set the request code use when launching the Settings screen for result, can be retrieved
* in the calling Activity's {@see Activity#onActivityResult(int, int, Intent)} method.
* Default is {@link #DEFAULT_SETTINGS_REQ_CODE}.
*/
@NonNull
public Builder setRequestCode(int requestCode) {
mRequestCode = requestCode;
return this;
}
/**
* Set whether the settings screen should be opened in a separate task. This is achieved by
* setting {@link Intent#FLAG_ACTIVITY_NEW_TASK#FLAG_ACTIVITY_NEW_TASK} on
* the Intent used to open the settings screen.
*/
@NonNull
public Builder setOpenInNewTask(boolean openInNewTask) {
mOpenInNewTask = openInNewTask;
return this;
}
/**
* Build the {@link AppSettingsDialog} from the specified options. Generally followed by a
* call to {@link AppSettingsDialog#show()}.
*/
@NonNull
public AppSettingsDialog build() {
mRationale = TextUtils.isEmpty(mRationale) ?
mContext.getString(R.string.rationale_ask_again) : mRationale;
mTitle = TextUtils.isEmpty(mTitle) ?
mContext.getString(R.string.title_settings_dialog) : mTitle;
mPositiveButtonText = TextUtils.isEmpty(mPositiveButtonText) ?
mContext.getString(android.R.string.ok) : mPositiveButtonText;
mNegativeButtonText = TextUtils.isEmpty(mNegativeButtonText) ?
mContext.getString(android.R.string.cancel) : mNegativeButtonText;
mRequestCode = mRequestCode > 0 ? mRequestCode : DEFAULT_SETTINGS_REQ_CODE;
int intentFlags = 0;
if (mOpenInNewTask) {
intentFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
}
return new AppSettingsDialog(
mActivityOrFragment,
mThemeResId,
mRationale,
mTitle,
mPositiveButtonText,
mNegativeButtonText,
mRequestCode,
intentFlags);
}
}
}

View File

@@ -0,0 +1,65 @@
package com.yizhuan.xchat_android_library;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import androidx.annotation.RestrictTo;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class AppSettingsDialogHolderActivity extends AppCompatActivity implements DialogInterface.OnClickListener {
private static final int APP_SETTINGS_RC = 7534;
private AlertDialog mDialog;
private int mIntentFlags;
public static Intent createShowDialogIntent(Context context, AppSettingsDialog dialog) {
Intent intent = new Intent(context, AppSettingsDialogHolderActivity.class);
intent.putExtra(AppSettingsDialog.EXTRA_APP_SETTINGS, dialog);
return intent;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AppSettingsDialog appSettingsDialog = AppSettingsDialog.fromIntent(getIntent(), this);
mIntentFlags = appSettingsDialog.getIntentFlags();
mDialog = appSettingsDialog.showDialog(this, this);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mDialog != null && mDialog.isShowing()) {
mDialog.dismiss();
}
}
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == Dialog.BUTTON_POSITIVE) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.fromParts("package", getPackageName(), null));
intent.addFlags(mIntentFlags);
startActivityForResult(intent, APP_SETTINGS_RC);
} else if (which == Dialog.BUTTON_NEGATIVE) {
setResult(Activity.RESULT_CANCELED);
finish();
} else {
throw new IllegalStateException("Unknown button type: " + which);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
setResult(resultCode, data);
finish();
}
}

View File

@@ -0,0 +1,358 @@
/*
* Copyright Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.yizhuan.xchat_android_library;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Size;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.core.content.ContextCompat;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import com.yizhuan.xchat_android_library.helper.PermissionHelper;
/**
* Utility to request and check System permissions for apps targeting Android M (API &gt;= 23).
*/
public class EasyPermissions {
/**
* Callback interface to receive the results of {@code EasyPermissions.requestPermissions()}
* calls.
*/
public interface PermissionCallbacks extends ActivityCompat.OnRequestPermissionsResultCallback {
void onPermissionsGranted(int requestCode, @NonNull List<String> perms);
void onPermissionsDenied(int requestCode, @NonNull List<String> perms);
}
/**
* Callback interface to receive button clicked events of the rationale dialog
*/
public interface RationaleCallbacks {
void onRationaleAccepted(int requestCode);
void onRationaleDenied(int requestCode);
}
private static final String TAG = "EasyPermissions";
/**
* Check if the calling context has a set of permissions.
*
* @param context the calling context.
* @param perms one ore more permissions, such as {@link Manifest.permission#CAMERA}.
* @return true if all permissions are already granted, false if at least one permission is not
* yet granted.
* @see Manifest.permission
*/
public static boolean hasPermissions(@NonNull Context context,
@Size(min = 1) @NonNull String... perms) {
// Always return true for SDK < M, let the system deal with the permissions
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
Log.w(TAG, "hasPermissions: API version < M, returning true by default");
// DANGER ZONE!!! Changing this will break the library.
return true;
}
// Null context may be passed if we have detected Low API (less than M) so getting
// to this point with a null context should not be possible.
if (context == null) {
throw new IllegalArgumentException("Can't check permissions for null context");
}
for (String perm : perms) {
if (ContextCompat.checkSelfPermission(context, perm)
!= PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
/**
* Request a set of permissions, showing a rationale if the system requests it.
*
* @param host requesting context.
* @param rationale a message explaining why the application needs this set of permissions;
* will be displayed if the user rejects the request the first time.
* @param requestCode request code to track this request, must be &lt; 256.
* @param perms a set of permissions to be requested.
* @see Manifest.permission
*/
public static void requestPermissions(
@NonNull Activity host, @NonNull String rationale,
@IntRange(from = 0, to = 255) int requestCode, @Size(min = 1) @NonNull String... perms) {
requestPermissions(
new PermissionRequest.Builder(host, requestCode, perms)
.setRationale(rationale)
.build());
}
/**
* Request permissions from a Support Fragment with standard OK/Cancel buttons.
*
* @see #requestPermissions(Activity, String, int, String...)
*/
public static void requestPermissions(
@NonNull Fragment host, @NonNull String rationale,
@IntRange(from = 0, to = 255) int requestCode, @Size(min = 1) @NonNull String... perms) {
requestPermissions(
new PermissionRequest.Builder(host, requestCode, perms)
.setRationale(rationale)
.build());
}
/**
* Request a set of permissions.
*
* @param request the permission request
* @see PermissionRequest
*/
public static void requestPermissions(PermissionRequest request) {
// Check for permissions before dispatching the request
if (hasPermissions(request.getHelper().getContext(), request.getPerms())) {
notifyAlreadyHasPermissions(
request.getHelper().getHost(), request.getRequestCode(), request.getPerms());
return;
}
// Request permissions
request.getHelper().requestPermissions(
request.getRationale(),
request.getPositiveButtonText(),
request.getNegativeButtonText(),
request.getTheme(),
request.getRequestCode(),
request.getPerms());
}
/**
* Handle the result of a permission request, should be called from the calling {@link
* Activity}'s {@link ActivityCompat.OnRequestPermissionsResultCallback#onRequestPermissionsResult(int,
* String[], int[])} method.
* <p>
* If any permissions were granted or denied, the {@code object} will receive the appropriate
* callbacks through {@link PermissionCallbacks} and methods annotated with {@link
* AfterPermissionGranted} will be run if appropriate.
*
* @param requestCode requestCode argument to permission result callback.
* @param permissions permissions argument to permission result callback.
* @param grantResults grantResults argument to permission result callback.
* @param receivers an array of objects that have a method annotated with {@link
* AfterPermissionGranted} or implement {@link PermissionCallbacks}.
*/
public static void onRequestPermissionsResult(@IntRange(from = 0, to = 255) int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults,
@NonNull Object... receivers) {
// Make a collection of granted and denied permissions from the request.
List<String> granted = new ArrayList<>();
List<String> denied = new ArrayList<>();
for (int i = 0; i < permissions.length; i++) {
String perm = permissions[i];
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
granted.add(perm);
} else {
denied.add(perm);
}
}
// iterate through all receivers
for (Object object : receivers) {
// Report granted permissions, if any.
if (!granted.isEmpty()) {
if (object instanceof PermissionCallbacks) {
((PermissionCallbacks) object).onPermissionsGranted(requestCode, granted);
}
}
// Report denied permissions, if any.
if (!denied.isEmpty()) {
if (object instanceof PermissionCallbacks) {
((PermissionCallbacks) object).onPermissionsDenied(requestCode, denied);
}
}
// If 100% successful, call annotated methods
if (!granted.isEmpty() && denied.isEmpty()) {
runAnnotatedMethods(object, requestCode);
}
}
}
/**
* Check if at least one permission in the list of denied permissions has been permanently
* denied (user clicked "Never ask again").
*
* <b>Note</b>: Due to a limitation in the information provided by the Android
* framework permissions API, this method only works after the permission
* has been denied and your app has received the onPermissionsDenied callback.
* Otherwise the library cannot distinguish permanent denial from the
* "not yet denied" case.
*
* @param host context requesting permissions.
* @param deniedPermissions list of denied permissions, usually from {@link
* PermissionCallbacks#onPermissionsDenied(int, List)}
* @return {@code true} if at least one permission in the list was permanently denied.
*/
public static boolean somePermissionPermanentlyDenied(@NonNull Activity host,
@NonNull List<String> deniedPermissions) {
return PermissionHelper.newInstance(host)
.somePermissionPermanentlyDenied(deniedPermissions);
}
/**
* @see #somePermissionPermanentlyDenied(Activity, List)
*/
public static boolean somePermissionPermanentlyDenied(@NonNull Fragment host,
@NonNull List<String> deniedPermissions) {
return PermissionHelper.newInstance(host)
.somePermissionPermanentlyDenied(deniedPermissions);
}
/**
* Check if a permission has been permanently denied (user clicked "Never ask again").
*
* @param host context requesting permissions.
* @param deniedPermission denied permission.
* @return {@code true} if the permissions has been permanently denied.
*/
public static boolean permissionPermanentlyDenied(@NonNull Activity host,
@NonNull String deniedPermission) {
return PermissionHelper.newInstance(host).permissionPermanentlyDenied(deniedPermission);
}
/**
* @see #permissionPermanentlyDenied(Activity, String)
*/
public static boolean permissionPermanentlyDenied(@NonNull Fragment host,
@NonNull String deniedPermission) {
return PermissionHelper.newInstance(host).permissionPermanentlyDenied(deniedPermission);
}
/**
* See if some denied permission has been permanently denied.
*
* @param host requesting context.
* @param perms array of permissions.
* @return true if the user has previously denied any of the {@code perms} and we should show a
* rationale, false otherwise.
*/
public static boolean somePermissionDenied(@NonNull Activity host,
@NonNull String... perms) {
return PermissionHelper.newInstance(host).somePermissionDenied(perms);
}
/**
* @see #somePermissionDenied(Activity, String...)
*/
public static boolean somePermissionDenied(@NonNull Fragment host,
@NonNull String... perms) {
return PermissionHelper.newInstance(host).somePermissionDenied(perms);
}
/**
* Run permission callbacks on an object that requested permissions but already has them by
* simulating {@link PackageManager#PERMISSION_GRANTED}.
*
* @param object the object requesting permissions.
* @param requestCode the permission request code.
* @param perms a list of permissions requested.
*/
private static void notifyAlreadyHasPermissions(@NonNull Object object,
int requestCode,
@NonNull String[] perms) {
int[] grantResults = new int[perms.length];
for (int i = 0; i < perms.length; i++) {
grantResults[i] = PackageManager.PERMISSION_GRANTED;
}
onRequestPermissionsResult(requestCode, perms, grantResults, object);
}
/**
* Find all methods annotated with {@link AfterPermissionGranted} on a given object with the
* correct requestCode argument.
*
* @param object the object with annotated methods.
* @param requestCode the requestCode passed to the annotation.
*/
private static void runAnnotatedMethods(@NonNull Object object, int requestCode) {
Class clazz = object.getClass();
if (isUsingAndroidAnnotations(object)) {
clazz = clazz.getSuperclass();
}
while (clazz != null) {
for (Method method : clazz.getDeclaredMethods()) {
AfterPermissionGranted ann = method.getAnnotation(AfterPermissionGranted.class);
if (ann != null) {
// Check for annotated methods with matching request code.
if (ann.value() == requestCode) {
// Method must be void so that we can invoke it
if (method.getParameterTypes().length > 0) {
throw new RuntimeException(
"Cannot execute method " + method.getName() + " because it is non-void method and/or has input parameters.");
}
try {
// Make method accessible if private
if (!method.isAccessible()) {
method.setAccessible(true);
}
method.invoke(object);
} catch (IllegalAccessException e) {
Log.e(TAG, "runDefaultMethod:IllegalAccessException", e);
} catch (InvocationTargetException e) {
Log.e(TAG, "runDefaultMethod:InvocationTargetException", e);
}
}
}
}
clazz = clazz.getSuperclass();
}
}
/**
* Determine if the project is using the AndroidAnnotations library.
*/
private static boolean isUsingAndroidAnnotations(@NonNull Object object) {
if (!object.getClass().getSimpleName().endsWith("_")) {
return false;
}
try {
Class clazz = Class.forName("org.androidannotations.api.view.HasViews");
return clazz.isInstance(object);
} catch (ClassNotFoundException e) {
return false;
}
}
}

View File

@@ -0,0 +1,259 @@
package com.yizhuan.xchat_android_library;
import android.app.Activity;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.Size;
import androidx.annotation.StringRes;
import androidx.annotation.StyleRes;
import androidx.fragment.app.Fragment;
import java.util.Arrays;
import com.yizhuan.xchat_android_library.helper.PermissionHelper;
/**
* An immutable model object that holds all of the parameters associated with a permission request,
* such as the permissions, request code, and rationale.
*
* @see EasyPermissions#requestPermissions(PermissionRequest)
* @see Builder
*/
public final class PermissionRequest {
private final PermissionHelper mHelper;
private final String[] mPerms;
private final int mRequestCode;
private final String mRationale;
private final String mPositiveButtonText;
private final String mNegativeButtonText;
private final int mTheme;
private PermissionRequest(PermissionHelper helper,
String[] perms,
int requestCode,
String rationale,
String positiveButtonText,
String negativeButtonText,
int theme) {
mHelper = helper;
mPerms = perms.clone();
mRequestCode = requestCode;
mRationale = rationale;
mPositiveButtonText = positiveButtonText;
mNegativeButtonText = negativeButtonText;
mTheme = theme;
}
@NonNull
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public PermissionHelper getHelper() {
return mHelper;
}
@NonNull
public String[] getPerms() {
return mPerms.clone();
}
public int getRequestCode() {
return mRequestCode;
}
@NonNull
public String getRationale() {
return mRationale;
}
@NonNull
public String getPositiveButtonText() {
return mPositiveButtonText;
}
@NonNull
public String getNegativeButtonText() {
return mNegativeButtonText;
}
@StyleRes
public int getTheme() {
return mTheme;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PermissionRequest request = (PermissionRequest) o;
return Arrays.equals(mPerms, request.mPerms) && mRequestCode == request.mRequestCode;
}
@Override
public int hashCode() {
int result = Arrays.hashCode(mPerms);
result = 31 * result + mRequestCode;
return result;
}
@Override
public String toString() {
return "PermissionRequest{" +
"mHelper=" + mHelper +
", mPerms=" + Arrays.toString(mPerms) +
", mRequestCode=" + mRequestCode +
", mRationale='" + mRationale + '\'' +
", mPositiveButtonText='" + mPositiveButtonText + '\'' +
", mNegativeButtonText='" + mNegativeButtonText + '\'' +
", mTheme=" + mTheme +
'}';
}
/**
* Builder to build a permission request with variable options.
*
* @see PermissionRequest
*/
public static final class Builder {
private final PermissionHelper mHelper;
private final int mRequestCode;
private final String[] mPerms;
private String mRationale;
private String mPositiveButtonText;
private String mNegativeButtonText;
private int mTheme = -1;
/**
* Construct a new permission request builder with a host, request code, and the requested
* permissions.
*
* @param activity the permission request host
* @param requestCode request code to track this request; must be &lt; 256
* @param perms the set of permissions to be requested
*/
public Builder(@NonNull Activity activity, int requestCode,
@NonNull @Size(min = 1) String... perms) {
mHelper = PermissionHelper.newInstance(activity);
mRequestCode = requestCode;
mPerms = perms;
}
/**
* @see #Builder(Activity, int, String...)
*/
public Builder(@NonNull Fragment fragment, int requestCode,
@NonNull @Size(min = 1) String... perms) {
mHelper = PermissionHelper.newInstance(fragment);
mRequestCode = requestCode;
mPerms = perms;
}
/**
* Set the rationale to display to the user if they don't allow your permissions on the
* first try. This rationale will be shown as long as the user has denied your permissions
* at least once, but has not yet permanently denied your permissions. Should the user
* permanently deny your permissions, use the {@link AppSettingsDialog} instead.
* <p>
*
* @param rationale the rationale to be displayed to the user should they deny your
* permission at least once
*/
@NonNull
public Builder setRationale(@Nullable String rationale) {
mRationale = rationale;
return this;
}
/**
* @param resId the string resource to be used as a rationale
* @see #setRationale(String)
*/
@NonNull
public Builder setRationale(@StringRes int resId) {
mRationale = mHelper.getContext().getString(resId);
return this;
}
/**
* Set the positive button text for the rationale dialog should it be shown.
* <p>
* The default is {@link android.R.string#ok}
*/
@NonNull
public Builder setPositiveButtonText(@Nullable String positiveButtonText) {
mPositiveButtonText = positiveButtonText;
return this;
}
/**
* @see #setPositiveButtonText(String)
*/
@NonNull
public Builder setPositiveButtonText(@StringRes int resId) {
mPositiveButtonText = mHelper.getContext().getString(resId);
return this;
}
/**
* Set the negative button text for the rationale dialog should it be shown.
* <p>
* The default is {@link android.R.string#cancel}
*/
@NonNull
public Builder setNegativeButtonText(@Nullable String negativeButtonText) {
mNegativeButtonText = negativeButtonText;
return this;
}
/**
* @see #setNegativeButtonText(String)
*/
@NonNull
public Builder setNegativeButtonText(@StringRes int resId) {
mNegativeButtonText = mHelper.getContext().getString(resId);
return this;
}
/**
* Set the theme to be used for the rationale dialog should it be shown.
*
* @param theme a style resource
*/
@NonNull
public Builder setTheme(@StyleRes int theme) {
mTheme = theme;
return this;
}
/**
* Build the permission request.
*
* @return the permission request
* @see EasyPermissions#requestPermissions(PermissionRequest)
* @see PermissionRequest
*/
@NonNull
public PermissionRequest build() {
if (mRationale == null) {
mRationale = mHelper.getContext().getString(R.string.rationale_ask);
}
if (mPositiveButtonText == null) {
mPositiveButtonText = mHelper.getContext().getString(android.R.string.ok);
}
if (mNegativeButtonText == null) {
mNegativeButtonText = mHelper.getContext().getString(android.R.string.cancel);
}
return new PermissionRequest(
mHelper,
mPerms,
mRequestCode,
mRationale,
mPositiveButtonText,
mNegativeButtonText,
mTheme);
}
}
}

View File

@@ -0,0 +1,77 @@
package com.yizhuan.xchat_android_library;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import androidx.fragment.app.Fragment;
import java.util.Arrays;
import com.yizhuan.xchat_android_library.helper.PermissionHelper;
/**
* Click listener for either {@link RationaleDialogFragment} or {@link RationaleDialogFragmentCompat}.
*/
class RationaleDialogClickListener implements Dialog.OnClickListener {
private Object mHost;
private RationaleDialogConfig mConfig;
private EasyPermissions.PermissionCallbacks mCallbacks;
private EasyPermissions.RationaleCallbacks mRationaleCallbacks;
RationaleDialogClickListener(RationaleDialogFragmentCompat compatDialogFragment,
RationaleDialogConfig config,
EasyPermissions.PermissionCallbacks callbacks,
EasyPermissions.RationaleCallbacks rationaleCallbacks) {
mHost = compatDialogFragment.getParentFragment() != null
? compatDialogFragment.getParentFragment()
: compatDialogFragment.getActivity();
mConfig = config;
mCallbacks = callbacks;
mRationaleCallbacks = rationaleCallbacks;
}
RationaleDialogClickListener(RationaleDialogFragment dialogFragment,
RationaleDialogConfig config,
EasyPermissions.PermissionCallbacks callbacks,
EasyPermissions.RationaleCallbacks dialogCallback) {
mHost = dialogFragment.getActivity();
mConfig = config;
mCallbacks = callbacks;
mRationaleCallbacks = dialogCallback;
}
@Override
public void onClick(DialogInterface dialog, int which) {
int requestCode = mConfig.requestCode;
if (which == Dialog.BUTTON_POSITIVE) {
String[] permissions = mConfig.permissions;
if (mRationaleCallbacks != null) {
mRationaleCallbacks.onRationaleAccepted(requestCode);
}
if (mHost instanceof Fragment) {
PermissionHelper.newInstance((Fragment) mHost).directRequestPermissions(requestCode, permissions);
} else if (mHost instanceof Activity) {
PermissionHelper.newInstance((Activity) mHost).directRequestPermissions(requestCode, permissions);
} else {
throw new RuntimeException("Host must be an Activity or Fragment!");
}
} else {
if (mRationaleCallbacks != null) {
mRationaleCallbacks.onRationaleDenied(requestCode);
}
notifyPermissionDenied();
}
}
private void notifyPermissionDenied() {
if (mCallbacks != null) {
mCallbacks.onPermissionsDenied(mConfig.requestCode, Arrays.asList(mConfig.permissions));
}
}
}

View File

@@ -0,0 +1,141 @@
package com.yizhuan.xchat_android_library;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.AlertDialog;
/**
* Configuration for either {@link RationaleDialogFragment} or {@link RationaleDialogFragmentCompat}.
*/
class RationaleDialogConfig {
private static final String KEY_POSITIVE_BUTTON = "positiveButton";
private static final String KEY_NEGATIVE_BUTTON = "negativeButton";
private static final String KEY_RATIONALE_MESSAGE = "rationaleMsg";
private static final String KEY_THEME = "theme";
private static final String KEY_REQUEST_CODE = "requestCode";
private static final String KEY_PERMISSIONS = "permissions";
String positiveButton;
String negativeButton;
int theme;
int requestCode;
String rationaleMsg;
String[] permissions;
RationaleDialogConfig(@NonNull String positiveButton,
@NonNull String negativeButton,
@NonNull String rationaleMsg,
@StyleRes int theme,
int requestCode,
@NonNull String[] permissions) {
this.positiveButton = positiveButton;
this.negativeButton = negativeButton;
this.rationaleMsg = rationaleMsg;
this.theme = theme;
this.requestCode = requestCode;
this.permissions = permissions;
}
RationaleDialogConfig(Bundle bundle) {
positiveButton = bundle.getString(KEY_POSITIVE_BUTTON);
negativeButton = bundle.getString(KEY_NEGATIVE_BUTTON);
rationaleMsg = bundle.getString(KEY_RATIONALE_MESSAGE);
theme = bundle.getInt(KEY_THEME);
requestCode = bundle.getInt(KEY_REQUEST_CODE);
permissions = bundle.getStringArray(KEY_PERMISSIONS);
}
Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putString(KEY_POSITIVE_BUTTON, positiveButton);
bundle.putString(KEY_NEGATIVE_BUTTON, negativeButton);
bundle.putString(KEY_RATIONALE_MESSAGE, rationaleMsg);
bundle.putInt(KEY_THEME, theme);
bundle.putInt(KEY_REQUEST_CODE, requestCode);
bundle.putStringArray(KEY_PERMISSIONS, permissions);
return bundle;
}
Dialog createDialog(Context context, Dialog.OnClickListener listener) {
Dialog dialog = new Dialog(context, R.style.Dialog_Transparent);
dialog.setCancelable(false);
dialog.setCanceledOnTouchOutside(false);
View view = LayoutInflater.from(context).inflate(R.layout.layout_permission_rationale_dialog, null);
View tv_rationale = view.findViewById(R.id.tv_rationale);
if (tv_rationale instanceof TextView) {
((TextView) tv_rationale).setText(rationaleMsg);
}
View btn_cancel = view.findViewById(R.id.btn_cancel);
if (btn_cancel instanceof Button) {
((Button) btn_cancel).setText(negativeButton);
btn_cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
if (listener != null) {
listener.onClick(dialog, DialogInterface.BUTTON_NEGATIVE);
}
}
});
}
View btn_ok = view.findViewById(R.id.btn_ok);
if (btn_ok instanceof Button) {
((Button) btn_ok).setText(positiveButton);
btn_ok.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
if (listener != null) {
listener.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
}
}
});
}
dialog.setContentView(view);
dialog.create();
return dialog;
}
AlertDialog createSupportDialog(Context context, Dialog.OnClickListener listener) {
AlertDialog.Builder builder;
if (theme > 0) {
builder = new AlertDialog.Builder(context, theme);
} else {
builder = new AlertDialog.Builder(context);
}
return builder
.setCancelable(false)
.setPositiveButton(positiveButton, listener)
.setNegativeButton(negativeButton, listener)
.setMessage(rationaleMsg)
.create();
}
android.app.AlertDialog createFrameworkDialog(Context context, Dialog.OnClickListener listener) {
android.app.AlertDialog.Builder builder;
if (theme > 0) {
builder = new android.app.AlertDialog.Builder(context, theme);
} else {
builder = new android.app.AlertDialog.Builder(context);
}
return builder
.setCancelable(false)
.setPositiveButton(positiveButton, listener)
.setNegativeButton(negativeButton, listener)
.setMessage(rationaleMsg)
.create();
}
}

View File

@@ -0,0 +1,113 @@
package com.yizhuan.xchat_android_library;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.FragmentManager;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.StyleRes;
/**
* {@link DialogFragment} to display rationale for permission requests when the request comes from
* a Fragment or Activity that can host a Fragment.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class RationaleDialogFragment extends DialogFragment {
public static final String TAG = "RationaleDialogFragment";
private EasyPermissions.PermissionCallbacks mPermissionCallbacks;
private EasyPermissions.RationaleCallbacks mRationaleCallbacks;
private boolean mStateSaved = false;
public static RationaleDialogFragment newInstance(
@NonNull String positiveButton,
@NonNull String negativeButton,
@NonNull String rationaleMsg,
@StyleRes int theme,
int requestCode,
@NonNull String[] permissions) {
// Create new Fragment
RationaleDialogFragment dialogFragment = new RationaleDialogFragment();
// Initialize configuration as arguments
RationaleDialogConfig config = new RationaleDialogConfig(
positiveButton, negativeButton, rationaleMsg, theme, requestCode, permissions);
dialogFragment.setArguments(config.toBundle());
return dialogFragment;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && getParentFragment() != null) {
if (getParentFragment() instanceof EasyPermissions.PermissionCallbacks) {
mPermissionCallbacks = (EasyPermissions.PermissionCallbacks) getParentFragment();
}
if (getParentFragment() instanceof EasyPermissions.RationaleCallbacks){
mRationaleCallbacks = (EasyPermissions.RationaleCallbacks) getParentFragment();
}
}
if (context instanceof EasyPermissions.PermissionCallbacks) {
mPermissionCallbacks = (EasyPermissions.PermissionCallbacks) context;
}
if (context instanceof EasyPermissions.RationaleCallbacks) {
mRationaleCallbacks = (EasyPermissions.RationaleCallbacks) context;
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
mStateSaved = true;
super.onSaveInstanceState(outState);
}
/**
* Version of {@link #show(FragmentManager, String)} that no-ops when an IllegalStateException
* would otherwise occur.
*/
public void showAllowingStateLoss(FragmentManager manager, String tag) {
// API 26 added this convenient method
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (manager.isStateSaved()) {
return;
}
}
if (mStateSaved) {
return;
}
show(manager, tag);
}
@Override
public void onDetach() {
super.onDetach();
mPermissionCallbacks = null;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Rationale dialog should not be cancelable
setCancelable(false);
// Get config from arguments, create click listener
RationaleDialogConfig config = new RationaleDialogConfig(getArguments());
RationaleDialogClickListener clickListener =
new RationaleDialogClickListener(this, config, mPermissionCallbacks, mRationaleCallbacks);
// Create an AlertDialog
return config.createDialog(getActivity(), clickListener);
}
}

View File

@@ -0,0 +1,97 @@
package com.yizhuan.xchat_android_library;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.StyleRes;
import androidx.fragment.app.FragmentManager;
import androidx.appcompat.app.AppCompatDialogFragment;
/**
* {@link AppCompatDialogFragment} to display rationale for permission requests when the request
* comes from a Fragment or Activity that can host a Fragment.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class RationaleDialogFragmentCompat extends AppCompatDialogFragment {
public static final String TAG = "RationaleDialogFragmentCompat";
private EasyPermissions.PermissionCallbacks mPermissionCallbacks;
private EasyPermissions.RationaleCallbacks mRationaleCallbacks;
public static RationaleDialogFragmentCompat newInstance(
@NonNull String rationaleMsg,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String[] permissions) {
// Create new Fragment
RationaleDialogFragmentCompat dialogFragment = new RationaleDialogFragmentCompat();
// Initialize configuration as arguments
RationaleDialogConfig config = new RationaleDialogConfig(
positiveButton, negativeButton, rationaleMsg, theme, requestCode, permissions);
dialogFragment.setArguments(config.toBundle());
return dialogFragment;
}
/**
* Version of {@link #show(FragmentManager, String)} that no-ops when an IllegalStateException
* would otherwise occur.
*/
public void showAllowingStateLoss(FragmentManager manager, String tag) {
if (manager.isStateSaved()) {
return;
}
show(manager, tag);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (getParentFragment() != null) {
if (getParentFragment() instanceof EasyPermissions.PermissionCallbacks) {
mPermissionCallbacks = (EasyPermissions.PermissionCallbacks) getParentFragment();
}
if (getParentFragment() instanceof EasyPermissions.RationaleCallbacks){
mRationaleCallbacks = (EasyPermissions.RationaleCallbacks) getParentFragment();
}
}
if (context instanceof EasyPermissions.PermissionCallbacks) {
mPermissionCallbacks = (EasyPermissions.PermissionCallbacks) context;
}
if (context instanceof EasyPermissions.RationaleCallbacks) {
mRationaleCallbacks = (EasyPermissions.RationaleCallbacks) context;
}
}
@Override
public void onDetach() {
super.onDetach();
mPermissionCallbacks = null;
mRationaleCallbacks = null;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Rationale dialog should not be cancelable
setCancelable(false);
// Get config from arguments, create click listener
RationaleDialogConfig config = new RationaleDialogConfig(getArguments());
RationaleDialogClickListener clickListener =
new RationaleDialogClickListener(this, config, mPermissionCallbacks, mRationaleCallbacks);
// Create an AlertDialog
return config.createDialog(getContext(), clickListener);
}
}

View File

@@ -0,0 +1,59 @@
package com.yizhuan.xchat_android_library.helper;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import androidx.core.app.ActivityCompat;
import android.util.Log;
import com.yizhuan.xchat_android_library.RationaleDialogFragment;
/**
* Permissions helper for {@link Activity}.
*/
class ActivityPermissionHelper extends PermissionHelper<Activity> {
private static final String TAG = "ActPermissionHelper";
public ActivityPermissionHelper(Activity host) {
super(host);
}
@Override
public void directRequestPermissions(int requestCode, @NonNull String... perms) {
ActivityCompat.requestPermissions(getHost(), perms, requestCode);
}
@Override
public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
return ActivityCompat.shouldShowRequestPermissionRationale(getHost(), perm);
}
@Override
public Context getContext() {
return getHost();
}
@Override
public void showRequestPermissionRationale(@NonNull String rationale,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String... perms) {
FragmentManager fm = getHost().getFragmentManager();
// Check if fragment is already showing
Fragment fragment = fm.findFragmentByTag(RationaleDialogFragment.TAG);
if (fragment instanceof RationaleDialogFragment) {
Log.d(TAG, "Found existing fragment, not showing rationale.");
return;
}
RationaleDialogFragment
.newInstance(positiveButton, negativeButton, rationale, theme, requestCode, perms)
.showAllowingStateLoss(fm, RationaleDialogFragment.TAG);
}
}

View File

@@ -0,0 +1,37 @@
package com.yizhuan.xchat_android_library.helper;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.FragmentManager;
import androidx.appcompat.app.AppCompatActivity;
/**
* Permissions helper for {@link AppCompatActivity}.
*/
class AppCompatActivityPermissionsHelper extends BaseSupportPermissionsHelper<AppCompatActivity> {
public AppCompatActivityPermissionsHelper(AppCompatActivity host) {
super(host);
}
@Override
public FragmentManager getSupportFragmentManager() {
return getHost().getSupportFragmentManager();
}
@Override
public void directRequestPermissions(int requestCode, @NonNull String... perms) {
ActivityCompat.requestPermissions(getHost(), perms, requestCode);
}
@Override
public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
return ActivityCompat.shouldShowRequestPermissionRationale(getHost(), perm);
}
@Override
public Context getContext() {
return getHost();
}
}

View File

@@ -0,0 +1,45 @@
package com.yizhuan.xchat_android_library.helper;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import android.util.Log;
import com.yizhuan.xchat_android_library.RationaleDialogFragmentCompat;
/**
* Implementation of {@link PermissionHelper} for Support Library host classes.
*/
public abstract class BaseSupportPermissionsHelper<T> extends PermissionHelper<T> {
private static final String TAG = "BSPermissionsHelper";
public BaseSupportPermissionsHelper(@NonNull T host) {
super(host);
}
public abstract FragmentManager getSupportFragmentManager();
@Override
public void showRequestPermissionRationale(@NonNull String rationale,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String... perms) {
FragmentManager fm = getSupportFragmentManager();
// Check if fragment is already showing
Fragment fragment = fm.findFragmentByTag(RationaleDialogFragmentCompat.TAG);
if (fragment instanceof RationaleDialogFragmentCompat) {
Log.d(TAG, "Found existing fragment, not showing rationale.");
return;
}
RationaleDialogFragmentCompat
.newInstance(rationale, positiveButton, negativeButton, theme, requestCode, perms)
.showAllowingStateLoss(fm, RationaleDialogFragmentCompat.TAG);
}
}

View File

@@ -0,0 +1,47 @@
package com.yizhuan.xchat_android_library.helper;
import android.app.Activity;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import androidx.fragment.app.Fragment;
/**
* Permissions helper for apps built against API < 23, which do not need runtime permissions.
*/
class LowApiPermissionsHelper<T> extends PermissionHelper<T> {
public LowApiPermissionsHelper(@NonNull T host) {
super(host);
}
@Override
public void directRequestPermissions(int requestCode, @NonNull String... perms) {
throw new IllegalStateException("Should never be requesting permissions on API < 23!");
}
@Override
public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
return false;
}
@Override
public void showRequestPermissionRationale(@NonNull String rationale,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String... perms) {
throw new IllegalStateException("Should never be requesting permissions on API < 23!");
}
@Override
public Context getContext() {
if (getHost() instanceof Activity) {
return (Context) getHost();
} else if (getHost() instanceof Fragment) {
return ((Fragment) getHost()).getContext();
} else {
throw new IllegalStateException("Unknown host: " + getHost());
}
}
}

View File

@@ -0,0 +1,113 @@
package com.yizhuan.xchat_android_library.helper;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import androidx.fragment.app.Fragment;
import androidx.appcompat.app.AppCompatActivity;
import java.util.List;
/**
* Delegate class to make permission calls based on the 'host' (Fragment, Activity, etc).
*/
public abstract class PermissionHelper<T> {
private T mHost;
@NonNull
public static PermissionHelper<? extends Activity> newInstance(Activity host) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return new LowApiPermissionsHelper<>(host);
}
if (host instanceof AppCompatActivity)
return new AppCompatActivityPermissionsHelper((AppCompatActivity) host);
else {
return new ActivityPermissionHelper(host);
}
}
@NonNull
public static PermissionHelper<Fragment> newInstance(Fragment host) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return new LowApiPermissionsHelper<>(host);
}
return new SupportFragmentPermissionHelper(host);
}
// ============================================================================
// Public concrete methods
// ============================================================================
public PermissionHelper(@NonNull T host) {
mHost = host;
}
private boolean shouldShowRationale(@NonNull String... perms) {
for (String perm : perms) {
if (shouldShowRequestPermissionRationale(perm)) {
return true;
}
}
return false;
}
public void requestPermissions(@NonNull String rationale,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String... perms) {
if (shouldShowRationale(perms)) {
showRequestPermissionRationale(
rationale, positiveButton, negativeButton, theme, requestCode, perms);
} else {
directRequestPermissions(requestCode, perms);
}
}
public boolean somePermissionPermanentlyDenied(@NonNull List<String> perms) {
for (String deniedPermission : perms) {
if (permissionPermanentlyDenied(deniedPermission)) {
return true;
}
}
return false;
}
public boolean permissionPermanentlyDenied(@NonNull String perms) {
return !shouldShowRequestPermissionRationale(perms);
}
public boolean somePermissionDenied(@NonNull String... perms) {
return shouldShowRationale(perms);
}
@NonNull
public T getHost() {
return mHost;
}
// ============================================================================
// Public abstract methods
// ============================================================================
public abstract void directRequestPermissions(int requestCode, @NonNull String... perms);
public abstract boolean shouldShowRequestPermissionRationale(@NonNull String perm);
public abstract void showRequestPermissionRationale(@NonNull String rationale,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String... perms);
public abstract Context getContext();
}

View File

@@ -0,0 +1,36 @@
package com.yizhuan.xchat_android_library.helper;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
/**
* Permissions helper for {@link Fragment} from the support library.
*/
class SupportFragmentPermissionHelper extends BaseSupportPermissionsHelper<Fragment> {
public SupportFragmentPermissionHelper(@NonNull Fragment host) {
super(host);
}
@Override
public FragmentManager getSupportFragmentManager() {
return getHost().getChildFragmentManager();
}
@Override
public void directRequestPermissions(int requestCode, @NonNull String... perms) {
getHost().requestPermissions(perms, requestCode);
}
@Override
public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
return getHost().shouldShowRequestPermissionRationale(perm);
}
@Override
public Context getContext() {
return getHost().getActivity();
}
}

View File

@@ -0,0 +1,4 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
package com.yizhuan.xchat_android_library.helper;
import androidx.annotation.RestrictTo;

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tv_rationale"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:minHeight="50dp"
android:padding="15dp"
android:textColor="#555555"
android:textSize="14sp"
tools:text="殴打司法局案件司法局OA四季豆覅降低" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="15dp"
android:layout_marginBottom="15dp"
android:orientation="horizontal">
<Button
android:id="@+id/btn_cancel"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_weight="1"
android:gravity="center"
android:textColor="#555555"
android:textSize="14sp"
tools:text="取消" />
<Button
android:id="@+id/btn_ok"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_weight="1"
android:gravity="center"
android:textColor="#555555"
android:textSize="14sp"
tools:text="确定" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Use system defaults for devs not using AppCompat -->
<color name="colorEasyPrimary">#ff212121</color>
<color name="colorEasyPrimaryDark">@android:color/black</color>
<color name="colorEasyAccent">#ff80cbc4</color>
</resources>

View File

@@ -0,0 +1,5 @@
<resources>
<string name="rationale_ask">如果沒有請求的權限,此應用程式可能無法正常工作。</string>
<string name="rationale_ask_again">如果沒有請求的權限,此應用程式可能無法正常工作,打開應用設置頁面以修改應用權限。</string>
<string name="title_settings_dialog">所需權限</string>
</resources>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="EasyPermissions" parent="Theme.AppCompat.DayNight.DarkActionBar">
<item name="colorPrimary">@color/colorEasyPrimary</item>
<item name="colorPrimaryDark">@color/colorEasyPrimaryDark</item>
<item name="colorAccent">@color/colorEasyAccent</item>
</style>
<style name="EasyPermissions.Transparent">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">false</item>
</style>
<style name="Dialog_Transparent" parent="@android:style/Theme.Dialog">
<item name="android:windowFrame">@null</item><!--边框-->
<item name="android:windowIsFloating">true</item><!--是否浮现在activity之上-->
<item name="android:windowIsTranslucent">false</item><!--半透明-->
<item name="android:windowNoTitle">true</item><!--无标题-->
<item name="android:windowBackground">@android:color/transparent</item><!--背景透明-->
<item name="android:backgroundDimEnabled">true</item><!--模糊-->
<item name="android:backgroundDimAmount">0.3</item>
</style>
</resources>

View File

@@ -0,0 +1,345 @@
package com.yizhuan.xchat_android_library;
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.yizhuan.xchat_android_library.builder.AlbumBuilder;
import com.yizhuan.xchat_android_library.callback.PuzzleCallback;
import com.yizhuan.xchat_android_library.engine.ImageEngine;
import com.yizhuan.xchat_android_library.models.ad.AdListener;
import com.yizhuan.xchat_android_library.models.album.AlbumModel;
import com.yizhuan.xchat_android_library.models.album.entity.Photo;
import com.yizhuan.xchat_android_library.models.sticker.StickerModel;
import com.yizhuan.xchat_android_library.models.sticker.entity.TextStickerData;
import com.yizhuan.xchat_android_library.ui.PuzzleActivity;
import com.yizhuan.xchat_android_library.utils.bitmap.BitmapUtils;
import com.yizhuan.xchat_android_library.utils.bitmap.SaveBitmapCallBack;
import com.yizhuan.xchat_android_library.utils.media.MediaScannerConnectionUtils;
import com.yizhuan.xchat_android_library.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();
}
}

View File

@@ -0,0 +1,618 @@
package com.yizhuan.xchat_android_library.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.yizhuan.xchat_android_library.callback.SelectCallback;
import com.yizhuan.xchat_android_library.constant.Type;
import com.yizhuan.xchat_android_library.engine.ImageEngine;
import com.yizhuan.xchat_android_library.models.ad.AdListener;
import com.yizhuan.xchat_android_library.models.album.entity.Photo;
import com.yizhuan.xchat_android_library.result.Result;
import com.yizhuan.xchat_android_library.setting.Setting;
import com.yizhuan.xchat_android_library.ui.EasyPhotosActivity;
import com.yizhuan.xchat_android_library.utils.result.EasyResult;
import com.yizhuan.xchat_android_library.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 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);
this.startupType = startupType;
}
private AlbumBuilder(android.app.Fragment fragment, StartupType startupType) {
mFragment = new WeakReference<>(fragment);
this.startupType = startupType;
}
private AlbumBuilder(FragmentActivity activity, StartupType startupType) {
mActivity = new WeakReference<>(activity);
this.startupType = startupType;
}
private AlbumBuilder(Fragment fragment, StartupType startupType) {
mFragmentV = new WeakReference<>(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 图片加载引擎的具体实现
*/
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 可选择图片类型文件的最大数
*/
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 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 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;
}
/**
* 相册选择页是否显示清空按钮
*/
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();
}
}

View File

@@ -0,0 +1,22 @@
package com.yizhuan.xchat_android_library.callback;
import com.yizhuan.xchat_android_library.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();
}

View File

@@ -0,0 +1,26 @@
package com.yizhuan.xchat_android_library.callback;
import com.yizhuan.xchat_android_library.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();
}

View File

@@ -0,0 +1,21 @@
package com.yizhuan.xchat_android_library.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;
}

View File

@@ -0,0 +1,23 @@
package com.yizhuan.xchat_android_library.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";
}

View File

@@ -0,0 +1,15 @@
package com.yizhuan.xchat_android_library.constant;
/**
* Created by huan on 2018/1/9.
*/
public class Type {
public static final String GIF = "gif";
public static final String VIDEO = "video";
public static final String JPEG = "jpeg";
public static final String JPG = "jpg";
public static final String PNG = "png";
public static final String BMP = "bmp";
public static final String WEBP = "webp";
}

View File

@@ -0,0 +1,63 @@
package com.yizhuan.xchat_android_library.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动图图片到ImageViewgif动图不动
*
* @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动图到ImageViewgif动图动
*
* @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;
}

View File

@@ -0,0 +1,18 @@
package com.yizhuan.xchat_android_library.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;
}
}

View File

@@ -0,0 +1,11 @@
package com.yizhuan.xchat_android_library.models.ad;
/**
* 广告监听
* Created by huan on 2017/10/24.
*/
public interface AdListener {
void onPhotosAdLoaded();
void onAlbumItemsAdLoaded();
}

View File

@@ -0,0 +1,20 @@
package com.yizhuan.xchat_android_library.models.ad;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
import android.widget.FrameLayout;
import com.yizhuan.xchat_android_library.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);
}
}

View File

@@ -0,0 +1,361 @@
package com.yizhuan.xchat_android_library.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.provider.MediaStore;
import android.text.TextUtils;
import androidx.core.content.PermissionChecker;
import com.yizhuan.xchat_android_library.R;
import com.yizhuan.xchat_android_library.constant.Type;
import com.yizhuan.xchat_android_library.models.album.entity.Album;
import com.yizhuan.xchat_android_library.models.album.entity.AlbumItem;
import com.yizhuan.xchat_android_library.models.album.entity.Photo;
import com.yizhuan.xchat_android_library.result.Result;
import com.yizhuan.xchat_android_library.setting.Setting;
import com.yizhuan.xchat_android_library.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 {
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;
}
/**
* 专辑查询
*/
public volatile boolean canRun = true;
public void query(Context context, final CallBack callBack) {
final Context appCxt = context.getApplicationContext();
if (PermissionChecker.checkSelfPermission(context,
Manifest.permission.READ_EXTERNAL_STORAGE) != PermissionChecker.PERMISSION_GRANTED) {
if (null != callBack) callBack.onAlbumWorkedCallBack();
return;
}
canRun = true;
new Thread(new Runnable() {
@Override
public void run() {
try {
initAlbum(appCxt);
if (null != callBack) callBack.onAlbumWorkedCallBack();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
public void stopQuery() {
canRun = false;
}
private synchronized void initAlbum(Context context) {
album.clear();
if (Setting.selectedPhotos.size() > Setting.count) {
throw new RuntimeException("AlbumBuilder: 默认勾选的图片张数不能大于设置的选择数!" + "|默认勾选图片张数:" + Setting.selectedPhotos.size() + "|设置的选择数:" + Setting.count);
}
final String sortOrder = MediaStore.MediaColumns.DATE_MODIFIED + " DESC";
Uri contentUri;
String selection = null;
String[] selectionAllArgs = null;
if (Setting.isOnlyVideo()) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if (!Setting.showVideo) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} 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.MediaColumns._ID);
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 (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 && !cursor.isClosed() && 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 (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(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
String path = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DATA));
String name = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME));
long dateTime = cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED));
String type = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE));
long size = cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns.SIZE));
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);
}
boolean showGif = path.endsWith(Type.GIF) || type.endsWith(Type.GIF);
if (!Setting.showGif) {
if (showGif) {
continue;
}
}
if (!Setting.filterTypes.isEmpty()) {
List<String> list = Setting.filterTypes;
boolean hasFilter = false;
for (String filterType : list) {
if (path.endsWith(filterType) || type.endsWith(filterType)) {
hasFilter = true;
break;
}
}
if (Setting.showGif) {
if (showGif) {
hasFilter = true;
}
}
if (!hasFilter) {
continue;
}
}
if (Setting.useWidth) {
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.EXTERNAL_CONTENT_URI :
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 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);
}
// 把图片全部放进“全部”专辑
if (album.getAlbumItem(albumItem_all_name) != null) {
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.isClosed() && cursor.moveToNext() && canRun);
cursor.close();
}
}
/**
* 获取全部专辑名
*
* @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.MediaColumns._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.MediaColumns._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;
}
}

View File

@@ -0,0 +1,49 @@
package com.yizhuan.xchat_android_library.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();
}
}

View File

@@ -0,0 +1,34 @@
package com.yizhuan.xchat_android_library.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);
}
}

View File

@@ -0,0 +1,114 @@
package com.yizhuan.xchat_android_library.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];
}
};
}

View File

@@ -0,0 +1,65 @@
package com.yizhuan.xchat_android_library.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);
}

View File

@@ -0,0 +1,354 @@
package com.yizhuan.xchat_android_library.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 android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import androidx.core.content.ContextCompat;
import com.yizhuan.xchat_android_library.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("",
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();
}
}

View File

@@ -0,0 +1,52 @@
package com.yizhuan.xchat_android_library.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);
}

View File

@@ -0,0 +1,167 @@
package com.yizhuan.xchat_android_library.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;
}
}

View File

@@ -0,0 +1,88 @@
package com.yizhuan.xchat_android_library.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;
}
}
}

View File

@@ -0,0 +1,433 @@
package com.yizhuan.xchat_android_library.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.yizhuan.xchat_android_library.models.puzzle.MatrixUtils.calculateImageIndents;
import static com.yizhuan.xchat_android_library.models.puzzle.MatrixUtils.getMinMatrixScale;
import static com.yizhuan.xchat_android_library.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;
}
}

View File

@@ -0,0 +1,93 @@
package com.yizhuan.xchat_android_library.models.puzzle;
import com.yizhuan.xchat_android_library.models.puzzle.template.slant.OneSlantLayout;
import com.yizhuan.xchat_android_library.models.puzzle.template.slant.SlantLayoutHelper;
import com.yizhuan.xchat_android_library.models.puzzle.template.slant.ThreeSlantLayout;
import com.yizhuan.xchat_android_library.models.puzzle.template.slant.TwoSlantLayout;
import com.yizhuan.xchat_android_library.models.puzzle.template.straight.EightStraightLayout;
import com.yizhuan.xchat_android_library.models.puzzle.template.straight.FiveStraightLayout;
import com.yizhuan.xchat_android_library.models.puzzle.template.straight.FourStraightLayout;
import com.yizhuan.xchat_android_library.models.puzzle.template.straight.NineStraightLayout;
import com.yizhuan.xchat_android_library.models.puzzle.template.straight.OneStraightLayout;
import com.yizhuan.xchat_android_library.models.puzzle.template.straight.SevenStraightLayout;
import com.yizhuan.xchat_android_library.models.puzzle.template.straight.SixStraightLayout;
import com.yizhuan.xchat_android_library.models.puzzle.template.straight.StraightLayoutHelper;
import com.yizhuan.xchat_android_library.models.puzzle.template.straight.ThreeStraightLayout;
import com.yizhuan.xchat_android_library.models.puzzle.template.straight.TwoStraightLayout;
import java.util.ArrayList;
import java.util.List;
/**
* @author wupanjie
*/
public class 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;
}
}

View File

@@ -0,0 +1,792 @@
package com.yizhuan.xchat_android_library.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 android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import androidx.core.content.ContextCompat;
import com.yizhuan.xchat_android_library.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) {
if (attrs != null) {
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);
}
}

View File

@@ -0,0 +1,32 @@
package com.yizhuan.xchat_android_library.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);
}
}

View File

@@ -0,0 +1,34 @@
package com.yizhuan.xchat_android_library.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);
}
}

View File

@@ -0,0 +1,294 @@
package com.yizhuan.xchat_android_library.models.puzzle.slant;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import com.yizhuan.xchat_android_library.models.puzzle.Area;
import com.yizhuan.xchat_android_library.models.puzzle.Line;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import static com.yizhuan.xchat_android_library.models.puzzle.slant.SlantUtils.distance;
import static com.yizhuan.xchat_android_library.models.puzzle.slant.SlantUtils.getPoint;
import static com.yizhuan.xchat_android_library.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;
}
}
}
}

View File

@@ -0,0 +1,173 @@
package com.yizhuan.xchat_android_library.models.puzzle.slant;
import android.graphics.PointF;
import com.yizhuan.xchat_android_library.models.puzzle.Line;
import static com.yizhuan.xchat_android_library.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 Direction direction;
SlantLine attachLineStart;
SlantLine attachLineEnd;
Line upperLine;
Line lowerLine;
SlantLine(Direction direction) {
this.direction = direction;
}
SlantLine(CrossoverPointF start, CrossoverPointF end, 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 == 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();
}
}

View File

@@ -0,0 +1,334 @@
package com.yizhuan.xchat_android_library.models.puzzle.slant;
import android.graphics.RectF;
import android.util.Pair;
import com.yizhuan.xchat_android_library.models.puzzle.Area;
import com.yizhuan.xchat_android_library.models.puzzle.Line;
import com.yizhuan.xchat_android_library.models.puzzle.PuzzleLayout;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import static com.yizhuan.xchat_android_library.models.puzzle.slant.SlantUtils.createLine;
import static com.yizhuan.xchat_android_library.models.puzzle.slant.SlantUtils.cutAreaCross;
import static com.yizhuan.xchat_android_library.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;
}
}

View File

@@ -0,0 +1,482 @@
package com.yizhuan.xchat_android_library.models.puzzle.slant;
import android.graphics.PointF;
import android.util.Pair;
import com.yizhuan.xchat_android_library.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;
}
}
}

View File

@@ -0,0 +1,231 @@
package com.yizhuan.xchat_android_library.models.puzzle.straight;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import com.yizhuan.xchat_android_library.models.puzzle.Area;
import com.yizhuan.xchat_android_library.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;
}
}
}
}

View File

@@ -0,0 +1,215 @@
package com.yizhuan.xchat_android_library.models.puzzle.straight;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.Log;
import com.yizhuan.xchat_android_library.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 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 = Direction.VERTICAL;
} else if (start.y == end.y) {
direction = 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 == 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 == 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 == 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 == Direction.HORIZONTAL) {
if (attachLineStart != null) {
start.x = attachLineStart.getPosition();
}
if (attachLineEnd != null) {
end.x = attachLineEnd.getPosition();
}
} else if (direction == Direction.VERTICAL) {
if (attachLineStart != null) {
start.y = attachLineStart.getPosition();
}
if (attachLineEnd != null) {
end.y = attachLineEnd.getPosition();
}
}
}
public float getPosition() {
if (direction == 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();
}
}

View File

@@ -0,0 +1,359 @@
package com.yizhuan.xchat_android_library.models.puzzle.straight;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.Pair;
import com.yizhuan.xchat_android_library.models.puzzle.Area;
import com.yizhuan.xchat_android_library.models.puzzle.Line;
import com.yizhuan.xchat_android_library.models.puzzle.PuzzleLayout;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import static com.yizhuan.xchat_android_library.models.puzzle.straight.StraightUtils.createLine;
import static com.yizhuan.xchat_android_library.models.puzzle.straight.StraightUtils.cutAreaCross;
import static com.yizhuan.xchat_android_library.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;
}
}

View File

@@ -0,0 +1,236 @@
package com.yizhuan.xchat_android_library.models.puzzle.straight;
import android.graphics.PointF;
import android.util.Pair;
import com.yizhuan.xchat_android_library.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);
}
}

View File

@@ -0,0 +1,33 @@
package com.yizhuan.xchat_android_library.models.puzzle.template.slant;
import android.util.Log;
import com.yizhuan.xchat_android_library.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;
}
}

View File

@@ -0,0 +1,36 @@
package com.yizhuan.xchat_android_library.models.puzzle.template.slant;
import com.yizhuan.xchat_android_library.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;
}
}
}

View File

@@ -0,0 +1,69 @@
package com.yizhuan.xchat_android_library.models.puzzle.template.slant;
import com.yizhuan.xchat_android_library.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;
}
}

View File

@@ -0,0 +1,48 @@
package com.yizhuan.xchat_android_library.models.puzzle.template.slant;
import com.yizhuan.xchat_android_library.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;
}
}
}

View File

@@ -0,0 +1,30 @@
package com.yizhuan.xchat_android_library.models.puzzle.template.slant;
import com.yizhuan.xchat_android_library.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;
}
}
}

View File

@@ -0,0 +1,89 @@
package com.yizhuan.xchat_android_library.models.puzzle.template.straight;
import com.yizhuan.xchat_android_library.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;
}
}
}

View File

@@ -0,0 +1,94 @@
package com.yizhuan.xchat_android_library.models.puzzle.template.straight;
import com.yizhuan.xchat_android_library.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;
}
}
}

View File

@@ -0,0 +1,52 @@
package com.yizhuan.xchat_android_library.models.puzzle.template.straight;
import com.yizhuan.xchat_android_library.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;
}
}
}

View File

@@ -0,0 +1,76 @@
package com.yizhuan.xchat_android_library.models.puzzle.template.straight;
import com.yizhuan.xchat_android_library.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;
}
}
}

View File

@@ -0,0 +1,31 @@
package com.yizhuan.xchat_android_library.models.puzzle.template.straight;
import android.util.Log;
import com.yizhuan.xchat_android_library.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;
}
}

View File

@@ -0,0 +1,45 @@
package com.yizhuan.xchat_android_library.models.puzzle.template.straight;
import com.yizhuan.xchat_android_library.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;
}
}
}

View File

@@ -0,0 +1,77 @@
package com.yizhuan.xchat_android_library.models.puzzle.template.straight;
import com.yizhuan.xchat_android_library.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;
}
}
}

View File

@@ -0,0 +1,93 @@
package com.yizhuan.xchat_android_library.models.puzzle.template.straight;
import com.yizhuan.xchat_android_library.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;
}
}
}

View File

@@ -0,0 +1,69 @@
package com.yizhuan.xchat_android_library.models.puzzle.template.straight;
import com.yizhuan.xchat_android_library.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;
}
}

View File

@@ -0,0 +1,50 @@
package com.yizhuan.xchat_android_library.models.puzzle.template.straight;
import com.yizhuan.xchat_android_library.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;
}
}
}

View File

@@ -0,0 +1,58 @@
package com.yizhuan.xchat_android_library.models.puzzle.template.straight;
import android.util.Log;
import com.yizhuan.xchat_android_library.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;
}
}
}

View File

@@ -0,0 +1,221 @@
package com.yizhuan.xchat_android_library.models.sticker;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.FragmentManager;
import com.yizhuan.xchat_android_library.EasyPhotos;
import com.yizhuan.xchat_android_library.models.sticker.cache.StickerCache;
import com.yizhuan.xchat_android_library.models.sticker.entity.TextStickerData;
import com.yizhuan.xchat_android_library.models.sticker.listener.OnStickerClickListener;
import com.yizhuan.xchat_android_library.models.sticker.view.BitmapSticker;
import com.yizhuan.xchat_android_library.models.sticker.view.EditFragment;
import com.yizhuan.xchat_android_library.models.sticker.view.TextSticker;
import com.yizhuan.xchat_android_library.utils.bitmap.BitmapUtils;
import com.yizhuan.xchat_android_library.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();
}
}

View File

@@ -0,0 +1,115 @@
package com.yizhuan.xchat_android_library.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.yizhuan.xchat_android_library.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);
}
}

View File

@@ -0,0 +1,48 @@
package com.yizhuan.xchat_android_library.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 Creator<TextStickerData> CREATOR = new Creator<TextStickerData>() {
@Override
public TextStickerData createFromParcel(Parcel source) {
return new TextStickerData(source);
}
@Override
public TextStickerData[] newArray(int size) {
return new TextStickerData[size];
}
};
}

View File

@@ -0,0 +1,16 @@
package com.yizhuan.xchat_android_library.models.sticker.listener;
/**
* 贴纸的点击监听
* Created by huan on 2017/12/12.
*/
public interface OnStickerClickListener {
void onDelete();
void onEditor();
void onTop();
void onUsing();
}

View File

@@ -0,0 +1,438 @@
package com.yizhuan.xchat_android_library.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 android.text.TextUtils;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import androidx.core.content.ContextCompat;
import com.yizhuan.xchat_android_library.R;
import com.yizhuan.xchat_android_library.models.sticker.cache.StickerCache;
import com.yizhuan.xchat_android_library.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.drawable.ic_delete_easy_photos);
btMirror = BitmapFactory.decodeResource(getResources(), R.drawable.ic_mirror_easy_photos);
btScale = BitmapFactory.decodeResource(getResources(), R.drawable.ic_controller_easy_photos);
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);
}
@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) {
switch (clickType) {
case DELETE:
break;
case MIRROR:
break;
case SCALE:
if (e2.getPointerCount() > 1) break;
controller(e2);
break;
case ROTATE:
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;
}
}
}

View File

@@ -0,0 +1,202 @@
package com.yizhuan.xchat_android_library.models.sticker.view;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
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 androidx.annotation.IdRes;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
import com.yizhuan.xchat_android_library.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);
}
}

View File

@@ -0,0 +1,535 @@
package com.yizhuan.xchat_android_library.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.RectF;
import android.graphics.Region;
import android.graphics.Typeface;
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 androidx.core.content.ContextCompat;
import com.yizhuan.xchat_android_library.EasyPhotos;
import com.yizhuan.xchat_android_library.R;
import com.yizhuan.xchat_android_library.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 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);
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.drawable.ic_delete_easy_photos);
btController = BitmapFactory.decodeResource(getResources(), R.drawable.ic_controller_easy_photos);
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(btController, dstPs[4] - btSize / 2, dstPs[5] - 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);
if (rectF.contains(dstPs[2] - 20, dstPs[3])) {
clickType = ClickType.DELETE;
}
else if (rectF.contains(dstPs[4] + 20, dstPs[5])) {
clickType = ClickType.SCALE;
}
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:
break;
case SCALE:
break;
case ROTATE:
break;
case IMAGE:
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:
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;
}
}

View File

@@ -0,0 +1,134 @@
package com.yizhuan.xchat_android_library.result;
import android.net.Uri;
import com.yizhuan.xchat_android_library.constant.Type;
import com.yizhuan.xchat_android_library.models.album.entity.Photo;
import com.yizhuan.xchat_android_library.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() {
if (Setting.showOriginalMenu) {
if (Setting.originalMenuUsable) {
for (Photo photo : photos) {
photo.selectedOriginal = Setting.selectedOriginal;
}
}
}
}
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;
}
}

View File

@@ -0,0 +1,121 @@
package com.yizhuan.xchat_android_library.setting;
import android.view.View;
import androidx.annotation.IntDef;
import com.yizhuan.xchat_android_library.constant.Type;
import com.yizhuan.xchat_android_library.engine.ImageEngine;
import com.yizhuan.xchat_android_library.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;
@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;
}
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;
}
}

View File

@@ -0,0 +1,487 @@
package com.yizhuan.xchat_android_library.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.yizhuan.xchat_android_library.R;
import com.yizhuan.xchat_android_library.constant.Code;
import com.yizhuan.xchat_android_library.constant.Key;
import com.yizhuan.xchat_android_library.models.album.AlbumModel;
import com.yizhuan.xchat_android_library.models.album.entity.Photo;
import com.yizhuan.xchat_android_library.result.Result;
import com.yizhuan.xchat_android_library.setting.Setting;
import com.yizhuan.xchat_android_library.ui.adapter.PreviewPhotosAdapter;
import com.yizhuan.xchat_android_library.ui.widget.PressedTextView;
import com.yizhuan.xchat_android_library.utils.color.ColorUtils;
import com.yizhuan.xchat_android_library.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_preview_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
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();
}
}
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_preview_fg_primary));
} else {
tvOriginal.setTextColor(ContextCompat.getColor(this,
R.color.easy_photos_preview_fg_primary_dark));
}
}
}
private void toggleSelector() {
if (photos.isEmpty() || lastPosition >= photos.size()) {
return;
}
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() {
if (photos.isEmpty() || lastPosition >= photos.size()) {
return;
}
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), 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);
tvDone.setText(getString(R.string.selector_action_done_easy_photos, Result.count(),
Setting.count));
}
}
@Override
public void onPreviewPhotoClick(int position) {
if (Result.isEmpty()) {
return;
}
if (photos.isEmpty()) {
return;
}
int size = photos.size();
String path = Result.getPhotoPath(position);
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);
}
}
}

View File

@@ -0,0 +1,74 @@
package com.yizhuan.xchat_android_library.ui;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.yizhuan.xchat_android_library.R;
import com.yizhuan.xchat_android_library.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);
}
}
}

View File

@@ -0,0 +1,664 @@
package com.yizhuan.xchat_android_library.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.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.yizhuan.xchat_android_library.EasyPhotos;
import com.yizhuan.xchat_android_library.R;
import com.yizhuan.xchat_android_library.constant.Code;
import com.yizhuan.xchat_android_library.constant.Key;
import com.yizhuan.xchat_android_library.engine.ImageEngine;
import com.yizhuan.xchat_android_library.models.album.entity.Photo;
import com.yizhuan.xchat_android_library.models.puzzle.Area;
import com.yizhuan.xchat_android_library.models.puzzle.DegreeSeekBar;
import com.yizhuan.xchat_android_library.models.puzzle.PuzzleLayout;
import com.yizhuan.xchat_android_library.models.puzzle.PuzzlePiece;
import com.yizhuan.xchat_android_library.models.puzzle.PuzzleUtils;
import com.yizhuan.xchat_android_library.models.puzzle.PuzzleView;
import com.yizhuan.xchat_android_library.models.sticker.StickerModel;
import com.yizhuan.xchat_android_library.setting.Setting;
import com.yizhuan.xchat_android_library.ui.adapter.PuzzleAdapter;
import com.yizhuan.xchat_android_library.ui.adapter.TextStickerAdapter;
import com.yizhuan.xchat_android_library.utils.bitmap.SaveBitmapCallBack;
import com.yizhuan.xchat_android_library.utils.media.DurationUtils;
import com.yizhuan.xchat_android_library.utils.permission.PermissionUtil;
import com.yizhuan.xchat_android_library.utils.settings.SettingsUtils;
import com.yizhuan.xchat_android_library.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 = Math.min(photos.size(), 9);
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();
resetDegrees();
}
private void resetDegrees() {
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() {
return new String[]{Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_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);
}
}
}

View File

@@ -0,0 +1,265 @@
package com.yizhuan.xchat_android_library.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 android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.widget.RelativeLayout;
import android.widget.Toast;
import androidx.annotation.IdRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import com.yizhuan.xchat_android_library.R;
import com.yizhuan.xchat_android_library.constant.Code;
import com.yizhuan.xchat_android_library.models.album.AlbumModel;
import com.yizhuan.xchat_android_library.models.album.entity.Photo;
import com.yizhuan.xchat_android_library.setting.Setting;
import com.yizhuan.xchat_android_library.ui.adapter.AlbumItemsAdapter;
import com.yizhuan.xchat_android_library.ui.adapter.PuzzleSelectorAdapter;
import com.yizhuan.xchat_android_library.ui.adapter.PuzzleSelectorPreviewAdapter;
import com.yizhuan.xchat_android_library.ui.widget.PressedTextView;
import com.yizhuan.xchat_android_library.utils.color.ColorUtils;
import com.yizhuan.xchat_android_library.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, getExternalFilesDir(Environment.DIRECTORY_DCIM).getAbsolutePath(), "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();
}
}
}
}

View File

@@ -0,0 +1,183 @@
package com.yizhuan.xchat_android_library.ui.adapter;
import android.content.Context;
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 androidx.constraintlayout.widget.ConstraintLayout;
import androidx.recyclerview.widget.RecyclerView;
import com.yizhuan.xchat_android_library.R;
import com.yizhuan.xchat_android_library.models.ad.AdViewHolder;
import com.yizhuan.xchat_android_library.models.album.entity.AlbumItem;
import com.yizhuan.xchat_android_library.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 static 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);
}
}
}

View File

@@ -0,0 +1,306 @@
package com.yizhuan.xchat_android_library.ui.adapter;
import android.content.Context;
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 androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.yizhuan.xchat_android_library.R;
import com.yizhuan.xchat_android_library.constant.Type;
import com.yizhuan.xchat_android_library.models.ad.AdViewHolder;
import com.yizhuan.xchat_android_library.models.album.entity.Photo;
import com.yizhuan.xchat_android_library.result.Result;
import com.yizhuan.xchat_android_library.setting.Setting;
import com.yizhuan.xchat_android_library.ui.widget.PressedImageView;
import com.yizhuan.xchat_android_library.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);
}
}
}

View File

@@ -0,0 +1,154 @@
package com.yizhuan.xchat_android_library.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.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.yizhuan.xchat_android_library.R;
import com.yizhuan.xchat_android_library.constant.Type;
import com.yizhuan.xchat_android_library.models.album.entity.Photo;
import com.yizhuan.xchat_android_library.setting.Setting;
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) {
Uri uri = photos.get(position).uri;
String path = photos.get(position).path;
String type = photos.get(position).type;
double ratio = 0;
if (photos.get(position).width != 0) {
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);
}
@Override
public int getItemCount() {
return photos.size();
}
public static 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);
}
}
}

View File

@@ -0,0 +1,104 @@
package com.yizhuan.xchat_android_library.ui.adapter;
import android.content.Context;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.yizhuan.xchat_android_library.R;
import com.yizhuan.xchat_android_library.constant.Type;
import com.yizhuan.xchat_android_library.result.Result;
import com.yizhuan.xchat_android_library.setting.Setting;
import com.yizhuan.xchat_android_library.ui.widget.PressedImageView;
import com.yizhuan.xchat_android_library.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();
}
static 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);
}
}

View File

@@ -0,0 +1,104 @@
package com.yizhuan.xchat_android_library.ui.adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.recyclerview.widget.RecyclerView;
import com.yizhuan.xchat_android_library.R;
import com.yizhuan.xchat_android_library.models.puzzle.PuzzleLayout;
import com.yizhuan.xchat_android_library.models.puzzle.SquarePuzzleView;
import com.yizhuan.xchat_android_library.models.puzzle.template.slant.NumberSlantLayout;
import com.yizhuan.xchat_android_library.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);
}
}

View File

@@ -0,0 +1,99 @@
package com.yizhuan.xchat_android_library.ui.adapter;
import android.content.Context;
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 androidx.recyclerview.widget.RecyclerView;
import com.yizhuan.xchat_android_library.R;
import com.yizhuan.xchat_android_library.constant.Type;
import com.yizhuan.xchat_android_library.models.album.entity.Photo;
import com.yizhuan.xchat_android_library.setting.Setting;
import com.yizhuan.xchat_android_library.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 static 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);
}
}
}

View File

@@ -0,0 +1,101 @@
package com.yizhuan.xchat_android_library.ui.adapter;
import android.content.Context;
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 androidx.recyclerview.widget.RecyclerView;
import com.yizhuan.xchat_android_library.R;
import com.yizhuan.xchat_android_library.constant.Type;
import com.yizhuan.xchat_android_library.models.album.entity.Photo;
import com.yizhuan.xchat_android_library.setting.Setting;
import com.yizhuan.xchat_android_library.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 static 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);
}
}
}

View File

@@ -0,0 +1,81 @@
package com.yizhuan.xchat_android_library.ui.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.yizhuan.xchat_android_library.R;
import com.yizhuan.xchat_android_library.models.sticker.StickerModel;
import com.yizhuan.xchat_android_library.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);
}
}

View File

@@ -0,0 +1,49 @@
package com.yizhuan.xchat_android_library.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.yizhuan.xchat_android_library.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();
}
}

View File

@@ -0,0 +1,48 @@
package com.yizhuan.xchat_android_library.ui.widget;
import android.content.Context;
import android.util.AttributeSet;
import androidx.annotation.Nullable;
/**
* 自带点击效果的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;
}
}

View File

@@ -0,0 +1,73 @@
package com.yizhuan.xchat_android_library.ui.widget;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import androidx.annotation.Nullable;
/**
* 带点击状态的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;
}
}

View File

@@ -0,0 +1,60 @@
package com.yizhuan.xchat_android_library.ui.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
/**
* 图片预览 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);
}
}

Some files were not shown because too many files have changed in this diff Show More