选择图片框架替换和Base层封装

This commit is contained in:
wushaocheng
2022-11-15 20:45:46 +08:00
parent 78db3eca67
commit 84f41bd60b
238 changed files with 21697 additions and 48 deletions

View File

@@ -2,11 +2,11 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 32
compileSdkVersion COMPILE_SDK_VERSION.toInteger()
defaultConfig {
minSdkVersion 21
targetSdkVersion 32
minSdkVersion MIN_SDK_VERSION.toInteger()
targetSdkVersion TARGET_SDK_VERSION.toInteger()
testApplicationId 'com.soundcloud.android.crop.test'
testInstrumentationRunner 'android.test.InstrumentationTestRunner'

View File

@@ -8,12 +8,12 @@ apply from: '../mob.gradle'
def onlyArm64 = Boolean.parseBoolean(only_arm64)
android {
compileSdkVersion 32
compileSdkVersion COMPILE_SDK_VERSION.toInteger()
defaultConfig {
applicationId "com.vele.peko"
minSdkVersion 21
targetSdkVersion 32
minSdkVersion MIN_SDK_VERSION.toInteger()
targetSdkVersion TARGET_SDK_VERSION.toInteger()
versionCode Integer.valueOf(version_code)
versionName version_name
@@ -177,9 +177,9 @@ def Lombok = "1.18.18"
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation fileTree(dir: 'aliyun-libs', include: ['*.jar', '*.aar'])
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.4.+'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
@@ -195,7 +195,7 @@ dependencies {
api "com.darsh.multipleimageselect:multipleimageselect:1.0.4"
api "me.shaohui.advancedluban:library:1.3.5"
api "pl.droidsonroids.gif:android-gif-drawable:1.2.7"
api "pl.droidsonroids.gif:android-gif-drawable:1.2.24"
api "com.makeramen:roundedimageview:2.3.0"
api "com.jzxiang.pickerview:TimePickerDialog:1.0.1"
api "com.github.zyyoona7:EasyPopup:1.0.2"
@@ -261,6 +261,12 @@ dependencies {
//Google Play Referrer API
implementation 'com.android.installreferrer:installreferrer:2.2'
// 引入easyphotos方便修改
api project(':easyphotos')
//mmkv
implementation 'com.tencent:mmkv:1.2.13'
}
channel {

View File

@@ -466,4 +466,5 @@
}
-keep public class com.android.installreferrer.**{ *; }
-keep public class com.android.installreferrer.**{ *; }
## EasyPhotos
-keep class com.huantansheng.easyphotos.models.** { *; }

View File

@@ -80,7 +80,6 @@
android:icon="@mipmap/app_logo"
android:label="@string/app_name"
android:largeHeap="true"
android:requestLegacyExternalStorage="true"
android:resizeableActivity="true"
android:supportsRtl="true"
android:theme="@style/MyMaterialTheme"

View File

@@ -75,7 +75,7 @@ import com.yizhuan.erban.ui.widget.dialog.AllServiceGiftLevelOneDialog;
import com.yizhuan.erban.ui.widget.dialog.AllServiceGiftLevelThreeDialog;
import com.yizhuan.erban.ui.widget.dialog.AllServiceGiftLevelTwoDialog;
import com.yizhuan.erban.ui.widget.dialog.MonsterDialog;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import com.yizhuan.erban.utils.UserUtils;
import com.yizhuan.xchat_android_core.Constants;
import com.yizhuan.xchat_android_core.DemoCache;

View File

@@ -16,7 +16,7 @@ import com.yizhuan.erban.avroom.adapter.RecordForPKAdapter;
import com.yizhuan.erban.avroom.presenter.RecordForPKPresenter;
import com.yizhuan.erban.avroom.view.IRecordForPKView;
import com.yizhuan.erban.base.BaseMvpActivity;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import com.yizhuan.erban.ui.widget.recyclerview.decoration.ColorDecoration;
import com.yizhuan.xchat_android_core.room.pk.bean.PKRecordInfo;
import com.yizhuan.xchat_android_library.base.factory.CreatePresenter;

View File

@@ -26,7 +26,7 @@ import com.yizhuan.erban.R;
import com.yizhuan.erban.avroom.adapter.MicQueueAdapter;
import com.yizhuan.erban.home.helper.LoadPageDataHelper;
import com.yizhuan.erban.ui.widget.dialog.BaseDialog;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import com.yizhuan.erban.ui.widget.recyclerview.decoration.ColorDecoration;
import com.yizhuan.xchat_android_core.Constants;
import com.yizhuan.xchat_android_core.auth.AuthModel;

View File

@@ -25,7 +25,7 @@ import com.yizhuan.erban.avroom.adapter.PKMicQueueAdapter;
import com.yizhuan.erban.home.helper.LoadPageDataHelper;
import com.yizhuan.erban.ui.widget.dialog.BaseDialog;
import com.yizhuan.erban.ui.widget.dialog.CommonLoadingDialog;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import com.yizhuan.erban.ui.widget.recyclerview.decoration.ColorDecoration;
import com.yizhuan.xchat_android_core.Constants;
import com.yizhuan.xchat_android_core.auth.AuthModel;

View File

@@ -21,7 +21,7 @@ import com.netease.nim.uikit.support.glide.GlideApp;
import com.yizhuan.erban.R;
import com.yizhuan.erban.common.widget.CircleImageView;
import com.yizhuan.erban.ui.widget.dialog.BaseDialog;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import com.yizhuan.erban.ui.widget.recyclerview.decoration.ColorDecoration;
import com.yizhuan.xchat_android_core.auth.AuthModel;
import com.yizhuan.xchat_android_core.room.pk.bean.PKTeamInfo;

View File

@@ -18,7 +18,7 @@ import com.netease.nim.uikit.support.glide.GlideApp;
import com.yizhuan.erban.R;
import com.yizhuan.erban.common.widget.CircleImageView;
import com.yizhuan.erban.ui.widget.dialog.BaseDialog;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import com.yizhuan.xchat_android_core.bean.RoomQueueInfo;
import com.yizhuan.xchat_android_core.manager.AvRoomDataManager;
import com.yizhuan.xchat_android_core.room.pk.bean.PKMemberInfo;

View File

@@ -19,7 +19,7 @@ import com.yizhuan.erban.avroom.activity.CpRoomInviteActivity;
import com.yizhuan.erban.avroom.widget.EditRoomTitleDialog;
import com.yizhuan.erban.databinding.DialogRoomImposeBinding;
import com.yizhuan.erban.ui.widget.magicindicator.buildins.UIUtil;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import com.yizhuan.xchat_android_core.auth.AuthModel;
import com.yizhuan.xchat_android_core.manager.AvRoomDataManager;
import com.yizhuan.xchat_android_core.room.bean.RoomInfo;

View File

@@ -24,7 +24,7 @@ import com.yizhuan.erban.avroom.anotherroompk.RoomPKCreateActivity;
import com.yizhuan.erban.avroom.giftvalue.GiftValueDialogUiHelper;
import com.yizhuan.erban.avroom.singleroompk.SingleRoomPKCreateActivity;
import com.yizhuan.erban.common.widget.dialog.DialogManager;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import com.yizhuan.erban.ui.widget.recyclerview.decoration.ColorDecoration;
import com.yizhuan.erban.ui.widget.recyclerview.layoutmanager.FullyGridLayoutManager;
import com.yizhuan.erban.vip.VipBroadcastDialog;

View File

@@ -64,7 +64,7 @@ import com.yizhuan.erban.ui.widget.MyItemAnimator;
import com.yizhuan.erban.ui.widget.RecyclerViewNoViewpagerScroll;
import com.yizhuan.erban.ui.widget.UserInfoDialog;
import com.yizhuan.erban.ui.widget.magicindicator.buildins.UIUtil;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import com.yizhuan.erban.utils.RegexUtil;
import com.yizhuan.xchat_android_constants.XChatConstants;
import com.yizhuan.xchat_android_core.DemoCache;

View File

@@ -0,0 +1,233 @@
package com.yizhuan.erban.base;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import androidx.annotation.CallSuper;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
import androidx.viewbinding.ViewBinding;
import com.yizhuan.erban.base.fragmentation.ISupportActivity;
import com.yizhuan.erban.base.fragmentation.ISupportFragment;
import com.yizhuan.erban.base.fragmentation.SupportFragmentDelegate;
import com.yizhuan.erban.base.fragmentation.windowcallback.WindowCallbackProxyUtil;
import com.yizhuan.erban.base.util.ViewBindingUtil;
import com.yizhuan.erban.common.util.ActivityHelper;
import com.yizhuan.xchat_android_core.utils.Logger;
public abstract class BaseDialogFragment<VB extends ViewBinding> extends DialogFragment implements ISupportFragment {
private final String TAG = getClass().getSimpleName();
private final SupportFragmentDelegate mFragmentDelegate = new SupportFragmentDelegate(this);
private View mContentView;
@Nullable
private VB mViewBinding;
private boolean isLoaded = false;
@Override
public void onStart() {
super.onStart();
if (getDialog() != null && getDialog().getWindow() != null) {
Window window = getDialog().getWindow();
Window.Callback callback = WindowCallbackProxyUtil.createWindowCallBack(window.getCallback(), true, this.getClass().getSimpleName());
if (callback != null) {
window.setCallback(callback);
}
}
}
@CallSuper
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (getActivity() == null) {
//这里需要关联一个Activity而不是Context
throw new IllegalStateException("Current class " + getClass().getName() + " not attached to an activity.");
}
if (!(getActivity() instanceof ISupportActivity)) {
//Activity必须是实现了ISupportActivity接口才可以
throw new IllegalStateException(getActivity().getClass().getName() + " must impl ISupportActivity! Current class is " + getClass().getName());
}
this.mFragmentDelegate.onAttach();
}
@CallSuper
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.mFragmentDelegate.onCreate(savedInstanceState);
this.initBefore(savedInstanceState);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mViewBinding = ViewBindingUtil.inflateWithFragment(this, inflater, container);
if (mViewBinding != null) {
this.mContentView = mViewBinding.getRoot();
this.mContentView.setTag(BaseViewTag.TAG_NAME, this);
}
this.findView();
return this.mContentView;
}
@CallSuper
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
this.mFragmentDelegate.onViewCreated();
this.setView();//重写该方法即可实现预加载数据即当Fragment创建布局以后还没有真正可见时就会加载数据不同与onResume()方法和onLazyLoad()方法
this.setListener();
}
@CallSuper
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
this.mFragmentDelegate.onSaveInstanceState(outState);
}
/**
* 重写该方法即可实现每次Fragment可见时都能加载数据不同与setView()方法和onLazyLoad()方法
*/
@CallSuper
@Override
public void onResume() {
super.onResume();
if (!isLoaded && !isHidden()) {
onLazyLoad();
isLoaded = true;
}
}
/**
* 重写该方法可实现懒加载数据即当前Fragment第一次可见时才会执行一次不同与setView()方法和onResume()方法
*/
protected void onLazyLoad() {
}
/**
* 重写该方法即可监听当前Fragment变为不可见时
*/
@CallSuper
@Override
public void onPause() {
super.onPause();
}
@CallSuper
@Override
public void onDestroyView() {
super.onDestroyView();
this.mFragmentDelegate.onDestroyView();
isLoaded = false;
this.onWillDestroy();
}
/**
* 建议子类各种释放操作放到onWillDestroy()方法中执行因为Fragment#onDestroyView()方法一定会执行所以也会触发该方法,在其他方法中处理有可能会导致内存泄露。
*/
@CallSuper
public void onWillDestroy() {
this.mContentView = null;
}
@CallSuper
@Override
public void show(@NonNull FragmentManager manager, @Nullable String tag) {
try {
if (!manager.isDestroyed() && !manager.isStateSaved() && !isAdded()) {
super.show(manager, tag);
}
} catch (Exception e) {
Logger.error(TAG, "show", e);
}
}
@Override
public final void dismiss() {
dismissAllowingStateLoss();
}
@Override
public final void dismissAllowingStateLoss() {
if (ActivityHelper.isCanUse(getActivity())) {
super.dismissAllowingStateLoss();
}
}
@Nullable
@Override
public final View getView() {
return this.mContentView;
}
protected final <T extends View> T findViewById(@IdRes int id) {
if (this.getView() != null) {
return this.getView().findViewById(id);
}
return null;
}
@Override
public final SupportFragmentDelegate getSupportDelegate() {
return this.mFragmentDelegate;
}
@Override
public final void setFragmentResult(int resultCode, Bundle bundle) {
this.mFragmentDelegate.setFragmentResult(resultCode, bundle);
}
@Override
public void onFragmentResult(int requestCode, int resultCode, Bundle data) {
this.mFragmentDelegate.onFragmentResult(requestCode, resultCode, data);
}
@Override
public boolean onBackPressedSupport() {
return this.mFragmentDelegate.onBackPressedSupport();
}
@Nullable
protected final VB getBinding() {
return mViewBinding;
}
////////////////////////////////////////以下是提供给子类复写的方法////////////////////////////////////////
/**
* 该方法是在onCreate()方法里执行在onCreateView()方法被调用之前触发可用于处理解析Fragment#getArguments()中的数据时的场景
*/
protected void initBefore(@Nullable Bundle savedInstanceState) {
}
/**
* 该方法是在onCreateView()方法里触发,可用于处理控件的初始化
*/
protected void findView() {
}
/**
* 该方法是在onViewCreated()方法里触发重写该方法即可实现预加载数据即当Fragment创建布局以后还没有真正可见时就会加载数据不同与onResume()方法和onLazyLoad()方法
*/
protected void setView() {
}
/**
* 该方法是在onViewCreated()方法里触发,可用于处理控件的设置监听器
*/
protected void setListener() {
}
}

View File

@@ -0,0 +1,118 @@
package com.yizhuan.erban.base;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import com.yizhuan.erban.R;
import com.yizhuan.xchat_android_core.utils.Logger;
/**
* author: wushaocheng
* time: 2022/11/15
* desc:将View关联上Activity或者Fragment的生命周期
*/
public class BaseViewTag {
private static final String TAG = "BaseViewTag";
public static final int TAG_NAME = R.id.baseViewTagID;
/**
* 注册Activity或者Fragment生命周期监听接口
*
* @param view 需要关联Activity或者Fragment生命周期的控件注意该控件需要实现LifecycleObserver接口
*/
public static void registerLifecycle(View view) {
if (view == null) {
Logger.warn(TAG, "registerLifecycle view is null");
return;
}
if (!(view instanceof LifecycleObserver)) {
Logger.warn(TAG, "registerLifecycle view: " + view.getClass().getSimpleName() + ", view must implements LifecycleObserver");
return;
}
view.post(new Runnable() {
@Override
public void run() {
try {
Object tag = view.getTag(BaseViewTag.TAG_NAME);
if (tag != null) {
register(view, tag);
return;
}
ViewParent viewParent = view.getParent();
ViewGroup parentView = (ViewGroup) viewParent;
while (parentView != null) {
tag = parentView.getTag(BaseViewTag.TAG_NAME);
if (tag instanceof LifecycleOwner) {
register(view, tag);
return;
}
viewParent = parentView.getParent();
if (viewParent instanceof ViewGroup) {
parentView = (ViewGroup) viewParent;
} else {
break;
}
}
register(view, view.getContext());
} catch (Exception e) {
Logger.error(TAG, "registerLifecycle view: " + view.getClass().getSimpleName() + ", error", e);
}
}
});
}
/**
* 真正将view关联到Activity或者Fragment的生命周期
*
* @param view 需要关联Activity或者Fragment生命周期的控件注意该控件需要实现LifecycleObserver接口
* @param owner 生命周期提供者一般是Activity或Fragment
*/
private static void register(View view, Object owner) {
if (view == null) {
Logger.warn(TAG, "registerLifecycle view is null");
return;
}
if (owner == null) {
Logger.warn(TAG, "registerLifecycle owner is null");
return;
}
try {
if (owner instanceof LifecycleOwner) {
LifecycleOwner lifecycleOwner = (LifecycleOwner) owner;
lifecycleOwner.getLifecycle().addObserver((LifecycleObserver) view);//核心代码
view.setTag(BaseViewTag.TAG_NAME, owner);
Logger.debug(TAG, "registerLifecycle view: " + view.getClass().getSimpleName() + ", owner: " + owner.getClass().getSimpleName());
} else {
Logger.warn(TAG, "registerLifecycle view:" + view.getClass().getSimpleName() + ", owner is not instanceof LifecycleOwner");
}
} catch (Exception e) {
Logger.error(TAG, "registerLifecycle view: " + view.getClass().getSimpleName() + ", owner: " + owner.getClass().getSimpleName() + ", error", e);
}
}
/**
* 解绑Activity或者Fragment生命周期监听接口
*
* @param view 需要关联Activity或者Fragment生命周期的控件注意该控件需要实现LifecycleObserver接口
* @param owner 生命周期提供者一般是Activity或Fragment
*/
public static void unRegisterLifecycle(View view, LifecycleOwner owner) {
if (view == null) {
Logger.warn(TAG, "unRegisterLifecycle view is null");
return;
}
if (owner == null) {
Logger.warn(TAG, "unRegisterLifecycle owner is null");
return;
}
try {
view.setTag(BaseViewTag.TAG_NAME, null);
owner.getLifecycle().removeObserver((LifecycleObserver) view);
Logger.debug(TAG, "unRegisterLifecycle view: " + view.getClass().getSimpleName() + ", owner: " + owner.getClass().getSimpleName());
} catch (Exception e) {
Logger.error(TAG, "unRegisterLifecycle view: " + view.getClass().getSimpleName() + ", owner: " + owner.getClass().getSimpleName() + ", error", e);
}
}
}

View File

@@ -0,0 +1,13 @@
package com.yizhuan.erban.base.fragmentation;
/**
* Created by YoKey on 17/6/13.
*/
public interface ISupportActivity {
SupportActivityDelegate getSupportDelegate();
void onBackPressed();
void onBackPressedSupport();
}

View File

@@ -0,0 +1,36 @@
package com.yizhuan.erban.base.fragmentation;
import android.os.Bundle;
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Created by YoKey on 17/6/23.
*/
public interface ISupportFragment {
// LaunchMode
int STANDARD = 0;
int SINGLETOP = 1;
int SINGLETASK = 2;
// ResultCode
int RESULT_CANCELED = 0;
int RESULT_OK = -1;
@IntDef({STANDARD, SINGLETOP, SINGLETASK})
@Retention(RetentionPolicy.SOURCE)
@interface LaunchMode {
}
SupportFragmentDelegate getSupportDelegate();
void setFragmentResult(int resultCode, Bundle bundle);
void onFragmentResult(int requestCode, int resultCode, Bundle data);
boolean onBackPressedSupport();
}

View File

@@ -0,0 +1,99 @@
package com.yizhuan.erban.base.fragmentation;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import com.yizhuan.erban.base.fragmentation.queue.Action;
public class SupportActivityDelegate {
private final ISupportActivity mSupport;
private final FragmentActivity mActivity;
private TransactionDelegate mTransactionDelegate;
public SupportActivityDelegate(ISupportActivity support) {
if (!(support instanceof FragmentActivity))
throw new RuntimeException("Must extends FragmentActivity/AppCompatActivity");
this.mSupport = support;
this.mActivity = (FragmentActivity) support;
}
public void onCreate() {
mTransactionDelegate = getTransactionDelegate();
}
public TransactionDelegate getTransactionDelegate() {
if (mTransactionDelegate == null) {
mTransactionDelegate = new TransactionDelegate();
}
return mTransactionDelegate;
}
/**
* 不建议复写该方法,请使用 {@link #onBackPressedSupport} 代替
*/
public void onBackPressed() {
mTransactionDelegate.mActionQueue.enqueue(new Action(Action.ACTION_BACK, getSupportFragmentManager()) {
@Override
public void run() {
// 获取activeFragment:即从栈顶开始 状态为show的那个Fragment
ISupportFragment activeFragment = SupportHelper.getAddedFragment(getSupportFragmentManager());
if (mTransactionDelegate.dispatchBackPressedEvent(activeFragment)) return;
mSupport.onBackPressedSupport();
}
});
}
/**
* 该方法回调时机为,Activity回退栈内Fragment的数量 小于等于1 时,默认finish Activity
* 请尽量复写该方法,避免复写onBackPress(),以保证SupportFragment内的onBackPressedSupport()回退事件正常执行
*/
public void onBackPressedSupport() {
if (getSupportFragmentManager().getBackStackEntryCount() > 1) {
pop();
} else {
ActivityCompat.finishAfterTransition(mActivity);
}
}
//**********************************************************************************************//
/**
* 加载根Fragment, 即Activity内的第一个Fragment 或 Fragment内的第一个子Fragment
*/
public void loadRootFragment(int containerId, ISupportFragment toFragment) {
loadRootFragment(containerId, toFragment, true);
}
public void loadRootFragment(int containerId, ISupportFragment toFragment, boolean addToBackStack) {
mTransactionDelegate.loadRootTransaction(getSupportFragmentManager(), containerId, toFragment, addToBackStack);
}
public void start(ISupportFragment toFragment) {
start(toFragment, ISupportFragment.STANDARD);
}
/**
* @param launchMode Similar to Activity's LaunchMode.
*/
public void start(ISupportFragment toFragment, @ISupportFragment.LaunchMode int launchMode) {
mTransactionDelegate.dispatchStartTransaction(getSupportFragmentManager(), getTopFragment(), toFragment, 0, launchMode, TransactionDelegate.TYPE_ADD);
}
/**
* Pop the child fragment.
*/
public void pop() {
mTransactionDelegate.pop(getSupportFragmentManager());
}
private FragmentManager getSupportFragmentManager() {
return mActivity.getSupportFragmentManager();
}
private ISupportFragment getTopFragment() {
return SupportHelper.getTopFragment(getSupportFragmentManager());
}
}

View File

@@ -0,0 +1,172 @@
package com.yizhuan.erban.base.fragmentation;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import com.yizhuan.erban.base.fragmentation.internal.ResultRecord;
import com.yizhuan.erban.base.fragmentation.internal.TransactionRecord;
public class SupportFragmentDelegate {
int mContainerId;
private TransactionDelegate mTransactionDelegate;
TransactionRecord mTransactionRecord;
private final ISupportFragment mSupportF;
private final Fragment mFragment;
public SupportFragmentDelegate(ISupportFragment support) {
if (!(support instanceof Fragment))
throw new RuntimeException("Must extends Fragment");
this.mSupportF = support;
this.mFragment = (Fragment) support;
}
public void onAttach() {
FragmentActivity activity = mFragment.getActivity();
if (activity instanceof ISupportActivity) {
ISupportActivity mSupportA = (ISupportActivity) activity;
mTransactionDelegate = mSupportA.getSupportDelegate().getTransactionDelegate();
} else {
if (activity != null) {
throw new RuntimeException(activity.getClass().getSimpleName() + " must impl ISupportActivity!");
} else {
throw new RuntimeException("fragment attached activity must not be null");
}
}
}
public void onCreate(@Nullable Bundle savedInstanceState) {
Bundle bundle = mFragment.getArguments();
if (bundle != null) {
mContainerId = bundle.getInt(TransactionDelegate.FRAGMENTATION_ARG_CONTAINER);
}
if (savedInstanceState != null) {
savedInstanceState.setClassLoader(getClass().getClassLoader());
mContainerId = savedInstanceState.getInt(TransactionDelegate.FRAGMENTATION_ARG_CONTAINER);
}
}
public void onSaveInstanceState(Bundle outState) {
outState.putInt(TransactionDelegate.FRAGMENTATION_ARG_CONTAINER, mContainerId);
}
public void onViewCreated() {
View view = mFragment.getView();
if (view != null) {
view.setClickable(true);
setBackground(view);
}
}
public void onDestroyView() {
mTransactionDelegate.handleResultRecord(mFragment);
}
/**
* 类似 {@link Activity#setResult(int, Intent)}
* <p>
* Similar to {@link Activity#setResult(int, Intent)}
*
* @see #startForResult(ISupportFragment, int)
*/
public void setFragmentResult(int resultCode, Bundle bundle) {
Bundle args = mFragment.getArguments();
if (args == null || !args.containsKey(TransactionDelegate.FRAGMENTATION_ARG_RESULT_RECORD)) {
return;
}
ResultRecord resultRecord = args.getParcelable(TransactionDelegate.FRAGMENTATION_ARG_RESULT_RECORD);
if (resultRecord != null) {
resultRecord.resultCode = resultCode;
resultRecord.resultBundle = bundle;
}
}
/**
* 类似Activity的onActivityResult(int, int, Intent)
*
* @see #startForResult(ISupportFragment, int)
*/
public void onFragmentResult(int requestCode, int resultCode, Bundle data) {
}
/**
* Back Event
*
* @return false则继续向上传递, true则消费掉该事件
*/
public boolean onBackPressedSupport() {
return false;
}
//**********************************************************************************************//
/**
* 加载根Fragment, 即Activity内的第一个Fragment 或 Fragment内的第一个子Fragment
*/
public void loadRootFragment(int containerId, ISupportFragment toFragment) {
loadRootFragment(containerId, toFragment, true);
}
public void loadRootFragment(int containerId, ISupportFragment toFragment, boolean addToBackStack) {
mTransactionDelegate.loadRootTransaction(getChildFragmentManager(), containerId, toFragment, addToBackStack);
}
public void start(ISupportFragment toFragment) {
start(toFragment, ISupportFragment.STANDARD);
}
/**
* @param launchMode Similar to Activity's LaunchMode.
*/
public void start(final ISupportFragment toFragment, @ISupportFragment.LaunchMode int launchMode) {
mTransactionDelegate.dispatchStartTransaction(mFragment.getFragmentManager(), mSupportF, toFragment, 0, launchMode, TransactionDelegate.TYPE_ADD);
}
/**
* Launch an fragment for which you would like a result when it poped.
*/
public void startForResult(ISupportFragment toFragment, int requestCode) {
mTransactionDelegate.dispatchStartTransaction(mFragment.getFragmentManager(), mSupportF, toFragment, requestCode, ISupportFragment.STANDARD, TransactionDelegate.TYPE_ADD_RESULT);
}
public void startChild(ISupportFragment toFragment) {
startChild(toFragment, ISupportFragment.STANDARD);
}
public void startChild(final ISupportFragment toFragment, @ISupportFragment.LaunchMode int launchMode) {
mTransactionDelegate.dispatchStartTransaction(getChildFragmentManager(), getTopFragment(), toFragment, 0, launchMode, TransactionDelegate.TYPE_ADD);
}
public void pop() {
mTransactionDelegate.pop(mFragment.getFragmentManager());
}
private FragmentManager getChildFragmentManager() {
return mFragment.getChildFragmentManager();
}
private ISupportFragment getTopFragment() {
return SupportHelper.getTopFragment(getChildFragmentManager());
}
public void setBackground(View view) {
if ((mFragment.getTag() != null && mFragment.getTag().startsWith("android:switcher:")) ||
view.getBackground() != null) {
return;
}
view.setBackgroundColor(Color.TRANSPARENT);
}
}

View File

@@ -0,0 +1,209 @@
package com.yizhuan.erban.base.fragmentation;
import android.content.Context;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import java.util.ArrayList;
import java.util.List;
/**
* Created by YoKey on 17/6/13.
*/
public class SupportHelper {
private static final long SHOW_SPACE = 200L;
private SupportHelper() {
}
/**
* 显示软键盘
*/
public static void showSoftInput(final View view) {
if (view == null || view.getContext() == null) return;
final InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
view.requestFocus();
view.postDelayed(new Runnable() {
@Override
public void run() {
imm.showSoftInput(view, InputMethodManager.SHOW_FORCED);
}
}, SHOW_SPACE);
}
/**
* 隐藏软键盘
*/
public static void hideSoftInput(View view) {
if (view == null || view.getContext() == null) return;
InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
/**
* 获得栈顶SupportFragment
*/
public static ISupportFragment getTopFragment(FragmentManager fragmentManager) {
return getTopFragment(fragmentManager, 0);
}
public static ISupportFragment getTopFragment(FragmentManager fragmentManager, int containerId) {
List<Fragment> fragmentList = fragmentManager.getFragments();
for (int i = fragmentList.size() - 1; i >= 0; i--) {
Fragment fragment = fragmentList.get(i);
if (fragment instanceof ISupportFragment) {
ISupportFragment iFragment = (ISupportFragment) fragment;
if (containerId == 0) return iFragment;
if (containerId == iFragment.getSupportDelegate().mContainerId) {
return iFragment;
}
}
}
return null;
}
/**
* Same as fragmentManager.findFragmentByTag(fragmentClass.getName());
* find Fragment from FragmentStack
*/
public static <T extends ISupportFragment> T findFragment(FragmentManager fragmentManager, Class<T> fragmentClass) {
return findAddedFragment(fragmentClass, null, fragmentManager);
}
/**
* Same as fragmentManager.findFragmentByTag(fragmentTag);
* <p>
* find Fragment from FragmentStack
*/
public static <T extends ISupportFragment> T findFragment(FragmentManager fragmentManager, String fragmentTag) {
return findAddedFragment(null, fragmentTag, fragmentManager);
}
/**
* 从栈顶开始寻找FragmentManager以及其所有子栈, 直到找到状态为show & userVisible的Fragment
*/
public static ISupportFragment getAddedFragment(FragmentManager fragmentManager) {
return getAddedFragment(fragmentManager, null);
}
static <T extends ISupportFragment> T findAddedFragment(Class<T> fragmentClass, String toFragmentTag, FragmentManager fragmentManager) {
Fragment fragment = null;
if (toFragmentTag == null) {
List<Fragment> fragmentList = fragmentManager.getFragments();
int sizeChildFrgList = fragmentList.size();
for (int i = sizeChildFrgList - 1; i >= 0; i--) {
Fragment brotherFragment = fragmentList.get(i);
if (brotherFragment instanceof ISupportFragment && brotherFragment.getClass().getName().equals(fragmentClass.getName())) {
fragment = brotherFragment;
break;
}
}
} else {
fragment = fragmentManager.findFragmentByTag(toFragmentTag);
if (fragment == null) return null;
}
return (T) fragment;
}
private static ISupportFragment getAddedFragment(FragmentManager fragmentManager, ISupportFragment parentFragment) {
List<Fragment> fragmentList = fragmentManager.getFragments();
if (fragmentList.size() == 0) {
return parentFragment;
}
for (int i = fragmentList.size() - 1; i >= 0; i--) {
Fragment fragment = fragmentList.get(i);
if (fragment instanceof ISupportFragment) {
if (fragment.isResumed() && !fragment.isHidden() && fragment.getUserVisibleHint()) {
return getAddedFragment(fragment.getChildFragmentManager(), (ISupportFragment) fragment);
}
}
}
return parentFragment;
}
/**
* Get the topFragment from BackStack
*/
public static ISupportFragment getBackStackTopFragment(FragmentManager fragmentManager) {
return getBackStackTopFragment(fragmentManager, 0);
}
/**
* Get the topFragment from BackStack
*/
public static ISupportFragment getBackStackTopFragment(FragmentManager fragmentManager, int containerId) {
int count = fragmentManager.getBackStackEntryCount();
for (int i = count - 1; i >= 0; i--) {
FragmentManager.BackStackEntry entry = fragmentManager.getBackStackEntryAt(i);
Fragment fragment = fragmentManager.findFragmentByTag(entry.getName());
if (fragment instanceof ISupportFragment) {
ISupportFragment supportFragment = (ISupportFragment) fragment;
if (containerId == 0) return supportFragment;
if (containerId == supportFragment.getSupportDelegate().mContainerId) {
return supportFragment;
}
}
}
return null;
}
static <T extends ISupportFragment> T findBackStackFragment(Class<T> fragmentClass, String toFragmentTag, FragmentManager fragmentManager) {
int count = fragmentManager.getBackStackEntryCount();
if (toFragmentTag == null) {
toFragmentTag = fragmentClass.getName();
}
for (int i = count - 1; i >= 0; i--) {
FragmentManager.BackStackEntry entry = fragmentManager.getBackStackEntryAt(i);
if (toFragmentTag.equals(entry.getName())) {
Fragment fragment = fragmentManager.findFragmentByTag(entry.getName());
if (fragment instanceof ISupportFragment) {
return (T) fragment;
}
}
}
return null;
}
static List<Fragment> getWillPopFragments(FragmentManager fm, String targetTag) {
Fragment target = fm.findFragmentByTag(targetTag);
List<Fragment> willPopFragments = new ArrayList<>();
List<Fragment> fragmentList = fm.getFragments();
int size = fragmentList.size();
int startIndex = -1;
for (int i = size - 1; i >= 0; i--) {
if (target == fragmentList.get(i)) {
if (i + 1 < size) {
startIndex = i + 1;
}
break;
}
}
if (startIndex == -1) return willPopFragments;
for (int i = size - 1; i >= startIndex; i--) {
Fragment fragment = fragmentList.get(i);
if (fragment != null && fragment.getView() != null) {
willPopFragments.add(fragment);
}
}
return willPopFragments;
}
}

View File

@@ -0,0 +1,305 @@
package com.yizhuan.erban.base.fragmentation;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.yizhuan.erban.base.fragmentation.internal.ResultRecord;
import com.yizhuan.erban.base.fragmentation.internal.TransactionRecord;
import com.yizhuan.erban.base.fragmentation.queue.Action;
import com.yizhuan.erban.base.fragmentation.queue.ActionQueue;
import java.util.List;
/**
* Controller
* Created by YoKeyword on 16/1/22.
*/
class TransactionDelegate {
private static final String TAG = "Fragmentation";
static final String FRAGMENTATION_ARG_RESULT_RECORD = "fragment_arg_result_record";
static final String FRAGMENTATION_ARG_CONTAINER = "fragmentation_arg_container";
static final String FRAGMENTATION_ARG_REPLACE = "fragmentation_arg_replace";
private static final String FRAGMENTATION_STATE_SAVE_RESULT = "fragmentation_state_save_result";
static final int TYPE_ADD = 0;
static final int TYPE_ADD_RESULT = 1;
static final int TYPE_ADD_WITHOUT_HIDE = 2;
static final int TYPE_ADD_RESULT_WITHOUT_HIDE = 3;
static final int TYPE_REPLACE = 10;
static final int TYPE_REPLACE_DONT_BACK = 11;
ActionQueue mActionQueue;
TransactionDelegate() {
Handler mHandler = new Handler(Looper.getMainLooper());
mActionQueue = new ActionQueue(mHandler);
}
void loadRootTransaction(final FragmentManager fm, final int containerId, final ISupportFragment to, final boolean addToBackStack) {
enqueue(fm, new Action(Action.ACTION_LOAD, fm) {
@Override
public void run() {
bindContainerId(containerId, to);
String toFragmentTag = to.getClass().getName();
TransactionRecord transactionRecord = to.getSupportDelegate().mTransactionRecord;
if (transactionRecord != null) {
if (transactionRecord.tag != null) {
toFragmentTag = transactionRecord.tag;
}
}
start(fm, null, to, toFragmentTag, !addToBackStack, TYPE_REPLACE);
}
});
}
private void start(FragmentManager fm, final ISupportFragment from, ISupportFragment to, String toFragmentTag, boolean dontAddToBackStack, int type) {
FragmentTransaction ft = fm.beginTransaction();
boolean addMode = (type == TYPE_ADD || type == TYPE_ADD_RESULT || type == TYPE_ADD_WITHOUT_HIDE || type == TYPE_ADD_RESULT_WITHOUT_HIDE);
Fragment fromF = (Fragment) from;
Fragment toF = (Fragment) to;
Bundle args = getArguments(toF);
args.putBoolean(FRAGMENTATION_ARG_REPLACE, !addMode);
if (from == null) {
ft.replace(args.getInt(FRAGMENTATION_ARG_CONTAINER), toF, toFragmentTag);
if (!addMode) {
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
}
} else {
if (addMode) {
ft.add(from.getSupportDelegate().mContainerId, toF, toFragmentTag);
if (type != TYPE_ADD_WITHOUT_HIDE && type != TYPE_ADD_RESULT_WITHOUT_HIDE) {
ft.hide(fromF);
}
} else {
ft.replace(from.getSupportDelegate().mContainerId, toF, toFragmentTag);
}
}
if (!dontAddToBackStack && type != TYPE_REPLACE_DONT_BACK) {
ft.addToBackStack(toFragmentTag);
}
supportCommit(ft);
}
/**
* Pop
*/
void pop(final FragmentManager fm) {
enqueue(fm, new Action(Action.ACTION_POP, fm) {
@Override
public void run() {
removeTopFragment(fm);
fm.popBackStackImmediate();
}
});
}
private void removeTopFragment(FragmentManager fm) {
try { // Safe popBackStack()
ISupportFragment top = SupportHelper.getBackStackTopFragment(fm);
if (top != null) {
fm.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE)
.remove((Fragment) top)
.commitAllowingStateLoss();
}
} catch (Exception ignored) {
}
}
/**
* Dispatch the start transaction.
*/
void dispatchStartTransaction(final FragmentManager fm, final ISupportFragment from, final ISupportFragment to, final int requestCode, final int launchMode, final int type) {
enqueue(fm, new Action(launchMode == ISupportFragment.SINGLETASK ? Action.ACTION_POP_MOCK : Action.ACTION_NORMAL, fm) {
@Override
public void run() {
doDispatchStartTransaction(fm, from, to, requestCode, launchMode, type);
}
});
}
private void doDispatchStartTransaction(FragmentManager fm, ISupportFragment from, ISupportFragment to, int requestCode, int launchMode, int type) {
if (to == null) {
throw new NullPointerException("toFragment == null");
}
if ((type == TYPE_ADD_RESULT || type == TYPE_ADD_RESULT_WITHOUT_HIDE) && from != null) {
if (!((Fragment) from).isAdded()) {
Log.w(TAG, ((Fragment) from).getClass().getSimpleName() + " has not been attached yet! startForResult() converted to start()");
} else {
saveRequestCode(fm, (Fragment) from, (Fragment) to, requestCode);
}
}
from = getTopFragmentForStart(from, fm);
int containerId = getArguments((Fragment) to).getInt(FRAGMENTATION_ARG_CONTAINER, 0);
if (from == null && containerId == 0) {
Log.e(TAG, "There is no Fragment in the FragmentManager, maybe you need to call loadRootFragment()!");
return;
}
if (from != null && containerId == 0) {
bindContainerId(from.getSupportDelegate().mContainerId, to);
}
// process ExtraTransaction
String toFragmentTag = to.getClass().getName();
boolean dontAddToBackStack = false;
TransactionRecord transactionRecord = to.getSupportDelegate().mTransactionRecord;
if (transactionRecord != null) {
if (transactionRecord.tag != null) {
toFragmentTag = transactionRecord.tag;
}
dontAddToBackStack = transactionRecord.dontAddToBackStack;
}
if (handleLaunchMode(fm, from, to, toFragmentTag, launchMode)) return;
start(fm, from, to, toFragmentTag, dontAddToBackStack, type);
}
private void doPopTo(final String targetFragmentTag, FragmentManager fm) {
Fragment targetFragment = fm.findFragmentByTag(targetFragmentTag);
if (targetFragment == null) {
Log.e(TAG, "Pop failure! Can't find FragmentTag:" + targetFragmentTag + " in the FragmentManager's Stack.");
return;
}
List<Fragment> willPopFragments = SupportHelper.getWillPopFragments(fm, targetFragmentTag);
if (willPopFragments.size() <= 0) return;
safePopTo(targetFragmentTag, fm, willPopFragments);
}
private void safePopTo(String fragmentTag, final FragmentManager fm, List<Fragment> willPopFragments) {
// 批量删除fragment static final int OP_REMOVE = 3;
FragmentTransaction transaction = fm.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
for (Fragment fragment : willPopFragments) {
transaction.remove(fragment);
}
transaction.commitAllowingStateLoss();
// 弹栈到指定fragment从数据上来看和上面的效果完全一样把栈中所有的backStackRecord 包含多个记录,每个记录包含多个操作
// 在每个记录中,对操作索引 按照从大到小的顺序,逐个进行反操作。
// 除了第一个记录其余每个记录都有两个操作一个是添加OP_ADD = 1;(反操作是remove) 一个是OP_HIDE = 4;反操作是show这是在start中设定的
// 之所以有上面的批量删除在执行动画的时候发现f.mView == null 就不去执行show动画。
fm.popBackStackImmediate(fragmentTag, 0);
}
private ISupportFragment getTopFragmentForStart(ISupportFragment from, FragmentManager fm) {
ISupportFragment top;
if (from == null) {
top = SupportHelper.getTopFragment(fm);
} else {
if (from.getSupportDelegate().mContainerId == 0) {
Fragment fromF = (Fragment) from;
if (fromF.getTag() != null && !fromF.getTag().startsWith("android:switcher:")) {
throw new IllegalStateException("Can't find container, please call loadRootFragment() first!");
}
}
top = SupportHelper.getTopFragment(fm, from.getSupportDelegate().mContainerId);
}
return top;
}
/**
* Dispatch the pop-event. Priority of the top of the stack of Fragment
*/
boolean dispatchBackPressedEvent(ISupportFragment activeFragment) {
if (activeFragment != null) {
boolean result = activeFragment.onBackPressedSupport();
if (result) {
return true;
}
Fragment parentFragment = ((Fragment) activeFragment).getParentFragment();
return dispatchBackPressedEvent((ISupportFragment) parentFragment);
}
return false;
}
void handleResultRecord(Fragment from) {
try {
Bundle args = from.getArguments();
if (args == null) return;
final ResultRecord resultRecord = args.getParcelable(FRAGMENTATION_ARG_RESULT_RECORD);
if (resultRecord == null) return;
ISupportFragment targetFragment = (ISupportFragment) from.getFragmentManager().getFragment(from.getArguments(), FRAGMENTATION_STATE_SAVE_RESULT);
targetFragment.onFragmentResult(resultRecord.requestCode, resultRecord.resultCode, resultRecord.resultBundle);
} catch (IllegalStateException ignored) {
// Fragment no longer exists
}
}
private void enqueue(FragmentManager fm, Action action) {
if (fm == null) {
Log.w(TAG, "FragmentManager is null, skip the action!");
return;
}
mActionQueue.enqueue(action);
}
private void bindContainerId(int containerId, ISupportFragment to) {
Bundle args = getArguments((Fragment) to);
args.putInt(FRAGMENTATION_ARG_CONTAINER, containerId);
}
private Bundle getArguments(Fragment fragment) {
Bundle bundle = fragment.getArguments();
if (bundle == null) {
bundle = new Bundle();
fragment.setArguments(bundle);
}
return bundle;
}
private void supportCommit(FragmentTransaction transaction) {
transaction.commitAllowingStateLoss();
}
private boolean handleLaunchMode(FragmentManager fm, ISupportFragment topFragment, final ISupportFragment to, String toFragmentTag, int launchMode) {
if (topFragment == null) return false;
final ISupportFragment stackToFragment = SupportHelper.findBackStackFragment(to.getClass(), toFragmentTag, fm);
if (stackToFragment == null) return false;
if (launchMode == ISupportFragment.SINGLETOP) {
if (to == topFragment || to.getClass().getName().equals(topFragment.getClass().getName())) {
return true;
}
} else if (launchMode == ISupportFragment.SINGLETASK) {
doPopTo(toFragmentTag, fm);
return true;
}
return false;
}
/**
* save requestCode
*/
private void saveRequestCode(FragmentManager fm, Fragment from, Fragment to, int requestCode) {
Bundle bundle = getArguments(to);
ResultRecord resultRecord = new ResultRecord();
resultRecord.requestCode = requestCode;
bundle.putParcelable(FRAGMENTATION_ARG_RESULT_RECORD, resultRecord);
fm.putFragment(bundle, FRAGMENTATION_STATE_SAVE_RESULT, from);
}
}

View File

@@ -0,0 +1,48 @@
package com.yizhuan.erban.base.fragmentation.internal;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
/**
* @Hide Result 记录
* Created by YoKeyword on 16/6/2.
*/
public final class ResultRecord implements Parcelable {
public int requestCode;
public int resultCode = 0;
public Bundle resultBundle;
public ResultRecord() {
}
protected ResultRecord(Parcel in) {
requestCode = in.readInt();
resultCode = in.readInt();
resultBundle = in.readBundle(getClass().getClassLoader());
}
public static final Creator<ResultRecord> CREATOR = new Creator<ResultRecord>() {
@Override
public ResultRecord createFromParcel(Parcel in) {
return new ResultRecord(in);
}
@Override
public ResultRecord[] newArray(int size) {
return new ResultRecord[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(requestCode);
dest.writeInt(resultCode);
dest.writeBundle(resultBundle);
}
}

View File

@@ -0,0 +1,9 @@
package com.yizhuan.erban.base.fragmentation.internal;
/**
* @hide Created by YoKey on 16/11/25.
*/
public final class TransactionRecord {
public String tag;
public boolean dontAddToBackStack = false;
}

View File

@@ -0,0 +1,35 @@
package com.yizhuan.erban.base.fragmentation.queue;
import androidx.fragment.app.FragmentManager;
/**
* Created by YoKey on 17/12/28.
*/
public abstract class Action {
public static final long DEFAULT_POP_TIME = 300L;
public static final int ACTION_NORMAL = 0;
public static final int ACTION_POP = 1;
public static final int ACTION_POP_MOCK = 2;
public static final int ACTION_BACK = 3;
public static final int ACTION_LOAD = 4;
public FragmentManager fragmentManager;
public int action = ACTION_NORMAL;
public long duration = 0;
public Action(int action) {
this.action = action;
}
public Action(int action, FragmentManager fragmentManager) {
this(action);
this.fragmentManager = fragmentManager;
}
public Action(FragmentManager fragmentManager) {
this.fragmentManager = fragmentManager;
}
public abstract void run();
}

View File

@@ -0,0 +1,81 @@
package com.yizhuan.erban.base.fragmentation.queue;
import android.os.Handler;
import android.os.Looper;
import java.util.LinkedList;
import java.util.Queue;
/**
* The queue of perform action.
* <p>
* Created by YoKey on 17/12/29.
*/
public class ActionQueue {
private Queue<Action> mQueue = new LinkedList<>();
private Handler mMainHandler;
public ActionQueue(Handler mainHandler) {
this.mMainHandler = mainHandler;
}
public void enqueue(final Action action) {
if (isThrottleBACK(action)) return;
if (action.action == Action.ACTION_LOAD && mQueue.isEmpty()
&& Thread.currentThread() == Looper.getMainLooper().getThread()) {
action.run();
return;
}
mMainHandler.post(new Runnable() {
@Override
public void run() {
enqueueAction(action);
}
});
}
private void enqueueAction(Action action) {
mQueue.add(action);
//第一次进来的时候执行完上局队列只有一个一旦进入handleAction就会一直执行直到队列为空
if (mQueue.size() == 1) {
handleAction();
}
}
private void handleAction() {
if (mQueue.isEmpty()) return;
Action action = mQueue.peek();
if (action == null || action.fragmentManager.isStateSaved()) {
mQueue.clear();
return;
}
action.run();
executeNextAction(action);
}
private void executeNextAction(Action action) {
if (action.action == Action.ACTION_POP) {
action.duration = Action.DEFAULT_POP_TIME;
}
mMainHandler.postDelayed(new Runnable() {
@Override
public void run() {
mQueue.poll();
handleAction();
}
}, action.duration);
}
private boolean isThrottleBACK(Action action) {
if (action.action == Action.ACTION_BACK) {
Action head = mQueue.peek();
return head != null && head.action == Action.ACTION_POP;
}
return false;
}
}

View File

@@ -0,0 +1,14 @@
package com.yizhuan.erban.base.fragmentation.windowcallback
import android.view.Window
/**
* author: wushaocheng
* time: 2022/11/15
* desc:
*/
interface IWindowCallbackProxy {
fun createCallBack(
callback: Window.Callback?, isDialog: Boolean, tag: String?
): Window.Callback?
}

View File

@@ -0,0 +1,25 @@
package com.yizhuan.erban.base.fragmentation.windowcallback
import android.view.Window
/**
* author: wushaocheng
* time: 2022/11/15
* desc: WindowCallback代理类工具
*/
object WindowCallbackProxyUtil {
var mCallbackProxy: IWindowCallbackProxy? = null
@JvmStatic
fun setWindowCallbackProxy(proxy: IWindowCallbackProxy?) {
mCallbackProxy = proxy
}
@JvmStatic
@JvmOverloads
fun createWindowCallBack(
callback: Window.Callback?, isDialog: Boolean = false, tag: String? = null
): Window.Callback? {
return mCallbackProxy?.createCallBack(callback, isDialog, tag)
}
}

View File

@@ -0,0 +1,97 @@
package com.yizhuan.erban.base.util
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.viewbinding.ViewBinding
import com.yizhuan.xchat_android_core.utils.Logger
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.ParameterizedType
/**
* author: wushaocheng
* time: 2022/11/15
* desc: viewBinding fragment、activity绑定布局
* @see 参考 https://github.com/DylanCaiCoding/ViewBindingKTX/blob/master/viewbinding-base/src/main/java/com/dylanc/viewbinding/base/ViewBindingUtil.kt
*/
object ViewBindingUtil {
private const val TAG = "ViewBindingUtil"
@JvmStatic
fun <VB : ViewBinding> inflateWithActivity(
genericOwner: Any, layoutInflater: LayoutInflater
): VB? = withGenericBindingClass<VB>(genericOwner) { clazz ->
clazz.getMethod("inflate", LayoutInflater::class.java).invoke(null, layoutInflater) as? VB
}
@JvmStatic
fun <VB : ViewBinding> inflateWithFragment(
genericOwner: Any,
layoutInflater: LayoutInflater,
parent: ViewGroup?,
): VB? = withGenericBindingClass<VB>(genericOwner) { clazz ->
clazz.getMethod(
"inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java
).invoke(null, layoutInflater, parent, false) as? VB
}
@JvmStatic
fun <VB : ViewBinding> inflateWithView(
genericOwner: Any,
layoutInflater: LayoutInflater,
parent: ViewGroup?,
): VB? = withGenericBindingClass<VB>(genericOwner) { clazz ->
var vb = try {
clazz.getMethod(
"inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java
).invoke(null, layoutInflater, parent, true) as? VB
} catch (e: Exception) {
//merge 标签会找不到
Logger.debug(
TAG,
"inflateWithView => maybe use merge " + genericOwner.javaClass.simpleName + ", genericSuperclass: " + genericOwner.javaClass.genericSuperclass
)
null
}
if (vb == null) {
//merge 标签的只有该方法
vb = clazz.getMethod(
"inflate", LayoutInflater::class.java, ViewGroup::class.java
).invoke(null, layoutInflater, parent) as? VB
}
return@withGenericBindingClass vb
}
private fun <VB : ViewBinding> withGenericBindingClass(
genericOwner: Any, block: (Class<VB>) -> VB?
): VB? {
var genericSuperclass = genericOwner.javaClass.genericSuperclass
var superclass = genericOwner.javaClass.superclass
while (superclass != null) {
if (genericSuperclass is ParameterizedType) {
genericSuperclass.actualTypeArguments.forEach {
try {
return block.invoke(it as Class<VB>)
} catch (e: NoSuchMethodException) {
} catch (e: ClassCastException) {
} catch (e: InvocationTargetException) {
Logger.error(
TAG,
"withGenericBindingClass => ${e.message}" + ", class: " + genericOwner.javaClass.simpleName
)
return null
}
}
}
genericSuperclass = superclass.genericSuperclass
superclass = superclass.superclass
}
Logger.error(
TAG,
"withGenericBindingClass " + genericOwner.javaClass.simpleName + ", genericSuperclass: " + genericOwner.javaClass.genericSuperclass
)
return null
}
}

View File

@@ -10,7 +10,7 @@ import com.yizhuan.erban.R;
import com.yizhuan.erban.ui.widget.magicindicator.buildins.commonnavigator.abs.CommonNavigatorAdapter;
import com.yizhuan.erban.ui.widget.magicindicator.buildins.commonnavigator.abs.IPagerIndicator;
import com.yizhuan.erban.ui.widget.magicindicator.buildins.commonnavigator.abs.IPagerTitleView;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import java.util.List;

View File

@@ -0,0 +1,50 @@
package com.yizhuan.erban.common.delegate
import com.yizhuan.erban.application.XChatApplication
import com.yizhuan.erban.common.util.SPUtils
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
/**
* author: wushaocheng
* time: 2022/11/15
* desc: sp存储和取出委托
*/
class SpDelegate<T>(private val key: String, private val default: T) : ReadWriteProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
val value = when (default) {
is Boolean -> SPUtils.getBoolean(key, default)
is String -> SPUtils.getString(key, default)
is Long -> SPUtils.getLong(key, default)
is Int -> SPUtils.getInt(key, default)
is Float -> SPUtils.getFloat(key, default)
is Double -> SPUtils.getDouble(key, default)
is ByteArray -> SPUtils.getBytes(key, default)
else -> {
if (XChatApplication.isDebug()) {
throw IllegalArgumentException("SpDelegate: this type is no supported")
} else {
null
}
}
}
return (value as? T) ?: default
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
when (value) {
is Boolean -> SPUtils.putBoolean(key, value)
is String -> SPUtils.putString(key, value)
is Long -> SPUtils.putLong(key, value)
is Int -> SPUtils.putInt(key, value)
is Float -> SPUtils.putFloat(key, value)
is Double -> SPUtils.putDouble(key, value)
is ByteArray -> SPUtils.putBytes(key, value)
else -> {
if (XChatApplication.isDebug()) {
throw IllegalArgumentException("SpDelegate: this type is no supported")
}
}
}
}
}

View File

@@ -0,0 +1,917 @@
package com.yizhuan.erban.common.file;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Environment;
import android.text.TextUtils;
import com.yizhuan.erban.application.XChatApplication;
import com.yizhuan.erban.common.util.StringUtils;
import com.yizhuan.xchat_android_core.utils.Logger;
import com.yizhuan.xchat_android_library.utils.FP;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* 文件工具类
*/
public class FileHelper {
private static final String TAG = "FileHelper";
private static final String NO_MEDIA = ".nomedia";
private static File rootCacheDir;
private static HashMap<String, File> rootFileDirMap = new HashMap<>();
private static final String[] mCs = new String[]{"/", "\\", "?", "*", ":", "<", ">", "|", "\""};
private static final char UNICODE_SURROGATE_START_CHAR = '\ud800';
private static final char UNICODE_SURROGATE_END_CHAR = '\udfff';
/**
* 创建.nomedia文件
*
* @param parentFile 上级目录
*/
private static void createNoMediaFile(File parentFile) {
File no_media = new File(parentFile, NO_MEDIA);
if (!no_media.exists()) {
try {
no_media.createNewFile();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 获取Android/data/当前应用包名/cache文件夹
*
* @return 当前应用缓存根目录
*/
public static File getRootCacheDir() {
if (rootCacheDir != null) {
//因为频繁调用getExternalCacheDir方法会在某些机器上面出现ANR所以这里降低频率调用。
return rootCacheDir;
}
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
File file = XChatApplication.gContext.getExternalCacheDir();
if (file != null) {
createNoMediaFile(file);
//因为频繁调用getExternalCacheDir方法会在某些机器上面出现ANR所以这里降低频率调用。
rootCacheDir = file;
return file;
}
}
File file = XChatApplication.gContext.getCacheDir();
createNoMediaFile(file);
//因为频繁调用getExternalCacheDir方法会在某些机器上面出现ANR所以这里降低频率调用。
rootCacheDir = file;
return file;
}
/**
* 获取Android/data/当前应用包名/files文件夹
*
* @param type 类型从以下方式中选择也可以为null:
* {@link Environment#DIRECTORY_MUSIC},
* {@link Environment#DIRECTORY_PODCASTS},
* {@link Environment#DIRECTORY_RINGTONES},
* {@link Environment#DIRECTORY_ALARMS},
* {@link Environment#DIRECTORY_NOTIFICATIONS},
* {@link Environment#DIRECTORY_PICTURES},
* {@link Environment#DIRECTORY_MOVIES},
* {@link Environment#DIRECTORY_DOWNLOADS},
* {@link Environment#DIRECTORY_DCIM},
* {@link Environment#DIRECTORY_DOCUMENTS},
* {@link Environment#DIRECTORY_AUDIOBOOKS}
* @return 当前应用文件根目录
*/
public static File getRootFilesDir(@androidx.annotation.Nullable String type) {
String dirName = (type != null && type.length() > 0) ? type.trim() : null;
if (TextUtils.isEmpty(dirName)) {
//因为频繁调用getExternalFilesDir方法会在某些机器上面出现ANR所以这里降低频率调用。
File file = rootFileDirMap.get("empty");
if (file != null) {
return file;
}
} else {
//因为频繁调用getExternalFilesDir方法会在某些机器上面出现ANR所以这里降低频率调用。
File file = rootFileDirMap.get(dirName);
if (file != null) {
return file;
}
}
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
File file = XChatApplication.gContext.getExternalFilesDir(dirName);
if (file != null) {
createNoMediaFile(file);
//因为频繁调用getExternalFilesDir方法会在某些机器上面出现ANR所以这里降低频率调用。
if (TextUtils.isEmpty(dirName)) {
rootFileDirMap.put("empty", file);
} else {
rootFileDirMap.put(dirName, file);
}
return file;
}
}
File file = XChatApplication.gContext.getFilesDir();
createNoMediaFile(file);
//因为频繁调用getExternalFilesDir方法会在某些机器上面出现ANR所以这里降低频率调用。
rootFileDirMap.put("empty", file);
return file;
}
/**
* 创建临时文件
*
* @param dir 文件夹
* @return 临时文件
*/
public static File createTempFile(File dir) {
try {
File file = File.createTempFile("pic", null, dir);
file.deleteOnExit();
return file;
} catch (IOException e) {
return null;
}
}
/**
* 判断文件或文件夹是否存在
*
* @param filePath 文件路径
* @return 是否存在这个文件或文件夹
*/
public static boolean isFileExist(String filePath) {
if (TextUtils.isEmpty(filePath)) {
return false;
}
File file = new File(filePath);
return file.exists();
}
/**
* 确保文件夹存在,不存在的时候就创建该目录
*
* @param dirPath 文件夹路径
* @return 确保是否已存在这个文件夹
*/
public static boolean ensureDirExists(String dirPath) {
File dirFile = new File(dirPath);
if (!dirFile.exists()) {
return dirFile.mkdirs();
}
return true;
}
/**
* 确保该文件的文件夹存在,不存在的时候就创建该文件的文件夹
*
* @param filePath 文件路径
* @return 确保是否已存在该文件的文件夹
*/
public static boolean ensureFileDirExists(String filePath) {
String dir = getDirOfFilePath(filePath);
if (TextUtils.isEmpty(dir)) {
return false;
}
ensureDirExists(dir);
return true;
}
/**
* 确保该文件存在,不存在的时候就创建该文件
*
* @param filePath 文件路径
* @return 确保是否已存在该文件
*/
public static File ensureFileExists(String filePath) {
if (!ensureFileDirExists(filePath)) {
return null;
}
File file = new File(filePath);
if (file.exists()) {
return file;
}
try {
if (!file.exists() && !file.createNewFile()) {
file = null;
}
} catch (IOException e) {
e.printStackTrace();
file = null;
}
return file;
}
/**
* 从文件路径里提取文件夹的路径
*
* @param filePath 文件路径
* @return 文件夹的路径
*/
public static String getDirOfFilePath(String filePath) {
if (TextUtils.isEmpty(filePath)) {
return null;
}
int sepPos = filePath.lastIndexOf(File.separatorChar);
if (sepPos == -1) {
return null;
}
return filePath.substring(0, sepPos);
}
/**
* 删除单个文件
*
* @param filePath 文件路径
*/
public static void removeFile(String filePath) {
if (!TextUtils.isEmpty(filePath)) {
try {
File file = new File(filePath);
file.delete();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 删除文件或文件夹下面所有的文件
*
* @param filePath 文件路径
* @return 是否删除成功
*/
public static boolean removeAllFile(String filePath) {
if (TextUtils.isEmpty(filePath)) {
return true;
}
File file = new File(filePath);
if (!file.exists()) {
return true;
}
if (file.isFile()) {
try {
return file.delete();
} catch (Exception e) {
return false;
}
}
if (!file.isDirectory()) {
return false;
}
File[] files = file.listFiles();
if (files != null && files.length > 0) {
for (File f : files) {
if (f.isFile()) {
try {
f.delete();
} catch (Exception e) {
e.printStackTrace();
}
} else if (f.isDirectory()) {
removeAllFile(f.getAbsolutePath());
}
}
}
try {
return file.delete();
} catch (Exception e) {
return false;
}
}
/**
* 将数据存储到缓存文件夹里
*
* @param data 数据
* @param filePath 文件路径
*/
public static void saveByteArrayIntoFile(byte[] data, String filePath) {
try {
File f = new File(filePath);
if (f.createNewFile()) {
FileOutputStream fos = new FileOutputStream(f);
fos.write(data);
fos.flush();
fos.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 将Bitmap保存为JPEG图片
*/
public static void saveBitmapAsJPEG(Bitmap bmp, String filePath) {
if (bmp == null) {
return;
}
FileOutputStream fos = null;
try {
if (!ensureFileDirExists(filePath)) {
return;
}
File file = new File(filePath);
if (file.exists()) {
file.delete();
}
fos = new FileOutputStream(file);
bmp.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 将Bitmap保存为PNG图片
*
* @param bmp 图片
* @param filePath 文件路径
* @return 文件路径
*/
public static String saveBitmapAsPNG(Bitmap bmp, String filePath) {
if (bmp == null) {
return "";
}
FileOutputStream fos = null;
try {
if (!ensureFileDirExists(filePath)) {
return "";
}
File file = new File(filePath);
if (file.exists()) {
file.delete();
}
fos = new FileOutputStream(file);
bmp.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
return filePath;
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return "";
}
/**
* 解压zip到指定的路径
*
* @param zipFileString ZIP的名称
* @param outPathString 要解压缩路径
*/
public static boolean unzipFile(String zipFileString, String outPathString) {
ZipInputStream inZip = null;
try {
inZip = new ZipInputStream(new FileInputStream(zipFileString));
ZipEntry zipEntry;
while ((zipEntry = inZip.getNextEntry()) != null) {
String szName = zipEntry.getName();
if (szName.contains("/")) {
szName = szName.substring(szName.indexOf("/") + 1);
if (TextUtils.isEmpty(szName)) {
continue;
}
}
if (!zipEntry.isDirectory()) {
File file = new File(outPathString + File.separator + szName);
if (!file.exists()) {
if (file.getParentFile() != null) {
file.getParentFile().mkdirs();
}
file.createNewFile();
}
FileOutputStream out = new FileOutputStream(file);
int len;
byte[] buffer = new byte[1024];
while ((len = inZip.read(buffer)) != -1) {
out.write(buffer, 0, len);
out.flush();
}
out.close();
} else {
szName = szName.substring(0, szName.length() - 1);
File folder = new File(outPathString + File.separator + szName);
folder.mkdirs();
}
}
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
try {
if (inZip != null) {
inZip.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return true;
}
/**
* 解压压缩包
*
* @param srcZipPath 原压缩包路径
* @param destFilePath 目标文件夹路径
* @return 解压之后的文件组
* @throws IOException 抛出异常
*/
public static File[] unzip(String srcZipPath, String destFilePath) throws IOException {
if (TextUtils.isEmpty(destFilePath)) {
throw new IOException();
}
if (!destFilePath.endsWith(File.separator)) {
destFilePath = destFilePath + File.separator;
}
File destDir = new File(destFilePath);
if (!destDir.isDirectory() || !destDir.exists()) {
destDir.mkdir();
}
ArrayList<File> extractedFileList = new ArrayList<>();
ZipInputStream inZip = null;
try {
ZipEntry zipEntry;
inZip = new ZipInputStream(new FileInputStream(srcZipPath));
String szName;
while ((zipEntry = inZip.getNextEntry()) != null) {
szName = zipEntry.getName();
if (zipEntry.isDirectory()) {
szName = szName.substring(0, szName.length() - 1);
File folder = new File(destDir.getAbsolutePath() + File.separator + szName);
folder.mkdirs();
continue;
}
FileOutputStream out = null;
try {
int len;
File file = new File(destDir.getAbsolutePath() + File.separator + szName);
file = ensureFileExists(file.getAbsolutePath());
if (file != null) {
out = new FileOutputStream(file);
byte[] buffer = new byte[1024];
while ((len = inZip.read(buffer)) != -1) {
out.write(buffer, 0, len);
out.flush();
}
out.close();
extractedFileList.add(file);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (out != null) {
out.close();
}
}
}
} finally {
if (inZip != null) {
inZip.close();
}
}
File[] extractedFiles = new File[extractedFileList.size()];
extractedFileList.toArray(extractedFiles);
return extractedFiles;
}
/**
* 从文件路径里面提取文件的后缀
*
* @param filePath 文件路径
* @return 后缀,如.mp3
*/
public static String getFileExtension(String filePath) {
String fileName = getFileName(filePath);
if (TextUtils.isEmpty(fileName)) {
return null;
}
int index = fileName.lastIndexOf(".");
if (index != -1) {
return fileName.substring(index);
}
return null;
}
/**
* 从文件路径里面提取文件名
*
* @param filePath 文件路径
* @return 文件名称
*/
public static String getFileName(String filePath) {
if (filePath != null) {
String slash = "/";
int pos = filePath.lastIndexOf(slash) + 1;
if (pos > 0) {
String name = filePath.substring(pos);
if (!TextUtils.isEmpty(name)) {
name = name.replace("?", "");
}
return name;
}
}
return null;
}
/**
* 从URL里面提取版本名
*
* @param url 链接
* @return 文件名
*/
public static String getFileNameWithVer(String url) {
String versionName = "";
String subName = getFileName(url);
if (subName != null && subName.contains("?")) {
String[] tempArr = subName.split("\\?");
if (tempArr.length > 1) {
subName = tempArr[0];
versionName = tempArr[1];
}
}
String fileName = dropExt(subName);
fileName = fileName + versionName;
return fileName;
}
/**
* 从文件名里面踢出点得到可用的文件名
*
* @param fileName 文件名
* @return 文件名
*/
public static String dropExt(String fileName) {
if (!TextUtils.isEmpty(fileName)) {
int pos = fileName.lastIndexOf(".");
if (pos != -1) {
return FP.take(pos, fileName);
}
}
return fileName;
}
/**
* 读取文件内容转换为字符串
*
* @param filePath 文件路径
*/
public static String getStringFromFile(String filePath) {
String result = "";
InputStream is = null;
try {
is = new FileInputStream(filePath);
int length = is.available();
byte[] buffer = new byte[length];
is.read(buffer);
result = new String(buffer, StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
/**
* 读取Assets内容转换为字符串
*
* @param fileName 文件名
* @return 字符串内容
*/
public static String getStringFromAssets(String fileName) {
String result = "";
try {
InputStream is = XChatApplication.gContext.getAssets().open(fileName);
int length = is.available();
byte[] buffer = new byte[length];
is.read(buffer);
result = new String(buffer, StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 从Assets里面读取内容覆盖本地文件
*
* @param dir 文件夹路径
* @param fileName 文件名称
* @param overwrite 是否覆盖,即删除旧文件重新创建新文件
* @return 操作是否成功
*/
public static boolean copyFileFromAssets(String dir, String fileName, boolean overwrite) {
String path = dir + File.separator + fileName;
File file = new File(path);
if (file.exists() && overwrite) {
file.delete();
}
if (!file.exists()) {
try {
if (!ensureDirExists(dir)) {
return false;
}
file.createNewFile();
InputStream in = XChatApplication.gContext.getAssets().open(fileName);
OutputStream out = new FileOutputStream(file);
byte[] buffer = new byte[4096];
int n;
while ((n = in.read(buffer)) > 0) {
out.write(buffer, 0, n);
}
out.flush();
in.close();
out.close();
} catch (Exception e) {
try {
file.delete();
} catch (Exception ex) {
ex.printStackTrace();
}
return false;
}
}
return true;
}
/**
* 将字节数组写入文件中
*
* @param buffer 字节数组
* @param folderPath 文件夹路径
* @param fileName 文件名称
*/
public static void saveDataToFile(byte[] buffer, String folderPath, String fileName) {
File fileDir = new File(folderPath);
if (!fileDir.exists()) {
fileDir.mkdirs();
}
File file = new File(folderPath + File.separator + fileName);
FileOutputStream out = null;
try {
out = new FileOutputStream(file);
out.write(buffer);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 格式化大小,带单位
*/
public static String formatSize(long size) {
//获取到的size为1705230
int GB = 1024 * 1024 * 1024;//定义GB的计算常量
int MB = 1024 * 1024;//定义MB的计算常量
int KB = 1024;//定义KB的计算常量
DecimalFormat df = new DecimalFormat("0.0");//格式化小数
String resultSize = "";
if (size / GB >= 1) {
//如果当前Byte的值大于等于1GB
resultSize = df.format(size / (float) GB) + "GB";
} else if (size / MB >= 1) {
//如果当前Byte的值大于等于1MB
resultSize = df.format(size / (float) MB) + "MB";
} else if (size / KB >= 1) {
//如果当前Byte的值大于等于1KB
resultSize = df.format(size / (float) KB) + "KB";
} else {
resultSize = size + "B";
}
return resultSize;
}
/**
* @param fileName 文件名
* @return 文件名是否正确
*/
public static boolean isFileNameCorrect(String fileName) {
if (null == fileName) {
return false;
} else {
fileName = fileName.trim();
if (fileName.length() == 0) {
return false;
} else {
for (String c : mCs) {
if (fileName.contains(c)) {
return false;
}
}
return !containsSurrogateChar(fileName);
}
}
}
/**
* @param string 字符串
* @return 是否包含需要替代的字符串
*/
public static boolean containsSurrogateChar(String string) {
if (TextUtils.isEmpty(string)) {
return false;
} else {
int length = string.length();
boolean hasSurrogateChar = false;
for (int i = 0; i < length; ++i) {
char c = string.charAt(i);
if (UNICODE_SURROGATE_START_CHAR <= c && c <= UNICODE_SURROGATE_END_CHAR) {
hasSurrogateChar = true;
break;
}
}
return hasSurrogateChar;
}
}
/**
* 把Uri转换为文件
*
* @param uri 源文件的Uri的路径
* @param path 要存入的文件的路径
* @param overwrite 是否需要复写
* @return 是否已经把Uri转换成功为文件了
*/
public static boolean copyFileFromUri(Uri uri, String path, boolean overwrite) {
File file = new File(path);
if (file.exists() && overwrite) {
file.delete();
}
if (!file.exists()) {
try {
if (!FileHelper.ensureDirExists(file.getParentFile().getAbsolutePath())) {
return false;
}
InputStream stream = XChatApplication.gContext.getContentResolver().openInputStream(uri);
if (stream != null) {
file.createNewFile();
OutputStream out = new FileOutputStream(file);
byte buffer[] = new byte[4096];
int n;
while ((n = stream.read(buffer)) > 0) {
out.write(buffer, 0, n);
}
out.flush();
stream.close();
out.close();
}
} catch (Exception e) {
try {
file.delete();
} catch (Exception ex) {
ex.printStackTrace();
}
return false;
}
}
return true;
}
/**
* 将文件转换成字节数组
*
* @param file 文件
* @return 字节数组
*/
public static byte[] fileToByteArray(File file) {
if (file.exists() && file.canRead()) {
try {
return streamToBytes(new FileInputStream(file));
} catch (Exception e) {
Logger.error(TAG, String.valueOf(e));
}
}
return null;
}
/**
* 将文件流转换成字节数组
*
* @param inputStream 输入流
* @return 字节数组
*/
public static byte[] streamToBytes(InputStream inputStream) {
byte[] content = null;
ByteArrayOutputStream baos = null;
BufferedInputStream bis = null;
try {
baos = new ByteArrayOutputStream();
bis = new BufferedInputStream(inputStream);
byte[] buffer = new byte[1024];
int length;
while ((length = bis.read(buffer)) != -1) {
baos.write(buffer, 0, length);
}
content = baos.toByteArray();
if (content.length == 0) {
content = null;
}
} catch (IOException e) {
Logger.error(TAG, String.valueOf(e));
} finally {
if (baos != null) {
try {
baos.close();
} catch (IOException e) {
Logger.error(TAG, String.valueOf(e));
}
}
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
Logger.error(TAG, String.valueOf(e));
}
}
}
return content;
}
/**
* 文件转换成字符串
*
* @param filePath 文件路径
* @return 字符串内容
*/
public static String getTxtFileContent(String filePath) {
String content = "";
if (!StringUtils.isNullOrEmpty(filePath)) {
File file = new File(filePath);
if (file.isFile()) {
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(file);
String line;
StringBuilder sb = new StringBuilder();
BufferedReader buffReader = new BufferedReader(new InputStreamReader(inputStream));
while ((line = buffReader.readLine()) != null) {
sb.append(line).append("\n");
}
content = sb.toString();
} catch (Exception e) {
Logger.error(TAG, "getTxtFileContent read fail, e = " + e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (Exception ignore) {
}
}
}
}
}
return content;
}
}

View File

@@ -0,0 +1,73 @@
package com.yizhuan.erban.common.glide;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import com.huantansheng.easyphotos.engine.ImageEngine;
/**
* Created by wushaocheng on 2021/3/29 15:18.
* DescGlide实现类
*/
public class GlideEngine implements ImageEngine {
/**
* 加载图片到ImageView
*
* @param context 上下文
* @param uri 图片路径Uri
* @param imageView 加载到的ImageView
*/
//安卓10推荐uri并且path的方式不再可用
@Override
public void loadPhoto(@NonNull Context context, @NonNull Uri uri, @NonNull ImageView imageView) {
GlideUtils.instance().loadUriCrossFade(uri, imageView);
}
/**
* 加载gif动图图片到ImageViewgif动图不动
*
* @param context 上下文
* @param gifUri gif动图路径Uri
* @param imageView 加载到的ImageView
* <p>
* 备注:不支持动图显示的情况下可以不写
*/
//安卓10推荐uri并且path的方式不再可用
@Override
public void loadGifAsBitmap(@NonNull Context context, @NonNull Uri gifUri, @NonNull ImageView imageView) {
GlideUtils.instance().loadUriGift(gifUri, imageView);
}
/**
* 加载gif动图到ImageViewgif动图动
*
* @param context 上下文
* @param gifUri gif动图路径Uri
* @param imageView 加载动图的ImageView
* <p>
* 备注:不支持动图显示的情况下可以不写
*/
//安卓10推荐uri并且path的方式不再可用
@Override
public void loadGif(@NonNull Context context, @NonNull Uri gifUri, @NonNull ImageView imageView) {
GlideUtils.instance().loadUriGiftAndCrossFade(gifUri, imageView);
}
/**
* 获取图片加载框架中的缓存Bitmap不用拼图功能可以直接返回null
*
* @param context 上下文
* @param uri 图片路径
* @param width 图片宽度
* @param height 图片高度
* @return Bitmap
* @throws Exception 异常直接抛出EasyPhotos内部处理
*/
//安卓10推荐uri并且path的方式不再可用
@Override
public Bitmap getCacheBitmap(@NonNull Context context, @NonNull Uri uri, int width, int height) throws Exception {
return null;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,224 @@
package com.yizhuan.erban.common.photo
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Environment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.huantansheng.easyphotos.EasyPhotos
import com.huantansheng.easyphotos.constant.Type.*
import com.huantansheng.easyphotos.models.album.entity.Photo
import com.yizhuan.erban.application.XChatApplication
import com.yizhuan.erban.common.delegate.SpDelegate
import com.yizhuan.erban.common.file.FileHelper
import com.yizhuan.erban.common.glide.GlideEngine
import com.yizhuan.xchat_android_core.utils.Logger
import com.yizhuan.xchat_android_library.utils.TimeUtils
import com.yizhuan.xchat_android_library.utils.TimeUtils.TIME_FORMAT
import kotlinx.coroutines.*
import java.io.File
/**
* Created by wushaocheng on 2022/11/15
* Desc图片选择二次封装
*/
object PhotoProvider {
private const val TAG = "PhotoProvider"
//上一次选择的时间,避免用户连续进入选择图片页导致删除缓存
private var mLastSelectTime: Long by SpDelegate("PhotoProvider_last_select_time", 0L)
/**
* easyPhoto库选择文件copy到内部的目录名
*/
private const val FOLD_EASY_PHOTO_INTERNAL = "selectPhotoTemp"
private var mPhotoJob: Job? = null
@JvmStatic
@JvmOverloads
fun photoCamera(fragment: Fragment, resultCode: Int, isClearCache: Boolean = true) {
cancelJop()
mPhotoJob = MainScope().launch {
if (isClearCache && isClearByTime()) {
withContext(Dispatchers.IO) { clearCache() }
}
EasyPhotos.createCamera(fragment, false)//参数说明:上下文,是否使用宽高数据false时宽高数据为0扫描速度更快
.setFileProviderAuthority("${XChatApplication.getApplication().packageName}.fileprovider")//参数说明:见下方`FileProvider的配置`
.start(resultCode)
}
}
/**
* 喵圈发布动态专用去掉bmp格式的图片
*/
@JvmStatic
@JvmOverloads
fun photoProviderPublish(activity: Activity, maxSelect: Int = 1, canChooseGif: Boolean = false, resultCode: Int, isClearCache: Boolean = true) {
cancelJop()
mPhotoJob = MainScope().launch {
if (isClearCache && isClearByTime()) {
withContext(Dispatchers.IO) { clearCache() }
}
EasyPhotos.createAlbum(activity, false, false, GlideEngine())//参数说明上下文是否显示相机按钮是否使用宽高数据false时宽高数据为0扫描速度更快[配置Glide为图片加载引擎](https://github.com/HuanTanSheng/EasyPhotos/wiki/12-%E9%85%8D%E7%BD%AEImageEngine%EF%BC%8C%E6%94%AF%E6%8C%81%E6%89%80%E6%9C%89%E5%9B%BE%E7%89%87%E5%8A%A0%E8%BD%BD%E5%BA%93)
.setCount(maxSelect)//参数说明最大可选数默认1
.setGif(canChooseGif)
.filter(JPEG, JPG, PNG, WEBP)
.setPuzzleMenu(false)
.setCleanMenu(false)
.start(resultCode)
}
}
@JvmStatic
@JvmOverloads
fun videoProvider(activity: Activity, maxSelect: Int = 1, resultCode: Int, isClearCache: Boolean = true) {
cancelJop()
mPhotoJob = MainScope().launch {
if (isClearCache && isClearByTime()) {
withContext(Dispatchers.IO) { clearCache() }
}
EasyPhotos.createAlbum(activity, false, false, GlideEngine())
.setCount(maxSelect)//参数说明最大可选数默认1
.setPuzzleMenu(false)
.onlyVideo()
.setCleanMenu(false)
.start(resultCode)
}
}
@JvmStatic
@JvmOverloads
fun photoProvider(activity: Activity, maxSelect: Int = 1, canChooseGif: Boolean = false, resultCode: Int, isClearCache: Boolean = true) {
cancelJop()
mPhotoJob = MainScope().launch {
if (isClearCache && isClearByTime()) {
withContext(Dispatchers.IO) { clearCache() }
}
EasyPhotos.createAlbum(activity, false, false, GlideEngine())//参数说明上下文是否显示相机按钮是否使用宽高数据false时宽高数据为0扫描速度更快[配置Glide为图片加载引擎](https://github.com/HuanTanSheng/EasyPhotos/wiki/12-%E9%85%8D%E7%BD%AEImageEngine%EF%BC%8C%E6%94%AF%E6%8C%81%E6%89%80%E6%9C%89%E5%9B%BE%E7%89%87%E5%8A%A0%E8%BD%BD%E5%BA%93)
.setCount(maxSelect)//参数说明最大可选数默认1
.setGif(canChooseGif)
.setPuzzleMenu(false)
.setCleanMenu(false)
.start(resultCode)
}
}
@JvmStatic
@JvmOverloads
fun photoProvider(activity: FragmentActivity, maxSelect: Int = 1, canChooseGif: Boolean = false, resultCode: Int, isClearCache: Boolean = true) {
cancelJop()
mPhotoJob = MainScope().launch {
if (isClearCache && isClearByTime()) {
withContext(Dispatchers.IO) { clearCache() }
}
EasyPhotos.createAlbum(activity, false, false, GlideEngine())//参数说明上下文是否显示相机按钮是否使用宽高数据false时宽高数据为0扫描速度更快[配置Glide为图片加载引擎](https://github.com/HuanTanSheng/EasyPhotos/wiki/12-%E9%85%8D%E7%BD%AEImageEngine%EF%BC%8C%E6%94%AF%E6%8C%81%E6%89%80%E6%9C%89%E5%9B%BE%E7%89%87%E5%8A%A0%E8%BD%BD%E5%BA%93)
.setCount(maxSelect)//参数说明最大可选数默认1
.setGif(canChooseGif)
.setPuzzleMenu(false)
.setCleanMenu(false)
.start(resultCode)
}
}
@JvmStatic
@JvmOverloads
fun photoProvider(fragment: Fragment, maxSelect: Int = 1, canChooseGif: Boolean = false, resultCode: Int, isClearCache: Boolean = true, minFileSize: Long = 0L) {
cancelJop()
mPhotoJob = MainScope().launch {
if (isClearCache && isClearByTime()) {
withContext(Dispatchers.IO) { clearCache() }
}
EasyPhotos.createAlbum(fragment, false, false, GlideEngine())//参数说明上下文是否显示相机按钮是否使用宽高数据false时宽高数据为0扫描速度更快[配置Glide为图片加载引擎](https://github.com/HuanTanSheng/EasyPhotos/wiki/12-%E9%85%8D%E7%BD%AEImageEngine%EF%BC%8C%E6%94%AF%E6%8C%81%E6%89%80%E6%9C%89%E5%9B%BE%E7%89%87%E5%8A%A0%E8%BD%BD%E5%BA%93)
.setCount(maxSelect)//参数说明最大可选数默认1
.setGif(canChooseGif)
.setMinFileSize(minFileSize)
.setPuzzleMenu(false)
.setCleanMenu(false)
.start(resultCode)
}
}
@JvmStatic
fun getResultUriList(data: Intent?): List<Uri>? {
val list: List<Photo>? = data?.getParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS)
return list?.takeIf { it.isNotEmpty() }?.map { it.uri }
}
@JvmStatic
fun getResultPhotoList(data: Intent?): List<Photo>? {
return data?.getParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS)
}
@JvmStatic
fun getResultPathListAsync(data: Intent?, resultListener: ((List<String>?) -> Unit)) {
cancelJop()
mPhotoJob = MainScope().launch {
val list: List<Photo>? = data?.getParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS)
val result = withContext(Dispatchers.IO) { copyToInternalCache(list) }
resultListener.invoke(result)
}
}
/**
* 外部的文件复制到项目目录下,再获取对应路径
* 修改方案原因:
* 1. android Q 外部文件path变更为类似这种结构/external/images/media/{文件id}导致无法通过path读取文件信息文件名字及格式
* 2. android Q 支持Uri获取文件但uri获取不到文件类型及不少自身或者sdk的函数从参数需要用到path
* 3. 原本项目功能逻辑很多用到了path(包括不仅仅文件大小,文件类型,作为参数传给其他函数使用(比如BitmapFactory.decodeFile))直接全局替换为Uri,影响面过大直接copy一份到自己内部返回内部的路径使得外部调用无感知
*
* 发现几个重点问题:
* 1. 项目使用到BitmapFactory.decodeFile(imgPath, options)之类的方法该方法在android Q直接使用外部path测试中发现获取图片信息失败
*
*/
private fun copyToInternalCache(photos: List<Photo>?): List<String>? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val foldPath = getInternalPath() + File.separator
val newPaths = ArrayList<String>()
photos?.forEach {
if (it.uri != null && !it.name.isNullOrEmpty()) {
val path = "$foldPath${it.name}"
if (FileHelper.copyFileFromUri(it.uri, path, true)) {
newPaths.add(path)
Logger.debug(TAG, "path: ${it.path} , displayName: ${it.name} , newPath: $path ")
}
}
}
newPaths
} else {
photos?.takeIf { it.isNotEmpty() }?.map { it.path }
}
}
/**
* 清除复制缓存
*/
fun clearCache() {
Logger.debug(TAG, "clearCache => mLastSelectTime: ${TimeUtils.getDateTimeString(mLastSelectTime, TIME_FORMAT)}")
FileHelper.removeAllFile(getInternalPath() + File.separator)
}
/**
* 检查时间,判断是否要删除缓冲
*/
private fun isClearByTime(): Boolean {
val currentTime = System.currentTimeMillis()
val isClear = currentTime - mLastSelectTime > 10 * 60 * 1000
mLastSelectTime = currentTime
return isClear
}
private fun cancelJop() {
if (mPhotoJob?.isActive == true) {
mPhotoJob?.cancel()
}
}
/**
* easyPhoto内部复制缓存的路径
*/
private fun getInternalPath(): String {
return ("${FileHelper.getRootFilesDir(Environment.DIRECTORY_PICTURES).absolutePath}${File.separator}$FOLD_EASY_PHOTO_INTERNAL")
}
}

View File

@@ -0,0 +1,96 @@
package com.yizhuan.erban.common.transform
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.RectF
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import java.security.MessageDigest
/**
* author: lishangming
* e-mail: lishangming@miya818.com
* time: 2021/09/17
* desc: glide转化器原图片宽高短的一边设置为指定目标值长的自适应拉伸按照指定宽高比例和截取方式进行截取
*/
class AssignScaleTransformation(private val targetSize: Int, private val whRadio: Float, private val clipType: ClipType) : BitmapTransformation() {
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update(("AssignScaleTransformation(${targetSize}_${whRadio}_${clipType})").toByteArray())
}
override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
return crop(pool, toTransform) ?: toTransform
}
private fun crop(pool: BitmapPool, source: Bitmap?): Bitmap? {
if (targetSize <= 0 || whRadio <= 0) return null
if (source == null) return null
//计算截取原文件的宽高
var clipWidth = 0
var clipHeight = 0
when {
source.width / whRadio <= source.height -> {
//用宽度计算,按比例拉伸高度比原本的小,故宽度不变,高度按比例设置
clipWidth = source.width
clipHeight = (source.width / whRadio).toInt()
}
source.height * whRadio <= source.width -> {
//用高度计算,按比例拉伸宽度比原本的小,故高度不变,宽度按比例设置
clipWidth = (source.height * whRadio).toInt()
clipHeight = source.height
}
else -> {
clipWidth = source.width
clipHeight = source.height
}
}
//需要生成图片的宽高
val resultWidth = targetSize
val resultHeight = (targetSize / whRadio).toInt()
//截取比例
val left = if (clipWidth < source.width) (source.width - clipWidth) / 2 else 0
val right = left + clipWidth
var result: Bitmap? = pool[resultWidth, resultHeight, Bitmap.Config.ARGB_8888]
if (result == null) {
result = Bitmap.createBitmap(resultWidth, resultHeight, Bitmap.Config.ARGB_8888)
}
val targetRect = RectF(0f, 0f, resultWidth.toFloat(), resultHeight.toFloat())
val sourceRect = when (clipType) {
ClipType.TOP -> {
//顶部截取,固定截取高度
Rect(left, 0, right, clipHeight)
}
else -> {
//默认中间截取
val top = if (clipHeight < source.height) (source.height - clipHeight) / 2 else 0
val bottom = top + clipHeight
Rect(left, top, right, bottom)
}
}
if (result != null) {
val canvas = Canvas(result)
if (clipWidth < source.width || clipHeight < source.height) {
canvas.drawBitmap(source, sourceRect, targetRect, null)
} else {
canvas.drawBitmap(source, null, targetRect, null)
}
}
return result
}
override fun equals(obj: Any?): Boolean {
return obj is AssignScaleTransformation
}
override fun hashCode(): Int {
return javaClass.name.hashCode()
}
enum class ClipType {
TOP,//截取顶部
CENTER,//截取中间
}
}

View File

@@ -0,0 +1,152 @@
package com.yizhuan.erban.common.transform;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
import java.security.MessageDigest;
/**
* Created by zhl on 2020/1/3.
*/
public class ComplexTransformation extends BitmapTransformation {
private ComplexParamsBuilder mBuilder;
public ComplexTransformation(@NonNull ComplexParamsBuilder builder) {
mBuilder = builder;
}
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
return crop(pool, toTransform, mBuilder);
}
private static Bitmap crop(BitmapPool pool, Bitmap source, ComplexParamsBuilder builder) {
if (source == null) return null;
int resultWidth = builder.mMaxWidth == 0 ? source.getWidth() : builder.mMaxWidth;
int resultHeight = builder.mMaxHeight == 0 ? source.getHeight() : builder.mMaxHeight;
if (builder.mIsNeedScale) {
if ((source.getWidth() > resultWidth || source.getHeight() > resultHeight)) {
float scaleX = (float) resultWidth / source.getWidth();
float scaleY = (float) resultHeight / source.getHeight();
float scale = Math.min(scaleX, scaleY);
resultWidth = (int) (scale * source.getWidth());
resultHeight = (int) (scale * source.getHeight());
} else {
resultWidth = source.getWidth();
resultHeight = source.getHeight();
}
}
Bitmap result = pool.get(resultWidth, resultHeight, Bitmap.Config.ARGB_8888);
RectF targetRect = new RectF(0, 0, resultWidth, resultHeight);
Canvas canvas = new Canvas(result);
if (builder.mIsNeedCorner && source.getWidth() == resultWidth && source.getHeight() == resultHeight) {
//该图没有过缩放,可直接绘制圆角
Paint paint = new Paint();
paint.setShader(new BitmapShader(source, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
paint.setAntiAlias(true);
canvas.drawRoundRect(targetRect, builder.mCorner, builder.mCorner, paint);
return result;
}
if (builder.mIsNeedCropCenter && source.getHeight() != source.getWidth()) {
int min = Math.min(source.getWidth(), source.getHeight());
Rect sourceRect = new Rect((source.getWidth() - min) / 2,
(source.getHeight() - min) / 2,
(source.getWidth() - min) / 2 + min,
(source.getHeight() - min) / 2 + min);
canvas.drawBitmap(source, sourceRect, targetRect, null);
} else {
canvas.drawBitmap(source, null, targetRect, null);
}
if (builder.mIsNeedCorner) {
Paint paint = new Paint();
paint.setShader(new BitmapShader(result, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
paint.setAntiAlias(true);
Bitmap cornerResult = pool.get(resultWidth, resultHeight, Bitmap.Config.ARGB_8888);
Canvas cornerCanvas = new Canvas(cornerResult);
cornerCanvas.drawRoundRect(targetRect, builder.mCorner, builder.mCorner, paint);
return cornerResult;
}
return result;
}
@Override
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
messageDigest.update(("ComplexTransformation(" + mBuilder.toString() + ")").getBytes());
}
public static class ComplexParamsBuilder {
int mMaxWidth;
int mMaxHeight;
boolean mIsNeedScale;
boolean mIsNeedCropCenter;
boolean mIsNeedCorner;
float mCorner;
public ComplexParamsBuilder setMaxWidth(int mMaxWidth) {
this.mMaxWidth = mMaxWidth;
return this;
}
public ComplexParamsBuilder setMaxHeight(int mMaxHeight) {
this.mMaxHeight = mMaxHeight;
return this;
}
public ComplexParamsBuilder setIsNeedScale(boolean mIsNeedScale) {
this.mIsNeedScale = mIsNeedScale;
return this;
}
public ComplexParamsBuilder setIsNeedCropCenter(boolean mIsNeedCropCenter) {
this.mIsNeedCropCenter = mIsNeedCropCenter;
return this;
}
public ComplexParamsBuilder setIsNeedCorner(boolean mIsNeedCorner) {
this.mIsNeedCorner = mIsNeedCorner;
return this;
}
public ComplexParamsBuilder setCorner(float mCorner) {
this.mCorner = mCorner;
return this;
}
@Override
public String toString() {
return "ComplexParamsBuilder{" +
"mMaxWidth=" + mMaxWidth +
", mMaxHeight=" + mMaxHeight +
", mIsNeedScale=" + mIsNeedScale +
", mIsNeedCropCenter=" + mIsNeedCropCenter +
", mIsNeedCorner=" + mIsNeedCorner +
", mCorner=" + mCorner +
'}';
}
}
@Override
public boolean equals(@Nullable Object obj) {
return obj instanceof ComplexTransformation;
}
@Override
public int hashCode() {
return getClass().getName().hashCode();
}
}

View File

@@ -0,0 +1,48 @@
package com.yizhuan.erban.common.util;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.view.ContextThemeWrapper;
import android.view.View;
import androidx.appcompat.widget.TintContextWrapper;
/**
* author: wushaocheng
* time: 2022/11/15
* desc: 判断Activity是否存在
*/
public class ActivityHelper {
/**
* 检查Activity是否可用
*/
public static boolean isCanUse(Activity activity) {
return activity != null && !activity.isFinishing() && !activity.isDestroyed();
}
/**
* 从View里面获取Activity对象
*/
public static Activity getActivityFromView(View view) {
Context context = view.getContext();
return getActivityFromContext(context);
}
/**
* 从Context里面获取Activity对象
*/
public static Activity getActivityFromContext(Context context) {
if (context instanceof Activity) {
return (Activity) context;
} else if (context instanceof ContextThemeWrapper && ((ContextThemeWrapper) context).getBaseContext() instanceof Activity) {
return (Activity) ((ContextWrapper) context).getBaseContext();
} else if (context instanceof TintContextWrapper) {
return (Activity) ((ContextWrapper) context).getBaseContext();
} else {
return null;
}
}
}

View File

@@ -0,0 +1,195 @@
package com.yizhuan.erban.common.util;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Parcelable;
import com.tencent.mmkv.MMKV;
import java.util.Set;
/**
* author: wushaocheng
* time: 2022/11/15
* desc: 使用腾讯MMKV框架替代SharedPreference参考文档https://github.com/Tencent/MMKV/wiki/android_tutorial_cn
*/
public class Config {
private volatile static Config instance = null;
private MMKV mmkv;
public static Config getInstance(Context context) {
if (instance == null) {
synchronized (Config.class) {
if (instance == null) {
instance = new Config(context);
}
}
}
return instance;
}
private Config(Context context) {
try {
if (context == null) {
return;
}
MMKV.initialize(context);
mmkv = MMKV.mmkvWithID("config", MMKV.MULTI_PROCESS_MODE);
} catch (Exception e) {
e.printStackTrace();
}
}
public void setOnChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener) {
if (listener != null && mmkv != null) {
mmkv.registerOnSharedPreferenceChangeListener(listener);
}
}
/************************************** 编码方法 **************************************/
public boolean putBytes(String key, byte[] bytes) {
if (mmkv == null) {
return false;
}
return mmkv.encode(key, bytes);
}
public boolean putInt(String key, int value) {
if (mmkv == null) {
return false;
}
return mmkv.encode(key, value);
}
public boolean putLong(String key, long value) {
if (mmkv == null) {
return false;
}
return mmkv.encode(key, value);
}
public boolean putFloat(String key, float value) {
if (mmkv == null) {
return false;
}
return mmkv.encode(key, value);
}
public boolean putDouble(String key, double value) {
if (mmkv == null) {
return false;
}
return mmkv.encode(key, value);
}
public boolean putBoolean(String key, boolean value) {
if (mmkv == null) {
return false;
}
return mmkv.encode(key, value);
}
public boolean putString(String key, String value) {
if (mmkv == null) {
return false;
}
return mmkv.encode(key, value);
}
public boolean putStringSet(String key, Set<String> value) {
if (mmkv == null) {
return false;
}
return mmkv.encode(key, value);
}
public boolean putParcelable(String key, Parcelable value) {
if (mmkv == null) {
return false;
}
return mmkv.encode(key, value);
}
/************************************** 解码方法 **************************************/
public byte[] getBytes(String key, byte[] defaultValue) {
if (mmkv == null) {
return defaultValue;
}
return mmkv.decodeBytes(key, defaultValue);
}
public int getInt(String key, int defaultValue) {
if (mmkv == null) {
return defaultValue;
}
return mmkv.decodeInt(key, defaultValue);
}
public long getLong(String key, long defaultValue) {
if (mmkv == null) {
return defaultValue;
}
return mmkv.decodeLong(key, defaultValue);
}
public float getFloat(String key, float defaultValue) {
if (mmkv == null) {
return defaultValue;
}
return mmkv.decodeFloat(key, defaultValue);
}
public double getDouble(String key, double defaultValue) {
if (mmkv == null) {
return defaultValue;
}
return mmkv.decodeDouble(key, defaultValue);
}
public boolean getBoolean(String key, boolean defaultValue) {
if (mmkv == null) {
return defaultValue;
}
return mmkv.decodeBool(key, defaultValue);
}
public String getString(String key, String defaultValue) {
if (mmkv == null) {
return defaultValue;
}
return mmkv.decodeString(key, defaultValue);
}
public Set<String> getStringSet(String key, Set<String> defaultValue) {
if (mmkv == null) {
return defaultValue;
}
return mmkv.decodeStringSet(key, defaultValue);
}
public <T extends Parcelable> T getParcelable(String key, Class<T> tClass, T defaultValue) {
if (mmkv == null) {
return defaultValue;
}
return mmkv.decodeParcelable(key, tClass, defaultValue);
}
/************************************** 清理方法 **************************************/
public void remove(String key) {
if (mmkv == null) {
return;
}
mmkv.remove(key);
}
public void clearAll() {
if (mmkv == null) {
return;
}
mmkv.clearAll();
}
}

View File

@@ -0,0 +1,107 @@
package com.yizhuan.erban.common.util;
import android.os.Parcelable;
import com.yizhuan.erban.application.XChatApplication;
import com.yizhuan.xchat_android_library.utils.TimeUtils;
import java.util.Date;
import java.util.Set;
/**
* author: wushaocheng
* time: 2022/11/15
* desc: 封装底层com.tcloud.core.util.Config方便使用
*/
public class SPUtils {
private static Config sConfig = Config.getInstance(XChatApplication.gContext);
public static void putBytes(String key, byte[] bytes) {
sConfig.putBytes(key, bytes);
}
public static void putInt(String key, int value) {
sConfig.putInt(key, value);
}
public static void putLong(String key, long value) {
sConfig.putLong(key, value);
}
public static void putFloat(String key, float value) {
sConfig.putFloat(key, value);
}
public static void putDouble(String key, double value) {
sConfig.putDouble(key, value);
}
public static void putBoolean(String key, boolean value) {
sConfig.putBoolean(key, value);
}
public static void putString(String key, String value) {
sConfig.putString(key, value);
}
public static void putStringSet(String key, Set<String> value) {
sConfig.putStringSet(key, value);
}
public static void putParcelable(String key, Parcelable value) {
sConfig.putParcelable(key, value);
}
public static byte[] getBytes(String key, byte[] defaultValue) {
return sConfig.getBytes(key, defaultValue);
}
public static int getInt(String key, int defaultValue) {
return sConfig.getInt(key, defaultValue);
}
public static long getLong(String key, long defaultValue) {
return sConfig.getLong(key, defaultValue);
}
public static float getFloat(String key, float defaultValue) {
return sConfig.getFloat(key, defaultValue);
}
public static double getDouble(String key, double defaultValue) {
return sConfig.getDouble(key, defaultValue);
}
public static boolean getBoolean(String key, boolean defaultValue) {
return sConfig.getBoolean(key, defaultValue);
}
public static String getString(String key, String defaultValue) {
return sConfig.getString(key, defaultValue);
}
public static Set<String> getStringSet(String key, Set<String> defaultValue) {
return sConfig.getStringSet(key, defaultValue);
}
public static <T extends Parcelable> T getParcelable(String key, Class<T> tClass, T defaultValue) {
return sConfig.getParcelable(key, tClass, defaultValue);
}
public static void remove(String key) {
sConfig.remove(key);
}
public static void clearAll() {
sConfig.clearAll();
}
public static String getSharedDataKey(String constants, long playerId) {
String toDayStr = TimeUtils.date2Str(new Date(), "yyyy-MM-dd");
return getAccountKey(constants, playerId) + toDayStr;
}
public static String getAccountKey(String key, long playerId) {
return key + playerId ;
}
}

View File

@@ -0,0 +1,106 @@
package com.yizhuan.erban.common.util;
import com.yizhuan.xchat_android_library.utils.FP;
public class StringUtils {
public static boolean isNullOrEmpty(String str) {
return FP.empty(str);
}
public static boolean isNotNullOrEmpty(String str) {
return !StringUtils.isNullOrEmpty(str);
}
public static boolean equal(String s1, String s2) {
return StringUtils.equal(s1, s2, false);
}
public static boolean equal(String s1, String s2, boolean ignoreCase) {
if (s1 != null && s2 != null) {
if (ignoreCase) {
return s1.equalsIgnoreCase(s2);
}
return s1.equals(s2);
}
return s1 == null && s2 == null;
}
public static int find(String pattern, String s) {
return StringUtils.find(pattern, s, false);
}
public static int find(String pattern, String s, boolean ignoreCase) {
return StringUtils.find(pattern, s, ignoreCase, false);
}
public static int find(String pattern, String s, boolean ignoreCase, boolean ignoreWidth) {
if (FP.empty(s)) {
return -1;
} else {
pattern = FP.ref(pattern);
if (ignoreCase) {
pattern = pattern.toLowerCase();
s = s.toLowerCase();
}
if (ignoreWidth) {
pattern = narrow(pattern);
s = narrow(s);
}
return s.indexOf(pattern);
}
}
public static String narrow(String s) {
if (FP.empty(s)) {
return "";
} else {
char[] cs = s.toCharArray();
for (int i = 0; i < cs.length; ++i) {
cs[i] = narrow(cs[i]);
}
return new String(cs);
}
}
public static char narrow(char c) {
if (c >= '' && c <= '') {
return (char) (c - 'ﻠ');
} else if (c == 12288) {
return (char) (c - 12288 + 32);
} else if (c == '。') {
return '。';
} else if (c == 12539) {
return '·';
} else {
return c == 8226 ? '·' : c;
}
}
public static int ord(char c) {
if ('a' <= c && c <= 'z') {
return c;
} else {
return 'A' <= c && c <= 'Z' ? c - 65 + 97 : 0;
}
}
public static int compare(String x, String y) {
return FP.ref(x).compareTo(FP.ref(y));
}
public static long parseLong(String s) {
long l = 0L;
try {
l = Long.parseLong(s.trim());
} catch (Exception e) {
return l;
}
return l;
}
}

View File

@@ -1,4 +1,4 @@
package com.yizhuan.erban.ui.widget.marqueeview;
package com.yizhuan.erban.common.util;
import android.annotation.TargetApi;
import android.content.Context;
@@ -10,7 +10,9 @@ import android.view.WindowManager;
import java.util.List;
/**
* Created by sunfusheng on 17/8/8.
* author: wushaocheng
* time: 2022/11/15
* desc: 转换帮助类
*/
public class Utils {

View File

@@ -16,7 +16,7 @@ import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import com.netease.nim.uikit.support.glide.GlideApp;
import com.yizhuan.erban.ui.gift.widget.GlideCircleTransform;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import com.yizhuan.xchat_android_library.utils.config.BasicConfig;
public class CircleImageSpan extends ImageSpan {

View File

@@ -16,7 +16,7 @@ import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import com.netease.nim.uikit.support.glide.GlideApp;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import com.yizhuan.xchat_android_library.utils.config.BasicConfig;
import java.lang.ref.WeakReference;

View File

@@ -20,7 +20,7 @@ import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import com.netease.nim.uikit.support.glide.GlideApp;
import com.yizhuan.erban.ui.widget.magicindicator.buildins.UIUtil;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import com.yizhuan.xchat_android_library.utils.SizeUtils;
import com.yizhuan.xchat_android_library.utils.config.BasicConfig;

View File

@@ -17,7 +17,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.yizhuan.erban.R;
import com.yizhuan.erban.ui.widget.ButtonItem;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import java.util.List;

View File

@@ -42,7 +42,7 @@ import com.yizhuan.erban.team.view.NimTeamMessageActivity;
import com.yizhuan.erban.ui.user.UserInfoActivity;
import com.yizhuan.erban.ui.webview.CommonWebViewActivity;
import com.yizhuan.erban.ui.widget.ShareDialog;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import com.yizhuan.erban.ui.widget.recyclerview.decoration.ColorDecoration;
import com.yizhuan.erban.ui.widget.recyclerview.layoutmanager.FullyLinearLayoutManager;
import com.yizhuan.xchat_android_core.family.bean.FamilyGameInfo;

View File

@@ -14,7 +14,7 @@ import com.chad.library.adapter.base.BaseViewHolder;
import com.netease.nim.uikit.support.glide.GlideApp;
import com.yizhuan.erban.R;
import com.yizhuan.erban.common.widget.CircleImageView;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import com.yizhuan.xchat_android_core.family.bean.FamilyMemberInfo;
import java.util.List;

View File

@@ -14,7 +14,7 @@ import com.yizhuan.erban.base.BaseMvpActivity;
import com.yizhuan.erban.home.adapter.FindNewUserListAdapter;
import com.yizhuan.erban.home.presenter.NewUserListPresenter;
import com.yizhuan.erban.home.view.INewUserListActivityView;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import com.yizhuan.erban.ui.widget.recyclerview.decoration.SpacingDecoration;
import com.yizhuan.xchat_android_core.user.bean.UserInfo;
import com.yizhuan.xchat_android_library.base.factory.CreatePresenter;

View File

@@ -14,7 +14,7 @@ import com.yizhuan.erban.ui.widget.magicindicator.buildins.commonnavigator.abs.C
import com.yizhuan.erban.ui.widget.magicindicator.buildins.commonnavigator.abs.IPagerIndicator;
import com.yizhuan.erban.ui.widget.magicindicator.buildins.commonnavigator.abs.IPagerTitleView;
import com.yizhuan.erban.ui.widget.magicindicator.buildins.commonnavigator.indicators.LinePagerIndicator;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import java.util.List;

View File

@@ -15,7 +15,7 @@ import androidx.annotation.Nullable;
import com.bumptech.glide.request.FutureTarget;
import com.netease.nim.uikit.support.glide.GlideApp;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import java.util.ArrayList;
import java.util.List;

View File

@@ -15,7 +15,7 @@ import androidx.annotation.Nullable;
import com.bumptech.glide.request.FutureTarget;
import com.netease.nim.uikit.support.glide.GlideApp;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import java.util.ArrayList;
import java.util.List;

View File

@@ -1067,7 +1067,6 @@ public class UserInfoDialog extends AppCompatDialog implements View.OnClickListe
private void handleInviteMicItem(List<TextView> buttonItems) {
if (!SuperAdminUtil.isSuperAdmin() &&
!(AvRoomDataManager.get().isSingleRoom() && AvRoomDataManager.get().isOpenAnotherPKMode())) {
// buttonItems.add(createGiveGiftMicItem());
buttonItems.add(createInviteMicItem());
}
}

View File

@@ -20,7 +20,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.yizhuan.erban.R;
import com.yizhuan.erban.common.widget.dialog.DialogManager;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import com.yizhuan.erban.vip.VipMainActivity;
import com.yizhuan.xchat_android_core.market_verify.MarketVerifyModel;
import com.yizhuan.xchat_android_core.room.event.FaceIsReadyEvent;

View File

@@ -16,6 +16,7 @@ import android.widget.ViewFlipper;
import androidx.annotation.AnimRes;
import com.yizhuan.erban.R;
import com.yizhuan.erban.common.util.Utils;
import java.util.ArrayList;
import java.util.List;

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/color_FFFFFF" />
<corners
android:topLeftRadius="@dimen/dp_15"
android:topRightRadius="@dimen/dp_15" />
</shape>

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tv_take_photo"
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="center"
android:textSize="17sp"
android:textColor="@color/color_666666"
android:text="@string/input_panel_take"/>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/color_E5E5E5"/>
<TextView
android:id="@+id/tv_choice_picture"
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="center"
android:textSize="17sp"
android:textColor="@color/color_666666"
android:text="@string/choose_from_photo_album"/>
<View
android:layout_width="match_parent"
android:layout_height="10dp"
android:background="@color/color_E5E5E5"/>
<TextView
android:id="@+id/tv_cancel"
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="center"
android:textSize="17sp"
android:textColor="@color/color_666666"
android:text="@string/cancel"/>
</LinearLayout>

View File

@@ -25,4 +25,6 @@
<item name="done" type="id"/>
<item name="month_text_view" type="id"/>
<item name="viewpager_inner" type="id"/>
<item name="baseViewTagID" type="id" />
</resources>

View File

@@ -114,7 +114,7 @@
<string name="menu_certification">實名認證</string>
<string name="menu_link_room">關聯房間</string>
<string name="text_chat_limit">僅%s或%s的用可發起聊天</string>
<string name="text_chat_limit">僅%s或%s的用可發起聊天</string>
<string name="room_offline">房主已下線</string>
<string name="root_offline_notice">更多好玩的房間在Peko等您喲去看看</string>

View File

@@ -16,7 +16,7 @@ import com.yizhuan.erban.module_hall.income.adapter.IncomeDetailAdapter;
import com.yizhuan.erban.module_hall.income.presenter.IncomeDetailPresenter;
import com.yizhuan.erban.module_hall.income.view.IIncomeDetailView;
import com.yizhuan.erban.ui.utils.ImageLoadUtils;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import com.yizhuan.erban.ui.widget.recyclerview.decoration.SpacingDecoration;
import com.yizhuan.xchat_android_core.module_hall.income.bean.IncomeGiftInfo;
import com.yizhuan.xchat_android_core.module_hall.income.bean.IncomeInfo;

View File

@@ -12,7 +12,7 @@ import androidx.annotation.NonNull;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.yizhuan.erban.R;
import com.yizhuan.erban.ui.widget.marqueeview.Utils;
import com.yizhuan.erban.common.util.Utils;
import com.yizhuan.xchat_android_core.music.model.PlayerModel;

View File

@@ -5,10 +5,10 @@ apply plugin: 'kotlin-android-extensions'
apply from: '../mob.gradle'
android {
compileSdkVersion 32
compileSdkVersion COMPILE_SDK_VERSION.toInteger()
defaultConfig {
minSdkVersion 21
targetSdkVersion 32
minSdkVersion MIN_SDK_VERSION.toInteger()
targetSdkVersion TARGET_SDK_VERSION.toInteger()
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

1
easyphotos/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

38
easyphotos/build.gradle Normal file
View File

@@ -0,0 +1,38 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdk COMPILE_SDK_VERSION.toInteger()
defaultConfig {
targetSdk TARGET_SDK_VERSION.toInteger()
minSdk MIN_SDK_VERSION.toInteger()
testApplicationId 'com.soundcloud.android.crop.test'
testInstrumentationRunner 'android.test.InstrumentationTestRunner'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
buildToolsVersion = '30.0.3'
}
dependencies {
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
}

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.huantansheng.easyphotos">
<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" />
<application>
<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,345 @@
package com.huantansheng.easyphotos;
import android.app.Activity;
import android.app.Fragment;
import android.content.Context;
import android.graphics.Bitmap;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;
import com.huantansheng.easyphotos.builder.AlbumBuilder;
import com.huantansheng.easyphotos.callback.PuzzleCallback;
import com.huantansheng.easyphotos.engine.ImageEngine;
import com.huantansheng.easyphotos.models.ad.AdListener;
import com.huantansheng.easyphotos.models.album.AlbumModel;
import com.huantansheng.easyphotos.models.album.entity.Photo;
import com.huantansheng.easyphotos.models.sticker.StickerModel;
import com.huantansheng.easyphotos.models.sticker.entity.TextStickerData;
import com.huantansheng.easyphotos.ui.PuzzleActivity;
import com.huantansheng.easyphotos.utils.bitmap.BitmapUtils;
import com.huantansheng.easyphotos.utils.bitmap.SaveBitmapCallBack;
import com.huantansheng.easyphotos.utils.media.MediaScannerConnectionUtils;
import com.huantansheng.easyphotos.utils.result.EasyResult;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* EasyPhotos的启动管理器
* Created by huan on 2017/10/18.
*/
public class EasyPhotos {
//easyPhotos的返回数据Key
public static final String RESULT_PHOTOS = "keyOfEasyPhotosResult";
public static final String RESULT_SELECTED_ORIGINAL = "keyOfEasyPhotosResultSelectedOriginal";
/**
* 预加载
* 调不调用该方法都可以不调用不影响EasyPhotos正常使用
* 第一次扫描媒体库可能会慢,调用预加载会使真正打开相册的速度加快
* 若调用该方法建议自行判断代码书写位置建议在用户打开相册的3秒前调用比如app主页面或调用相册的上一页
* 该方法如果没有授权读取权限的话,是无效的,所以外部加不加权限控制都可以,加的话保证执行,不加也不影响程序正常使用
*
* @param cxt 上下文
*/
public static void preLoad(Context cxt) {
AlbumModel.getInstance().query(cxt, null);
}
/**
* 预加载
* 调不调用该方法都可以不调用不影响EasyPhotos正常使用
* 第一次扫描媒体库可能会慢,调用预加载会使真正打开相册的速度加快
* 若调用该方法建议自行判断代码书写位置建议在用户打开相册的3秒前调用比如app主页面或调用相册的上一页
* 该方法如果没有授权读取权限的话,是无效的,所以外部加不加权限控制都可以,加的话保证执行,不加也不影响程序正常使用
*
* @param cxt 上下文
* @param callBack 预加载完成的回调若进行UI操作需自行切回主线程。
*/
public static void preLoad(Context cxt, AlbumModel.CallBack callBack) {
AlbumModel.getInstance().query(cxt, callBack);
}
/**
* 创建相机
*
* @param activity 上下文
* @param useWidth 是否使用宽高数据
* @return AlbumBuilder
*/
public static AlbumBuilder createCamera(Activity activity,
boolean useWidth) {
return AlbumBuilder.createCamera(activity).setUseWidth(useWidth);
}
public static AlbumBuilder createCamera(Fragment fragment,
boolean useWidth) {
return AlbumBuilder.createCamera(fragment).setUseWidth(useWidth);
}
public static AlbumBuilder createCamera(FragmentActivity activity,
boolean useWidth) {
return AlbumBuilder.createCamera(activity).setUseWidth(useWidth);
}
public static AlbumBuilder createCamera(androidx.fragment.app.Fragment fragmentV,
boolean useWidth) {
return AlbumBuilder.createCamera(fragmentV).setUseWidth(useWidth);
}
/**
* 创建相册
*
* @param activity 上下文
* @param isShowCamera 是否显示相机按钮
* @param useWidth 是否使用宽高数据。
* true会保证宽高数据的正确性返回速度慢耗时尤其在华为mate30上可能点击完成后会加载三四秒才能返回。
* false:有宽高数据但不保证正确性点击完成后秒回但可能有因旋转问题导致的宽高相反的情况以及极少数的宽高为0情况。
* @param imageEngine 图片加载引擎的具体实现
* @return AlbumBuilder 建造者模式配置其他选项
*/
public static AlbumBuilder createAlbum(Activity activity, boolean isShowCamera,
boolean useWidth, @NonNull ImageEngine imageEngine) {
return AlbumBuilder.createAlbum(activity, isShowCamera, imageEngine).setUseWidth(useWidth);
}
public static AlbumBuilder createAlbum(Fragment fragment, boolean isShowCamera,
boolean useWidth, @NonNull ImageEngine imageEngine) {
return AlbumBuilder.createAlbum(fragment, isShowCamera, imageEngine).setUseWidth(useWidth);
}
public static AlbumBuilder createAlbum(FragmentActivity activity, boolean isShowCamera,
boolean useWidth, @NonNull ImageEngine imageEngine) {
return AlbumBuilder.createAlbum(activity, isShowCamera, imageEngine).setUseWidth(useWidth);
}
public static AlbumBuilder createAlbum(androidx.fragment.app.Fragment fragmentV,
boolean isShowCamera, boolean useWidth,
@NonNull ImageEngine imageEngine) {
return AlbumBuilder.createAlbum(fragmentV, isShowCamera, imageEngine).setUseWidth(useWidth);
}
//*********************AD************************************
/**
* 设置广告监听
* 内部使用,无需关心
*
* @param adListener 广告监听
*/
public static void setAdListener(AdListener adListener) {
AlbumBuilder.setAdListener(adListener);
}
/**
* 刷新图片列表广告数据
*/
public static void notifyPhotosAdLoaded() {
AlbumBuilder.notifyPhotosAdLoaded();
}
/**
* 刷新专辑项目列表广告
*/
public static void notifyAlbumItemsAdLoaded() {
AlbumBuilder.notifyAlbumItemsAdLoaded();
}
//*************************bitmap功能***********************************/
/**
* 回收bitmap
*
* @param bitmap 要回收的bitmap
*/
public static void recycle(Bitmap bitmap) {
BitmapUtils.recycle(bitmap);
}
/**
* 回收bitmap数组中的所有图片
*
* @param bitmaps 要回收的bitmap数组
*/
public static void recycle(Bitmap... bitmaps) {
BitmapUtils.recycle(bitmaps);
}
/**
* 回收bitmap集合中的所有图片
*
* @param bitmaps 要回收的bitmap集合
*/
public static void recycle(List<Bitmap> bitmaps) {
BitmapUtils.recycle(bitmaps);
}
/**
* 给图片添加水印,水印会根据图片宽高自动缩放处理
*
* @param watermark 水印
* @param image 添加水印的图片
* @param srcImageWidth 水印对应的原图片宽度,即ui制作水印时参考的要添加水印的图片的宽度
* @param offsetX 添加水印的X轴偏移量
* @param offsetY 添加水印的Y轴偏移量
* @param addInLeft true 在左下角添加水印false 在右下角添加水印
* @param orientation Bitmap的旋转角度。当useWidth为true时Photo实体类中会有orientation若bitmap
* 不是用户手机内图片填0即可。
* @return 添加水印后的bitmap
*/
public static Bitmap addWatermark(Bitmap watermark, Bitmap image, int srcImageWidth,
int offsetX, int offsetY, boolean addInLeft,
int orientation) {
return BitmapUtils.addWatermark(watermark, image, srcImageWidth, offsetX, offsetY,
addInLeft, orientation);
}
/**
* 给图片添加带文字和图片的水印,水印会根据图片宽高自动缩放处理
*
* @param watermark 水印图片
* @param image 要加水印的图片
* @param srcImageWidth 水印对应的原图片宽度,即ui制作水印时参考的要添加水印的图片的宽度
* @param text 要添加的文字
* @param offsetX 添加水印的X轴偏移量
* @param offsetY 添加水印的Y轴偏移量
* @param addInLeft true 在左下角添加水印false 在右下角添加水印
* @param orientation Bitmap的旋转角度。当useWidth为true时Photo实体类中会有orientation若bitmap
* 不是用户手机内图片填0即可。
* @return 添加水印后的bitmap
*/
public static Bitmap addWatermarkWithText(Bitmap watermark, Bitmap image, int srcImageWidth,
@NonNull String text, int offsetX, int offsetY,
boolean addInLeft, int orientation) {
return BitmapUtils.addWatermarkWithText(watermark, image, srcImageWidth, text, offsetX,
offsetY,
addInLeft, orientation);
}
/**
* 保存Bitmap到指定文件夹
*
* @param act 上下文
* @param dirPath 文件夹全路径
* @param bitmap bitmap
* @param namePrefix 保存文件的前缀名,文件最终名称格式为:前缀名+自动生成的唯一数字字符+.png
* @param notifyMedia 是否更新到媒体库
* @param callBack 保存图片后的回调回调已经处于UI线程
*/
public static void saveBitmapToDir(Activity act, String dirPath, String namePrefix,
Bitmap bitmap, boolean notifyMedia,
SaveBitmapCallBack callBack) {
BitmapUtils.saveBitmapToDir(act, dirPath, namePrefix, bitmap, notifyMedia, callBack);
}
/**
* 把View画成Bitmap
*
* @param view 要处理的View
* @return Bitmap
*/
public static Bitmap createBitmapFromView(View view) {
return BitmapUtils.createBitmapFromView(view);
}
/**
* 启动拼图最多对9张图片进行拼图
*
* @param act 上下文
* @param photos 图片集合最多对9张图片进行拼图
* @param puzzleSaveDirPath 拼图完成保存的文件夹全路径
* @param puzzleSaveNamePrefix 拼图完成保存的文件名前缀,最终格式:前缀+默认生成唯一数字标识+.png
* @param requestCode 请求code
* @param replaceCustom 单击替换拼图中的某张图片时是否以startForResult的方式启动你的自定义界面该界面与传进来的act
* 为同一界面。false则在EasyPhotos内部完成正常需求直接写false即可。
* true的情况适用于用于拼图的图片集合中包含网络图片是在你的act界面中获取并下载的也可以直接用网络地址不用下载后的本地地址也就是可以不下载下来而非单纯本地相册。举例你的act中有两个按钮一个指向本地相册一个指向网络相册用户在该界面任意选择选择好图片后跳转到拼图界面用户在拼图界面点击替换按钮将会启动一个新的act界面这时act只让用户在网络相册和本地相册选择一张图片选择好执行
* Intent intent = new Intent();
* intent.putParcelableArrayListExtra(AlbumBuilder.RESULT_PHOTOS
* , photos);
* act.setResult(RESULT_OK,intent); 并关闭act回到拼图界面完成替换。
* @param imageEngine 图片加载引擎的具体实现
*/
public static void startPuzzleWithPhotos(Activity act, ArrayList<Photo> photos,
String puzzleSaveDirPath,
String puzzleSaveNamePrefix, int requestCode,
boolean replaceCustom,
@NonNull ImageEngine imageEngine) {
act.setResult(Activity.RESULT_OK);
PuzzleActivity.startWithPhotos(act, photos, puzzleSaveDirPath, puzzleSaveNamePrefix,
requestCode, replaceCustom, imageEngine);
}
public static void startPuzzleWithPhotos(FragmentActivity act, ArrayList<Photo> photos,
String puzzleSaveDirPath,
String puzzleSaveNamePrefix, boolean replaceCustom,
@NonNull ImageEngine imageEngine,
PuzzleCallback callback) {
act.setResult(Activity.RESULT_OK);
EasyResult.get(act).startPuzzleWithPhotos(photos, puzzleSaveDirPath, puzzleSaveNamePrefix
, replaceCustom, imageEngine, callback);
}
//**************更新媒体库***********************
/**
* 更新媒体文件到媒体库
*
* @param cxt 上下文
* @param filePaths 更新的文件地址
*/
public static void notifyMedia(Context cxt, String... filePaths) {
MediaScannerConnectionUtils.refresh(cxt, filePaths);
}
/**
* 更新媒体文件到媒体库
*
* @param cxt 上下文
* @param files 更新的文件
*/
public static void notifyMedia(Context cxt, File... files) {
MediaScannerConnectionUtils.refresh(cxt, files);
}
/**
* 更新媒体文件到媒体库
*
* @param cxt 上下文
* @param fileList 更新的文件地址集合
*/
public static void notifyMedia(Context cxt, List<String> fileList) {
MediaScannerConnectionUtils.refresh(cxt, fileList);
}
//*********************************贴纸***************************
/**
* 添加文字贴纸的文字数据
*
* @param textStickerData 文字贴纸的文字数据
*/
public static void addTextStickerData(TextStickerData... textStickerData) {
StickerModel.textDataList.addAll(Arrays.asList(textStickerData));
}
/**
* 清空文字贴纸的数据
*/
public static void clearTextStickerDataList() {
StickerModel.textDataList.clear();
}
}

View File

@@ -0,0 +1,618 @@
package com.huantansheng.easyphotos.builder;
import android.app.Activity;
import android.net.Uri;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import com.huantansheng.easyphotos.callback.SelectCallback;
import com.huantansheng.easyphotos.constant.Type;
import com.huantansheng.easyphotos.engine.ImageEngine;
import com.huantansheng.easyphotos.models.ad.AdListener;
import com.huantansheng.easyphotos.models.album.entity.Photo;
import com.huantansheng.easyphotos.result.Result;
import com.huantansheng.easyphotos.setting.Setting;
import com.huantansheng.easyphotos.ui.EasyPhotosActivity;
import com.huantansheng.easyphotos.utils.result.EasyResult;
import com.huantansheng.easyphotos.utils.uri.UriUtils;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
/**
* EasyPhotos的启动管理器
* Created by huan on 2017/10/18.
*/
public class AlbumBuilder {
/**
* 启动模式
* CAMERA-相机
* ALBUM-相册专辑
* ALBUM_CAMERA-带有相机按钮的相册专辑
*/
private enum StartupType {
CAMERA,
ALBUM,
ALBUM_CAMERA
}
private static 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.huantansheng.easyphotos.callback;
import com.huantansheng.easyphotos.models.album.entity.Photo;
/**
* PuzzleCallback
*
* @author joker
* @date 2019/4/9.
*/
public abstract class PuzzleCallback {
/**
* 选择结果回调
*
* @param photo 返回对象:如果你需要了解图片的宽、高、大小、用户是否选中原图选项等信息,可以用这个
*/
public abstract void onResult(Photo photo);
/**
* 什么都没选,取消选择回调
*/
public abstract void onCancel();
}

View File

@@ -0,0 +1,26 @@
package com.huantansheng.easyphotos.callback;
import com.huantansheng.easyphotos.models.album.entity.Photo;
import java.util.ArrayList;
/**
* SelectCallback
*
* @author joker
* @date 2019/4/9.
*/
public abstract class SelectCallback {
/**
* 选择结果回调
*
* @param photos 返回对象集合:如果你需要了解图片的宽、高、大小、用户是否选中原图选项等信息,可以用这个
* @param isOriginal 返回图片地址集合时如果你需要知道用户选择图片时是否选择了原图选项,用如下方法获取
*/
public abstract void onResult(ArrayList<Photo> photos, boolean isOriginal);
/**
* 什么都没选,取消选择回调
*/
public abstract void onCancel();
}

View File

@@ -0,0 +1,21 @@
package com.huantansheng.easyphotos.constant;
/**
* Code常量
* Created by huan on 2017/10/19.
*/
public class Code {
//相机请求码
public static final int REQUEST_CAMERA = 11;
//权限请求码
public static final int REQUEST_PERMISSION = 12;
//预览activity请求码
public static final int REQUEST_PREVIEW_ACTIVITY = 13;
//请求应用详情
public static final int REQUEST_SETTING_APP_DETAILS = 14;
//拼图activity请求吗
public static final int REQUEST_PUZZLE = 15;
//拼图选择activity请求吗
public static final int REQUEST_PUZZLE_SELECTOR = 16;
}

View File

@@ -0,0 +1,23 @@
package com.huantansheng.easyphotos.constant;
/**
* key的常量
* Created by huan on 2017/10/19.
*/
public class Key {
//预览图片的当前角标
public static final String PREVIEW_PHOTO_INDEX = "keyOfPreviewPhotoIndex";
//当前预览界面的专辑index
public static final String PREVIEW_ALBUM_ITEM_INDEX = "keyOfPreviewAlbumItemIndex";
//预览界面是否点击完成
public static final String PREVIEW_CLICK_DONE = "keyOfPreviewClickDone";
//拼图界面图片类型,true-Photo,false-String
public static final String PUZZLE_FILE_IS_PHOTO = "keyOfPuzzleFilesTypeIsPhoto";
//拼图界面图片结合
public static final String PUZZLE_FILES = "keyOfPuzzleFiles";
//拼图界面图片保存文件夹地址
public static final String PUZZLE_SAVE_DIR = "keyOfPuzzleSaveDir";
//拼图界面图片保存文件名前缀
public static final String PUZZLE_SAVE_NAME_PREFIX = "keyOfPuzzleSaveNamePrefix";
}

View File

@@ -0,0 +1,15 @@
package com.huantansheng.easyphotos.constant;
/**
* Created by huan on 2018/1/9.
*/
public class Type {
public static final String GIF = "gif";
public static final String VIDEO = "video";
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.huantansheng.easyphotos.engine;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.widget.ImageView;
import androidx.annotation.NonNull;
/**
* 自定义图片加载方式
* Created by huan on 2018/1/15.
*/
public interface ImageEngine {
/**
* 加载图片到ImageView
*
* @param context 上下文
* @param uri 图片Uri
* @param imageView 加载到的ImageView
*/
//安卓10推荐uri并且path的方式不再可用
void loadPhoto(@NonNull Context context, @NonNull Uri uri,@NonNull ImageView imageView);
/**
* 加载gif动图图片到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.huantansheng.easyphotos.models.ad;
import android.view.View;
/**
* 广告实体
* Created by huan on 2017/10/24.
*/
public class AdEntity {
public View adView;
public int lineIndex;
public AdEntity(View adView, int lineIndex) {
this.adView = adView;
this.lineIndex = lineIndex;
}
}

View File

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

View File

@@ -0,0 +1,20 @@
package com.huantansheng.easyphotos.models.ad;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
import android.widget.FrameLayout;
import com.huantansheng.easyphotos.R;
/**
* 广告viewolder
* Created by huan on 2017/10/28.
*/
public class AdViewHolder extends RecyclerView.ViewHolder {
public FrameLayout adFrame;
public AdViewHolder(View itemView) {
super(itemView);
adFrame = (FrameLayout) itemView.findViewById(R.id.ad_frame_easy_photos);
}
}

View File

@@ -0,0 +1,361 @@
package com.huantansheng.easyphotos.models.album;
import android.Manifest;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.provider.MediaStore;
import android.text.TextUtils;
import androidx.core.content.PermissionChecker;
import com.huantansheng.easyphotos.R;
import com.huantansheng.easyphotos.constant.Type;
import com.huantansheng.easyphotos.models.album.entity.Album;
import com.huantansheng.easyphotos.models.album.entity.AlbumItem;
import com.huantansheng.easyphotos.models.album.entity.Photo;
import com.huantansheng.easyphotos.result.Result;
import com.huantansheng.easyphotos.setting.Setting;
import com.huantansheng.easyphotos.utils.string.StringUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* 专辑模型
* Created by huan on 2017/10/20.
* <p>
* Modified by Eagle on 2018/08/31.
* 修改内容将AlbumModel的实例化与数据查询分开
*/
public class AlbumModel {
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.huantansheng.easyphotos.models.album.entity;
import android.net.Uri;
import java.util.ArrayList;
import java.util.LinkedHashMap;
/**
* 专辑模型实体类
* Created by huan on 2017/10/20.
*/
public class Album {
public ArrayList<AlbumItem> albumItems;
private LinkedHashMap<String, AlbumItem> hasAlbumItems;//用于记录专辑项目
public Album() {
albumItems = new ArrayList<>();
hasAlbumItems = new LinkedHashMap<>();
}
private void addAlbumItem(AlbumItem albumItem) {
this.hasAlbumItems.put(albumItem.name, albumItem);
this.albumItems.add(albumItem);
}
public void addAlbumItem(String name, String folderPath, String coverImagePath, Uri coverImageUri) {
if (null == hasAlbumItems.get(name)) {
addAlbumItem(new AlbumItem(name, folderPath, coverImagePath,coverImageUri));
}
}
public AlbumItem getAlbumItem(String name) {
return hasAlbumItems.get(name);
}
public AlbumItem getAlbumItem(int currIndex) {
return albumItems.get(currIndex);
}
public boolean isEmpty() {
return albumItems.isEmpty();
}
public void clear() {
albumItems.clear();
hasAlbumItems.clear();
}
}

View File

@@ -0,0 +1,34 @@
package com.huantansheng.easyphotos.models.album.entity;
import android.net.Uri;
import java.util.ArrayList;
/**
* 专辑项目实体类
* Created by huan on 2017/10/20.
*/
public class AlbumItem {
public String name;
public String folderPath;
public String coverImagePath;
public Uri coverImageUri;
public ArrayList<Photo> photos;
AlbumItem(String name, String folderPath, String coverImagePath, Uri coverImageUri) {
this.name = name;
this.folderPath = folderPath;
this.coverImagePath = coverImagePath;
this.coverImageUri = coverImageUri;
this.photos = new ArrayList<>();
}
public void addImageItem(Photo imageItem) {
this.photos.add(imageItem);
}
public void addImageItem(int index, Photo imageItem) {
this.photos.add(index, imageItem);
}
}

View File

@@ -0,0 +1,114 @@
package com.huantansheng.easyphotos.models.album.entity;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
/**
* 图片item实体类
* Created by huan on 2017/10/20.
*/
public class Photo implements Parcelable {
private static final String TAG = "Photo";
public Uri uri;//图片Uri
public String name;//图片名称
public String path;//图片全路径
public String type;//图片类型
public int width;//图片宽度
public int height;//图片高度
public int orientation;//图片旋转角度
public long size;//图片文件大小单位Bytes
public long duration;//视频时长,单位:毫秒
public long time;//图片拍摄的时间戳,单位:毫秒
public boolean selected;//是否被选中,内部使用,无需关心
public boolean selectedOriginal;//用户选择时是否选择了原图选项
public Photo(String name, Uri uri, String path, long time, int width, int height, int orientation, long size, long duration, String type) {
this.name = name;
this.uri = uri;
this.path = path;
this.time = time;
this.width = width;
this.height = height;
this.orientation = orientation;
this.type = type;
this.size = size;
this.duration = duration;
this.selected = false;
this.selectedOriginal = false;
}
@Override
public boolean equals(Object o) {
try {
Photo other = (Photo) o;
return this.path.equalsIgnoreCase(other.path);
} catch (ClassCastException e) {
Log.e(TAG, "equals: " + Log.getStackTraceString(e));
}
return super.equals(o);
}
@Override
public String toString() {
return "Photo{" +
"name='" + name + '\'' +
", uri='" + uri.toString() + '\'' +
", path='" + path + '\'' +
", time=" + time + '\'' +
", minWidth=" + width + '\'' +
", minHeight=" + height +
", orientation=" + orientation +
'}';
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(this.uri, flags);
dest.writeString(this.name);
dest.writeString(this.path);
dest.writeString(this.type);
dest.writeInt(this.width);
dest.writeInt(this.height);
dest.writeInt(this.orientation);
dest.writeLong(this.size);
dest.writeLong(this.duration);
dest.writeLong(this.time);
dest.writeByte(this.selected ? (byte) 1 : (byte) 0);
dest.writeByte(this.selectedOriginal ? (byte) 1 : (byte) 0);
}
protected Photo(Parcel in) {
this.uri = in.readParcelable(Uri.class.getClassLoader());
this.name = in.readString();
this.path = in.readString();
this.type = in.readString();
this.width = in.readInt();
this.height = in.readInt();
this.orientation = in.readInt();
this.size = in.readLong();
this.duration = in.readLong();
this.time = in.readLong();
this.selected = in.readByte() != 0;
this.selectedOriginal = in.readByte() != 0;
}
public static final Creator<Photo> CREATOR = new Creator<Photo>() {
@Override
public Photo createFromParcel(Parcel source) {
return new Photo(source);
}
@Override
public Photo[] newArray(int size) {
return new Photo[size];
}
};
}

View File

@@ -0,0 +1,65 @@
package com.huantansheng.easyphotos.models.puzzle;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import java.util.List;
/**
* @author wupanjie
*/
public interface Area {
float left();
float top();
float right();
float bottom();
float centerX();
float centerY();
float width();
float height();
PointF getCenterPoint();
boolean contains(PointF point);
boolean contains(float x, float y);
boolean contains(Line line);
Path getAreaPath();
RectF getAreaRect();
List<Line> getLines();
PointF[] getHandleBarPoints(Line line);
float radian();
void setRadian(float radian);
float getPaddingLeft();
float getPaddingTop();
float getPaddingRight();
float getPaddingBottom();
void setPadding(float padding);
void setPadding(float paddingLeft, float paddingTop, float paddingRight, float paddingBottom);
}

View File

@@ -0,0 +1,354 @@
package com.huantansheng.easyphotos.models.puzzle;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import androidx.core.content.ContextCompat;
import com.huantansheng.easyphotos.R;
/**
* @author wupanjie
*/
public class DegreeSeekBar extends View {
private static final String TAG = "DegreeSeekBar";
private Paint mTextPaint;
private Paint mCirclePaint;
private Paint.FontMetricsInt mFontMetrics;
private int mBaseLine;
private float[] mTextWidths;
private final Rect mCanvasClipBounds = new Rect();
private ScrollingListener mScrollingListener;
private float mLastTouchedPosition;
private Paint mPointPaint;
private float mPointMargin;
private boolean mScrollStarted;
private int mTotalScrollDistance;
private Path mIndicatorPath = new Path();
private int mCurrentDegrees = 0;
private int mPointCount = 51;
private int mPointColor;
private int mTextColor;
private int mCenterTextColor;
//阻尼系数的倒数
private float mDragFactor = 2.1f;
private int mMinReachableDegrees = -45;
private int mMaxReachableDegrees = 45;
private String suffix = "";
public DegreeSeekBar(Context context) {
this(context, null);
}
public DegreeSeekBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DegreeSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public DegreeSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
mPointColor = ContextCompat.getColor(getContext(), R.color.easy_photos_fg_primary);
mTextColor = ContextCompat.getColor(getContext(), R.color.easy_photos_fg_primary);
mCenterTextColor = ContextCompat.getColor(getContext(), R.color.easy_photos_fg_accent);
mPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPointPaint.setStyle(Paint.Style.STROKE);
mPointPaint.setColor(mPointColor);
mPointPaint.setStrokeWidth(2);
mTextPaint = new Paint();
mTextPaint.setColor(mTextColor);
mTextPaint.setStyle(Paint.Style.STROKE);
mTextPaint.setAntiAlias(true);
mTextPaint.setTextSize(24f);
mTextPaint.setTextAlign(Paint.Align.LEFT);
mTextPaint.setAlpha(100);
mFontMetrics = mTextPaint.getFontMetricsInt();
mTextWidths = new float[1];
mTextPaint.getTextWidths("0", mTextWidths);
mCirclePaint = new Paint();
mCirclePaint.setStyle(Paint.Style.FILL);
mCirclePaint.setAlpha(255);
mCirclePaint.setAntiAlias(true);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mPointMargin = (float) w / mPointCount;
mBaseLine = (h - mFontMetrics.bottom + mFontMetrics.top) / 2 - mFontMetrics.top;
mIndicatorPath.moveTo(w / 2, h / 2 + mFontMetrics.top / 2 - 18);
mIndicatorPath.rLineTo(-8, -8);
mIndicatorPath.rLineTo(16, 0);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastTouchedPosition = event.getX();
if (!mScrollStarted) {
mScrollStarted = true;
if (mScrollingListener != null) {
mScrollingListener.onScrollStart();
}
}
break;
case MotionEvent.ACTION_UP:
mScrollStarted = false;
if (mScrollingListener != null) {
mScrollingListener.onScrollEnd();
}
invalidate();
break;
case MotionEvent.ACTION_MOVE:
float distance = event.getX() - mLastTouchedPosition;
if (mCurrentDegrees >= mMaxReachableDegrees && distance < 0) {
mCurrentDegrees = mMaxReachableDegrees;
invalidate();
break;
}
if (mCurrentDegrees <= mMinReachableDegrees && distance > 0) {
mCurrentDegrees = mMinReachableDegrees;
invalidate();
break;
}
if (distance != 0) {
onScrollEvent(event, distance);
}
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.getClipBounds(mCanvasClipBounds);
int zeroIndex = mPointCount / 2 + (0 - mCurrentDegrees) / 2;
mPointPaint.setColor(mPointColor);
for (int i = 0; i < mPointCount; i++) {
if (i > zeroIndex - Math.abs(mMinReachableDegrees) / 2
&& i < zeroIndex + Math.abs(mMaxReachableDegrees) / 2
&& mScrollStarted) {
mPointPaint.setAlpha(255);
} else {
mPointPaint.setAlpha(100);
}
if (i > mPointCount / 2 - 8
&& i < mPointCount / 2 + 8
&& i > zeroIndex - Math.abs(mMinReachableDegrees) / 2
&& i < zeroIndex + Math.abs(mMaxReachableDegrees) / 2) {
if (mScrollStarted) {
mPointPaint.setAlpha(Math.abs(mPointCount / 2 - i) * 255 / 8);
} else {
mPointPaint.setAlpha(Math.abs(mPointCount / 2 - i) * 100 / 8);
}
}
canvas.drawPoint(mCanvasClipBounds.centerX() + (i - mPointCount / 2) * mPointMargin,
mCanvasClipBounds.centerY(), mPointPaint);
if (mCurrentDegrees != 0 && i == zeroIndex) {
if (mScrollStarted) {
mTextPaint.setAlpha(255);
} else {
mTextPaint.setAlpha(192);
}
mPointPaint.setStrokeWidth(4);
canvas.drawPoint((mCanvasClipBounds.centerX() + (i - mPointCount / 2) * mPointMargin),
mCanvasClipBounds.centerY(), mPointPaint);
mPointPaint.setStrokeWidth(2);
mTextPaint.setAlpha(100);
}
}
for (int i = -180; i <= 180; i += 15) {
if (i >= mMinReachableDegrees && i <= mMaxReachableDegrees) {
drawDegreeText(i, canvas, true);
} else {
drawDegreeText(i, canvas, false);
}
}
mTextPaint.setTextSize(28f);
mTextPaint.setAlpha(255);
mTextPaint.setColor(mCenterTextColor);
if (mCurrentDegrees >= 10) {
canvas.drawText(mCurrentDegrees + suffix, getWidth() / 2 - mTextWidths[0], mBaseLine,
mTextPaint);
} else if (mCurrentDegrees <= -10) {
canvas.drawText(mCurrentDegrees + suffix, getWidth() / 2 - mTextWidths[0] / 2 * 3, mBaseLine,
mTextPaint);
} else if (mCurrentDegrees < 0) {
canvas.drawText(mCurrentDegrees + suffix, getWidth() / 2 - mTextWidths[0], mBaseLine,
mTextPaint);
} else {
canvas.drawText(mCurrentDegrees + suffix, getWidth() / 2 - mTextWidths[0] / 2, mBaseLine,
mTextPaint);
}
mTextPaint.setAlpha(100);
mTextPaint.setTextSize(24f);
mTextPaint.setColor(mTextColor);
//画中心三角
mCirclePaint.setColor(mCenterTextColor);
canvas.drawPath(mIndicatorPath, mCirclePaint);
mCirclePaint.setColor(mCenterTextColor);
}
private void drawDegreeText(int degrees, Canvas canvas, boolean canReach) {
if (canReach) {
if (mScrollStarted) {
mTextPaint.setAlpha(Math.min(255, Math.abs(degrees - mCurrentDegrees) * 255 / 15));
if (Math.abs(degrees - mCurrentDegrees) <= 7) {
mTextPaint.setAlpha(0);
}
} else {
mTextPaint.setAlpha(100);
if (Math.abs(degrees - mCurrentDegrees) <= 7) {
mTextPaint.setAlpha(0);
}
}
} else {
mTextPaint.setAlpha(100);
}
if (degrees == 0) {
if (Math.abs(mCurrentDegrees) >= 15 && !mScrollStarted) {
mTextPaint.setAlpha(180);
}
canvas.drawText("",
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.huantansheng.easyphotos.models.puzzle;
import android.graphics.PointF;
/**
* @author wupanjie
*/
public interface Line {
enum Direction {
HORIZONTAL, VERTICAL
}
float length();
PointF startPoint();
PointF endPoint();
Line lowerLine();
Line upperLine();
Line attachStartLine();
Line attachEndLine();
void setLowerLine(Line lowerLine);
void setUpperLine(Line upperLine);
Direction direction();
float slope();
boolean contains(float x, float y, float extra);
void prepareMove();
boolean move(float offset, float extra);
void update(float layoutWidth, float layoutHeight);
float minX();
float maxX();
float minY();
float maxY();
void offset(float x, float y);
}

View File

@@ -0,0 +1,167 @@
package com.huantansheng.easyphotos.models.puzzle;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import java.util.Arrays;
import static java.lang.Math.round;
/**
* some useful matrix operation methods
*
* @author wupanjie
*/
public class MatrixUtils {
private MatrixUtils() {
//no instance
}
private static final float[] sMatrixValues = new float[9];
private static final Matrix sTempMatrix = new Matrix();
/**
* This method calculates scale value for given Matrix object.
*/
public static float getMatrixScale(Matrix matrix) {
return (float) Math.sqrt(Math.pow(getMatrixValue(matrix, Matrix.MSCALE_X), 2) + Math.pow(
getMatrixValue(matrix, Matrix.MSKEW_Y), 2));
}
/**
* This method calculates rotation angle for given Matrix object.
*/
public static float getMatrixAngle(Matrix matrix) {
return (float) -(Math.atan2(getMatrixValue(matrix, Matrix.MSKEW_X),
getMatrixValue(matrix, Matrix.MSCALE_X)) * (180 / Math.PI));
}
public static float getMatrixValue(Matrix matrix, int valueIndex) {
matrix.getValues(sMatrixValues);
return sMatrixValues[valueIndex];
}
public static float getMinMatrixScale(PuzzlePiece piece) {
if (piece != null) {
sTempMatrix.reset();
sTempMatrix.setRotate(-piece.getMatrixAngle());
float[] unrotatedCropBoundsCorners = getCornersFromRect(piece.getArea().getAreaRect());
sTempMatrix.mapPoints(unrotatedCropBoundsCorners);
RectF unrotatedCropRect = trapToRect(unrotatedCropBoundsCorners);
return Math.max(unrotatedCropRect.width() / piece.getWidth(),
unrotatedCropRect.height() / piece.getHeight());
}
return 1f;
}
//判断剪裁框是否在图片内
static boolean judgeIsImageContainsBorder(PuzzlePiece piece, float rotateDegrees) {
sTempMatrix.reset();
sTempMatrix.setRotate(-rotateDegrees);
float[] unrotatedWrapperCorner = new float[8];
float[] unrotateBorderCorner = new float[8];
sTempMatrix.mapPoints(unrotatedWrapperCorner, piece.getCurrentDrawablePoints());
sTempMatrix.mapPoints(unrotateBorderCorner, getCornersFromRect(piece.getArea().getAreaRect()));
return trapToRect(unrotatedWrapperCorner).contains(trapToRect(unrotateBorderCorner));
}
static float[] calculateImageIndents(PuzzlePiece piece) {
if (piece == null) return new float[]{0, 0, 0, 0, 0, 0, 0, 0};
sTempMatrix.reset();
sTempMatrix.setRotate(-piece.getMatrixAngle());
final float[] currentImageCorners = piece.getCurrentDrawablePoints();
final float[] unrotatedImageCorners =
Arrays.copyOf(currentImageCorners, currentImageCorners.length);
final float[] unrotatedCropBoundsCorners = getCornersFromRect(piece.getArea().getAreaRect());
sTempMatrix.mapPoints(unrotatedImageCorners);
sTempMatrix.mapPoints(unrotatedCropBoundsCorners);
RectF unrotatedImageRect = trapToRect(unrotatedImageCorners);
RectF unrotatedCropRect = trapToRect(unrotatedCropBoundsCorners);
float deltaLeft = unrotatedImageRect.left - unrotatedCropRect.left;
float deltaTop = unrotatedImageRect.top - unrotatedCropRect.top;
float deltaRight = unrotatedImageRect.right - unrotatedCropRect.right;
float deltaBottom = unrotatedImageRect.bottom - unrotatedCropRect.bottom;
float indents[] = new float[4];
indents[0] = (deltaLeft > 0) ? deltaLeft : 0;
indents[1] = (deltaTop > 0) ? deltaTop : 0;
indents[2] = (deltaRight < 0) ? deltaRight : 0;
indents[3] = (deltaBottom < 0) ? deltaBottom : 0;
sTempMatrix.reset();
sTempMatrix.setRotate(piece.getMatrixAngle());
sTempMatrix.mapPoints(indents);
return indents;
}
//计算包含给出点的最小矩形
public static RectF trapToRect(float[] array) {
RectF r = new RectF(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY,
Float.NEGATIVE_INFINITY);
int length = array.length;
for (int i = 1; i < length; i += 2) {
float x = round(array[i - 1] * 10) / 10.f;
float y = round(array[i] * 10) / 10.f;
r.left = (x < r.left) ? x : r.left;
r.top = (y < r.top) ? y : r.top;
r.right = (x > r.right) ? x : r.right;
r.bottom = (y > r.bottom) ? y : r.bottom;
}
r.sort();
return r;
}
public static float[] getCornersFromRect(RectF r) {
return new float[]{
r.left, r.top, r.right, r.top, r.right, r.bottom, r.left, r.bottom
};
}
public static Matrix generateMatrix(PuzzlePiece piece, float extra) {
return generateMatrix(piece.getArea(), piece.getDrawable(), extra);
}
public static Matrix generateMatrix(Area area, Drawable drawable, float extraSize) {
return generateCenterCropMatrix(area, drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), extraSize);
}
private static Matrix generateCenterCropMatrix(Area area, int width, int height,
float extraSize) {
final RectF rectF = area.getAreaRect();
Matrix matrix = new Matrix();
float offsetX = rectF.centerX() - width / 2;
float offsetY = rectF.centerY() - height / 2;
matrix.postTranslate(offsetX, offsetY);
float scale;
if (width * rectF.height() > rectF.width() * height) {
scale = (rectF.height() + extraSize) / height;
} else {
scale = (rectF.width() + extraSize) / width;
}
matrix.postScale(scale, scale, rectF.centerX(), rectF.centerY());
return matrix;
}
}

View File

@@ -0,0 +1,88 @@
package com.huantansheng.easyphotos.models.puzzle;
import android.graphics.RectF;
import java.util.ArrayList;
import java.util.List;
/**
* @author wupanjie
*/
public interface PuzzleLayout {
void setOuterBounds(RectF bounds);
void layout();
int getAreaCount();
List<Line> getOuterLines();
List<Line> getLines();
Area getOuterArea();
void update();
void reset();
Area getArea(int position);
float width();
float height();
void setPadding(float padding);
float getPadding();
float getRadian();
void setRadian(float radian);
Info generateInfo();
void setColor(int color);
int getColor();
class Info {
public static final int TYPE_STRAIGHT = 0;
public static final int TYPE_SLANT = 1;
public int type;
public ArrayList<Step> steps;
public ArrayList<LineInfo> lineInfos;
public float padding;
public float radian;
public int color;
}
class Step {
public static final int ADD_LINE = 0;
public static final int ADD_CROSS = 1;
public static final int CUT_EQUAL_PART_ONE = 2;
public static final int CUT_EQUAL_PART_TWO = 3;
public static final int CUT_SPIRAL = 4;
public int type;
public int direction;
public int position;
public int part;
public int hSize;
public int vSize;
}
class LineInfo {
public float startX;
public float startY;
public float endX;
public float endY;
public LineInfo(Line line) {
startX = line.startPoint().x;
startY = line.startPoint().y;
endX = line.endPoint().x;
endY = line.endPoint().y;
}
}
}

View File

@@ -0,0 +1,433 @@
package com.huantansheng.easyphotos.models.puzzle;
import android.animation.ValueAnimator;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Xfermode;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import static com.huantansheng.easyphotos.models.puzzle.MatrixUtils.calculateImageIndents;
import static com.huantansheng.easyphotos.models.puzzle.MatrixUtils.getMinMatrixScale;
import static com.huantansheng.easyphotos.models.puzzle.MatrixUtils.judgeIsImageContainsBorder;
/**
* @author wupanjie
*/
@SuppressWarnings("WeakerAccess")
public class PuzzlePiece {
private static Xfermode SRC_IN = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
private Drawable drawable;
private Matrix matrix;
private Matrix previousMatrix;
private Area area;
private Rect drawableBounds;
private float[] drawablePoints;
private float[] mappedDrawablePoints;
private float previousMoveX;
private float previousMoveY;
private final RectF mappedBounds;
private final PointF centerPoint;
private final PointF mappedCenterPoint;
private ValueAnimator animator;
private int duration = 300;
private Matrix tempMatrix;
PuzzlePiece(Drawable drawable, Area area, Matrix matrix) {
this.drawable = drawable;
this.area = area;
this.matrix = matrix;
this.previousMatrix = new Matrix();
this.drawableBounds = new Rect(0, 0, getWidth(), getHeight());
this.drawablePoints = new float[]{
0f, 0f, getWidth(), 0f, getWidth(), getHeight(), 0f, getHeight()
};
this.mappedDrawablePoints = new float[8];
this.mappedBounds = new RectF();
this.centerPoint = new PointF(area.centerX(), area.centerY());
this.mappedCenterPoint = new PointF();
this.animator = ValueAnimator.ofFloat(0f, 1f);
this.animator.setInterpolator(new DecelerateInterpolator());
this.tempMatrix = new Matrix();
}
void draw(Canvas canvas) {
draw(canvas, 255, true);
}
void draw(Canvas canvas, int alpha) {
draw(canvas, alpha, false);
}
private void draw(Canvas canvas, int alpha, boolean needClip) {
if (drawable instanceof BitmapDrawable) {
int saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
Paint paint = ((BitmapDrawable) drawable).getPaint();
paint.setColor(Color.WHITE);
paint.setAlpha(alpha);
if (needClip) {
canvas.drawPath(area.getAreaPath(), paint);
paint.setXfermode(SRC_IN);
}
canvas.drawBitmap(bitmap, matrix, paint);
paint.setXfermode(null);
canvas.restoreToCount(saved);
} else {
canvas.save();
if (needClip) {
canvas.clipPath(area.getAreaPath());
}
canvas.concat(matrix);
drawable.setBounds(drawableBounds);
drawable.setAlpha(alpha);
drawable.draw(canvas);
canvas.restore();
}
}
public Area getArea() {
return area;
}
public void setDrawable(Drawable drawable) {
this.drawable = drawable;
this.drawableBounds = new Rect(0, 0, getWidth(), getHeight());
this.drawablePoints = new float[]{
0f, 0f, getWidth(), 0f, getWidth(), getHeight(), 0f, getHeight()
};
}
public Drawable getDrawable() {
return drawable;
}
public int getWidth() {
return drawable.getIntrinsicWidth();
}
public int getHeight() {
return drawable.getIntrinsicHeight();
}
public boolean contains(float x, float y) {
return area.contains(x, y);
}
public boolean contains(Line line) {
return area.contains(line);
}
public Rect getDrawableBounds() {
return drawableBounds;
}
void setPreviousMoveX(float previousMoveX) {
this.previousMoveX = previousMoveX;
}
void setPreviousMoveY(float previousMoveY) {
this.previousMoveY = previousMoveY;
}
private RectF getCurrentDrawableBounds() {
matrix.mapRect(mappedBounds, new RectF(drawableBounds));
return mappedBounds;
}
private PointF getCurrentDrawableCenterPoint() {
getCurrentDrawableBounds();
mappedCenterPoint.x = mappedBounds.centerX();
mappedCenterPoint.y = mappedBounds.centerY();
return mappedCenterPoint;
}
public PointF getAreaCenterPoint() {
centerPoint.x = area.centerX();
centerPoint.y = area.centerY();
return centerPoint;
}
private float getMatrixScale() {
return MatrixUtils.getMatrixScale(matrix);
}
float getMatrixAngle() {
return MatrixUtils.getMatrixAngle(matrix);
}
float[] getCurrentDrawablePoints() {
matrix.mapPoints(mappedDrawablePoints, drawablePoints);
return mappedDrawablePoints;
}
boolean isFilledArea() {
RectF bounds = getCurrentDrawableBounds();
return !(bounds.left > area.left()
|| bounds.top > area.top()
|| bounds.right < area.right()
|| bounds.bottom < area.bottom());
}
boolean canFilledArea() {
float scale = MatrixUtils.getMatrixScale(matrix);
float minScale = getMinMatrixScale(this);
return scale >= minScale;
}
void record() {
previousMatrix.set(matrix);
}
void translate(float offsetX, float offsetY) {
matrix.set(previousMatrix);
postTranslate(offsetX, offsetY);
}
private void zoom(float scaleX, float scaleY, PointF midPoint) {
matrix.set(previousMatrix);
postScale(scaleX, scaleY, midPoint);
}
void zoomAndTranslate(float scaleX, float scaleY, PointF midPoint, float offsetX, float offsetY) {
matrix.set(previousMatrix);
postTranslate(offsetX, offsetY);
postScale(scaleX, scaleY, midPoint);
}
void set(Matrix matrix) {
this.matrix.set(matrix);
moveToFillArea(null);
}
void postTranslate(float x, float y) {
this.matrix.postTranslate(x, y);
}
void postScale(float scaleX, float scaleY, PointF midPoint) {
this.matrix.postScale(scaleX, scaleY, midPoint.x, midPoint.y);
}
void postFlipVertically() {
this.matrix.postScale(1, -1, area.centerX(), area.centerY());
}
void postFlipHorizontally() {
this.matrix.postScale(-1, 1, area.centerX(), area.centerY());
}
void postRotate(float degree) {
this.matrix.postRotate(degree, area.centerX(), area.centerY());
float minScale = getMinMatrixScale(this);
if (getMatrixScale() < minScale) {
final PointF midPoint = new PointF();
midPoint.set(getCurrentDrawableCenterPoint());
postScale(minScale / getMatrixScale(), minScale / getMatrixScale(), midPoint);
}
if (!judgeIsImageContainsBorder(this, getMatrixAngle())) {
final float[] imageIndents = calculateImageIndents(this);
float deltaX = -(imageIndents[0] + imageIndents[2]);
float deltaY = -(imageIndents[1] + imageIndents[3]);
postTranslate(deltaX, deltaY);
}
}
private void animateTranslate(final View view, final float translateX, final float translateY) {
animator.end();
animator.removeAllUpdateListeners();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float x = translateX * (float) animation.getAnimatedValue();
float y = translateY * (float) animation.getAnimatedValue();
translate(x, y);
view.invalidate();
}
});
animator.setDuration(duration);
animator.start();
}
void moveToFillArea(final View view) {
if (isFilledArea()) return;
record();
RectF rectF = getCurrentDrawableBounds();
float offsetX = 0f;
float offsetY = 0f;
if (rectF.left > area.left()) {
offsetX = area.left() - rectF.left;
}
if (rectF.top > area.top()) {
offsetY = area.top() - rectF.top;
}
if (rectF.right < area.right()) {
offsetX = area.right() - rectF.right;
}
if (rectF.bottom < area.bottom()) {
offsetY = area.bottom() - rectF.bottom;
}
if (view == null) {
postTranslate(offsetX, offsetY);
} else {
animateTranslate(view, offsetX, offsetY);
}
}
void fillArea(final View view, boolean quick) {
if (isFilledArea()) return;
record();
final float startScale = getMatrixScale();
final float endScale = getMinMatrixScale(this);
final PointF midPoint = new PointF();
midPoint.set(getCurrentDrawableCenterPoint());
tempMatrix.set(matrix);
tempMatrix.postScale(endScale / startScale, endScale / startScale, midPoint.x, midPoint.y);
RectF rectF = new RectF(drawableBounds);
tempMatrix.mapRect(rectF);
float offsetX = 0f;
float offsetY = 0f;
if (rectF.left > area.left()) {
offsetX = area.left() - rectF.left;
}
if (rectF.top > area.top()) {
offsetY = area.top() - rectF.top;
}
if (rectF.right < area.right()) {
offsetX = area.right() - rectF.right;
}
if (rectF.bottom < area.bottom()) {
offsetY = area.bottom() - rectF.bottom;
}
final float translateX = offsetX;
final float translateY = offsetY;
animator.end();
animator.removeAllUpdateListeners();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
float scale = (startScale + (endScale - startScale) * value) / startScale;
float x = translateX * value;
float y = translateY * value;
zoom(scale, scale, midPoint);
postTranslate(x, y);
view.invalidate();
}
});
if (quick) {
animator.setDuration(0);
} else {
animator.setDuration(duration);
}
animator.start();
}
void updateWith(final MotionEvent event, final Line line) {
float offsetX = (event.getX() - previousMoveX) / 2;
float offsetY = (event.getY() - previousMoveY) / 2;
if (!canFilledArea()) {
final Area area = getArea();
float deltaScale = getMinMatrixScale(this) / getMatrixScale();
postScale(deltaScale, deltaScale, area.getCenterPoint());
record();
previousMoveX = event.getX();
previousMoveY = event.getY();
}
if (line.direction() == Line.Direction.HORIZONTAL) {
translate(0, offsetY);
} else if (line.direction() == Line.Direction.VERTICAL) {
translate(offsetX, 0);
}
final RectF rectF = getCurrentDrawableBounds();
final Area area = getArea();
float moveY = 0f;
if (rectF.top > area.top()) {
moveY = area.top() - rectF.top;
}
if (rectF.bottom < area.bottom()) {
moveY = area.bottom() - rectF.bottom;
}
float moveX = 0f;
if (rectF.left > area.left()) {
moveX = area.left() - rectF.left;
}
if (rectF.right < area.right()) {
moveX = area.right() - rectF.right;
}
if (moveX != 0 || moveY != 0) {
previousMoveX = event.getX();
previousMoveY = event.getY();
postTranslate(moveX, moveY);
record();
}
}
public void setArea(Area area) {
this.area = area;
}
boolean isAnimateRunning() {
return animator.isRunning();
}
void setAnimateDuration(int duration) {
this.duration = duration;
}
}

View File

@@ -0,0 +1,93 @@
package com.huantansheng.easyphotos.models.puzzle;
import com.huantansheng.easyphotos.models.puzzle.template.slant.OneSlantLayout;
import com.huantansheng.easyphotos.models.puzzle.template.slant.SlantLayoutHelper;
import com.huantansheng.easyphotos.models.puzzle.template.slant.ThreeSlantLayout;
import com.huantansheng.easyphotos.models.puzzle.template.slant.TwoSlantLayout;
import com.huantansheng.easyphotos.models.puzzle.template.straight.EightStraightLayout;
import com.huantansheng.easyphotos.models.puzzle.template.straight.FiveStraightLayout;
import com.huantansheng.easyphotos.models.puzzle.template.straight.FourStraightLayout;
import com.huantansheng.easyphotos.models.puzzle.template.straight.NineStraightLayout;
import com.huantansheng.easyphotos.models.puzzle.template.straight.OneStraightLayout;
import com.huantansheng.easyphotos.models.puzzle.template.straight.SevenStraightLayout;
import com.huantansheng.easyphotos.models.puzzle.template.straight.SixStraightLayout;
import com.huantansheng.easyphotos.models.puzzle.template.straight.StraightLayoutHelper;
import com.huantansheng.easyphotos.models.puzzle.template.straight.ThreeStraightLayout;
import com.huantansheng.easyphotos.models.puzzle.template.straight.TwoStraightLayout;
import java.util.ArrayList;
import java.util.List;
/**
* @author wupanjie
*/
public class PuzzleUtils {
private 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.huantansheng.easyphotos.models.puzzle;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import androidx.core.content.ContextCompat;
import com.huantansheng.easyphotos.R;
import java.util.ArrayList;
import java.util.List;
/**
* @author wupanjie
*/
public class PuzzleView extends View {
private static final String TAG = "SlantPuzzleView";
private enum ActionMode {
NONE,
DRAG,
ZOOM,
MOVE,
SWAP
}
private ActionMode currentMode = ActionMode.NONE;
private List<PuzzlePiece> puzzlePieces = new ArrayList<>();
private List<PuzzlePiece> needChangePieces = new ArrayList<>();
private PuzzleLayout puzzleLayout;
private RectF bounds;
private int lineSize;
private int duration;
private Line handlingLine;
private PuzzlePiece handlingPiece;
private PuzzlePiece replacePiece;
private PuzzlePiece previousHandlingPiece;
private Paint linePaint;
private Paint selectedAreaPaint;
private Paint handleBarPaint;
private float downX;
private float downY;
private float previousDistance;
private PointF midPoint;
private boolean needDrawLine;
private boolean needDrawOuterLine;
private boolean touchEnable = true;
private int lineColor;
private int selectedLineColor;
private int handleBarColor;
private float piecePadding;
private float pieceRadian;
private boolean needResetPieceMatrix = true;
private OnPieceSelectedListener onPieceSelectedListener;
private Runnable switchToSwapAction = new Runnable() {
@Override
public void run() {
currentMode = ActionMode.SWAP;
invalidate();
}
};
public PuzzleView(Context context) {
this(context, null);
}
public PuzzleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PuzzleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
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.huantansheng.easyphotos.models.puzzle;
import android.content.Context;
import android.util.AttributeSet;
/**
* @author wupanjie
*/
public class SquarePuzzleView extends PuzzleView {
public SquarePuzzleView(Context context) {
super(context);
}
public SquarePuzzleView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SquarePuzzleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
int length = width > height ? height : width;
setMeasuredDimension(length, length);
}
}

View File

@@ -0,0 +1,34 @@
package com.huantansheng.easyphotos.models.puzzle.slant;
import android.graphics.PointF;
/**
* 两条线的交点
*
* @author wupanjie
*/
class CrossoverPointF extends PointF {
SlantLine horizontal;
SlantLine vertical;
CrossoverPointF() {
}
CrossoverPointF(float x, float y) {
this.x = x;
this.y = y;
}
CrossoverPointF(SlantLine horizontal, SlantLine vertical) {
this.horizontal = horizontal;
this.vertical = vertical;
}
void update() {
if (horizontal == null || vertical == null) {
return;
}
SlantUtils.intersectionOfLines(this, horizontal, vertical);
}
}

View File

@@ -0,0 +1,294 @@
package com.huantansheng.easyphotos.models.puzzle.slant;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import com.huantansheng.easyphotos.models.puzzle.Area;
import com.huantansheng.easyphotos.models.puzzle.Line;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.distance;
import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.getPoint;
import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.intersectionOfLines;
/**
* @author wupanjie
*/
class SlantArea implements Area {
SlantLine lineLeft;
SlantLine lineTop;
SlantLine lineRight;
SlantLine lineBottom;
CrossoverPointF leftTop;
CrossoverPointF leftBottom;
CrossoverPointF rightTop;
CrossoverPointF rightBottom;
private PointF tempPoint;
private float paddingLeft;
private float paddingTop;
private float paddingRight;
private float paddingBottom;
private float radian;
private Path areaPath = new Path();
private RectF areaRect = new RectF();
private PointF[] handleBarPoints = new PointF[2];
SlantArea() {
handleBarPoints[0] = new PointF();
handleBarPoints[1] = new PointF();
leftTop = new CrossoverPointF();
leftBottom = new CrossoverPointF();
rightTop = new CrossoverPointF();
rightBottom = new CrossoverPointF();
tempPoint = new PointF();
}
SlantArea(SlantArea src) {
this();
this.lineLeft = src.lineLeft;
this.lineTop = src.lineTop;
this.lineRight = src.lineRight;
this.lineBottom = src.lineBottom;
this.leftTop = src.leftTop;
this.leftBottom = src.leftBottom;
this.rightTop = src.rightTop;
this.rightBottom = src.rightBottom;
updateCornerPoints();
}
@Override
public float left() {
return Math.min(leftTop.x, leftBottom.x) + paddingLeft;
}
@Override
public float top() {
return Math.min(leftTop.y, rightTop.y) + paddingTop;
}
@Override
public float right() {
return Math.max(rightTop.x, rightBottom.x) - paddingRight;
}
@Override
public float bottom() {
return Math.max(leftBottom.y, rightBottom.y) - paddingBottom;
}
@Override
public float centerX() {
return (left() + right()) / 2;
}
@Override
public float centerY() {
return (top() + bottom()) / 2;
}
@Override
public float width() {
return right() - left();
}
@Override
public float height() {
return bottom() - top();
}
@Override
public PointF getCenterPoint() {
return new PointF(centerX(), centerY());
}
public Path getAreaPath() {
areaPath.reset();
if (radian > 0) {
float tempRatio = radian / distance(leftTop, leftBottom);
getPoint(tempPoint, leftTop, leftBottom, Line.Direction.VERTICAL, tempRatio);
tempPoint.offset(paddingLeft, paddingTop);
areaPath.moveTo(tempPoint.x, tempPoint.y);
tempRatio = radian / distance(leftTop, rightTop);
getPoint(tempPoint, leftTop, rightTop, Line.Direction.HORIZONTAL, tempRatio);
tempPoint.offset(paddingLeft, paddingTop);
areaPath.quadTo(leftTop.x + paddingLeft, leftTop.y + paddingTop, tempPoint.x, tempPoint.y);
tempRatio = 1 - tempRatio;
getPoint(tempPoint, leftTop, rightTop, Line.Direction.HORIZONTAL, tempRatio);
tempPoint.offset(-paddingRight, paddingTop);
areaPath.lineTo(tempPoint.x, tempPoint.y);
tempRatio = radian / distance(rightTop, rightBottom);
getPoint(tempPoint, rightTop, rightBottom, Line.Direction.VERTICAL, tempRatio);
tempPoint.offset(-paddingRight, paddingTop);
areaPath.quadTo(rightTop.x - paddingLeft, rightTop.y + paddingTop, tempPoint.x, tempPoint.y);
tempRatio = 1 - tempRatio;
getPoint(tempPoint, rightTop, rightBottom, Line.Direction.VERTICAL, tempRatio);
tempPoint.offset(-paddingRight, -paddingBottom);
areaPath.lineTo(tempPoint.x, tempPoint.y);
tempRatio = 1 - radian / distance(leftBottom, rightBottom);
getPoint(tempPoint, leftBottom, rightBottom, Line.Direction.HORIZONTAL, tempRatio);
tempPoint.offset(-paddingRight, -paddingBottom);
areaPath.quadTo(rightBottom.x - paddingRight, rightBottom.y - paddingTop, tempPoint.x, tempPoint.y);
tempRatio = 1 - tempRatio;
getPoint(tempPoint, leftBottom, rightBottom, Line.Direction.HORIZONTAL, tempRatio);
tempPoint.offset(paddingLeft, -paddingBottom);
areaPath.lineTo(tempPoint.x, tempPoint.y);
tempRatio = 1 - radian / distance(leftTop, leftBottom);
getPoint(tempPoint, leftTop, leftBottom, Line.Direction.VERTICAL, tempRatio);
tempPoint.offset(paddingLeft, -paddingBottom);
areaPath.quadTo(leftBottom.x + paddingLeft, leftBottom.y - paddingBottom, tempPoint.x, tempPoint.y);
tempRatio = 1 - tempRatio;
getPoint(tempPoint, leftTop, leftBottom, Line.Direction.VERTICAL, tempRatio);
tempPoint.offset(paddingLeft, paddingTop);
areaPath.lineTo(tempPoint.x, tempPoint.y);
} else {
areaPath.moveTo(leftTop.x + paddingLeft, leftTop.y + paddingTop);
areaPath.lineTo(rightTop.x - paddingRight, rightTop.y + paddingTop);
areaPath.lineTo(rightBottom.x - paddingRight, rightBottom.y - paddingBottom);
areaPath.lineTo(leftBottom.x + paddingLeft, leftBottom.y - paddingBottom);
areaPath.lineTo(leftTop.x + paddingLeft, leftTop.y + paddingTop);
}
return areaPath;
}
@Override
public RectF getAreaRect() {
areaRect.set(left(), top(), right(), bottom());
return areaRect;
}
public boolean contains(float x, float y) {
return SlantUtils.contains(this, x, y);
}
@Override
public boolean contains(Line line) {
return lineLeft == line || lineTop == line || lineRight == line || lineBottom == line;
}
@Override
public boolean contains(PointF point) {
return contains(point.x, point.y);
}
@Override
public List<Line> getLines() {
return Arrays.asList((Line) lineLeft, lineTop, lineRight, lineBottom);
}
@Override
public PointF[] getHandleBarPoints(Line line) {
if (line == lineLeft) {
getPoint(handleBarPoints[0], leftTop, leftBottom, line.direction(), 0.25f);
getPoint(handleBarPoints[1], leftTop, leftBottom, line.direction(), 0.75f);
handleBarPoints[0].offset(paddingLeft, 0);
handleBarPoints[1].offset(paddingLeft, 0);
} else if (line == lineTop) {
getPoint(handleBarPoints[0], leftTop, rightTop, line.direction(), 0.25f);
getPoint(handleBarPoints[1], leftTop, rightTop, line.direction(), 0.75f);
handleBarPoints[0].offset(0, paddingTop);
handleBarPoints[1].offset(0, paddingTop);
} else if (line == lineRight) {
getPoint(handleBarPoints[0], rightTop, rightBottom, line.direction(), 0.25f);
getPoint(handleBarPoints[1], rightTop, rightBottom, line.direction(), 0.75f);
handleBarPoints[0].offset(-paddingRight, 0);
handleBarPoints[1].offset(-paddingRight, 0);
} else if (line == lineBottom) {
getPoint(handleBarPoints[0], leftBottom, rightBottom, line.direction(), 0.25f);
getPoint(handleBarPoints[1], leftBottom, rightBottom, line.direction(), 0.75f);
handleBarPoints[0].offset(0, -paddingBottom);
handleBarPoints[1].offset(0, -paddingBottom);
}
return handleBarPoints;
}
@Override
public float radian() {
return radian;
}
@Override
public void setRadian(float radian) {
this.radian = radian;
}
@Override
public float getPaddingLeft() {
return paddingLeft;
}
@Override
public float getPaddingTop() {
return paddingTop;
}
@Override
public float getPaddingRight() {
return paddingRight;
}
@Override
public float getPaddingBottom() {
return paddingBottom;
}
@Override
public void setPadding(float padding) {
setPadding(padding, padding, padding, padding);
}
@Override
public void setPadding(float paddingLeft, float paddingTop, float paddingRight, float paddingBottom) {
this.paddingLeft = paddingLeft;
this.paddingTop = paddingTop;
this.paddingRight = paddingRight;
this.paddingBottom = paddingBottom;
}
void updateCornerPoints() {
intersectionOfLines(leftTop, lineLeft, lineTop);
intersectionOfLines(leftBottom, lineLeft, lineBottom);
intersectionOfLines(rightTop, lineRight, lineTop);
intersectionOfLines(rightBottom, lineRight, lineBottom);
}
static class AreaComparator implements Comparator<SlantArea> {
@Override
public int compare(SlantArea one, SlantArea two) {
if (one.leftTop.y < two.leftTop.y) {
return -1;
} else if (one.leftTop.y == two.leftTop.y) {
if (one.leftTop.x < two.leftTop.x) {
return -1;
} else {
return 1;
}
} else {
return 1;
}
}
}
}

View File

@@ -0,0 +1,173 @@
package com.huantansheng.easyphotos.models.puzzle.slant;
import android.graphics.PointF;
import com.huantansheng.easyphotos.models.puzzle.Line;
import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.intersectionOfLines;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.Math.pow;
import static java.lang.Math.sqrt;
/**
* 分为两种斜线,横谢线和竖线线
* 横斜线-->start为左边的点end为右边的点
* 竖斜线-->start为上面的点end为下面的点
*
* @author wupanjie
*/
class SlantLine implements Line {
CrossoverPointF start;
CrossoverPointF end;
// 移动前的点
private PointF previousStart = new PointF();
private PointF previousEnd = new PointF();
public final 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.huantansheng.easyphotos.models.puzzle.slant;
import android.graphics.RectF;
import android.util.Pair;
import com.huantansheng.easyphotos.models.puzzle.Area;
import com.huantansheng.easyphotos.models.puzzle.Line;
import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.createLine;
import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.cutAreaCross;
import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.cutAreaWith;
/**
* 斜线布局,外围区域为一矩形
*
* @author wupanjie
*/
public abstract class SlantPuzzleLayout implements PuzzleLayout {
private RectF bounds;
private SlantArea outerArea;
private List<Line> outerLines = new ArrayList<>(4);
private List<SlantArea> areas = new ArrayList<>();
private List<Line> lines = new ArrayList<>();
private float padding;
private float radian;
private int color;
private Comparator<SlantArea> areaComparator = new SlantArea.AreaComparator();
private ArrayList<Step> steps = new ArrayList<>();
protected SlantPuzzleLayout() {
}
@Override
public void setOuterBounds(RectF bounds) {
reset();
this.bounds = bounds;
CrossoverPointF leftTop = new CrossoverPointF(bounds.left, bounds.top);
CrossoverPointF rightTop = new CrossoverPointF(bounds.right, bounds.top);
CrossoverPointF leftBottom = new CrossoverPointF(bounds.left, bounds.bottom);
CrossoverPointF rightBottom = new CrossoverPointF(bounds.right, bounds.bottom);
SlantLine lineLeft = new SlantLine(leftTop, leftBottom, Line.Direction.VERTICAL);
SlantLine lineTop = new SlantLine(leftTop, rightTop, Line.Direction.HORIZONTAL);
SlantLine lineRight = new SlantLine(rightTop, rightBottom, Line.Direction.VERTICAL);
SlantLine lineBottom = new SlantLine(leftBottom, rightBottom, Line.Direction.HORIZONTAL);
outerLines.clear();
outerLines.add(lineLeft);
outerLines.add(lineTop);
outerLines.add(lineRight);
outerLines.add(lineBottom);
outerArea = new SlantArea();
outerArea.lineLeft = lineLeft;
outerArea.lineTop = lineTop;
outerArea.lineRight = lineRight;
outerArea.lineBottom = lineBottom;
outerArea.updateCornerPoints();
areas.clear();
areas.add(outerArea);
}
public abstract void layout();
private void updateLineLimit() {
int size = lines.size();
for (int i = 0; i < size; i++) {
Line line = lines.get(i);
updateUpperLine(line);
updateLowerLine(line);
}
}
private void updateLowerLine(final Line line) {
int size = lines.size();
for (int i = 0; i < size; i++) {
Line l = lines.get(i);
if (l.direction() != line.direction()) {
continue;
}
if (l.attachStartLine() != line.attachStartLine()
|| l.attachEndLine() != line.attachEndLine()) {
continue;
}
if (l.direction() == Line.Direction.HORIZONTAL) {
if (l.minY() > line.lowerLine().maxY() && l.maxY() < line.minY()) {
line.setLowerLine(l);
}
} else {
if (l.minX() > line.lowerLine().maxX() && l.maxX() < line.minX()) {
line.setLowerLine(l);
}
}
}
}
private void updateUpperLine(final Line line) {
int size = lines.size();
for (int i = 0; i < size; i++) {
Line l = lines.get(i);
if (l.direction() != line.direction()) {
continue;
}
if (l.attachStartLine() != line.attachStartLine()
|| l.attachEndLine() != line.attachEndLine()) {
continue;
}
if (l.direction() == Line.Direction.HORIZONTAL) {
if (l.maxY() < line.upperLine().minY() && l.minY() > line.maxY()) {
line.setUpperLine(l);
}
} else {
if (l.maxX() < line.upperLine().minX() && l.minX() > line.maxX()) {
line.setUpperLine(l);
}
}
}
}
@Override
public int getAreaCount() {
return areas.size();
}
@Override
public void reset() {
lines.clear();
areas.clear();
areas.add(outerArea);
steps.clear();
}
@Override
public void update() {
int size = lines.size();
for (int i = 0; i < size; i++) {
lines.get(i).update(width(), height());
}
int areasSize = areas.size();
for (int i = 0; i < areasSize; i++) {
areas.get(i).updateCornerPoints();
}
}
@Override
public float width() {
return outerArea == null ? 0 : outerArea.width();
}
@Override
public float height() {
return outerArea == null ? 0 : outerArea.height();
}
private void sortAreas() {
Collections.sort(areas, areaComparator);
}
@Override
public List<Line> getOuterLines() {
return outerLines;
}
@Override
public Area getOuterArea() {
return outerArea;
}
public List<SlantArea> getAreas() {
return areas;
}
@Override
public SlantArea getArea(int position) {
return areas.get(position);
}
@Override
public List<Line> getLines() {
return lines;
}
@Override
public void setPadding(float padding) {
this.padding = padding;
for (Area area : areas) {
area.setPadding(padding);
}
outerArea.lineLeft.startPoint().set(bounds.left + padding, bounds.top + padding);
outerArea.lineLeft.endPoint().set(bounds.left + padding, bounds.bottom - padding);
outerArea.lineRight.startPoint().set(bounds.right - padding, bounds.top + padding);
outerArea.lineRight.endPoint().set(bounds.right - padding, bounds.bottom - padding);
outerArea.updateCornerPoints();
update();
}
@Override
public float getPadding() {
return padding;
}
@Override
public float getRadian() {
return radian;
}
@Override
public void setRadian(float radian) {
this.radian = radian;
for (Area area : areas) {
area.setRadian(radian);
}
}
@Override
public int getColor() {
return color;
}
@Override
public void setColor(int color) {
this.color = color;
}
protected List<SlantArea> addLine(int position, Line.Direction direction, float ratio) {
return addLine(position, direction, ratio, ratio);
}
protected List<SlantArea> addLine(int position, Line.Direction direction, float startRatio,
float endRatio) {
SlantArea area = areas.get(position);
areas.remove(area);
SlantLine line = createLine(area, direction, startRatio, endRatio);
lines.add(line);
List<SlantArea> increasedAreas = cutAreaWith(area, line);
areas.addAll(increasedAreas);
updateLineLimit();
sortAreas();
Step step = new Step();
step.type = Step.ADD_LINE;
step.direction = direction == Line.Direction.HORIZONTAL ? 0 : 1;
step.position = position;
steps.add(step);
return increasedAreas;
}
protected void addCross(int position, float startRatio1, float endRatio1,
float startRatio2, float endRatio2) {
SlantArea area = areas.get(position);
areas.remove(area);
SlantLine horizontal = createLine(area, Line.Direction.HORIZONTAL, startRatio1, endRatio1);
SlantLine vertical = createLine(area, Line.Direction.VERTICAL, startRatio2, endRatio2);
lines.add(horizontal);
lines.add(vertical);
List<SlantArea> increasedAreas = cutAreaCross(area, horizontal, vertical);
areas.addAll(increasedAreas);
sortAreas();
Step step = new Step();
step.type = Step.ADD_CROSS;
step.position = position;
steps.add(step);
}
protected void cutArea(int position, int hSize, int vSize) {
SlantArea area = areas.get(position);
areas.remove(area);
Pair<List<SlantLine>, List<SlantArea>> spilt =
cutAreaWith(area, hSize, vSize);
lines.addAll(spilt.first);
areas.addAll(spilt.second);
updateLineLimit();
sortAreas();
Step step = new Step();
step.type = Step.CUT_EQUAL_PART_ONE;
step.position = position;
step.hSize = hSize;
step.vSize = vSize;
steps.add(step);
}
@Override
public Info generateInfo() {
Info info = new Info();
info.type = Info.TYPE_SLANT;
info.padding = padding;
info.radian = radian;
info.color = color;
info.steps = steps;
ArrayList<LineInfo> lineInfos = new ArrayList<>();
for (Line line : lines) {
LineInfo lineInfo = new LineInfo(line);
lineInfos.add(lineInfo);
}
info.lineInfos = lineInfos;
return info;
}
}

View File

@@ -0,0 +1,482 @@
package com.huantansheng.easyphotos.models.puzzle.slant;
import android.graphics.PointF;
import android.util.Pair;
import com.huantansheng.easyphotos.models.puzzle.Line;
import java.util.ArrayList;
import java.util.List;
/**
* @author wupanjie
*/
class SlantUtils {
private static final PointF A = new PointF();
private static final PointF B = new PointF();
private static final PointF C = new PointF();
private static final PointF D = new PointF();
private static final PointF AB = new PointF();
private static final PointF AM = new PointF();
private static final PointF BC = new PointF();
private static final PointF BM = new PointF();
private static final PointF CD = new PointF();
private static final PointF CM = new PointF();
private static final PointF DA = new PointF();
private static final PointF DM = new PointF();
private SlantUtils() {
//no instance
}
static float distance(PointF one, PointF two) {
return (float) Math.sqrt(Math.pow(two.x - one.x, 2) + Math.pow(two.y - one.y, 2));
}
static List<SlantArea> cutAreaWith(SlantArea area, SlantLine line) {
List<SlantArea> areas = new ArrayList<>();
SlantArea area1 = new SlantArea(area);
SlantArea area2 = new SlantArea(area);
if (line.direction == Line.Direction.HORIZONTAL) {
area1.lineBottom = line;
area1.leftBottom = line.start;
area1.rightBottom = line.end;
area2.lineTop = line;
area2.leftTop = line.start;
area2.rightTop = line.end;
} else {
area1.lineRight = line;
area1.rightTop = line.start;
area1.rightBottom = line.end;
area2.lineLeft = line;
area2.leftTop = line.start;
area2.leftBottom = line.end;
}
areas.add(area1);
areas.add(area2);
return areas;
}
static SlantLine createLine(SlantArea area, Line.Direction direction, float startratio,
float endratio) {
SlantLine line = new SlantLine(direction);
if (direction == Line.Direction.HORIZONTAL) {
line.start = getPoint(area.leftTop, area.leftBottom, Line.Direction.VERTICAL, startratio);
line.end = getPoint(area.rightTop, area.rightBottom, Line.Direction.VERTICAL, endratio);
line.attachLineStart = area.lineLeft;
line.attachLineEnd = area.lineRight;
line.upperLine = area.lineBottom;
line.lowerLine = area.lineTop;
} else {
line.start = getPoint(area.leftTop, area.rightTop, Line.Direction.HORIZONTAL, startratio);
line.end = getPoint(area.leftBottom, area.rightBottom, Line.Direction.HORIZONTAL, endratio);
line.attachLineStart = area.lineTop;
line.attachLineEnd = area.lineBottom;
line.upperLine = area.lineRight;
line.lowerLine = area.lineLeft;
}
return line;
}
static Pair<List<SlantLine>, List<SlantArea>> cutAreaWith(final SlantArea area,
final int horizontalSize, final int verticalSize) {
List<SlantArea> areaList = new ArrayList<>();
List<SlantLine> horizontalLines = new ArrayList<>(horizontalSize);
SlantArea restArea = new SlantArea(area);
for (int i = horizontalSize + 1; i > 1; i--) {
SlantLine horizontalLine =
createLine(restArea, Line.Direction.HORIZONTAL, (float) (i - 1) / i - 0.025f, (float) (i - 1) / i + 0.025f);
horizontalLines.add(horizontalLine);
restArea.lineBottom = horizontalLine;
restArea.leftBottom = horizontalLine.start;
restArea.rightBottom = horizontalLine.end;
}
List<SlantLine> verticalLines = new ArrayList<>();
restArea = new SlantArea(area);
for (int i = verticalSize + 1; i > 1; i--) {
SlantLine verticalLine =
createLine(restArea, Line.Direction.VERTICAL, (float) (i - 1) / i + 0.025f, (float) (i - 1) / i - 0.025f);
verticalLines.add(verticalLine);
SlantArea spiltArea = new SlantArea(restArea);
spiltArea.lineLeft = verticalLine;
spiltArea.leftTop = verticalLine.start;
spiltArea.leftBottom = verticalLine.end;
int size = horizontalLines.size();
for (int j = 0; j <= size; j++) {
SlantArea blockArea = new SlantArea(spiltArea);
if (j == 0) {
blockArea.lineTop = horizontalLines.get(j);
} else if (j == size) {
blockArea.lineBottom = horizontalLines.get(j - 1);
CrossoverPointF leftBottom =
new CrossoverPointF(blockArea.lineBottom, blockArea.lineLeft);
intersectionOfLines(leftBottom, blockArea.lineBottom, blockArea.lineLeft);
CrossoverPointF rightBottom =
new CrossoverPointF(blockArea.lineBottom, blockArea.lineRight);
intersectionOfLines(rightBottom, blockArea.lineBottom, blockArea.lineRight);
blockArea.leftBottom = leftBottom;
blockArea.rightBottom = rightBottom;
} else {
blockArea.lineTop = horizontalLines.get(j);
blockArea.lineBottom = horizontalLines.get(j - 1);
}
CrossoverPointF leftTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineLeft);
intersectionOfLines(leftTop, blockArea.lineTop, blockArea.lineLeft);
CrossoverPointF rightTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineRight);
intersectionOfLines(rightTop, blockArea.lineTop, blockArea.lineRight);
blockArea.leftTop = leftTop;
blockArea.rightTop = rightTop;
areaList.add(blockArea);
}
restArea.lineRight = verticalLine;
restArea.rightTop = verticalLine.start;
restArea.rightBottom = verticalLine.end;
}
int size = horizontalLines.size();
for (int j = 0; j <= size; j++) {
SlantArea blockArea = new SlantArea(restArea);
if (j == 0) {
blockArea.lineTop = horizontalLines.get(j);
} else if (j == size) {
blockArea.lineBottom = horizontalLines.get(j - 1);
CrossoverPointF leftBottom = new CrossoverPointF(blockArea.lineBottom, blockArea.lineLeft);
intersectionOfLines(leftBottom, blockArea.lineBottom, blockArea.lineLeft);
CrossoverPointF rightBottom =
new CrossoverPointF(blockArea.lineBottom, blockArea.lineRight);
intersectionOfLines(rightBottom, blockArea.lineBottom, blockArea.lineRight);
blockArea.leftBottom = leftBottom;
blockArea.rightBottom = rightBottom;
} else {
blockArea.lineTop = horizontalLines.get(j);
blockArea.lineBottom = horizontalLines.get(j - 1);
}
CrossoverPointF leftTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineLeft);
intersectionOfLines(leftTop, blockArea.lineTop, blockArea.lineLeft);
CrossoverPointF rightTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineRight);
intersectionOfLines(rightTop, blockArea.lineTop, blockArea.lineRight);
blockArea.leftTop = leftTop;
blockArea.rightTop = rightTop;
areaList.add(blockArea);
}
List<SlantLine> lines = new ArrayList<>();
lines.addAll(horizontalLines);
lines.addAll(verticalLines);
return new Pair<>(lines, areaList);
}
static List<SlantArea> cutAreaCross(final SlantArea area, final SlantLine horizontal,
final SlantLine vertical) {
List<SlantArea> list = new ArrayList<>();
CrossoverPointF crossoverPoint = new CrossoverPointF(horizontal, vertical);
intersectionOfLines(crossoverPoint, horizontal, vertical);
SlantArea one = new SlantArea(area);
one.lineBottom = horizontal;
one.lineRight = vertical;
one.rightTop = vertical.start;
one.rightBottom = crossoverPoint;
one.leftBottom = horizontal.start;
list.add(one);
SlantArea two = new SlantArea(area);
two.lineBottom = horizontal;
two.lineLeft = vertical;
two.leftTop = vertical.start;
two.rightBottom = horizontal.end;
two.leftBottom = crossoverPoint;
list.add(two);
SlantArea three = new SlantArea(area);
three.lineTop = horizontal;
three.lineRight = vertical;
three.leftTop = horizontal.start;
three.rightTop = crossoverPoint;
three.rightBottom = vertical.end;
list.add(three);
SlantArea four = new SlantArea(area);
four.lineTop = horizontal;
four.lineLeft = vertical;
four.leftTop = crossoverPoint;
four.rightTop = horizontal.end;
four.leftBottom = vertical.end;
list.add(four);
return list;
}
private static CrossoverPointF getPoint(final PointF start, final PointF end,
final Line.Direction direction, float ratio) {
CrossoverPointF point = new CrossoverPointF();
getPoint(point, start, end, direction, ratio);
return point;
}
static void getPoint(final PointF dst, final PointF start, final PointF end,
final Line.Direction direction, float ratio) {
float deltaY = Math.abs(start.y - end.y);
float deltaX = Math.abs(start.x - end.x);
float maxY = Math.max(start.y, end.y);
float minY = Math.min(start.y, end.y);
float maxX = Math.max(start.x, end.x);
float minX = Math.min(start.x, end.x);
if (direction == Line.Direction.HORIZONTAL) {
dst.x = minX + deltaX * ratio;
if (start.y < end.y) {
dst.y = minY + ratio * deltaY;
} else {
dst.y = maxY - ratio * deltaY;
}
} else {
dst.y = minY + deltaY * ratio;
if (start.x < end.x) {
dst.x = minX + ratio * deltaX;
} else {
dst.x = maxX - ratio * deltaX;
}
}
}
// 叉乘
private static float crossProduct(final PointF a, final PointF b) {
return a.x * b.y - b.x * a.y;
}
/**
* 判断一个斜线区域是否包含(x,y)点
*
* @param area 斜线区域
* @param x x
* @param y y
* @return 是否包含
*/
static boolean contains(SlantArea area, float x, float y) {
AB.x = area.rightTop.x - area.leftTop.x;
AB.y = area.rightTop.y - area.leftTop.y;
AM.x = x - area.leftTop.x;
AM.y = y - area.leftTop.y;
BC.x = area.rightBottom.x - area.rightTop.x;
BC.y = area.rightBottom.y - area.rightTop.y;
BM.x = x - area.rightTop.x;
BM.y = y - area.rightTop.y;
CD.x = area.leftBottom.x - area.rightBottom.x;
CD.y = area.leftBottom.y - area.rightBottom.y;
CM.x = x - area.rightBottom.x;
CM.y = y - area.rightBottom.y;
DA.x = area.leftTop.x - area.leftBottom.x;
DA.y = area.leftTop.y - area.leftBottom.y;
DM.x = x - area.leftBottom.x;
DM.y = y - area.leftBottom.y;
return crossProduct(AB, AM) > 0
&& crossProduct(BC, BM) > 0
&& crossProduct(CD, CM) > 0
&& crossProduct(DA, DM) > 0;
}
static boolean contains(SlantLine line, float x, float y, float extra) {
PointF start = line.start;
PointF end = line.end;
if (line.direction == Line.Direction.VERTICAL) {
A.x = start.x - extra;
A.y = start.y;
B.x = start.x + extra;
B.y = start.y;
C.x = end.x + extra;
C.y = end.y;
D.x = end.x - extra;
D.y = end.y;
} else {
A.x = start.x;
A.y = start.y - extra;
B.x = end.x;
B.y = end.y - extra;
C.x = end.x;
C.y = end.y + extra;
D.x = start.x;
D.y = start.y + extra;
}
AB.x = B.x - A.x;
AB.y = B.y - A.y;
AM.x = x - A.x;
AM.y = y - A.y;
BC.x = C.x - B.x;
BC.y = C.y - B.y;
BM.x = x - B.x;
BM.y = y - B.y;
CD.x = D.x - C.x;
CD.y = D.y - C.y;
CM.x = x - C.x;
CM.y = y - C.y;
DA.x = A.x - D.x;
DA.y = A.y - D.y;
DM.x = x - D.x;
DM.y = y - D.y;
return crossProduct(AB, AM) > 0
&& crossProduct(BC, BM) > 0
&& crossProduct(CD, CM) > 0
&& crossProduct(DA, DM) > 0;
}
/**
* 计算两线的交点
*
* @param dst 计算出的交点
* @param lineOne 线一
* @param lineTwo 线二
*/
static void intersectionOfLines(final CrossoverPointF dst, final SlantLine lineOne,
final SlantLine lineTwo) {
dst.horizontal = lineOne;
dst.vertical = lineTwo;
if (isParallel(lineOne, lineTwo)) {
dst.set(0, 0);
return;
}
if (isHorizontalLine(lineOne) && isVerticalLine(lineTwo)) {
dst.set(lineTwo.start.x, lineOne.start.y);
return;
}
if (isVerticalLine(lineOne) && isHorizontalLine(lineTwo)) {
dst.set(lineOne.start.x, lineTwo.start.y);
return;
}
if (isHorizontalLine(lineOne) && !isVerticalLine(lineTwo)) {
float k = calculateSlope(lineTwo);
float b = calculateVerticalIntercept(lineTwo);
dst.y = lineOne.start.y;
dst.x = (dst.y - b) / k;
return;
}
if (isVerticalLine(lineOne) && !isHorizontalLine(lineTwo)) {
float k = calculateSlope(lineTwo);
float b = calculateVerticalIntercept(lineTwo);
dst.x = lineOne.start.x;
dst.y = k * dst.x + b;
return;
}
if (isHorizontalLine(lineTwo) && !isVerticalLine(lineOne)) {
float k = calculateSlope(lineOne);
float b = calculateVerticalIntercept(lineOne);
dst.y = lineTwo.start.y;
dst.x = (dst.y - b) / k;
return;
}
if (isVerticalLine(lineTwo) && !isHorizontalLine(lineOne)) {
float k = calculateSlope(lineOne);
float b = calculateVerticalIntercept(lineOne);
dst.x = lineTwo.start.x;
dst.y = k * dst.x + b;
return;
}
final float k1 = calculateSlope(lineOne);
final float b1 = calculateVerticalIntercept(lineOne);
final float k2 = calculateSlope(lineTwo);
final float b2 = calculateVerticalIntercept(lineTwo);
dst.x = (b2 - b1) / (k1 - k2);
dst.y = dst.x * k1 + b1;
}
private static boolean isHorizontalLine(SlantLine line) {
return line.start.y == line.end.y;
}
private static boolean isVerticalLine(SlantLine line) {
return line.start.x == line.end.x;
}
/**
* 判断两条线是否平行
*
* @param lineOne 第一条
* @param lineTwo 第二条
* @return 是否平行
*/
private static boolean isParallel(final SlantLine lineOne, final SlantLine lineTwo) {
return calculateSlope(lineOne) == calculateSlope(lineTwo);
}
/**
* 计算线的斜率
*
* @param line 线
* @return 线的斜率
*/
static float calculateSlope(final SlantLine line) {
if (isHorizontalLine(line)) {
return 0f;
} else if (isVerticalLine(line)) {
return Float.POSITIVE_INFINITY;
} else {
return (line.start.y - line.end.y) / (line.start.x - line.end.x);
}
}
/**
* 计算纵截距
*
* @param line 线
* @return 纵截距
*/
private static float calculateVerticalIntercept(final SlantLine line) {
if (isHorizontalLine(line)) {
return line.start.y;
} else if (isVerticalLine(line)) {
return Float.POSITIVE_INFINITY;
} else {
float k = calculateSlope(line);
return line.start.y - k * line.start.x;
}
}
}

View File

@@ -0,0 +1,231 @@
package com.huantansheng.easyphotos.models.puzzle.straight;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import com.huantansheng.easyphotos.models.puzzle.Area;
import com.huantansheng.easyphotos.models.puzzle.Line;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* @author wupanjie
*/
class StraightArea implements Area {
StraightLine lineLeft;
StraightLine lineTop;
StraightLine lineRight;
StraightLine lineBottom;
private Path areaPath = new Path();
private RectF areaRect = new RectF();
private PointF[] handleBarPoints = new PointF[2];
private float paddingLeft;
private float paddingTop;
private float paddingRight;
private float paddingBottom;
private float radian;
StraightArea() {
handleBarPoints[0] = new PointF();
handleBarPoints[1] = new PointF();
}
StraightArea(RectF baseRect) {
this();
setBaseRect(baseRect);
}
private void setBaseRect(RectF baseRect) {
PointF one = new PointF(baseRect.left, baseRect.top);
PointF two = new PointF(baseRect.right, baseRect.top);
PointF three = new PointF(baseRect.left, baseRect.bottom);
PointF four = new PointF(baseRect.right, baseRect.bottom);
lineLeft = new StraightLine(one, three);
lineTop = new StraightLine(one, two);
lineRight = new StraightLine(two, four);
lineBottom = new StraightLine(three, four);
}
StraightArea(StraightArea src) {
this.lineLeft = src.lineLeft;
this.lineTop = src.lineTop;
this.lineRight = src.lineRight;
this.lineBottom = src.lineBottom;
handleBarPoints[0] = new PointF();
handleBarPoints[1] = new PointF();
}
@Override
public float left() {
return lineLeft.minX() + paddingLeft;
}
@Override
public float top() {
return lineTop.minY() + paddingTop;
}
@Override
public float right() {
return lineRight.maxX() - paddingRight;
}
@Override
public float bottom() {
return lineBottom.maxY() - paddingBottom;
}
@Override
public float centerX() {
return (left() + right()) / 2;
}
@Override
public float centerY() {
return (top() + bottom()) / 2;
}
@Override
public float width() {
return right() - left();
}
@Override
public float height() {
return bottom() - top();
}
@Override
public PointF getCenterPoint() {
return new PointF(centerX(), centerY());
}
@Override
public boolean contains(PointF point) {
return contains(point.x, point.y);
}
@Override
public boolean contains(float x, float y) {
return getAreaRect().contains(x, y);
}
@Override
public boolean contains(Line line) {
return lineLeft == line || lineTop == line || lineRight == line || lineBottom == line;
}
@Override
public Path getAreaPath() {
areaPath.reset();
areaPath.addRoundRect(getAreaRect(), radian, radian, Path.Direction.CCW);
//areaPath.addRect(getAreaRect(), Path.Direction.CCW);
return areaPath;
}
@Override
public RectF getAreaRect() {
areaRect.set(left(), top(), right(), bottom());
return areaRect;
}
@Override
public List<Line> getLines() {
return Arrays.asList((Line) lineLeft, lineTop, lineRight, lineBottom);
}
@Override
public PointF[] getHandleBarPoints(Line line) {
if (line == lineLeft) {
handleBarPoints[0].x = left();
handleBarPoints[0].y = top() + height() / 4;
handleBarPoints[1].x = left();
handleBarPoints[1].y = top() + height() / 4 * 3;
} else if (line == lineTop) {
handleBarPoints[0].x = left() + width() / 4;
handleBarPoints[0].y = top();
handleBarPoints[1].x = left() + width() / 4 * 3;
handleBarPoints[1].y = top();
} else if (line == lineRight) {
handleBarPoints[0].x = right();
handleBarPoints[0].y = top() + height() / 4;
handleBarPoints[1].x = right();
handleBarPoints[1].y = top() + height() / 4 * 3;
} else if (line == lineBottom) {
handleBarPoints[0].x = left() + width() / 4;
handleBarPoints[0].y = bottom();
handleBarPoints[1].x = left() + width() / 4 * 3;
handleBarPoints[1].y = bottom();
}
return handleBarPoints;
}
@Override
public float radian() {
return radian;
}
@Override
public void setRadian(float radian) {
this.radian = radian;
}
@Override
public float getPaddingLeft() {
return paddingLeft;
}
@Override
public float getPaddingTop() {
return paddingTop;
}
@Override
public float getPaddingRight() {
return paddingRight;
}
@Override
public float getPaddingBottom() {
return paddingBottom;
}
@Override
public void setPadding(float padding) {
setPadding(padding, padding, padding, padding);
}
@Override
public void setPadding(float paddingLeft, float paddingTop, float paddingRight,
float paddingBottom) {
this.paddingLeft = paddingLeft;
this.paddingTop = paddingTop;
this.paddingRight = paddingRight;
this.paddingBottom = paddingBottom;
}
static class AreaComparator implements Comparator<StraightArea> {
@Override
public int compare(StraightArea lhs, StraightArea rhs) {
if (lhs.top() < rhs.top()) {
return -1;
} else if (lhs.top() == rhs.top()) {
if (lhs.left() < rhs.left()) {
return -1;
} else {
return 1;
}
} else {
return 1;
}
}
}
}

View File

@@ -0,0 +1,215 @@
package com.huantansheng.easyphotos.models.puzzle.straight;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.Log;
import com.huantansheng.easyphotos.models.puzzle.Line;
import static java.lang.Math.max;
import static java.lang.Math.min;
/**
* @author wupanjie
*/
class StraightLine implements Line {
private PointF start;
private PointF end;
private PointF previousStart = new PointF();
private PointF previousEnd = new PointF();
public 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.huantansheng.easyphotos.models.puzzle.straight;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.Pair;
import com.huantansheng.easyphotos.models.puzzle.Area;
import com.huantansheng.easyphotos.models.puzzle.Line;
import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import static com.huantansheng.easyphotos.models.puzzle.straight.StraightUtils.createLine;
import static com.huantansheng.easyphotos.models.puzzle.straight.StraightUtils.cutAreaCross;
import static com.huantansheng.easyphotos.models.puzzle.straight.StraightUtils.cutAreaSpiral;
/**
* @author wupanjie
*/
public abstract class StraightPuzzleLayout implements PuzzleLayout {
private RectF bounds;
private StraightArea outerArea;
private List<StraightArea> areas = new ArrayList<>();
private List<Line> lines = new ArrayList<>();
private List<Line> outerLines = new ArrayList<>(4);
private float padding;
private float radian;
private int color;
private Comparator<StraightArea> areaComparator = new StraightArea.AreaComparator();
private ArrayList<Step> steps = new ArrayList<>();
protected StraightPuzzleLayout() {
}
@Override
public void setOuterBounds(RectF bounds) {
reset();
this.bounds = bounds;
PointF one = new PointF(bounds.left, bounds.top);
PointF two = new PointF(bounds.right, bounds.top);
PointF three = new PointF(bounds.left, bounds.bottom);
PointF four = new PointF(bounds.right, bounds.bottom);
StraightLine lineLeft = new StraightLine(one, three);
StraightLine lineTop = new StraightLine(one, two);
StraightLine lineRight = new StraightLine(two, four);
StraightLine lineBottom = new StraightLine(three, four);
outerLines.clear();
outerLines.add(lineLeft);
outerLines.add(lineTop);
outerLines.add(lineRight);
outerLines.add(lineBottom);
outerArea = new StraightArea();
outerArea.lineLeft = lineLeft;
outerArea.lineTop = lineTop;
outerArea.lineRight = lineRight;
outerArea.lineBottom = lineBottom;
areas.clear();
areas.add(outerArea);
}
@Override
public abstract void layout();
@Override
public int getAreaCount() {
return areas.size();
}
@Override
public List<Line> getOuterLines() {
return outerLines;
}
@Override
public List<Line> getLines() {
return lines;
}
@Override
public void update() {
for (Line line : lines) {
line.update(width(), height());
}
}
@Override
public float width() {
return outerArea == null ? 0 : outerArea.width();
}
@Override
public float height() {
return outerArea == null ? 0 : outerArea.height();
}
@Override
public void reset() {
lines.clear();
areas.clear();
areas.add(outerArea);
steps.clear();
}
@Override
public Area getArea(int position) {
return areas.get(position);
}
@Override
public StraightArea getOuterArea() {
return outerArea;
}
@Override
public void setPadding(float padding) {
this.padding = padding;
for (Area area : areas) {
area.setPadding(padding);
}
outerArea.lineLeft.startPoint().set(bounds.left + padding, bounds.top + padding);
outerArea.lineLeft.endPoint().set(bounds.left + padding, bounds.bottom - padding);
outerArea.lineRight.startPoint().set(bounds.right - padding, bounds.top + padding);
outerArea.lineRight.endPoint().set(bounds.right - padding, bounds.bottom - padding);
update();
}
@Override
public float getPadding() {
return padding;
}
protected void addLine(int position, Line.Direction direction, float ratio) {
StraightArea area = areas.get(position);
addLine(area, direction, ratio);
Step step = new Step();
step.type = Step.ADD_LINE;
step.direction = direction == Line.Direction.HORIZONTAL ? 0 : 1;
step.position = position;
steps.add(step);
}
private List<StraightArea> addLine(StraightArea area, Line.Direction direction, float ratio) {
areas.remove(area);
StraightLine line = createLine(area, direction, ratio);
lines.add(line);
List<StraightArea> increasedArea = StraightUtils.cutArea(area, line);
areas.addAll(increasedArea);
updateLineLimit();
sortAreas();
return increasedArea;
}
protected void cutAreaEqualPart(int position, int part, Line.Direction direction) {
StraightArea temp = areas.get(position);
for (int i = part; i > 1; i--) {
temp = addLine(temp, direction, (float) (i - 1) / i).get(0);
}
Step step = new Step();
step.type = Step.CUT_EQUAL_PART_TWO;
step.part = part;
step.position = position;
step.direction = direction == Line.Direction.HORIZONTAL ? 0 : 1;
steps.add(step);
}
protected void addCross(int position, float ratio) {
addCross(position, ratio, ratio);
}
protected void addCross(int position, float horizontalRatio, float verticalRatio) {
StraightArea area = areas.get(position);
areas.remove(area);
StraightLine horizontal = createLine(area, Line.Direction.HORIZONTAL, horizontalRatio);
StraightLine vertical = createLine(area, Line.Direction.VERTICAL, verticalRatio);
lines.add(horizontal);
lines.add(vertical);
List<StraightArea> newAreas = cutAreaCross(area, horizontal, vertical);
areas.addAll(newAreas);
updateLineLimit();
sortAreas();
Step step = new Step();
step.type = Step.ADD_CROSS;
step.position = position;
steps.add(step);
}
protected void cutAreaEqualPart(int position, int hSize, int vSize) {
StraightArea area = areas.get(position);
areas.remove(area);
Pair<List<StraightLine>, List<StraightArea>> increased =
StraightUtils.cutArea(area, hSize, vSize);
List<StraightLine> newLines = increased.first;
List<StraightArea> newAreas = increased.second;
lines.addAll(newLines);
areas.addAll(newAreas);
updateLineLimit();
sortAreas();
Step step = new Step();
step.type = Step.CUT_EQUAL_PART_ONE;
step.position = position;
step.hSize = hSize;
step.vSize = vSize;
steps.add(step);
}
protected void cutSpiral(int position) {
StraightArea area = areas.get(position);
areas.remove(area);
Pair<List<StraightLine>, List<StraightArea>> spilt = cutAreaSpiral(area);
lines.addAll(spilt.first);
areas.addAll(spilt.second);
updateLineLimit();
sortAreas();
Step step = new Step();
step.type = Step.CUT_SPIRAL;
step.position = position;
steps.add(step);
}
private void sortAreas() {
Collections.sort(areas, areaComparator);
}
private void updateLineLimit() {
int size = lines.size();
for (int i = 0; i < size; i++) {
Line line = lines.get(i);
updateUpperLine(line);
updateLowerLine(line);
}
}
private void updateLowerLine(final Line line) {
int size = lines.size();
for (int i = 0; i < size; i++) {
Line l = lines.get(i);
if (l == line) {
continue;
}
if (l.direction() != line.direction()) {
continue;
}
if (l.direction() == Line.Direction.HORIZONTAL) {
if (l.maxX() <= line.minX() || line.maxX() <= l.minX()) continue;
if (l.minY() > line.lowerLine().maxY() && l.maxY() < line.minY()) {
line.setLowerLine(l);
}
} else {
if (l.maxY() <= line.minY() || line.maxY() <= l.minY()) continue;
if (l.minX() > line.lowerLine().maxX() && l.maxX() < line.minX()) {
line.setLowerLine(l);
}
}
}
}
private void updateUpperLine(final Line line) {
int size = lines.size();
for (int i = 0; i < size; i++) {
Line l = lines.get(i);
if (l == line) {
continue;
}
if (l.direction() != line.direction()) {
continue;
}
if (l.direction() == Line.Direction.HORIZONTAL) {
if (l.maxX() <= line.minX() || line.maxX() <= l.minX()) continue;
if (l.maxY() < line.upperLine().minY() && l.minY() > line.maxY()) {
line.setUpperLine(l);
}
} else {
if (l.maxY() <= line.minY() || line.maxY() <= l.minY()) continue;
if (l.maxX() < line.upperLine().minX() && l.minX() > line.maxX()) {
line.setUpperLine(l);
}
}
}
}
@Override
public float getRadian() {
return radian;
}
@Override
public void setRadian(float radian) {
this.radian = radian;
for (Area area : areas) {
area.setRadian(radian);
}
}
@Override
public int getColor() {
return color;
}
@Override
public void setColor(int color) {
this.color = color;
}
@Override
public Info generateInfo() {
Info info = new Info();
info.type = Info.TYPE_STRAIGHT;
info.padding = padding;
info.radian = radian;
info.color = color;
info.steps = steps;
ArrayList<LineInfo> lineInfos = new ArrayList<>();
for (Line line : lines) {
LineInfo lineInfo = new LineInfo(line);
lineInfos.add(lineInfo);
}
info.lineInfos = lineInfos;
return info;
}
}

View File

@@ -0,0 +1,236 @@
package com.huantansheng.easyphotos.models.puzzle.straight;
import android.graphics.PointF;
import android.util.Pair;
import com.huantansheng.easyphotos.models.puzzle.Line;
import java.util.ArrayList;
import java.util.List;
/**
* @author wupanjie
*/
class StraightUtils {
static StraightLine createLine(final StraightArea area, final Line.Direction direction,
final float ratio) {
PointF one = new PointF();
PointF two = new PointF();
if (direction == Line.Direction.HORIZONTAL) {
one.x = area.left();
one.y = area.height() * ratio + area.top();
two.x = area.right();
two.y = area.height() * ratio + area.top();
} else if (direction == Line.Direction.VERTICAL) {
one.x = area.width() * ratio + area.left();
one.y = area.top();
two.x = area.width() * ratio + area.left();
two.y = area.bottom();
}
StraightLine line = new StraightLine(one, two);
if (direction == Line.Direction.HORIZONTAL) {
line.attachLineStart = area.lineLeft;
line.attachLineEnd = area.lineRight;
line.setUpperLine(area.lineBottom);
line.setLowerLine(area.lineTop);
} else if (direction == Line.Direction.VERTICAL) {
line.attachLineStart = area.lineTop;
line.attachLineEnd = area.lineBottom;
line.setUpperLine(area.lineRight);
line.setLowerLine(area.lineLeft);
}
return line;
}
static List<StraightArea> cutArea(final StraightArea area, final StraightLine line) {
List<StraightArea> list = new ArrayList<>();
if (line.direction() == Line.Direction.HORIZONTAL) {
StraightArea one = new StraightArea(area);
one.lineBottom = line;
list.add(one);
StraightArea two = new StraightArea(area);
two.lineTop = line;
list.add(two);
} else if (line.direction() == Line.Direction.VERTICAL) {
StraightArea one = new StraightArea(area);
one.lineRight = line;
list.add(one);
StraightArea two = new StraightArea(area);
two.lineLeft = line;
list.add(two);
}
return list;
}
static Pair<List<StraightLine>, List<StraightArea>> cutArea(final StraightArea area,
final int horizontalSize,
final int verticalSize) {
List<StraightArea> areaList = new ArrayList<>();
List<StraightLine> horizontalLines = new ArrayList<>(horizontalSize);
StraightArea restArea = new StraightArea(area);
for (int i = horizontalSize + 1; i > 1; i--) {
StraightLine horizontalLine =
createLine(restArea, Line.Direction.HORIZONTAL, (float) (i - 1) / i);
horizontalLines.add(horizontalLine);
restArea.lineBottom = horizontalLine;
}
List<StraightLine> verticalLines = new ArrayList<>();
restArea = new StraightArea(area);
for (int i = verticalSize + 1; i > 1; i--) {
StraightLine verticalLine =
createLine(restArea, Line.Direction.VERTICAL, (float) (i - 1) / i);
verticalLines.add(verticalLine);
StraightArea spiltArea = new StraightArea(restArea);
spiltArea.lineLeft = verticalLine;
int size = horizontalLines.size();
for (int j = 0; j <= size; j++) {
StraightArea blockArea = new StraightArea(spiltArea);
if (j == 0) {
blockArea.lineTop = horizontalLines.get(j);
} else if (j == size) {
blockArea.lineBottom = horizontalLines.get(j - 1);
} else {
blockArea.lineTop = horizontalLines.get(j);
blockArea.lineBottom = horizontalLines.get(j - 1);
}
areaList.add(blockArea);
}
restArea.lineRight = verticalLine;
}
int size = horizontalLines.size();
for (int j = 0; j <= size; j++) {
StraightArea blockArea = new StraightArea(restArea);
if (j == 0) {
blockArea.lineTop = horizontalLines.get(j);
} else if (j == horizontalLines.size()) {
blockArea.lineBottom = horizontalLines.get(j - 1);
} else {
blockArea.lineTop = horizontalLines.get(j);
blockArea.lineBottom = horizontalLines.get(j - 1);
}
areaList.add(blockArea);
}
List<StraightLine> lines = new ArrayList<>();
lines.addAll(horizontalLines);
lines.addAll(verticalLines);
return new Pair<>(lines, areaList);
}
static List<StraightArea> cutAreaCross(final StraightArea area, final StraightLine horizontal,
final StraightLine vertical) {
List<StraightArea> list = new ArrayList<>();
StraightArea one = new StraightArea(area);
one.lineBottom = horizontal;
one.lineRight = vertical;
list.add(one);
StraightArea two = new StraightArea(area);
two.lineBottom = horizontal;
two.lineLeft = vertical;
list.add(two);
StraightArea three = new StraightArea(area);
three.lineTop = horizontal;
three.lineRight = vertical;
list.add(three);
StraightArea four = new StraightArea(area);
four.lineTop = horizontal;
four.lineLeft = vertical;
list.add(four);
return list;
}
static Pair<List<StraightLine>, List<StraightArea>> cutAreaSpiral(final StraightArea area) {
List<StraightLine> lines = new ArrayList<>();
List<StraightArea> areas = new ArrayList<>();
float width = area.width();
float height = area.height();
float left = area.left();
float top = area.top();
PointF one = new PointF(left, top + height / 3);
PointF two = new PointF(left + width / 3 * 2, top);
PointF three = new PointF(left + width, top + height / 3 * 2);
PointF four = new PointF(left + width / 3, top + height);
PointF five = new PointF(left + width / 3, top + height / 3);
PointF six = new PointF(left + width / 3 * 2, top + height / 3);
PointF seven = new PointF(left + width / 3 * 2, top + height / 3 * 2);
PointF eight = new PointF(left + width / 3, top + height / 3 * 2);
StraightLine l1 = new StraightLine(one, six);
StraightLine l2 = new StraightLine(two, seven);
StraightLine l3 = new StraightLine(eight, three);
StraightLine l4 = new StraightLine(five, four);
l1.setAttachLineStart(area.lineLeft);
l1.setAttachLineEnd(l2);
l1.setLowerLine(area.lineTop);
l1.setUpperLine(l3);
l2.setAttachLineStart(area.lineTop);
l2.setAttachLineEnd(l3);
l2.setLowerLine(l4);
l2.setUpperLine(area.lineRight);
l3.setAttachLineStart(l4);
l3.setAttachLineEnd(area.lineRight);
l3.setLowerLine(l1);
l3.setUpperLine(area.lineBottom);
l4.setAttachLineStart(l1);
l4.setAttachLineEnd(area.lineBottom);
l4.setLowerLine(area.lineLeft);
l4.setUpperLine(l2);
lines.add(l1);
lines.add(l2);
lines.add(l3);
lines.add(l4);
StraightArea b1 = new StraightArea(area);
b1.lineRight = l2;
b1.lineBottom = l1;
areas.add(b1);
StraightArea b2 = new StraightArea(area);
b2.lineLeft = l2;
b2.lineBottom = l3;
areas.add(b2);
StraightArea b3 = new StraightArea(area);
b3.lineRight = l4;
b3.lineTop = l1;
areas.add(b3);
StraightArea b4 = new StraightArea(area);
b4.lineTop = l1;
b4.lineRight = l2;
b4.lineLeft = l4;
b4.lineBottom = l3;
areas.add(b4);
StraightArea b5 = new StraightArea(area);
b5.lineLeft = l4;
b5.lineTop = l3;
areas.add(b5);
return new Pair<>(lines, areas);
}
}

View File

@@ -0,0 +1,33 @@
package com.huantansheng.easyphotos.models.puzzle.template.slant;
import android.util.Log;
import com.huantansheng.easyphotos.models.puzzle.slant.SlantPuzzleLayout;
/**
* @author wupanjie
*/
public abstract class NumberSlantLayout extends SlantPuzzleLayout {
static final String TAG = "NumberSlantLayout";
protected int theme;
public NumberSlantLayout(int theme) {
if (theme >= getThemeCount()) {
Log.e(TAG, "NumberSlantLayout: the most theme count is "
+ getThemeCount()
+ " ,you should let theme from 0 to "
+ (getThemeCount() - 1)
+ " .");
}
this.theme = theme;
}
public abstract int getThemeCount();
public int getTheme() {
return theme;
}
}

View File

@@ -0,0 +1,36 @@
package com.huantansheng.easyphotos.models.puzzle.template.slant;
import com.huantansheng.easyphotos.models.puzzle.Line;
/**
* @author wupanjie
*/
public class OneSlantLayout extends NumberSlantLayout {
public OneSlantLayout(int theme) {
super(theme);
}
@Override
public int getThemeCount() {
return 4;
}
@Override
public void layout() {
switch (theme) {
case 0:
addLine(0, Line.Direction.HORIZONTAL, 0.56f, 0.44f);
break;
case 1:
addLine(0, Line.Direction.VERTICAL, 0.56f, 0.44f);
break;
case 2:
addCross(0, 0.56f, 0.44f, 0.56f, 0.44f);
break;
case 3:
cutArea(0, 1, 2);
break;
}
}
}

View File

@@ -0,0 +1,69 @@
package com.huantansheng.easyphotos.models.puzzle.template.slant;
import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout;
import java.util.ArrayList;
import java.util.List;
/**
* @author wupanjie
*/
public class SlantLayoutHelper {
private SlantLayoutHelper() {
}
public static List<PuzzleLayout> getAllThemeLayout(int pieceCount) {
List<PuzzleLayout> puzzleLayouts = new ArrayList<>();
switch (pieceCount) {
case 1:
for (int i = 0; i < 4; i++) {
puzzleLayouts.add(new OneSlantLayout(i));
}
break;
case 2:
for (int i = 0; i < 2; i++) {
puzzleLayouts.add(new TwoSlantLayout(i));
}
break;
case 3:
for (int i = 0; i < 6; i++) {
puzzleLayouts.add(new ThreeSlantLayout(i));
}
break;
//case 4:
// for (int i = 0; i < 8; i++) {
// puzzleLayouts.add(new FourStraightLayout(i));
// }
// break;
//case 5:
// for (int i = 0; i < 17; i++) {
// puzzleLayouts.add(new FiveStraightLayout(i));
// }
// break;
//case 6:
// for (int i = 0; i < 12; i++) {
// puzzleLayouts.add(new SixStraightLayout(i));
// }
// break;
//case 7:
// for (int i = 0; i < 9; i++) {
// puzzleLayouts.add(new SevenStraightLayout(i));
// }
// break;
//case 8:
// for (int i = 0; i < 11; i++) {
// puzzleLayouts.add(new EightStraightLayout(i));
// }
// break;
//case 9:
// for (int i = 0; i < 8; i++) {
// puzzleLayouts.add(new NineStraightLayout(i));
// }
// break;
}
return puzzleLayouts;
}
}

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