From e8f7eca1affa71446c195bbb835fe89d92bbcc8e Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 25 Oct 2023 16:56:05 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8Dgoogle=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E6=97=B6=E5=AF=BC=E8=87=B4=E7=9A=84=E7=A9=BA?= =?UTF-8?q?=E6=8C=87=E9=92=88=E5=BC=82=E5=B8=B8=20fix:=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E6=88=BF=E9=97=B4=E4=B8=8A=E9=BA=A6=E6=97=B6=E6=9D=83=E9=99=90?= =?UTF-8?q?=E7=94=B3=E8=AF=B7=E7=9A=84=E5=89=8D=E7=BD=AE=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../erban/avroom/fragment/BaseRoomFragment.kt | 3 + .../widget/dialog/CommonPopupDialog.java | 4 +- .../yizhuan/erban/ui/pay/BillingManager.java | 2 +- library/build.gradle | 7 + .../java/com/chuhai/core/LifecycleCleared.kt | 62 +++ .../core/component/SuperBottomSheetDialog.kt | 54 +++ .../java/com/chuhai/utils/AppUtils.java | 403 ++++++++++++++++++ .../java/com/chuhai/utils/ServiceTime.kt | 25 ++ .../chuhai/utils/ShapeViewOutlineProvider.kt | 42 ++ .../java/com/chuhai/utils/UiUtils.kt | 84 ++++ .../java/com/chuhai/utils/ktx/ContextKtx.kt | 70 +++ .../java/com/chuhai/utils/ktx/EditTextKtx.kt | 106 +++++ .../java/com/chuhai/utils/ktx/ResourcesKtx.kt | 194 +++++++++ .../java/com/chuhai/utils/ktx/UiKtx.kt | 51 +++ .../java/com/chuhai/utils/ktx/ViewKtx.kt | 192 +++++++++ library/src/module_utils/res/values/ids.xml | 5 + .../src/module_utils/res/values/strings.xml | 3 + 17 files changed, 1304 insertions(+), 3 deletions(-) create mode 100644 library/src/module_core/java/com/chuhai/core/LifecycleCleared.kt create mode 100644 library/src/module_core/java/com/chuhai/core/component/SuperBottomSheetDialog.kt create mode 100644 library/src/module_utils/java/com/chuhai/utils/AppUtils.java create mode 100644 library/src/module_utils/java/com/chuhai/utils/ServiceTime.kt create mode 100644 library/src/module_utils/java/com/chuhai/utils/ShapeViewOutlineProvider.kt create mode 100644 library/src/module_utils/java/com/chuhai/utils/UiUtils.kt create mode 100644 library/src/module_utils/java/com/chuhai/utils/ktx/ContextKtx.kt create mode 100644 library/src/module_utils/java/com/chuhai/utils/ktx/EditTextKtx.kt create mode 100644 library/src/module_utils/java/com/chuhai/utils/ktx/ResourcesKtx.kt create mode 100644 library/src/module_utils/java/com/chuhai/utils/ktx/UiKtx.kt create mode 100644 library/src/module_utils/java/com/chuhai/utils/ktx/ViewKtx.kt create mode 100644 library/src/module_utils/res/values/ids.xml create mode 100644 library/src/module_utils/res/values/strings.xml diff --git a/app/src/main/java/com/yizhuan/erban/avroom/fragment/BaseRoomFragment.kt b/app/src/main/java/com/yizhuan/erban/avroom/fragment/BaseRoomFragment.kt index 49951dc4e..0c691aa36 100644 --- a/app/src/main/java/com/yizhuan/erban/avroom/fragment/BaseRoomFragment.kt +++ b/app/src/main/java/com/yizhuan/erban/avroom/fragment/BaseRoomFragment.kt @@ -1021,6 +1021,9 @@ open class BaseRoomFragment?> : @SuppressLint("CheckResult") override fun toUpMicroPhone(micPosition: Int, currentUid: String, b: Boolean) { + if (!lifecycle.currentState.isAtLeast(androidx.lifecycle.Lifecycle.State.CREATED)) { + return + } if (AvRoomDataManager.get().isSelfGamePlaying) { SingleToastUtil.showToast("遊戲中不可以換麥!") return diff --git a/app/src/main/java/com/yizhuan/erban/common/widget/dialog/CommonPopupDialog.java b/app/src/main/java/com/yizhuan/erban/common/widget/dialog/CommonPopupDialog.java index 214f0df2f..6bc123d13 100644 --- a/app/src/main/java/com/yizhuan/erban/common/widget/dialog/CommonPopupDialog.java +++ b/app/src/main/java/com/yizhuan/erban/common/widget/dialog/CommonPopupDialog.java @@ -13,8 +13,8 @@ import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.TextView; +import com.chuhai.core.component.SuperBottomSheetDialog; import com.google.android.material.bottomsheet.BottomSheetBehavior; -import com.google.android.material.bottomsheet.BottomSheetDialog; import com.yizhuan.erban.R; import com.yizhuan.erban.common.util.Utils; import com.yizhuan.erban.ui.widget.ButtonItem; @@ -24,7 +24,7 @@ import java.util.List; /** * @author xiaoyu */ -public class CommonPopupDialog extends BottomSheetDialog implements OnClickListener { +public class CommonPopupDialog extends SuperBottomSheetDialog implements OnClickListener { private static final int BUTTON_ITEM_ID = 135798642; private int mId; diff --git a/app/src/main/java/com/yizhuan/erban/ui/pay/BillingManager.java b/app/src/main/java/com/yizhuan/erban/ui/pay/BillingManager.java index 3593ac543..ee3169a39 100644 --- a/app/src/main/java/com/yizhuan/erban/ui/pay/BillingManager.java +++ b/app/src/main/java/com/yizhuan/erban/ui/pay/BillingManager.java @@ -107,7 +107,7 @@ public class BillingManager implements PurchasesUpdatedListener { /*更新商品*/ @Override public void onPurchasesUpdated(BillingResult billingResult, List purchases) { - Log.i(TAG, "billingResult.getResponseCode()==" + billingResult.getResponseCode() + ",purchases.size()==" + purchases.size()); + Log.i(TAG, "billingResult.getResponseCode()==" + billingResult.getResponseCode()); purchaseList.clear(); if (billingResult.getResponseCode() == BillingResponseCode.OK) { for (Purchase purchase : purchases) { diff --git a/library/build.gradle b/library/build.gradle index 49eb74e47..c29605115 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -35,6 +35,8 @@ android { 'src/module_luban/java', 'src/module_easyphoto/java', 'src/module_common/java', + 'src/module_utils/java', + 'src/module_core/java', ] @@ -43,6 +45,8 @@ android { 'src/module_easypermission/res', 'src/module_easyphoto/res', 'src/module_common/res', + 'src/module_utils/res', + 'src/module_core/res', ] @@ -144,6 +148,9 @@ dependencies { api 'com.facebook.android:facebook-android-sdk:16.2.0' api 'com.facebook.android:facebook-login:16.2.0' + // 网络请求chrome数据调试 + api 'com.facebook.stetho:stetho:1.5.1' + api 'com.facebook.stetho:stetho-okhttp3:1.5.1' } repositories { mavenCentral() diff --git a/library/src/module_core/java/com/chuhai/core/LifecycleCleared.kt b/library/src/module_core/java/com/chuhai/core/LifecycleCleared.kt new file mode 100644 index 000000000..c9a5391f5 --- /dev/null +++ b/library/src/module_core/java/com/chuhai/core/LifecycleCleared.kt @@ -0,0 +1,62 @@ +import androidx.lifecycle.* + +/** + * Created by Max on 2023/10/24 15:11 + * Desc:跟随目标生命周期销毁 + **/ +interface LifecycleCleared : LifecycleEventObserver { + + /** + * 是否启用 + */ + fun isEnabledLifecycleClear(): Boolean { + return true + } + + /** + * 获取监听的目标生命周期 + */ + abstract fun getTargetLifecycle(): Lifecycle? + + /** + * 目标生命周期已销毁:执行清除资源操作 + */ + abstract fun onTargetCleared() + + /** + * 获取要执行清理的事件 + */ + fun getClearEvent(): Lifecycle.Event? { + return Lifecycle.Event.ON_DESTROY + } + + /** + * 绑定生命周期 + */ + fun bindLifecycleClear() { + if (!isEnabledLifecycleClear()) { + return + } + getTargetLifecycle()?.addObserver(this) + } + + /** + * 取消绑定生命周期(如果实现类是自己主动销毁的,需要主动调下本方法) + */ + fun unBindLifecycleClear() { + if (!isEnabledLifecycleClear()) { + return + } + getTargetLifecycle()?.removeObserver(this) + } + + override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { + if (!isEnabledLifecycleClear()) { + return + } + if (getClearEvent() == event) { + unBindLifecycleClear() + onTargetCleared() + } + } +} \ No newline at end of file diff --git a/library/src/module_core/java/com/chuhai/core/component/SuperBottomSheetDialog.kt b/library/src/module_core/java/com/chuhai/core/component/SuperBottomSheetDialog.kt new file mode 100644 index 000000000..9435cf31b --- /dev/null +++ b/library/src/module_core/java/com/chuhai/core/component/SuperBottomSheetDialog.kt @@ -0,0 +1,54 @@ +package com.chuhai.core.component + +import LifecycleCleared +import android.content.Context +import android.content.DialogInterface +import androidx.lifecycle.Lifecycle +import com.chuhai.utils.ktx.asLifecycle +import com.google.android.material.bottomsheet.BottomSheetDialog + + +/** + * Created by Max on 2023/10/24 15:11 + * Desc:BottomSheetDialog + */ +open class SuperBottomSheetDialog : BottomSheetDialog, LifecycleCleared { + + constructor(context: Context) : super(context) { + init() + } + + constructor(context: Context, theme: Int) : super(context, theme) { + init() + } + + constructor( + context: Context, + cancelable: Boolean, + cancelListener: DialogInterface.OnCancelListener? + ) : super(context, cancelable, cancelListener) { + init() + } + + protected open fun init() { + + } + + override fun getTargetLifecycle(): Lifecycle? { + return context.asLifecycle() + } + + override fun onTargetCleared() { + dismiss() + } + + override fun show() { + super.show() + bindLifecycleClear() + } + + override fun dismiss() { + super.dismiss() + unBindLifecycleClear() + } +} \ No newline at end of file diff --git a/library/src/module_utils/java/com/chuhai/utils/AppUtils.java b/library/src/module_utils/java/com/chuhai/utils/AppUtils.java new file mode 100644 index 000000000..c232c3605 --- /dev/null +++ b/library/src/module_utils/java/com/chuhai/utils/AppUtils.java @@ -0,0 +1,403 @@ +package com.chuhai.utils; + +import android.annotation.SuppressLint; +import android.app.ActivityManager; +import android.app.Application; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.location.LocationManager; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.text.TextUtils; +import android.view.View; +import android.view.Window; +import android.view.inputmethod.InputMethodManager; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + *
+ *     author:
+ *                                      ___           ___           ___         ___
+ *         _____                       /  /\         /__/\         /__/|       /  /\
+ *        /  /::\                     /  /::\        \  \:\       |  |:|      /  /:/
+ *       /  /:/\:\    ___     ___    /  /:/\:\        \  \:\      |  |:|     /__/::\
+ *      /  /:/~/::\  /__/\   /  /\  /  /:/~/::\   _____\__\:\   __|  |:|     \__\/\:\
+ *     /__/:/ /:/\:| \  \:\ /  /:/ /__/:/ /:/\:\ /__/::::::::\ /__/\_|:|____    \  \:\
+ *     \  \:\/:/~/:/  \  \:\  /:/  \  \:\/:/__\/ \  \:\~~\~~\/ \  \:\/:::::/     \__\:\
+ *      \  \::/ /:/    \  \:\/:/    \  \::/       \  \:\  ~~~   \  \::/~~~~      /  /:/
+ *       \  \:\/:/      \  \::/      \  \:\        \  \:\        \  \:\         /__/:/
+ *        \  \::/        \__\/        \  \:\        \  \:\        \  \:\        \__\/
+ *         \__\/                       \__\/         \__\/         \__\/
+ *     blog  : http://blankj.com
+ *     time  : 16/12/08
+ *     desc  : utils about initialization
+ * 
+ */ +public final class AppUtils { + + private static final ExecutorService UTIL_POOL = Executors.newFixedThreadPool(3); + private static final Handler UTIL_HANDLER = new Handler(Looper.getMainLooper()); + + @SuppressLint("StaticFieldLeak") + private static Application sApplication; + + + private AppUtils() { + throw new UnsupportedOperationException("u can't instantiate me..."); + } + + /** + * Init utils. + *

Init it in the class of Application.

+ * + * @param context context + */ + public static void init(final Context context) { + if (context == null) { + init(getApplicationByReflect()); + return; + } + init((Application) context.getApplicationContext()); + } + + /** + * Init utils. + *

Init it in the class of Application.

+ * + * @param app application + */ + public static void init(final Application app) { + if (sApplication == null) { + if (app == null) { + sApplication = getApplicationByReflect(); + } else { + sApplication = app; + } + } else { + sApplication = app; + } + } + + /** + * Return the context of Application object. + * + * @return the context of Application object + */ + public static Application getApp() { + if (sApplication != null) return sApplication; + Application app = getApplicationByReflect(); + init(app); + return app; + } + + + public static String getPackageName(Context context) { + return context.getPackageName(); + } + + /** + * 获取版本名 + * + * @param noSuffix 是否去掉后缀 (如:-debug、-test) + */ + public static String getVersionName(boolean noSuffix) { + PackageInfo packageInfo = getPackageInfo(getApp()); + if (packageInfo != null) { + String versionName = packageInfo.versionName; + if (noSuffix && versionName != null) { + int index = versionName.indexOf("-"); + if (index >= 0) { + return versionName.substring(0, index); + } + } + return versionName; + } + return ""; + } + + //版本号 + public static int getVersionCode() { + PackageInfo packageInfo = getPackageInfo(getApp()); + if (packageInfo != null) { + return packageInfo.versionCode; + } + return 0; + } + + /** + * 比较版本 + * 1 = 大于当前版本 + * 0 = 版本一样 + * -1 = 当前版本大于更新版本 + */ + public static int compareVersionNames(String newVersionName) { + try { + if (TextUtils.isEmpty(newVersionName)) { + return -1; + } + int res = 0; + String currentVersionName = getVersionName(true); + if (currentVersionName.equals(newVersionName)) { + return 0; + } + + String[] oldNumbers = currentVersionName.split("\\."); + String[] newNumbers = newVersionName.split("\\."); + + // To avoid IndexOutOfBounds + int minIndex = Math.min(oldNumbers.length, newNumbers.length); + + for (int i = 0; i < minIndex; i++) { + int oldVersionPart = Integer.parseInt(oldNumbers[i]); + int newVersionPart = Integer.parseInt(newNumbers[i]); + + if (oldVersionPart < newVersionPart) { + res = 1; + break; + } else if (oldVersionPart > newVersionPart) { + res = -1; + break; + } + } + + // If versions are the same so far, but they have different length... + if (res == 0 && oldNumbers.length != newNumbers.length) { + res = (oldNumbers.length > newNumbers.length) ? -1 : 1; + } + + return res; + } catch (Exception e) { + return -1; + } + } + + private static PackageInfo getPackageInfo(Context context) { + PackageInfo packageInfo; + try { + PackageManager pm = context.getPackageManager(); + packageInfo = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_CONFIGURATIONS); + return packageInfo; + } catch (Exception e) { + return null; + } + } + + static Task doAsync(final Task task) { + UTIL_POOL.execute(task); + return task; + } + + public static void runOnUiThread(final Runnable runnable) { + if (Looper.myLooper() == Looper.getMainLooper()) { + runnable.run(); + } else { + AppUtils.UTIL_HANDLER.post(runnable); + } + } + + public static void runOnUiThreadDelayed(final Runnable runnable, long delayMillis) { + AppUtils.UTIL_HANDLER.postDelayed(runnable, delayMillis); + } + + static String getCurrentProcessName() { + String name = getCurrentProcessNameByFile(); + if (!TextUtils.isEmpty(name)) return name; + name = getCurrentProcessNameByAms(); + if (!TextUtils.isEmpty(name)) return name; + name = getCurrentProcessNameByReflect(); + return name; + } + + static void fixSoftInputLeaks(final Window window) { + InputMethodManager imm = + (InputMethodManager) AppUtils.getApp().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm == null) return; + String[] leakViews = new String[]{"mLastSrvView", "mCurRootView", "mServedView", "mNextServedView"}; + for (String leakView : leakViews) { + try { + Field leakViewField = InputMethodManager.class.getDeclaredField(leakView); + if (leakViewField == null) continue; + if (!leakViewField.isAccessible()) { + leakViewField.setAccessible(true); + } + Object obj = leakViewField.get(imm); + if (!(obj instanceof View)) continue; + View view = (View) obj; + if (view.getRootView() == window.getDecorView().getRootView()) { + leakViewField.set(imm, null); + } + } catch (Throwable ignore) {/**/} + } + } + + + /////////////////////////////////////////////////////////////////////////// + // private method + /////////////////////////////////////////////////////////////////////////// + + private static String getCurrentProcessNameByFile() { + try { + File file = new File("/proc/" + android.os.Process.myPid() + "/" + "cmdline"); + BufferedReader mBufferedReader = new BufferedReader(new FileReader(file)); + String processName = mBufferedReader.readLine().trim(); + mBufferedReader.close(); + return processName; + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + private static String getCurrentProcessNameByAms() { + ActivityManager am = (ActivityManager) AppUtils.getApp().getSystemService(Context.ACTIVITY_SERVICE); + if (am == null) return ""; + List info = am.getRunningAppProcesses(); + if (info == null || info.size() == 0) return ""; + int pid = android.os.Process.myPid(); + for (ActivityManager.RunningAppProcessInfo aInfo : info) { + if (aInfo.pid == pid) { + if (aInfo.processName != null) { + return aInfo.processName; + } + } + } + return ""; + } + + private static String getCurrentProcessNameByReflect() { + String processName = ""; + try { + Application app = AppUtils.getApp(); + Field loadedApkField = app.getClass().getField("mLoadedApk"); + loadedApkField.setAccessible(true); + Object loadedApk = loadedApkField.get(app); + + Field activityThreadField = loadedApk.getClass().getDeclaredField("mActivityThread"); + activityThreadField.setAccessible(true); + Object activityThread = activityThreadField.get(loadedApk); + + Method getProcessName = activityThread.getClass().getDeclaredMethod("getProcessName"); + processName = (String) getProcessName.invoke(activityThread); + } catch (Exception e) { + e.printStackTrace(); + } + return processName; + } + + private static Application getApplicationByReflect() { + try { + @SuppressLint("PrivateApi") + Class activityThread = Class.forName("android.app.ActivityThread"); + Object thread = activityThread.getMethod("currentActivityThread").invoke(null); + Object app = activityThread.getMethod("getApplication").invoke(thread); + if (app == null) { + throw new NullPointerException("u should init first"); + } + return (Application) app; + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + throw new NullPointerException("u should init first"); + } + + + /////////////////////////////////////////////////////////////////////////// + // interface + /////////////////////////////////////////////////////////////////////////// + + public abstract static class Task implements Runnable { + + private static final int NEW = 0; + private static final int COMPLETING = 1; + private static final int CANCELLED = 2; + private static final int EXCEPTIONAL = 3; + + private volatile int state = NEW; + + abstract Result doInBackground(); + + private final Callback mCallback; + + public Task(final Callback callback) { + mCallback = callback; + } + + @Override + public void run() { + try { + final Result t = doInBackground(); + + if (state != NEW) return; + state = COMPLETING; + UTIL_HANDLER.post(new Runnable() { + @Override + public void run() { + mCallback.onCall(t); + } + }); + } catch (Throwable th) { + if (state != NEW) return; + state = EXCEPTIONAL; + } + } + + public void cancel() { + state = CANCELLED; + } + + public boolean isDone() { + return state != NEW; + } + + public boolean isCanceled() { + return state == CANCELLED; + } + } + + public interface Callback { + void onCall(T data); + } + + /** + * 判断是否打开定位 + */ + public static boolean getGpsStatus(Context ctx) { + //从系统服务中获取定位管理器 + LocationManager locationManager + = (LocationManager) ctx.getSystemService(Context.LOCATION_SERVICE); + // 通过GPS卫星定位,定位级别可以精确到街(通过24颗卫星定位,在室外和空旷的地方定位准确、速度快) + boolean gps = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); + // 通过WLAN或移动网络(3G/2G)确定的位置(也称作AGPS,辅助GPS定位。主要用于在室内或遮盖物(建筑群或茂密的深林等)密集的地方定位) + boolean network = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); + if (gps || network) { + return true; + } + return false; + } + + /** + * 打开系统定位界面 + */ + public static void goToOpenGps(Context ctx) { + Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); + ctx.startActivity(intent); + } +} \ No newline at end of file diff --git a/library/src/module_utils/java/com/chuhai/utils/ServiceTime.kt b/library/src/module_utils/java/com/chuhai/utils/ServiceTime.kt new file mode 100644 index 000000000..fdaf87b46 --- /dev/null +++ b/library/src/module_utils/java/com/chuhai/utils/ServiceTime.kt @@ -0,0 +1,25 @@ +package com.chuhai.utils + +import android.os.SystemClock + +/** + * Created by Max on 2023/10/24 15:11 + * Desc:服务器时间 + */ +object ServiceTime { + + // 服务器时间与系统开机时间的时差 + private var serviceTimeDiff: Long? = null + + val time + get() = if (serviceTimeDiff == null) System.currentTimeMillis() + else SystemClock.elapsedRealtime() + serviceTimeDiff!! + + /** + * 刷新服务器时间 + */ + fun refreshServiceTime(time: Long) { + //serviceTimeDiff = 服务器时间 - 此刻系统启动时间 + serviceTimeDiff = time - SystemClock.elapsedRealtime() + } +} \ No newline at end of file diff --git a/library/src/module_utils/java/com/chuhai/utils/ShapeViewOutlineProvider.kt b/library/src/module_utils/java/com/chuhai/utils/ShapeViewOutlineProvider.kt new file mode 100644 index 000000000..c1f975b9e --- /dev/null +++ b/library/src/module_utils/java/com/chuhai/utils/ShapeViewOutlineProvider.kt @@ -0,0 +1,42 @@ +package com.chuhai.utils + +import android.graphics.Outline +import android.view.View +import android.view.ViewOutlineProvider +import kotlin.math.min + +/** + * Created by Max on 2023/10/24 15:11 + * Desc: + */ +class ShapeViewOutlineProvider { + + /** + * Created by Max on 2/25/21 1:48 PM + * Desc:圆角 + */ + class Round(var corner: Float) : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + outline.setRoundRect( + 0, + 0, + view.width, + view.height, + corner + ) + } + } + + /** + * Created by Max on 2/25/21 1:48 PM + * Desc:圆形 + */ + class Circle : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + val min = min(view.width, view.height) + val left = (view.width - min) / 2 + val top = (view.height - min) / 2 + outline.setOval(left, top, min, min) + } + } +} \ No newline at end of file diff --git a/library/src/module_utils/java/com/chuhai/utils/UiUtils.kt b/library/src/module_utils/java/com/chuhai/utils/UiUtils.kt new file mode 100644 index 000000000..0fd1074fb --- /dev/null +++ b/library/src/module_utils/java/com/chuhai/utils/UiUtils.kt @@ -0,0 +1,84 @@ +package com.chuhai.utils + +import android.annotation.SuppressLint +import android.content.Context +import android.os.Build +import android.util.DisplayMetrics +import android.util.TypedValue +import android.view.View +import android.view.WindowManager +import androidx.core.text.TextUtilsCompat +import androidx.core.view.ViewCompat + + +/** + * Created by Max on 2023/10/24 15:11 + */ + + +object UiUtils { + fun getScreenWidth(context: Context): Int { + val wm = context.getSystemService(Context.WINDOW_SERVICE) as? WindowManager + val outMetrics = DisplayMetrics() + wm?.defaultDisplay?.getMetrics(outMetrics) + return outMetrics.widthPixels + } + + fun getScreenHeight(context: Context): Int { + val wm = context.getSystemService(Context.WINDOW_SERVICE) as? WindowManager + val outMetrics = DisplayMetrics() + wm?.defaultDisplay?.getMetrics(outMetrics) + return outMetrics.heightPixels + } + + fun getScreenRatio(context: Context): Float { + return getScreenWidth(context) * 1.0f / getScreenHeight(context) + } + + /** + * 根据手机的分辨率从 dp 的单位 转成为 px(像素) + */ + fun dip2px(dpValue: Float): Int { + return dip2px(AppUtils.getApp(), dpValue) + } + + /** + * 根据手机的分辨率从 px(像素) 的单位 转成为 dp + */ + fun px2dip(pxValue: Float): Float { + return px2dip(AppUtils.getApp(), pxValue) + } + + /** + * 根据手机的分辨率从 dp 的单位 转成为 px(像素) + */ + fun dip2px(context: Context, dpValue: Float): Int { + return (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, context.resources.displayMetrics) + 0.5f).toInt() + } + + /** + * 根据手机的分辨率从 px(像素) 的单位 转成为 dp + */ + fun px2dip(context: Context, pxValue: Float): Float { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, pxValue, context.resources.displayMetrics) + } + + /** + * 是否从右到左布局 + */ + @SuppressLint("NewApi") + fun isRtl(view: View): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + View.LAYOUT_DIRECTION_RTL == view.layoutDirection + } else { + false + } + } + + /** + * 是否从右到左布局 + */ + fun isRtl(context: Context): Boolean { + return TextUtilsCompat.getLayoutDirectionFromLocale(context.resources.configuration.locale) == ViewCompat.LAYOUT_DIRECTION_RTL + } +} diff --git a/library/src/module_utils/java/com/chuhai/utils/ktx/ContextKtx.kt b/library/src/module_utils/java/com/chuhai/utils/ktx/ContextKtx.kt new file mode 100644 index 000000000..65a454613 --- /dev/null +++ b/library/src/module_utils/java/com/chuhai/utils/ktx/ContextKtx.kt @@ -0,0 +1,70 @@ +package com.chuhai.utils.ktx + +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner + +/** + * Created by Max on 2023/10/25 15:57 + * Desc:Context相关工具 + **/ + + +/** + * Context转换为Activity + */ +fun Context?.asActivity(): Activity? { + return when { + this is Activity -> { + this + } + (this as? ContextWrapper)?.baseContext?.applicationContext != null -> { + baseContext.asActivity() + } + else -> { + null + } + } +} + +/** + * Context转换为Lifecycle + */ +fun Context?.asLifecycle(): Lifecycle? { + if (this == null) return null + return when (this) { + is Lifecycle -> { + this + } + is LifecycleOwner -> { + this.lifecycle + } + is ContextWrapper -> { + this.baseContext.asLifecycle() + } + else -> { + null + } + } +} + + +/** + * Context转换为LifecycleOwner + */ +fun Context?.asLifecycleOwner(): LifecycleOwner? { + if (this == null) return null + return when (this) { + is LifecycleOwner -> { + this + } + is ContextWrapper -> { + this.baseContext.asLifecycleOwner() + } + else -> { + null + } + } +} diff --git a/library/src/module_utils/java/com/chuhai/utils/ktx/EditTextKtx.kt b/library/src/module_utils/java/com/chuhai/utils/ktx/EditTextKtx.kt new file mode 100644 index 000000000..cd01b47fb --- /dev/null +++ b/library/src/module_utils/java/com/chuhai/utils/ktx/EditTextKtx.kt @@ -0,0 +1,106 @@ +package com.chuhai.utils.ktx + +import android.text.Editable +import android.text.InputFilter +import android.text.InputFilter.LengthFilter +import android.text.Spanned +import android.text.TextWatcher +import android.text.method.HideReturnsTransformationMethod +import android.text.method.PasswordTransformationMethod +import android.widget.EditText + + +/** + * 设置editText输入监听 + * @param onChanged 改变事件 + * @return 是否接受此次文本的改变 + */ +inline fun EditText.setOnInputChangedListener( + /** + * @param Int:当前长度 + * @return 是否接受此次文本的改变 + */ + crossinline onChanged: (Int).() -> Boolean +) { + this.addTextChangedListener(object : TextWatcher { + + var flag = false + + override fun afterTextChanged(p0: Editable?) { + if (flag) { + return + } + if (!onChanged(p0?.length ?: 0)) { + flag = true + this@setOnInputChangedListener.setText( + this@setOnInputChangedListener.getTag( + 1982329101 + ) as? String + ) + this@setOnInputChangedListener.setSelection(this@setOnInputChangedListener.length()) + flag = false + } else { + flag = false + } + } + + override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + this@setOnInputChangedListener.setTag(1982329101, p0?.toString()) + } + + override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + } + }) +} + +/** + * 切换密码可见度 + */ +fun EditText.switchPasswordVisibility(visibility: Boolean) { + transformationMethod = + if (!visibility) HideReturnsTransformationMethod.getInstance() else PasswordTransformationMethod.getInstance() + +} + +/** + * 设置输入功能是否启用(不启用就相当于TextView) + */ +fun EditText.setInputEnabled(isEnabled: Boolean) { + if (isEnabled) { + isFocusable = true + isFocusableInTouchMode = true + isClickable = true + } else { + isFocusable = false + isFocusableInTouchMode = false + isClickable = false + keyListener = null + } +} + +/** + * 添加输入长度限制过滤器 + */ +fun EditText.addLengthFilter(maxLength: Int) { + val newFilters = filters.copyOf(filters.size + 1) + newFilters[newFilters.size - 1] = LengthFilter(maxLength) + filters = newFilters +} + + +/** + * 添加禁用文本过滤器 + * @param disableText 不允许输入该文本 + */ +fun EditText.addDisableFilter(vararg disableText: CharSequence) { + val newFilters = filters.copyOf(filters.size + 1) + newFilters[newFilters.size - 1] = InputFilter { source, p1, p2, p3, p4, p5 -> + disableText.forEach { + if (source.equals(it)) { + return@InputFilter "" + } + } + return@InputFilter null + } + filters = newFilters +} \ No newline at end of file diff --git a/library/src/module_utils/java/com/chuhai/utils/ktx/ResourcesKtx.kt b/library/src/module_utils/java/com/chuhai/utils/ktx/ResourcesKtx.kt new file mode 100644 index 000000000..b3d3bd66b --- /dev/null +++ b/library/src/module_utils/java/com/chuhai/utils/ktx/ResourcesKtx.kt @@ -0,0 +1,194 @@ +package com.chuhai.utils.ktx + +import android.app.Activity +import android.content.Context +import android.content.res.TypedArray +import android.graphics.drawable.Drawable +import android.util.TypedValue +import androidx.annotation.* +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import androidx.fragment.app.Fragment +import com.chuhai.utils.AppUtils + +/** + * Created by Max on 2023/10/24 15:11 + * 资源工具类 + */ + + +/** + * 获取颜色 + */ +fun Fragment.getColorById(@ColorRes colorResId: Int): Int { + return ContextCompat.getColor(context!!, colorResId) +} + + +/** + * 获取图片 + */ +fun Fragment.getDrawableById(@DrawableRes drawableRedId: Int): Drawable? { + return ContextCompat.getDrawable(context!!, drawableRedId) +} + + +/** + * 获取颜色 + */ +fun Activity.getColorById(@ColorRes colorResId: Int): Int { + return ContextCompat.getColor(this, colorResId) +} + +/** + * 获取图片 + */ +fun Activity.getDrawableById(@DrawableRes drawableRedId: Int): Drawable? { + return ContextCompat.getDrawable(this, drawableRedId) +} + + +/** + * 获取颜色 + */ +fun Context.getColorById(@ColorRes colorResId: Int): Int { + return ContextCompat.getColor(this, colorResId) +} + +/** + * 获取图片 + */ +fun Context.getDrawableById(@DrawableRes drawableRedId: Int): Drawable? { + return ContextCompat.getDrawable(this, drawableRedId) +} + + +/** + * 获取字符串资源 + */ +fun Any.getStringById(@StringRes stringResId: Int): String { + return AppUtils.getApp().getString(stringResId) +} + +/** + * 获取字符串资源 + */ +fun Int.toStringRes(): String { + return AppUtils.getApp().getString(this) +} + +/** + * 获取资源drawable + * */ +fun Int.toDrawableRes(): Drawable? { + return ContextCompat.getDrawable(AppUtils.getApp(), this) +} + +/** + * 获取资源color + * */ +fun Int.toColorRes(): Int { + return ContextCompat.getColor(AppUtils.getApp(), this) +} + +/** + * 通过自定义属性-获取DrawableRes + */ +@DrawableRes +fun Context.getDrawableResFromAttr( + @AttrRes attrResId: Int, + typedValue: TypedValue = TypedValue(), + resolveRefs: Boolean = true +): Int? { + return try { + theme.resolveAttribute(attrResId, typedValue, resolveRefs) + return typedValue.resourceId + } catch (e: Exception) { + e.printStackTrace() + null + } +} + +/** + * 通过自定义属性-获取Drawable + */ +fun Context.getDrawableFromAttr(@AttrRes attrId: Int): Drawable? { + return try { + val drawableRes = getDrawableResFromAttr(attrId) ?: return null + ResourcesCompat.getDrawable(resources, drawableRes, null) + } catch (e: Exception) { + e.printStackTrace() + null + } +} + +/** + * 通过自定义属性-获取ColorRes + */ +@ColorRes +fun Context.getColorResFromAttr( + @AttrRes attrResId: Int, + typedValue: TypedValue = TypedValue(), + resolveRefs: Boolean = true +): Int? { + return try { + theme.resolveAttribute(attrResId, typedValue, resolveRefs) + return typedValue.resourceId + } catch (e: Exception) { + e.printStackTrace() + null + } +} + +/** + * 通过自定义属性-获取Color + */ +@ColorRes +fun Context.getColorFromAttr( + @AttrRes attrResId: Int +): Int? { + return try { + val colorRes = getColorFromAttr(attrResId) ?: return null + ResourcesCompat.getColor(resources, colorRes, null) + } catch (e: Exception) { + e.printStackTrace() + null + } +} + +/** + * 通过自定义属性-获取LayoutRes + */ +@LayoutRes +fun Context.getLayoutResFromAttr( + @AttrRes attrResId: Int, + typedValue: TypedValue = TypedValue(), + resolveRefs: Boolean = true +): Int? { + return try { + theme.resolveAttribute(attrResId, typedValue, resolveRefs) + return typedValue.resourceId + } catch (e: Exception) { + e.printStackTrace() + null + } +} + +/** + * 通过自定义属性-获取Boolean + */ +fun Context.getBooleanResFromAttr( + @AttrRes attrResId: Int, + defValue: Boolean = false +): Boolean { + var attrs: TypedArray? = null + try { + attrs = obtainStyledAttributes(null, intArrayOf(attrResId)) + return attrs.getBoolean(0, defValue) + } catch (e: Exception) { + e.printStackTrace() + } finally { + attrs?.recycle() + } + return defValue +} diff --git a/library/src/module_utils/java/com/chuhai/utils/ktx/UiKtx.kt b/library/src/module_utils/java/com/chuhai/utils/ktx/UiKtx.kt new file mode 100644 index 000000000..b0a6784de --- /dev/null +++ b/library/src/module_utils/java/com/chuhai/utils/ktx/UiKtx.kt @@ -0,0 +1,51 @@ +package com.chuhai.utils.ktx + +import com.chuhai.utils.UiUtils +import kotlin.math.roundToInt + +/** + * Created by Max on 2023/10/24 15:11 + */ + + +/** + * 转换为PX值 + */ +val Float.dp: Int get() = this.toPX() +val Int.dp: Int get() = this.toPX() + +/** + * 转换为DP值 + */ +val Float.px: Int get() = this.toDP().roundToInt() +val Int.px: Int get() = this.toDP().roundToInt() + + +fun Long.toDP(): Float { + return UiUtils.px2dip(this.toFloat()) +} + + +fun Float.toDP(): Float { + return UiUtils.px2dip(this) +} + + +fun Int.toDP(): Float { + return UiUtils.px2dip(this.toFloat()) +} + + +fun Long.toPX(): Int { + return UiUtils.dip2px(this.toFloat()) +} + + +fun Float.toPX(): Int { + return UiUtils.dip2px(this) +} + + +fun Int.toPX(): Int { + return UiUtils.dip2px(this.toFloat()) +} \ No newline at end of file diff --git a/library/src/module_utils/java/com/chuhai/utils/ktx/ViewKtx.kt b/library/src/module_utils/java/com/chuhai/utils/ktx/ViewKtx.kt new file mode 100644 index 000000000..6d2e4e15c --- /dev/null +++ b/library/src/module_utils/java/com/chuhai/utils/ktx/ViewKtx.kt @@ -0,0 +1,192 @@ +package com.chuhai.utils.ktx + +import android.graphics.* +import android.os.Build +import android.os.SystemClock +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.Checkable +import android.widget.TextView +import androidx.core.view.ScrollingView +import com.chuhai.utils.ShapeViewOutlineProvider +import com.chuhai.utils.UiUtils + + +/** + * 是否右-左布局 + */ +fun View.isRtl(): Boolean { + return UiUtils.isRtl(this) +} + + +/** + * 展示or隐藏 + */ +fun View.visibleOrGone(isShow: Boolean) { + visibility = if (isShow) { + View.VISIBLE + } else { + View.GONE + } +} + +/** + * 展示or隐藏 + */ +inline fun View.visibleOrGone(show: View.() -> Boolean = { true }) { + visibility = if (show(this)) { + View.VISIBLE + } else { + View.GONE + } +} + +/** + * 展示or不可见 + */ +inline fun View.visibleOrInvisible(show: View.() -> Boolean = { true }) { + visibility = if (show(this)) { + View.VISIBLE + } else { + View.INVISIBLE + } +} + +/** + * 点击事件 + */ +inline fun T.singleClick(time: Long = 800, crossinline block: (T) -> Unit) { + setOnClickListener(object : View.OnClickListener { + private var lastClickTime: Long = 0L + override fun onClick(v: View?) { + val currentTimeMillis = SystemClock.elapsedRealtime() + if (currentTimeMillis - lastClickTime > time || this is Checkable) { + lastClickTime = currentTimeMillis + block(this@singleClick) + } + } + }) +} + +/** + * 点击事件 + */ +fun T.singleClick(onClickListener: View.OnClickListener, time: Long = 800) { + setOnClickListener(object : View.OnClickListener { + private var lastClickTime: Long = 0L + override fun onClick(v: View?) { + val currentTimeMillis = SystemClock.elapsedRealtime() + if (currentTimeMillis - lastClickTime > time || this is Checkable) { + lastClickTime = currentTimeMillis + onClickListener.onClick(v) + } + } + }) +} + +/** + * 设置View圆角矩形 + */ +fun T.roundCorner(corner: Int) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (outlineProvider == null || outlineProvider !is ShapeViewOutlineProvider.Round) { + outlineProvider = ShapeViewOutlineProvider.Round(corner.toFloat()) + } else if (outlineProvider != null && outlineProvider is ShapeViewOutlineProvider.Round) { + (outlineProvider as ShapeViewOutlineProvider.Round).corner = corner.toFloat() + } + clipToOutline = true + } +} + +/** + * 设置View为圆形 + */ +fun T.circle() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (outlineProvider == null || outlineProvider !is ShapeViewOutlineProvider.Circle) { + outlineProvider = ShapeViewOutlineProvider.Circle() + } + clipToOutline = true + } +} + +fun View.getBitmap(): Bitmap { + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + canvas.translate(scrollX.toFloat(), scrollY.toFloat()) + draw(canvas) + return bitmap +} + +/** + * 设置边距 + */ +fun View?.setMargin(start: Int? = null, top: Int? = null, end: Int? = null, bottom: Int? = null) { + (this?.layoutParams as? ViewGroup.MarginLayoutParams)?.apply { + start?.let { + this.marginStart = start + } + top?.let { + this.topMargin = top + } + end?.let { + this.marginEnd = end + } + bottom?.let { + this.bottomMargin = bottom + } + } +} + + +/** + * 设置内边距 + */ +fun View?.setPadding2(start: Int? = null, top: Int? = null, end: Int? = null, bottom: Int? = null) { + if (this == null) return + this.setPadding( + start ?: paddingStart, top ?: paddingTop, end ?: paddingEnd, bottom ?: paddingBottom + ) +} + +/** + * 描边宽度 + */ +fun TextView.strokeWidth(width: Float) { + this.paint?.style = Paint.Style.FILL_AND_STROKE + this.paint?.strokeWidth = width + this.invalidate() +} + +/** + * 模拟点击并取消 + */ +fun ScrollingView.simulateClickAndCancel() { + val view = this as? View ?: return + val downEvent = MotionEvent.obtain( + System.currentTimeMillis(), System.currentTimeMillis(), MotionEvent.ACTION_DOWN, (view.right - view.left) / 2f, (view.bottom - view.top) / 2f, 0 + ) + view.dispatchTouchEvent(downEvent) + val cancelEvent = MotionEvent.obtain( + System.currentTimeMillis(), System.currentTimeMillis(), MotionEvent.ACTION_CANCEL, (view.right - view.left) / 2f, (view.bottom - view.top) / 2f, 0 + ) + view.dispatchTouchEvent(cancelEvent) +} + +/** + * 使用灰色滤镜 + */ +fun View.applyGrayFilter(isGray: Boolean) { + try { + val paint = Paint() + val colorMatrix = ColorMatrix() + colorMatrix.setSaturation(if (isGray) 0f else 1f) + paint.colorFilter = ColorMatrixColorFilter(colorMatrix) + setLayerType(View.LAYER_TYPE_HARDWARE, paint) + } catch (e: Exception) { + e.printStackTrace() + } +} + diff --git a/library/src/module_utils/res/values/ids.xml b/library/src/module_utils/res/values/ids.xml new file mode 100644 index 000000000..78554f9eb --- /dev/null +++ b/library/src/module_utils/res/values/ids.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/library/src/module_utils/res/values/strings.xml b/library/src/module_utils/res/values/strings.xml new file mode 100644 index 000000000..f11f7450a --- /dev/null +++ b/library/src/module_utils/res/values/strings.xml @@ -0,0 +1,3 @@ + + +