diff --git a/app/build.gradle b/app/build.gradle index f02f316..42f23df 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -218,17 +218,20 @@ android { dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation project(':viewlibrary') + implementation project(path: ':niceimageview') implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.6.0' implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'androidx.legacy:legacy-support-v4:1.0.0' //constraintlayout:2.0.4以上在Android studio 3.6.2不能预览 -// implementation 'androidx.constraintlayout:constraintlayout:2.0.4' - implementation 'androidx.constraintlayout:constraintlayout:2.1.1' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' +// implementation 'androidx.constraintlayout:constraintlayout:2.1.1' + implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.preference:preference:1.1.1' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation "androidx.security:security-crypto:1.1.0-alpha03" + implementation 'androidx.multidex:multidex:2.0.1' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' @@ -246,17 +249,19 @@ dependencies { implementation 'com.squareup.retrofit2:retrofit:2.3.0' implementation 'com.squareup.retrofit2:converter-gson:2.3.0' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' - //Gson + //Google implementation 'com.google.code.gson:gson:2.8.6' + implementation 'com.google.zxing:core:3.3.0' + implementation 'com.google.android.material:material:1.4.0' //Jackson implementation 'org.codehaus.jackson:jackson-mapper-asl:1.9.13' implementation 'org.codehaus.jackson:jackson-core-asl:1.9.13' //view绑定 - implementation 'com.jakewharton:butterknife:10.1.0' - annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0' - //二维码 - implementation 'com.google.zxing:core:3.3.0' - implementation 'com.google.android.material:material:1.4.0' + implementation 'com.jakewharton:butterknife:10.2.1' +// If you are using Kotlin, replace annotationProcessor with kapt. +// annotationProcessor rootProject.ext.dependencies["butterknife-compiler"] + annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1' + //MMKV implementation 'com.tencent:mmkv-static:1.2.10' //内存泄漏检测 @@ -300,4 +305,6 @@ dependencies { implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' // kotlin扩展(可选) implementation 'com.gyf.immersionbar:immersionbar-ktx:3.0.0' + //网络监听 + implementation 'com.github.tianma8023:NetDetector:v0.2.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b75e851..312d54c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -44,8 +44,8 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/AppTheme"> - + android:theme="@style/ImmerseTheme"> + diff --git a/app/src/main/java/com/tt/activity/JMainActivity.java b/app/src/main/java/com/tt/activity/JMainActivity.java index f1b594a..b64b0d2 100644 --- a/app/src/main/java/com/tt/activity/JMainActivity.java +++ b/app/src/main/java/com/tt/activity/JMainActivity.java @@ -24,6 +24,7 @@ import com.info.sn.IGetInfoInterface; import com.jiaoguanyi.appstore.IGetLicenseInterface; import com.notepad.uiui.INoteAidlInterface; import com.tt.base.BaseActivity; +import com.tt.base.ImmerseBaseActivity; import com.tt.ttutils.R; import com.tt.ttutils.java.ApkUtils; import com.tt.ttutils.java.ToastUtil; @@ -35,7 +36,7 @@ import java.util.List; import butterknife.BindView; import butterknife.ButterKnife; -public class JMainActivity extends BaseActivity { +public class JMainActivity extends ImmerseBaseActivity { @BindView(R.id.textView) TextView textView; @BindView(R.id.button) @@ -49,13 +50,14 @@ public class JMainActivity extends BaseActivity { private IGetInfoInterface getInfoInterface; private String TAG = JMainActivity.class.getSimpleName(); + @Override - protected int setLayoutResourceID() { + public int getLayoutId() { return R.layout.activity_main; } @Override - protected void initView() { + public void initView() { ButterKnife.bind(this); View decorView = getWindow().getDecorView(); @@ -63,13 +65,18 @@ public class JMainActivity extends BaseActivity { // SYSTEM_UI_FLAG_FULLSCREEN is only available on Android 4.1 and higher, but as // a general rule, you should design your app to hide the status bar whenever you // hide the navigation bar. - int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION //导航栏 - | View.SYSTEM_UI_FLAG_FULLSCREEN;//全屏 - decorView.setSystemUiVisibility(uiOptions); +// int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION //导航栏 +// | View.SYSTEM_UI_FLAG_FULLSCREEN;//全屏 +// decorView.setSystemUiVisibility(uiOptions); } @Override - protected void initData() { + public void setListener() { + + } + + @Override + public void initData() { registHideAppReceiver(); registForbidAppReceiver(); registerUserInfoReceiver(); diff --git a/app/src/main/java/com/tt/base/ImmerseBaseActivity.java b/app/src/main/java/com/tt/base/ImmerseBaseActivity.java new file mode 100644 index 0000000..04f7bbd --- /dev/null +++ b/app/src/main/java/com/tt/base/ImmerseBaseActivity.java @@ -0,0 +1,53 @@ +package com.tt.base; + +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.tt.ttutils.R; +import com.zackratos.ultimatebarx.ultimatebarx.java.UltimateBarX; + +public abstract class ImmerseBaseActivity extends AppCompatActivity { + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); +// StatusBarUtil.init(this); + UltimateBarX.statusBar(this) + .transparent() + .colorRes(R.color.colorPrimaryDark) + .light(true) + .apply(); + UltimateBarX.navigationBar(this) + .transparent() + .colorRes(R.color.colorPrimaryDark) + .light(true) + .apply(); + //状态栏改变放在setContentView前后有所不同 + setContentView(this.getLayoutId()); + initView(); + setListener(); + initData(); + } + + /** + * 设置布局 + */ + public abstract int getLayoutId(); + + /** + * 初始化视图 + */ + public abstract void initView(); + + /** + * + */ + public abstract void setListener(); + + /** + * 初始化数据 + */ + public abstract void initData(); +} diff --git a/app/src/main/java/com/tt/base/JMyApplication.java b/app/src/main/java/com/tt/base/JMyApplication.java index ba30f39..1bab3b3 100644 --- a/app/src/main/java/com/tt/base/JMyApplication.java +++ b/app/src/main/java/com/tt/base/JMyApplication.java @@ -41,5 +41,13 @@ public class JMyApplication extends Application { }); } + @Override + public void onLowMemory() { + super.onLowMemory(); + } + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + } } diff --git a/app/src/main/java/com/tt/ttutils/java/StatusBarUtil.java b/app/src/main/java/com/tt/ttutils/java/StatusBarUtil.java new file mode 100644 index 0000000..3fca479 --- /dev/null +++ b/app/src/main/java/com/tt/ttutils/java/StatusBarUtil.java @@ -0,0 +1,344 @@ +package com.tt.ttutils.java; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Color; +import android.os.Build; +import android.os.Environment; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Properties; + +public class StatusBarUtil { + public static int screenWidth; + public static int screenHeight; + public static int navigationHeight = 0; + + private static DisplayMetrics mMetrics; + public static final String HOME_CURRENT_TAB_POSITION = "HOME_CURRENT_TAB_POSITION"; + + public static final String SYS_EMUI = "sys_emui"; + public static final String SYS_MIUI = "sys_miui"; + public static final String SYS_FLYME = "sys_flyme"; + private static final String KEY_MIUI_VERSION_CODE = "ro.miui.ui.version.code"; + private static final String KEY_MIUI_VERSION_NAME = "ro.miui.ui.version.name"; + private static final String KEY_MIUI_INTERNAL_STORAGE = "ro.miui.internal.storage"; + private static final String KEY_EMUI_API_LEVEL = "ro.build.hw_emui_api_level"; + private static final String KEY_EMUI_VERSION = "ro.build.version.emui"; + private static final String KEY_EMUI_CONFIG_HW_SYS_VERSION = "ro.confg.hw_systemversion"; + + public static void init(Activity activity) { + ViewGroup contentLayout = activity.getWindow().getDecorView().findViewById(Window.ID_ANDROID_CONTENT); + StatusBarUtil.setStatusBar(activity, false, true); + StatusBarUtil.setStatusTextColor(true, activity); + StatusBarUtil.setStatusBarPadding(activity, contentLayout); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + activity.getWindow().setStatusBarColor(activity.getResources().getColor(android.R.color.transparent)); + } + } + + /** + * 通过反射的方式获取状态栏高度 + * + * @return + */ + public static int getStatusBarHeight(Context context) { + try { + Class c = Class.forName("com.android.internal.R$dimen"); + Object obj = c.newInstance(); + Field field = c.getField("status_bar_height"); + int x = Integer.parseInt(field.get(obj).toString()); + return context.getResources().getDimensionPixelSize(x); + } catch (Exception e) { + } + return 0; + } + + /** + * 获取底部导航栏高度 + * + * @return + */ + public static int getNavigationBarHeight(Context context) { + Resources resources = context.getResources(); + int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); + //获取NavigationBar的高度 + navigationHeight = resources.getDimensionPixelSize(resourceId); + return navigationHeight; + } + + //获取是否存在NavigationBar + public static boolean checkDeviceHasNavigationBar(Context context) { + boolean hasNavigationBar = false; + Resources rs = context.getResources(); + int id = rs.getIdentifier("config_showNavigationBar", "bool", "android"); + if (id > 0) { + hasNavigationBar = rs.getBoolean(id); + } + try { + Class systemPropertiesClass = Class.forName("android.os.SystemProperties"); + Method m = systemPropertiesClass.getMethod("get", String.class); + String navBarOverride = (String) m.invoke(systemPropertiesClass, "qemu.hw.mainkeys"); + if ("1".equals(navBarOverride)) { + hasNavigationBar = false; + } else if ("0".equals(navBarOverride)) { + hasNavigationBar = true; + } + } catch (Exception e) { + + } + return hasNavigationBar; + + } + + /** + * @param activity + * @param useThemestatusBarColor 是否要状态栏的颜色,不设置则为透明色 + * @param withoutUseStatusBarColor 是否不需要使用状态栏为暗色调 + */ + public static void setStatusBar(Activity activity, boolean useThemestatusBarColor, boolean withoutUseStatusBarColor) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//5.0及以上 + View decorView = activity.getWindow().getDecorView(); + int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; + decorView.setSystemUiVisibility(option); + if (useThemestatusBarColor) { + activity.getWindow().setStatusBarColor(activity.getResources().getColor(android.R.color.white)); + } else { + activity.getWindow().setStatusBarColor(Color.TRANSPARENT); + } + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {//4.4到5.0 + WindowManager.LayoutParams localLayoutParams = activity.getWindow().getAttributes(); + localLayoutParams.flags = (WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS | localLayoutParams.flags); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !withoutUseStatusBarColor) { + activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } + } + + public static void reMeasure(Activity activity) { + Display display = activity.getWindowManager().getDefaultDisplay(); + mMetrics = new DisplayMetrics(); + + if (Build.VERSION.SDK_INT >= 17) { + display.getRealMetrics(mMetrics); + } else { + display.getMetrics(mMetrics); + } + + screenWidth = mMetrics.widthPixels; + screenHeight = mMetrics.heightPixels; + } + + /** + * 改变魅族的状态栏字体为黑色,要求FlyMe4以上 + */ + private static void processFlyMe(boolean isLightStatusBar, Activity activity) { + WindowManager.LayoutParams lp = activity.getWindow().getAttributes(); + try { + Class instance = Class.forName("android.view.WindowManager$LayoutParams"); + int value = instance.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON").getInt(lp); + Field field = instance.getDeclaredField("meizuFlags"); + field.setAccessible(true); + int origin = field.getInt(lp); + if (isLightStatusBar) { + field.set(lp, origin | value); + } else { + field.set(lp, (~value) & origin); + } + } catch (Exception e) { + } + } + + /** + * 改变小米的状态栏字体颜色为黑色, 要求MIUI6以上 lightStatusBar为真时表示黑色字体 + */ + private static void processMIUI(boolean lightStatusBar, Activity activity) { + Class clazz = activity.getWindow().getClass(); + try { + int darkModeFlag; + Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams"); + Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE"); + darkModeFlag = field.getInt(layoutParams); + Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class); + extraFlagField.invoke(activity.getWindow(), lightStatusBar ? darkModeFlag : 0, darkModeFlag); + } catch (Exception e) { + } + } + + /** + * 设置OPPO手机状态栏字体为黑色(colorOS3.0,6.0以下部分手机) + * + * @param lightStatusBar + * @param activity + */ + private static final int SYSTEM_UI_FLAG_OP_STATUS_BAR_TINT = 0x00000010; + + private static void setOPPOStatusTextColor(boolean lightStatusBar, Activity activity) { + Window window = activity.getWindow(); + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + int vis = window.getDecorView().getSystemUiVisibility(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (lightStatusBar) { + vis |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } else { + vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (lightStatusBar) { + vis |= SYSTEM_UI_FLAG_OP_STATUS_BAR_TINT; + } else { + vis &= ~SYSTEM_UI_FLAG_OP_STATUS_BAR_TINT; + } + } + window.getDecorView().setSystemUiVisibility(vis); + } + + + /** + * 判断手机是否是小米 + * + * @return + */ + public static boolean isMIUI() { + return SYS_MIUI.equals(getSystem()); + } + + /** + * 判断手机是否是魅族 + * + * @return + */ + public static boolean isFlyme() { + try { + // Invoke Build.hasSmartBar() + final Method method = Build.class.getMethod("hasSmartBar"); + return method != null; + } catch (final Exception e) { + return false; + } + } + + /** + * 设置状态栏文字色值为深色调 + * + * @param useDart 是否使用深色调 + * @param activity + */ + public static void setStatusTextColor(boolean useDart, Activity activity) { + if (isFlyme()) { + processFlyMe(useDart, activity); + } else if (isMIUI()) { + processMIUI(useDart, activity); + } else if (Build.MANUFACTURER.equalsIgnoreCase("OPPO")) { + //OPPO + setOPPOStatusTextColor(useDart, activity); + } else { + if (useDart) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } + } else { + activity.getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + } + activity.getWindow().getDecorView().findViewById(android.R.id.content).setPadding(0, 0, 0, navigationHeight); + } + } + + /** + * 作者:Loyea + * 链接:https://www.jianshu.com/p/abd021c22728 + * 來源:简书 + * 简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。 + */ + public static void setAndroidNativeLightStatusBar(Activity activity, boolean dark) { + View decor = activity.getWindow().getDecorView(); + if (dark) { + decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } else { + // We want to change tint color to white again. + // You can also record the flags in advance so that you can turn UI back completely if + // you have set other flags before, such as translucent or full screen. + decor.setSystemUiVisibility(0); + } + } + + public static String getSystem() { + String SYS = ""; + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) { + if (!TextUtils.isEmpty(getSystemProperty(KEY_MIUI_VERSION_CODE, "")) + || !TextUtils.isEmpty(getSystemProperty(KEY_MIUI_VERSION_NAME, "")) + || !TextUtils.isEmpty(getSystemProperty(KEY_MIUI_INTERNAL_STORAGE, ""))) { + SYS = SYS_MIUI;//小米 + } else if (!TextUtils.isEmpty(getSystemProperty(KEY_EMUI_API_LEVEL, "")) + || !TextUtils.isEmpty(getSystemProperty(KEY_EMUI_VERSION, "")) + || !TextUtils.isEmpty(getSystemProperty(KEY_EMUI_CONFIG_HW_SYS_VERSION, ""))) { + SYS = SYS_EMUI;//华为 + } else if (getMeizuFlymeOSFlag().toLowerCase().contains("flyme")) { + SYS = SYS_FLYME;//魅族 + } + return SYS; + } else { + try { + Properties prop = new Properties(); + prop.load(new FileInputStream(new File(Environment.getRootDirectory(), "build.prop"))); + if (prop.getProperty(KEY_MIUI_VERSION_CODE, null) != null + || prop.getProperty(KEY_MIUI_VERSION_NAME, null) != null + || prop.getProperty(KEY_MIUI_INTERNAL_STORAGE, null) != null) { + SYS = SYS_MIUI;//小米 + } else if (prop.getProperty(KEY_EMUI_API_LEVEL, null) != null + || prop.getProperty(KEY_EMUI_VERSION, null) != null + || prop.getProperty(KEY_EMUI_CONFIG_HW_SYS_VERSION, null) != null) { + SYS = SYS_EMUI;//华为 + } else if (getMeizuFlymeOSFlag().toLowerCase().contains("flyme")) { + SYS = SYS_FLYME;//魅族 + } + } catch (IOException e) { + e.printStackTrace(); + return SYS; + } finally { + return SYS; + } + } + } + + private static String getSystemProperty(String key, String defaultValue) { + try { + Class clz = Class.forName("android.os.SystemProperties"); + Method get = clz.getMethod("get", String.class, String.class); + return (String) get.invoke(clz, key, defaultValue); + } catch (Exception e) { + } + return defaultValue; + } + + public static String getMeizuFlymeOSFlag() { + return getSystemProperty("ro.build.display.id", ""); + } + + /** + * 全屏模式下 针对刘海屏向下移动view + * + * @param context + * @param view 需要设置padding的view + */ + public static void setStatusBarPadding(Context context, View view) { + if (view != null) { + view.setPadding(0, getStatusBarHeight(context), 0, 0); + } + } +} diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index 9b2e58a..24982db 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -4,6 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" tools:context="com.tt.activity.JMainActivity"> - #6200EE - #3700B3 - #03DAC5 + #FFFFFF + #FFFFFF + #FFFFFF + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index fac9291..8c47132 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -7,4 +7,14 @@ @color/colorAccent + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 0b103e4..e17269d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = "1.3.72" + ext.kotlin_version = "1.4.10" repositories { google() jcenter() diff --git a/niceimageview/.gitignore b/niceimageview/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/niceimageview/.gitignore @@ -0,0 +1 @@ +/build diff --git a/niceimageview/build.gradle b/niceimageview/build.gradle new file mode 100644 index 0000000..381745a --- /dev/null +++ b/niceimageview/build.gradle @@ -0,0 +1,42 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 28 + + defaultConfig { + minSdkVersion 14 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + } + + buildTypes { + zhanRuiRelease { + + } + + zhanRuiDebug { + + } + + zhanRuiUserdebug { + + } + + debug { + + } + + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation 'com.android.support:appcompat-v7:28.0.0' +} diff --git a/niceimageview/proguard-rules.pro b/niceimageview/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/niceimageview/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/niceimageview/src/main/AndroidManifest.xml b/niceimageview/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6dab987 --- /dev/null +++ b/niceimageview/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/niceimageview/src/main/java/com/shehuan/niv/NiceImageView.java b/niceimageview/src/main/java/com/shehuan/niv/NiceImageView.java new file mode 100644 index 0000000..2d31326 --- /dev/null +++ b/niceimageview/src/main/java/com/shehuan/niv/NiceImageView.java @@ -0,0 +1,338 @@ +package com.shehuan.niv; + + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.RectF; +import android.graphics.Xfermode; +import android.os.Build; +import android.util.AttributeSet; + +import androidx.annotation.ColorInt; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatImageView; + +public class NiceImageView extends AppCompatImageView { + private Context context; + + private boolean isCircle; // 是否显示为圆形,如果为圆形则设置的corner无效 + private boolean isCoverSrc; // border、inner_border是否覆盖图片 + private int borderWidth; // 边框宽度 + private int borderColor = Color.WHITE; // 边框颜色 + private int innerBorderWidth; // 内层边框宽度 + private int innerBorderColor = Color.WHITE; // 内层边框充色 + + private int cornerRadius; // 统一设置圆角半径,优先级高于单独设置每个角的半径 + private int cornerTopLeftRadius; // 左上角圆角半径 + private int cornerTopRightRadius; // 右上角圆角半径 + private int cornerBottomLeftRadius; // 左下角圆角半径 + private int cornerBottomRightRadius; // 右下角圆角半径 + + private int maskColor; // 遮罩颜色 + + private Xfermode xfermode; + + private int width; + private int height; + private float radius; + + private float[] borderRadii; + private float[] srcRadii; + + private RectF srcRectF; // 图片占的矩形区域 + private RectF borderRectF; // 边框的矩形区域 + + private Paint paint; + private Path path; // 用来裁剪图片的ptah + private Path srcPath; // 图片区域大小的path + + public NiceImageView(Context context) { + this(context, null); + } + + public NiceImageView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public NiceImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + this.context = context; + + TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.NiceImageView, 0, 0); + for (int i = 0; i < ta.getIndexCount(); i++) { + int attr = ta.getIndex(i); + if (attr == R.styleable.NiceImageView_is_cover_src) { + isCoverSrc = ta.getBoolean(attr, isCoverSrc); + } else if (attr == R.styleable.NiceImageView_is_circle) { + isCircle = ta.getBoolean(attr, isCircle); + } else if (attr == R.styleable.NiceImageView_border_width) { + borderWidth = ta.getDimensionPixelSize(attr, borderWidth); + } else if (attr == R.styleable.NiceImageView_border_color) { + borderColor = ta.getColor(attr, borderColor); + } else if (attr == R.styleable.NiceImageView_inner_border_width) { + innerBorderWidth = ta.getDimensionPixelSize(attr, innerBorderWidth); + } else if (attr == R.styleable.NiceImageView_inner_border_color) { + innerBorderColor = ta.getColor(attr, innerBorderColor); + } else if (attr == R.styleable.NiceImageView_corner_radius) { + cornerRadius = ta.getDimensionPixelSize(attr, cornerRadius); + } else if (attr == R.styleable.NiceImageView_corner_top_left_radius) { + cornerTopLeftRadius = ta.getDimensionPixelSize(attr, cornerTopLeftRadius); + } else if (attr == R.styleable.NiceImageView_corner_top_right_radius) { + cornerTopRightRadius = ta.getDimensionPixelSize(attr, cornerTopRightRadius); + } else if (attr == R.styleable.NiceImageView_corner_bottom_left_radius) { + cornerBottomLeftRadius = ta.getDimensionPixelSize(attr, cornerBottomLeftRadius); + } else if (attr == R.styleable.NiceImageView_corner_bottom_right_radius) { + cornerBottomRightRadius = ta.getDimensionPixelSize(attr, cornerBottomRightRadius); + } else if (attr == R.styleable.NiceImageView_mask_color) { + maskColor = ta.getColor(attr, maskColor); + } + } + ta.recycle(); + + borderRadii = new float[8]; + srcRadii = new float[8]; + + borderRectF = new RectF(); + srcRectF = new RectF(); + + paint = new Paint(); + path = new Path(); + + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) { + xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN); + } else { + xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT); + srcPath = new Path(); + } + + calculateRadii(); + clearInnerBorderWidth(); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + width = w; + height = h; + + initBorderRectF(); + initSrcRectF(); + } + + @Override + protected void onDraw(Canvas canvas) { + // 使用图形混合模式来显示指定区域的图片 + canvas.saveLayer(srcRectF, null, Canvas.ALL_SAVE_FLAG); + if (!isCoverSrc) { + float sx = 1.0f * (width - 2 * borderWidth - 2 * innerBorderWidth) / width; + float sy = 1.0f * (height - 2 * borderWidth - 2 * innerBorderWidth) / height; + // 缩小画布,使图片内容不被borders覆盖 + canvas.scale(sx, sy, width / 2.0f, height / 2.0f); + } + super.onDraw(canvas); + paint.reset(); + path.reset(); + if (isCircle) { + path.addCircle(width / 2.0f, height / 2.0f, radius, Path.Direction.CCW); + } else { + path.addRoundRect(srcRectF, srcRadii, Path.Direction.CCW); + } + + paint.setAntiAlias(true); + paint.setStyle(Paint.Style.FILL); + paint.setXfermode(xfermode); + //9.0及以上系统图片一次圆一次方的解决办法 + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) { + canvas.drawPath(path, paint); + } else { + srcPath.addRect(srcRectF, Path.Direction.CCW); + // 计算tempPath和path的差集 + srcPath.op(path, Path.Op.DIFFERENCE); + canvas.drawPath(srcPath, paint); + srcPath.reset();//1 + } + paint.setXfermode(null); + + // 绘制遮罩 + if (maskColor != 0) { + paint.setColor(maskColor); + canvas.drawPath(path, paint); + } + // 恢复画布 + canvas.restore(); + // 绘制边框 + drawBorders(canvas); + } + + private void drawBorders(Canvas canvas) { + if (isCircle) { + if (borderWidth > 0) { + drawCircleBorder(canvas, borderWidth, borderColor, radius - borderWidth / 2.0f); + } + if (innerBorderWidth > 0) { + drawCircleBorder(canvas, innerBorderWidth, innerBorderColor, radius - borderWidth - innerBorderWidth / 2.0f); + } + } else { + if (borderWidth > 0) { + drawRectFBorder(canvas, borderWidth, borderColor, borderRectF, borderRadii); + } + } + } + + private void drawCircleBorder(Canvas canvas, int borderWidth, int borderColor, float radius) { + initBorderPaint(borderWidth, borderColor); + path.addCircle(width / 2.0f, height / 2.0f, radius, Path.Direction.CCW); + canvas.drawPath(path, paint); + } + + private void drawRectFBorder(Canvas canvas, int borderWidth, int borderColor, RectF rectF, float[] radii) { + initBorderPaint(borderWidth, borderColor); + path.addRoundRect(rectF, radii, Path.Direction.CCW); + canvas.drawPath(path, paint); + } + + private void initBorderPaint(int borderWidth, int borderColor) { + path.reset(); + paint.setStrokeWidth(borderWidth); + paint.setColor(borderColor); + paint.setStyle(Paint.Style.STROKE); + } + + /** + * 计算外边框的RectF + */ + private void initBorderRectF() { + if (!isCircle) { + borderRectF.set(borderWidth / 2.0f, borderWidth / 2.0f, width - borderWidth / 2.0f, height - borderWidth / 2.0f); + } + } + + /** + * 计算图片原始区域的RectF + */ + private void initSrcRectF() { + if (isCircle) { + radius = Math.min(width, height) / 2.0f; + srcRectF.set(width / 2.0f - radius, height / 2.0f - radius, width / 2.0f + radius, height / 2.0f + radius); + } else { + srcRectF.set(0, 0, width, height); + if (isCoverSrc) { + srcRectF = borderRectF; + } + } + } + + /** + * 计算RectF的圆角半径 + */ + private void calculateRadii() { + if (isCircle) { + return; + } + if (cornerRadius > 0) { + for (int i = 0; i < borderRadii.length; i++) { + borderRadii[i] = cornerRadius; + srcRadii[i] = cornerRadius - borderWidth / 2.0f; + } + } else { + borderRadii[0] = borderRadii[1] = cornerTopLeftRadius; + borderRadii[2] = borderRadii[3] = cornerTopRightRadius; + borderRadii[4] = borderRadii[5] = cornerBottomRightRadius; + borderRadii[6] = borderRadii[7] = cornerBottomLeftRadius; + + srcRadii[0] = srcRadii[1] = cornerTopLeftRadius - borderWidth / 2.0f; + srcRadii[2] = srcRadii[3] = cornerTopRightRadius - borderWidth / 2.0f; + srcRadii[4] = srcRadii[5] = cornerBottomRightRadius - borderWidth / 2.0f; + srcRadii[6] = srcRadii[7] = cornerBottomLeftRadius - borderWidth / 2.0f; + } + } + + private void calculateRadiiAndRectF(boolean reset) { + if (reset) { + cornerRadius = 0; + } + calculateRadii(); + initBorderRectF(); + invalidate(); + } + + /** + * 目前圆角矩形情况下不支持inner_border,需要将其置0 + */ + private void clearInnerBorderWidth() { + if (!isCircle) { + this.innerBorderWidth = 0; + } + } + + public void isCoverSrc(boolean isCoverSrc) { + this.isCoverSrc = isCoverSrc; + initSrcRectF(); + invalidate(); + } + + public void isCircle(boolean isCircle) { + this.isCircle = isCircle; + clearInnerBorderWidth(); + initSrcRectF(); + invalidate(); + } + + public void setBorderWidth(int borderWidth) { + this.borderWidth = Utils.dp2px(context, borderWidth); + calculateRadiiAndRectF(false); + } + + public void setBorderColor(@ColorInt int borderColor) { + this.borderColor = borderColor; + invalidate(); + } + + public void setInnerBorderWidth(int innerBorderWidth) { + this.innerBorderWidth = Utils.dp2px(context, innerBorderWidth); + clearInnerBorderWidth(); + invalidate(); + } + + public void setInnerBorderColor(@ColorInt int innerBorderColor) { + this.innerBorderColor = innerBorderColor; + invalidate(); + } + + public void setCornerRadius(int cornerRadius) { + this.cornerRadius = Utils.dp2px(context, cornerRadius); + calculateRadiiAndRectF(false); + } + + public void setCornerTopLeftRadius(int cornerTopLeftRadius) { + this.cornerTopLeftRadius = Utils.dp2px(context, cornerTopLeftRadius); + calculateRadiiAndRectF(true); + } + + public void setCornerTopRightRadius(int cornerTopRightRadius) { + this.cornerTopRightRadius = Utils.dp2px(context, cornerTopRightRadius); + calculateRadiiAndRectF(true); + } + + public void setCornerBottomLeftRadius(int cornerBottomLeftRadius) { + this.cornerBottomLeftRadius = Utils.dp2px(context, cornerBottomLeftRadius); + calculateRadiiAndRectF(true); + } + + public void setCornerBottomRightRadius(int cornerBottomRightRadius) { + this.cornerBottomRightRadius = Utils.dp2px(context, cornerBottomRightRadius); + calculateRadiiAndRectF(true); + } + + public void setMaskColor(@ColorInt int maskColor) { + this.maskColor = maskColor; + invalidate(); + } +} diff --git a/niceimageview/src/main/java/com/shehuan/niv/Utils.java b/niceimageview/src/main/java/com/shehuan/niv/Utils.java new file mode 100644 index 0000000..f9958ad --- /dev/null +++ b/niceimageview/src/main/java/com/shehuan/niv/Utils.java @@ -0,0 +1,11 @@ +package com.shehuan.niv; + + +import android.content.Context; + +public class Utils { + public static int dp2px(Context context, float dipValue) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (dipValue * scale + 0.5f); + } +} diff --git a/niceimageview/src/main/res/values/attrs.xml b/niceimageview/src/main/res/values/attrs.xml new file mode 100644 index 0000000..c7c51cd --- /dev/null +++ b/niceimageview/src/main/res/values/attrs.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/niceimageview/src/main/res/values/strings.xml b/niceimageview/src/main/res/values/strings.xml new file mode 100644 index 0000000..1a1d942 --- /dev/null +++ b/niceimageview/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + niceimageview + diff --git a/settings.gradle b/settings.gradle index f900a8b..9c18986 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,3 @@ -include ':app' +include ':app',':viewlibrary',':niceimageview' rootProject.name = "TTUtils" -include ':viewlibrary' +