主页固定显示
This commit is contained in:
@@ -31,6 +31,12 @@ android {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments = [AROUTER_MODULE_NAME: project.getName()]
|
||||
}
|
||||
}
|
||||
|
||||
dataBinding {
|
||||
enabled true
|
||||
}
|
||||
@@ -46,7 +52,10 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
// implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
implementation project(path: ':niceimageview')
|
||||
implementation project(path: ':iconloader')
|
||||
|
||||
//保持1.3.1 更新会报错
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
@@ -59,6 +68,18 @@ dependencies {
|
||||
implementation "androidx.fragment:fragment:1.4.1"
|
||||
implementation "androidx.viewpager2:viewpager2:1.0.0"
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
// Room依赖
|
||||
implementation "androidx.room:room-runtime:2.3.0"
|
||||
implementation "androidx.room:room-rxjava3:2.3.0"
|
||||
annotationProcessor "androidx.room:room-compiler:2.3.0"
|
||||
// ViewModel和LiveData
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel:2.3.0"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata:2.3.0"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime:2.3.0"
|
||||
annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.3.0"
|
||||
|
||||
// 添加这行,使用 BOM 统一 Kotlin 相关库的版本
|
||||
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.10"))
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.3.0'
|
||||
@@ -80,4 +101,48 @@ dependencies {
|
||||
//Gson
|
||||
implementation 'com.google.code.gson:gson:2.9.0'
|
||||
implementation 'com.google.zxing:core:3.5.0'
|
||||
//生命周期管理
|
||||
implementation 'com.trello.rxlifecycle4:rxlifecycle:4.0.2'
|
||||
implementation 'com.trello.rxlifecycle4:rxlifecycle-android:4.0.2'
|
||||
implementation 'com.trello.rxlifecycle4:rxlifecycle-components:4.0.2'
|
||||
implementation 'com.trello.rxlifecycle4:rxlifecycle-components-preference:4.0.2'
|
||||
implementation 'com.trello.rxlifecycle4:rxlifecycle-android-lifecycle:4.0.2'
|
||||
|
||||
|
||||
implementation 'com.jakewharton.rxbinding4:rxbinding:4.0.0'
|
||||
/*https://github.com/JeremyLiao/LiveEventBus*/
|
||||
implementation 'com.jeremyliao:live-event-bus-x:1.7.3'
|
||||
|
||||
implementation 'com.facebook.rebound:rebound:0.3.8'
|
||||
//MMKV
|
||||
implementation 'com.tencent:mmkv-static:1.2.14'
|
||||
//bugly
|
||||
implementation 'com.tencent.bugly:crashreport:4.1.9.2'
|
||||
implementation 'com.iqiyi.xcrash:xcrash-android-lib:3.0.0'
|
||||
// 替换成最新版本, 需要注意的是api
|
||||
// 要与compiler匹配使用,均使用最新版可以保证兼容
|
||||
implementation 'com.alibaba:arouter-api:1.5.2'
|
||||
annotationProcessor 'com.alibaba:arouter-compiler:1.5.2'
|
||||
|
||||
//指示器
|
||||
implementation 'com.github.hackware1993:MagicIndicator:1.7.0'
|
||||
//工具类
|
||||
implementation 'com.blankj:utilcodex:1.31.0'
|
||||
//aria
|
||||
implementation 'com.arialyy.aria:core:3.8.15'
|
||||
annotationProcessor 'com.arialyy.aria:compiler:3.8.15'
|
||||
//状态栏透明
|
||||
implementation 'com.gitee.zackratos:UltimateBarX:0.8.0'
|
||||
//指示器
|
||||
implementation 'com.github.hackware1993:MagicIndicator:1.7.0'
|
||||
|
||||
// 吐司框架:https://github.com/getActivity/Toaster
|
||||
implementation 'com.github.getActivity:Toaster:12.6'
|
||||
// 权限请求框架:https://github.com/getActivity/XXPermissions
|
||||
implementation 'com.github.getActivity:XXPermissions:20.0'
|
||||
//autosize会改变第三方view的大小
|
||||
//https://github.com/JessYanCoding/AndroidAutoSize
|
||||
implementation 'me.jessyan:autosize:1.2.1'
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,22 +2,53 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.ttstd.dialer">
|
||||
|
||||
|
||||
<application
|
||||
android:name=".base.BaseApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".activity.MainActivity"
|
||||
android:exported="true">
|
||||
android:name=".activity.main.MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/AppThemeFitsSystemWindows">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.FileProvider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
<meta-data
|
||||
android:name="com.ttstd.elderlyassistant.annotations.CustomGlideModule"
|
||||
android:value="AppGlideModule" />
|
||||
|
||||
<meta-data
|
||||
android:name="design_width_in_dp"
|
||||
android:value="360" />
|
||||
|
||||
<meta-data
|
||||
android:name="design_height_in_dp"
|
||||
android:value="640" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.ttstd.dialer.activity;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.ttstd.dialer.R;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.ttstd.dialer.activity.main;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import com.tencent.mmkv.MMKV;
|
||||
import com.ttstd.dialer.R;
|
||||
import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
|
||||
import com.ttstd.dialer.config.CommonConfig;
|
||||
import com.ttstd.dialer.databinding.ActivityMainBinding;
|
||||
import com.ttstd.dialer.fragment.contact.ContactFragment;
|
||||
import com.ttstd.dialer.fragment.home.HomeFragment;
|
||||
import com.ttstd.dialer.view.BaseFragmentPagerAdapter;
|
||||
import com.ttstd.dialer.view.ScaleCircleNavigator;
|
||||
|
||||
import net.lucode.hackware.magicindicator.ViewPagerHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBinding> {
|
||||
protected MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
||||
|
||||
private FragmentManager mFragmentManager = getSupportFragmentManager();
|
||||
private BaseFragmentPagerAdapter mBaseFragmentPagerAdapter;
|
||||
|
||||
private List<Fragment> mFragments = new ArrayList<>();
|
||||
private HomeFragment mHomeFragment;
|
||||
private ContactFragment mContactFragment;
|
||||
|
||||
private int mCurrentIndex = 0;
|
||||
|
||||
private ScaleCircleNavigator mScaleCircleNavigator;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean setNightMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setfitWindow() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutId() {
|
||||
return R.layout.activity_main;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDataBinding() {
|
||||
mViewModel.setContext(this);
|
||||
mViewModel.setVDBinding(mViewDataBinding);
|
||||
mViewModel.setLifecycle(getLifecycleSubject());
|
||||
mViewDataBinding.setClick(new BtnClick());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initView() {
|
||||
mScaleCircleNavigator = new ScaleCircleNavigator(this);
|
||||
|
||||
if (mContactFragment == null) {
|
||||
mContactFragment = new ContactFragment();
|
||||
mFragments.add(mContactFragment);
|
||||
}
|
||||
|
||||
boolean contactHome = mMMKV.decodeBool(CommonConfig.CONTACT_HOME_PAGE, false);
|
||||
if (!contactHome) {
|
||||
mCurrentIndex += 1;
|
||||
}
|
||||
if (mHomeFragment == null) {
|
||||
mHomeFragment = new HomeFragment();
|
||||
mFragments.add(mHomeFragment);
|
||||
}
|
||||
|
||||
|
||||
mScaleCircleNavigator.setCircleCount(mFragments.size());
|
||||
mScaleCircleNavigator.notifyDataSetChanged();
|
||||
mScaleCircleNavigator.setNormalCircleColor(getColor(R.color.indicator_color_normal));
|
||||
mScaleCircleNavigator.setSelectedCircleColor(getColor(R.color.indicator_color_selected));
|
||||
mScaleCircleNavigator.setCircleClickListener(new ScaleCircleNavigator.OnCircleClickListener() {
|
||||
@Override
|
||||
public void onClick(int index) {
|
||||
mViewDataBinding.viewPager.setCurrentItem(index);
|
||||
}
|
||||
});
|
||||
mBaseFragmentPagerAdapter = new BaseFragmentPagerAdapter(mFragmentManager, mFragments);
|
||||
mViewDataBinding.viewPager.setAdapter(mBaseFragmentPagerAdapter);
|
||||
mViewDataBinding.viewPager.setOffscreenPageLimit(10);
|
||||
mViewDataBinding.viewPager.setCurrentItem(mCurrentIndex);
|
||||
|
||||
mViewDataBinding.magicIndicator.setNavigator(mScaleCircleNavigator);
|
||||
ViewPagerHelper.bind(mViewDataBinding.magicIndicator, mViewDataBinding.viewPager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initData() {
|
||||
|
||||
}
|
||||
|
||||
public class BtnClick {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.ttstd.dialer.activity.main;
|
||||
|
||||
import com.trello.rxlifecycle4.android.ActivityEvent;
|
||||
import com.ttstd.dialer.base.mvvm.BaseViewModel;
|
||||
import com.ttstd.dialer.databinding.ActivityMainBinding;
|
||||
|
||||
public class MainViewModel extends BaseViewModel<ActivityMainBinding, ActivityEvent> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.ttstd.dialer.annotations;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.bumptech.glide.GlideBuilder;
|
||||
import com.bumptech.glide.annotation.GlideModule;
|
||||
import com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool;
|
||||
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory;
|
||||
import com.bumptech.glide.load.engine.cache.LruResourceCache;
|
||||
import com.bumptech.glide.module.AppGlideModule;
|
||||
|
||||
@GlideModule
|
||||
public class CustomGlideModule extends AppGlideModule {
|
||||
@Override
|
||||
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
|
||||
super.applyOptions(context, builder);
|
||||
//内存缓存
|
||||
int memoryCacheSizeBytes = 1024 * 1024 * 32; // 20mb
|
||||
builder.setMemoryCache(new LruResourceCache(memoryCacheSizeBytes));
|
||||
//Bitmap 池
|
||||
int bitmapPoolSizeBytes = 1024 * 1024 * 64; // 30mb
|
||||
builder.setBitmapPool(new LruBitmapPool(bitmapPoolSizeBytes));
|
||||
//磁盘缓存
|
||||
int diskCacheSizeBytes = 1024 * 1024 * 128; // 100MB
|
||||
builder.setDiskCache(new InternalCacheDiskCacheFactory(context, diskCacheSizeBytes));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.ttstd.dialer.annotations;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.databinding.BindingAdapter;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.ttstd.dialer.R;
|
||||
|
||||
public class ImageViewAdapter {
|
||||
@BindingAdapter("android:src")
|
||||
public static void setSrc(ImageView view, Bitmap bitmap) {
|
||||
view.setImageBitmap(bitmap);
|
||||
}
|
||||
|
||||
@BindingAdapter("android:src")
|
||||
public static void setSrc(ImageView view, int resId) {
|
||||
view.setImageResource(resId);
|
||||
}
|
||||
|
||||
@BindingAdapter("imageUrl")
|
||||
public static void setSrc(ImageView imageView, String url) {
|
||||
Glide.with(imageView.getContext())
|
||||
.load(url)
|
||||
.error(R.mipmap.ic_launcher)
|
||||
.centerCrop()
|
||||
.into(imageView);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义设置图片属性 - 在匹配时自定义命名空间会被忽略
|
||||
*/
|
||||
@BindingAdapter({"imageUrl", "error"})
|
||||
public static void loadImage(ImageView imageView, String url, Drawable error) {
|
||||
Glide.with(imageView.getContext())
|
||||
.load(url)
|
||||
.error(error)
|
||||
.into(imageView);
|
||||
}
|
||||
|
||||
@BindingAdapter({"imageAvatarUrl", "error"})
|
||||
public static void loadAvatarImage(ImageView imageView, String url, Drawable error) {
|
||||
Glide.with(imageView.getContext())
|
||||
.load(url)
|
||||
.error(error)
|
||||
.into(imageView);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.ttstd.dialer.base;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.ttstd.dialer.utils.ScreenUtils;
|
||||
|
||||
import me.jessyan.autosize.AutoSizeCompat;
|
||||
|
||||
public class BaseAlertDialogBuilder extends AlertDialog.Builder {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final float ALERT_BASE_WIDTH = 480f;
|
||||
private static final float ALERT_BASE_WIDTH_TABLE = 640f;
|
||||
|
||||
public BaseAlertDialogBuilder(@NonNull Context context) {
|
||||
super(adjustAutoSize(context));
|
||||
}
|
||||
|
||||
public BaseAlertDialogBuilder(@NonNull Context context, int themeResId) {
|
||||
super(adjustAutoSize(context), themeResId);
|
||||
}
|
||||
|
||||
private static Context adjustAutoSize(Context context) {
|
||||
return new ContextWrapper(context) {
|
||||
private Resources mResources;
|
||||
|
||||
{
|
||||
Resources oldResources = super.getResources();
|
||||
mResources = new Resources(oldResources.getAssets(), oldResources.getDisplayMetrics(), oldResources.getConfiguration());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resources getResources() {
|
||||
if (ScreenUtils.isTablet(context)) {
|
||||
AutoSizeCompat.autoConvertDensityBaseOnWidth(mResources, ALERT_BASE_WIDTH_TABLE);
|
||||
} else {
|
||||
AutoSizeCompat.autoConvertDensityBaseOnWidth(mResources, ALERT_BASE_WIDTH);
|
||||
}
|
||||
return mResources;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
100
app/src/main/java/com/ttstd/dialer/base/BaseApplication.java
Normal file
100
app/src/main/java/com/ttstd/dialer/base/BaseApplication.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package com.ttstd.dialer.base;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.alibaba.android.arouter.launcher.ARouter;
|
||||
import com.arialyy.aria.core.Aria;
|
||||
import com.hjq.toast.Toaster;
|
||||
import com.tencent.bugly.crashreport.CrashReport;
|
||||
import com.tencent.mmkv.MMKV;
|
||||
import com.ttstd.dialer.BuildConfig;
|
||||
import com.ttstd.dialer.config.CommonConfig;
|
||||
import com.ttstd.dialer.utils.SystemUtils;
|
||||
|
||||
|
||||
public class BaseApplication extends Application {
|
||||
private static final String TAG = "BaseApplication";
|
||||
|
||||
/**
|
||||
* ViewModel中因为经常旋转导致弱引用为空
|
||||
*/
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private static Context context;
|
||||
|
||||
public static Context getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
Log.e(TAG, "onCreate: ");
|
||||
context = getApplicationContext();
|
||||
if (!BuildConfig.DEBUG) {
|
||||
catchException();
|
||||
}
|
||||
// 在开始分析的地方调用,传入路径
|
||||
// 如果是放到外部路径,需要添加权限
|
||||
// 默认存储在/sdcard/Android/data/packagename/files
|
||||
// Debug.startMethodTracing("App" + System.currentTimeMillis());
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
Log.e(TAG, "init: ");
|
||||
if (SystemUtils.isMainProcessName(this, android.os.Process.myPid())) {
|
||||
String rootDir = MMKV.initialize(this);
|
||||
Log.e(TAG, "mmkv root: " + rootDir);
|
||||
|
||||
if (BuildConfig.DEBUG) { // 这两行必须写在init之前,否则这些配置在init过程中将无效
|
||||
ARouter.openLog(); // 打印日志
|
||||
ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
|
||||
}
|
||||
ARouter.init(this); // 尽可能早,推荐在Application中初始化
|
||||
|
||||
// 初始化 Toast 框架
|
||||
Toaster.init(this);
|
||||
|
||||
Log.e(TAG, "slowInit: ");
|
||||
Aria.init(this);
|
||||
CrashReport.initCrashReport(getApplicationContext(), "845e3ed68c", false);
|
||||
CrashReport.setDeviceId(this, Build.MODEL);
|
||||
xcrash.XCrash.init(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void catchException() {
|
||||
Thread.setDefaultUncaughtExceptionHandler(
|
||||
new Thread.UncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void uncaughtException(Thread t, Throwable e) {
|
||||
Log.e("捕获异常子线程:", Thread.currentThread().getName() +
|
||||
"在:" + e.getStackTrace()[0].getClassName());
|
||||
}
|
||||
}
|
||||
);
|
||||
//下面是新增方法!
|
||||
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
try {
|
||||
Looper.loop(); //会先执行这个方法,然后在执行下面的异常捕获方法!
|
||||
} catch (Exception e) {
|
||||
Log.e("捕获异常主线程:", Thread.currentThread().getName() + "在:" + e.getStackTrace()[0].getClassName());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.ttstd.dialer.base;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.graphics.Insets;
|
||||
import androidx.core.view.OnApplyWindowInsetsListener;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
|
||||
import com.ttstd.dialer.R;
|
||||
import com.ttstd.dialer.base.rx.BaseRxActivity;
|
||||
import com.zackratos.ultimatebarx.ultimatebarx.java.UltimateBarX;
|
||||
|
||||
public abstract class BaseDataBindingActivity extends BaseRxActivity {
|
||||
|
||||
public BaseDataBindingActivity() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// StatusBarUtil.init(this);
|
||||
UltimateBarX.statusBar(this)
|
||||
.transparent()
|
||||
.colorRes(R.color.colorPrimaryDark)
|
||||
.light(setNightMode())
|
||||
.fitWindow(setfitWindow())
|
||||
.apply();
|
||||
UltimateBarX.navigationBar(this)
|
||||
.transparent()
|
||||
.colorRes(R.color.colorPrimaryDark)
|
||||
.light(setNightMode())
|
||||
.fitWindow(setfitWindow())
|
||||
.apply();
|
||||
initDataBinding();
|
||||
initView();
|
||||
initData();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否是黑色状态栏
|
||||
*/
|
||||
// protected abstract boolean setNightMode();
|
||||
public boolean setNightMode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否是入侵
|
||||
*/
|
||||
// protected abstract boolean setNightMode();
|
||||
public boolean setfitWindow() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
protected abstract void initDataBinding();
|
||||
|
||||
/**
|
||||
* 初始化视图
|
||||
*/
|
||||
protected abstract void initView();
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
protected abstract void initData();
|
||||
|
||||
public void addNavigationBarBottomPadding(View view) {
|
||||
UltimateBarX.addNavigationBarBottomPadding(view);
|
||||
if (Build.VERSION.SDK_INT >= 35) {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(view, new OnApplyWindowInsetsListener() {
|
||||
@Override
|
||||
public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
|
||||
Insets systemInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars());
|
||||
v.setPadding(0, 0, 0, systemInsets.bottom);
|
||||
return insets;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.ttstd.dialer.base;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.ttstd.dialer.base.rx.BaseRxDialogFragment;
|
||||
|
||||
public abstract class BaseDialogFragment extends BaseRxDialogFragment {
|
||||
|
||||
protected boolean isViewInitiated;
|
||||
protected boolean isVisibleToUser;
|
||||
protected boolean isDataInitiated;
|
||||
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
isViewInitiated = true;
|
||||
prepareFetchData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
this.isVisibleToUser = isVisibleToUser;
|
||||
prepareFetchData();
|
||||
}
|
||||
|
||||
public abstract void fetchData();
|
||||
|
||||
public boolean prepareFetchData() {
|
||||
return prepareFetchData(false);
|
||||
}
|
||||
|
||||
public boolean prepareFetchData(boolean forceUpdate) {
|
||||
if (isVisibleToUser && isViewInitiated && (!isDataInitiated || forceUpdate)) {
|
||||
fetchData();
|
||||
//注释掉保证每次都更新数据
|
||||
// isDataInitiated = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
44
app/src/main/java/com/ttstd/dialer/base/BaseFragment.java
Normal file
44
app/src/main/java/com/ttstd/dialer/base/BaseFragment.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package com.ttstd.dialer.base;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.ttstd.dialer.base.rx.BaseRxFragment;
|
||||
|
||||
public abstract class BaseFragment extends BaseRxFragment {
|
||||
|
||||
protected boolean isViewInitiated;
|
||||
protected boolean isVisibleToUser;
|
||||
protected boolean isDataInitiated;
|
||||
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
isViewInitiated = true;
|
||||
prepareFetchData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
this.isVisibleToUser = isVisibleToUser;
|
||||
prepareFetchData();
|
||||
}
|
||||
|
||||
public abstract void fetchData();
|
||||
|
||||
public boolean prepareFetchData() {
|
||||
return prepareFetchData(false);
|
||||
}
|
||||
|
||||
public boolean prepareFetchData(boolean forceUpdate) {
|
||||
if (isVisibleToUser && isViewInitiated && (!isDataInitiated || forceUpdate)) {
|
||||
fetchData();
|
||||
//注释掉保证每次都更新数据
|
||||
// isDataInitiated = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.ttstd.dialer.base;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.graphics.Insets;
|
||||
import androidx.core.view.OnApplyWindowInsetsListener;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
|
||||
import com.ttstd.dialer.R;
|
||||
import com.ttstd.dialer.base.rx.BaseRxActivity;
|
||||
import com.zackratos.ultimatebarx.ultimatebarx.java.UltimateBarX;
|
||||
|
||||
import me.jessyan.autosize.AutoSizeCompat;
|
||||
|
||||
public abstract class BaseTransparentActivity extends BaseRxActivity {
|
||||
|
||||
public BaseTransparentActivity() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// StatusBarUtil.init(this);
|
||||
UltimateBarX.statusBar(this)
|
||||
.transparent()
|
||||
.colorRes(R.color.colorPrimaryDark)
|
||||
.light(setNightMode())
|
||||
.fitWindow(setfitWindow())
|
||||
.apply();
|
||||
UltimateBarX.navigationBar(this)
|
||||
.transparent()
|
||||
.colorRes(R.color.colorPrimaryDark)
|
||||
.light(setNightMode())
|
||||
.fitWindow(setfitWindow())
|
||||
.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* 修补autozie RecyclerView item大小不一致
|
||||
*/
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
AutoSizeCompat.autoConvertDensityOfGlobal(getResources());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置布局
|
||||
*/
|
||||
protected abstract int getLayoutId();
|
||||
|
||||
/**
|
||||
* @return 是否是黑色状态栏
|
||||
*/
|
||||
// protected abstract boolean setNightMode();
|
||||
public boolean setNightMode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否是入侵
|
||||
*/
|
||||
// protected abstract boolean setNightMode();
|
||||
public boolean setfitWindow() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param view android 15 edge-to-edge会覆盖导航栏
|
||||
*/
|
||||
public void addNavigationBarBottomPadding(View view) {
|
||||
UltimateBarX.addNavigationBarBottomPadding(view);
|
||||
if (Build.VERSION.SDK_INT >= 35) {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(view, new OnApplyWindowInsetsListener() {
|
||||
@Override
|
||||
public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
|
||||
Insets systemInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars());
|
||||
v.setPadding(0, 0, 0, systemInsets.bottom);
|
||||
return insets;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.ttstd.dialer.base.mvp;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.ttstd.dialer.base.BaseTransparentActivity;
|
||||
|
||||
@Deprecated
|
||||
public abstract class BaseMvpActivity extends BaseTransparentActivity {
|
||||
|
||||
public BaseMvpActivity() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(getLayoutId());
|
||||
initView();
|
||||
initData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化视图
|
||||
*/
|
||||
protected abstract void initView();
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
protected abstract void initData();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.ttstd.dialer.base.mvp;
|
||||
|
||||
@Deprecated
|
||||
public interface BasePresenter<V extends BaseView> {
|
||||
void attachView(V view);
|
||||
|
||||
void detachView();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.ttstd.dialer.base.mvp;
|
||||
|
||||
@Deprecated
|
||||
public interface BaseView {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.ttstd.dialer.base.mvvm;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.databinding.DataBindingUtil;
|
||||
import androidx.databinding.ViewDataBinding;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.ttstd.dialer.base.BaseTransparentActivity;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
|
||||
public abstract class BaseMvvmActivity<VM extends ViewModel, VDB extends ViewDataBinding> extends BaseTransparentActivity {
|
||||
|
||||
private static final String TAG = BaseMvvmActivity.class.getSimpleName();
|
||||
|
||||
protected VM mViewModel;
|
||||
protected VDB mViewDataBinding;
|
||||
protected Class<VM> vmClass;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
//ViewDataBinding
|
||||
mViewDataBinding = DataBindingUtil.setContentView(this, getLayoutId());
|
||||
mViewDataBinding.setLifecycleOwner(this);
|
||||
//ViewModel
|
||||
vmClass = (Class<VM>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
|
||||
boolean isAbstract = Modifier.isAbstract(vmClass.getModifiers());
|
||||
Log.e(TAG, "isLocalClass:" + vmClass.getSimpleName().equals(ViewModel.class.getSimpleName()) + " isAbstract:" + isAbstract);
|
||||
if (!isAbstract) {//不是一个抽象类
|
||||
mViewModel = new ViewModelProvider(this).get(vmClass);
|
||||
}
|
||||
initDataBinding();
|
||||
initView();
|
||||
initData();
|
||||
}
|
||||
|
||||
protected abstract void initDataBinding();
|
||||
|
||||
/**
|
||||
* 初始化视图
|
||||
*/
|
||||
protected abstract void initView();
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
protected abstract void initData();
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.ttstd.dialer.base.mvvm;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.databinding.ViewDataBinding;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import io.reactivex.rxjava3.subjects.BehaviorSubject;
|
||||
|
||||
public abstract class BaseViewModel<VDB extends ViewDataBinding, T> extends ViewModel {
|
||||
|
||||
/**
|
||||
* 当前viewmodel对应的页面binding
|
||||
*/
|
||||
protected VDB binding;
|
||||
|
||||
public void setVDBinding(ViewDataBinding vdBinding) {
|
||||
binding = (VDB) vdBinding;
|
||||
}
|
||||
|
||||
public VDB getVDBinding() {
|
||||
if (binding == null) {
|
||||
throw new NullPointerException("BaseViewModel >> getVDBinding >> null!!!");
|
||||
}
|
||||
return binding;
|
||||
}
|
||||
|
||||
private WeakReference<Context> weakContext;
|
||||
|
||||
public void setContext(Context context) {
|
||||
if (weakContext == null) {
|
||||
weakContext = new WeakReference<>(context.getApplicationContext());
|
||||
}
|
||||
}
|
||||
|
||||
public Context getSafeContext() {
|
||||
if (weakContext == null) {
|
||||
throw new NullPointerException("BaseViewModel >> getCtx >> null!!!");
|
||||
}
|
||||
return weakContext.get();
|
||||
}
|
||||
|
||||
private BehaviorSubject<T> mBehaviorSubject;
|
||||
|
||||
public void setLifecycle(BehaviorSubject subject) {
|
||||
this.mBehaviorSubject = (BehaviorSubject<T>) subject;
|
||||
}
|
||||
|
||||
public BehaviorSubject<T> getLifecycle() {
|
||||
return mBehaviorSubject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
package com.ttstd.dialer.base.mvvm.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.databinding.DataBindingUtil;
|
||||
import androidx.databinding.ViewDataBinding;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.ttstd.dialer.base.BaseFragment;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
|
||||
/**
|
||||
* @author: lml
|
||||
* @date: 2021/12/15
|
||||
*/
|
||||
public abstract class BaseMvvmFragment<VM extends ViewModel, VDB extends ViewDataBinding> extends BaseFragment {
|
||||
protected String mTag = this.getClass().getSimpleName();
|
||||
/**
|
||||
* 是否顯示了
|
||||
*/
|
||||
protected boolean mIsVisible;
|
||||
/**
|
||||
* 是否準備好了-Created
|
||||
*/
|
||||
protected boolean mHasPrepare;
|
||||
|
||||
|
||||
protected VM mViewModel;
|
||||
protected VDB mViewDataBinding;
|
||||
protected Class<VM> vmClass;
|
||||
//
|
||||
// protected Toolbar toolbar;
|
||||
// protected View statusBarView;
|
||||
//
|
||||
protected Bundle bundle;//来自getArguments()
|
||||
protected Bundle savedInstanceState;
|
||||
|
||||
// protected Context context;
|
||||
|
||||
/**
|
||||
* 上下文
|
||||
*/
|
||||
private WeakReference<Context> ctx;
|
||||
|
||||
public Context getCtx() {
|
||||
return ctx == null ? null : ctx.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
// this.context = context;
|
||||
ctx = new WeakReference<>(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* onCreate、onResume里不能调用
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isAttached() {
|
||||
boolean flag = getCtx() != null && isAdded();
|
||||
Log.e(" >> isAttached >>", "flag = " + flag);
|
||||
return flag;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
//ViewDataBinding
|
||||
mViewDataBinding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false);
|
||||
mViewDataBinding.setLifecycleOwner(this);
|
||||
|
||||
//ViewModel
|
||||
vmClass = (Class<VM>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
|
||||
mViewModel = new ViewModelProvider(this).get(vmClass);
|
||||
//
|
||||
return mViewDataBinding.getRoot();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
// if (initStatusBarToolBar()) {
|
||||
// toolbar = getToolbar();
|
||||
// }
|
||||
//注册eventbus
|
||||
// if (getClass().isAnnotationPresent(BindEventBus.class))
|
||||
// EventBusManager.register(this);
|
||||
//
|
||||
|
||||
// fitsLayoutOverlap();
|
||||
initDataBinding();
|
||||
initView(bundle = getArguments());
|
||||
//
|
||||
initData(this.savedInstanceState = savedInstanceState);
|
||||
//
|
||||
if (mIsVisible) {
|
||||
onEnter();
|
||||
}
|
||||
mHasPrepare = true;
|
||||
//
|
||||
// LiveDataBus.get().with(ConstantUtils.DATA_BUS_LOADING_FRAGMENT, Boolean.class).observe(getActivity(), bool -> {
|
||||
// L.e(" >> LiveDataBus >> DATA_BUS_LOADING_FRAGMENT: %s", bool);
|
||||
// if(bool) {
|
||||
// showLoading(R.string.str_please_wait);
|
||||
// } else {
|
||||
// hideLoading();
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
mHasPrepare = false;
|
||||
mViewDataBinding = null;
|
||||
//移除eventbus
|
||||
// if (getClass().isAnnotationPresent(BindEventBus.class))
|
||||
// EventBusManager.unregister(this);
|
||||
//
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
if (mIsVisible == getUserVisibleHint())
|
||||
return;
|
||||
mIsVisible = getUserVisibleHint();
|
||||
if (mIsVisible) {
|
||||
if (!mHasPrepare)
|
||||
return;
|
||||
onEnter();
|
||||
} else {
|
||||
onExit();
|
||||
}
|
||||
}
|
||||
|
||||
@LayoutRes
|
||||
protected abstract int getLayoutId();
|
||||
|
||||
// protected abstract Toolbar getToolbar();
|
||||
|
||||
// protected View getStatusView() {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
protected abstract void initDataBinding();
|
||||
|
||||
protected abstract void initView(Bundle bundle);
|
||||
|
||||
protected abstract void initData(Bundle savedInstanceState);
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
// fitsLayoutOverlap();
|
||||
}
|
||||
|
||||
// protected boolean isImmersionBarEnabled() {
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// protected boolean initStatusBarToolBar() {
|
||||
// return true;
|
||||
// }
|
||||
|
||||
|
||||
// private void fitsLayoutOverlap() {
|
||||
// if (!isImmersionBarEnabled()) return;
|
||||
// if (statusBarView != null) {
|
||||
// ImmersionBar.setStatusBarView(getActivity(), statusBarView);
|
||||
// }
|
||||
// if (toolbar != null) {
|
||||
// ImmersionBar.setTitleBar(getActivity(), toolbar);
|
||||
// }
|
||||
// }
|
||||
|
||||
protected void hideInputMethod(Activity activity) {
|
||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||
View view = activity.getCurrentFocus();
|
||||
if (view != null) {
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
protected void hideInputMethod(Activity activity, EditText editText) {
|
||||
InputMethodManager imm = (InputMethodManager) editText.getContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||
View view = activity.getCurrentFocus();
|
||||
if (view != null) {
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
|
||||
}
|
||||
}
|
||||
|
||||
protected void showInputMethod(EditText editText) {
|
||||
InputMethodManager imm = (InputMethodManager) editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED);
|
||||
}
|
||||
|
||||
|
||||
// private CustomDialog mWaitDialog;
|
||||
//
|
||||
// public void showLoading(@StringRes int contentID) {
|
||||
// showLoading(contentID, R.color.white);
|
||||
// }
|
||||
//
|
||||
// public void showLoading(@StringRes int contentID, @ColorRes int color) {
|
||||
// hideLoading();
|
||||
// DialogX.init(getActivity());
|
||||
// if (color == R.color.white) {
|
||||
// mWaitDialog = DialogXUtil.getInstance().showLoading(getActivity(), getString(contentID), getResources().getColor(color));
|
||||
// } else {
|
||||
// mWaitDialog = DialogXUtil.getInstance().showLoading_black(getActivity(), getString(contentID), getResources().getColor(color));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public void updateLoadingTip(@StringRes int messageID, int percent) {
|
||||
// try {
|
||||
// if (mWaitDialog != null && mWaitDialog.isShow()) {
|
||||
// TextView tvTip = mWaitDialog.getCustomView().findViewById(R.id.tv_load_tip);
|
||||
// if (tvTip != null)
|
||||
// tvTip.setText(getResources().getString(messageID) + (percent == -1 ? "" : percent + "%"));
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public boolean isShowLoading() {
|
||||
// return mWaitDialog != null && mWaitDialog.isShow();
|
||||
// }
|
||||
//
|
||||
// public void hideLoading() {
|
||||
// try {
|
||||
// boolean isShow = isShowLoading();
|
||||
// L.d(" >> hideLoading :: isShow: %s", isShow);
|
||||
// if (isShow)
|
||||
// mWaitDialog.dismiss();
|
||||
// } catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* 進入界面
|
||||
*/
|
||||
protected void onEnter() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 離開界面
|
||||
*/
|
||||
protected void onExit() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.ttstd.dialer.base.rx;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.trello.rxlifecycle4.LifecycleProvider;
|
||||
import com.trello.rxlifecycle4.LifecycleTransformer;
|
||||
import com.trello.rxlifecycle4.RxLifecycle;
|
||||
import com.trello.rxlifecycle4.android.ActivityEvent;
|
||||
import com.trello.rxlifecycle4.android.RxLifecycleAndroid;
|
||||
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.subjects.BehaviorSubject;
|
||||
|
||||
/**
|
||||
* {@link com.trello.rxlifecycle4.components.RxActivity}
|
||||
* copied form RxActivity}
|
||||
*/
|
||||
public abstract class BaseRxActivity extends AppCompatActivity implements LifecycleProvider<ActivityEvent> {
|
||||
private final BehaviorSubject<ActivityEvent> lifecycleSubject = BehaviorSubject.create();
|
||||
|
||||
public BehaviorSubject<ActivityEvent> getLifecycleSubject() {
|
||||
return lifecycleSubject;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public final Observable<ActivityEvent> lifecycle() {
|
||||
return lifecycleSubject.hide();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public final <T> LifecycleTransformer<T> bindUntilEvent(@NonNull ActivityEvent event) {
|
||||
return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public final <T> LifecycleTransformer<T> bindToLifecycle() {
|
||||
return RxLifecycleAndroid.bindActivity(lifecycleSubject);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
lifecycleSubject.onNext(ActivityEvent.CREATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
lifecycleSubject.onNext(ActivityEvent.START);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
lifecycleSubject.onNext(ActivityEvent.RESUME);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
protected void onPause() {
|
||||
lifecycleSubject.onNext(ActivityEvent.PAUSE);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
protected void onStop() {
|
||||
lifecycleSubject.onNext(ActivityEvent.STOP);
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
protected void onDestroy() {
|
||||
lifecycleSubject.onNext(ActivityEvent.DESTROY);
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.ttstd.dialer.base.rx;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.trello.rxlifecycle4.LifecycleProvider;
|
||||
import com.trello.rxlifecycle4.LifecycleTransformer;
|
||||
import com.trello.rxlifecycle4.RxLifecycle;
|
||||
import com.trello.rxlifecycle4.android.FragmentEvent;
|
||||
import com.trello.rxlifecycle4.android.RxLifecycleAndroid;
|
||||
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.subjects.BehaviorSubject;
|
||||
|
||||
/**
|
||||
* {@link com.trello.rxlifecycle4.components.RxFragment}
|
||||
* copied form RxFragment}
|
||||
*/
|
||||
public class BaseRxDialogFragment extends DialogFragment implements LifecycleProvider<FragmentEvent> {
|
||||
private final BehaviorSubject<FragmentEvent> lifecycleSubject = BehaviorSubject.create();
|
||||
|
||||
public BehaviorSubject<FragmentEvent> getLifecycleSubject() {
|
||||
return lifecycleSubject;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public final Observable<FragmentEvent> lifecycle() {
|
||||
return lifecycleSubject.hide();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public final <T> LifecycleTransformer<T> bindUntilEvent(@NonNull FragmentEvent event) {
|
||||
return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public final <T> LifecycleTransformer<T> bindToLifecycle() {
|
||||
return RxLifecycleAndroid.bindFragment(lifecycleSubject);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onAttach(android.app.Activity activity) {
|
||||
super.onAttach(activity);
|
||||
lifecycleSubject.onNext(FragmentEvent.ATTACH);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
lifecycleSubject.onNext(FragmentEvent.CREATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
lifecycleSubject.onNext(FragmentEvent.CREATE_VIEW);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
lifecycleSubject.onNext(FragmentEvent.START);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
lifecycleSubject.onNext(FragmentEvent.RESUME);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onPause() {
|
||||
lifecycleSubject.onNext(FragmentEvent.PAUSE);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onStop() {
|
||||
lifecycleSubject.onNext(FragmentEvent.STOP);
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onDestroyView() {
|
||||
lifecycleSubject.onNext(FragmentEvent.DESTROY_VIEW);
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onDestroy() {
|
||||
lifecycleSubject.onNext(FragmentEvent.DESTROY);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onDetach() {
|
||||
lifecycleSubject.onNext(FragmentEvent.DETACH);
|
||||
super.onDetach();
|
||||
}
|
||||
}
|
||||
|
||||
123
app/src/main/java/com/ttstd/dialer/base/rx/BaseRxFragment.java
Normal file
123
app/src/main/java/com/ttstd/dialer/base/rx/BaseRxFragment.java
Normal file
@@ -0,0 +1,123 @@
|
||||
package com.ttstd.dialer.base.rx;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.trello.rxlifecycle4.LifecycleProvider;
|
||||
import com.trello.rxlifecycle4.LifecycleTransformer;
|
||||
import com.trello.rxlifecycle4.RxLifecycle;
|
||||
import com.trello.rxlifecycle4.android.FragmentEvent;
|
||||
import com.trello.rxlifecycle4.android.RxLifecycleAndroid;
|
||||
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.subjects.BehaviorSubject;
|
||||
|
||||
/**
|
||||
* {@link com.trello.rxlifecycle4.components.RxFragment}
|
||||
* copied form RxFragment}
|
||||
*/
|
||||
public class BaseRxFragment extends Fragment implements LifecycleProvider<FragmentEvent> {
|
||||
private final BehaviorSubject<FragmentEvent> lifecycleSubject = BehaviorSubject.create();
|
||||
|
||||
public BehaviorSubject<FragmentEvent> getLifecycleSubject() {
|
||||
return lifecycleSubject;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public final Observable<FragmentEvent> lifecycle() {
|
||||
return lifecycleSubject.hide();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public final <T> LifecycleTransformer<T> bindUntilEvent(@NonNull FragmentEvent event) {
|
||||
return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public final <T> LifecycleTransformer<T> bindToLifecycle() {
|
||||
return RxLifecycleAndroid.bindFragment(lifecycleSubject);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onAttach(android.app.Activity activity) {
|
||||
super.onAttach(activity);
|
||||
lifecycleSubject.onNext(FragmentEvent.ATTACH);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
lifecycleSubject.onNext(FragmentEvent.CREATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
lifecycleSubject.onNext(FragmentEvent.CREATE_VIEW);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
lifecycleSubject.onNext(FragmentEvent.START);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
lifecycleSubject.onNext(FragmentEvent.RESUME);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onPause() {
|
||||
lifecycleSubject.onNext(FragmentEvent.PAUSE);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onStop() {
|
||||
lifecycleSubject.onNext(FragmentEvent.STOP);
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onDestroyView() {
|
||||
lifecycleSubject.onNext(FragmentEvent.DESTROY_VIEW);
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onDestroy() {
|
||||
lifecycleSubject.onNext(FragmentEvent.DESTROY);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onDetach() {
|
||||
lifecycleSubject.onNext(FragmentEvent.DETACH);
|
||||
super.onDetach();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.ttstd.dialer.base.rx;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.trello.rxlifecycle4.LifecycleProvider;
|
||||
import com.trello.rxlifecycle4.LifecycleTransformer;
|
||||
import com.trello.rxlifecycle4.RxLifecycle;
|
||||
import com.trello.rxlifecycle4.android.ActivityEvent;
|
||||
import com.trello.rxlifecycle4.android.RxLifecycleAndroid;
|
||||
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.subjects.BehaviorSubject;
|
||||
|
||||
public abstract class BaseRxService extends Service implements LifecycleProvider<ActivityEvent> {
|
||||
private final BehaviorSubject<ActivityEvent> lifecycleSubject = BehaviorSubject.create();
|
||||
|
||||
public BehaviorSubject<ActivityEvent> getLifecycleSubject() {
|
||||
return lifecycleSubject;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public final Observable<ActivityEvent> lifecycle() {
|
||||
return lifecycleSubject.hide();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public final <T> LifecycleTransformer<T> bindUntilEvent(@NonNull ActivityEvent event) {
|
||||
return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public final <T> LifecycleTransformer<T> bindToLifecycle() {
|
||||
return RxLifecycleAndroid.bindActivity(lifecycleSubject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
lifecycleSubject.onNext(ActivityEvent.CREATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
return super.onStartCommand(intent, flags, startId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
lifecycleSubject.onNext(ActivityEvent.STOP);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.ttstd.dialer.config;
|
||||
|
||||
public class CommonConfig {
|
||||
public static final String MMKV_ID = "InterProcessKV";
|
||||
|
||||
public static final String CONTACT_HOME_PAGE = "contact_home_page_key";
|
||||
|
||||
}
|
||||
28
app/src/main/java/com/ttstd/dialer/contact/AppDatabase.java
Normal file
28
app/src/main/java/com/ttstd/dialer/contact/AppDatabase.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package com.ttstd.dialer.contact;
|
||||
|
||||
import androidx.room.Database;
|
||||
import androidx.room.Room;
|
||||
import androidx.room.RoomDatabase;
|
||||
import android.content.Context;
|
||||
|
||||
@Database(entities = {Contact.class}, version = 1, exportSchema = false)
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
public abstract ContactDao contactDao();
|
||||
|
||||
private static volatile AppDatabase INSTANCE;
|
||||
|
||||
// 单例模式获取数据库实例
|
||||
public static AppDatabase getDatabase(final Context context) {
|
||||
if (INSTANCE == null) {
|
||||
synchronized (AppDatabase.class) {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
|
||||
AppDatabase.class, "contact_database")
|
||||
.allowMainThreadQueries() // 为了简化示例,允许主线程查询
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
75
app/src/main/java/com/ttstd/dialer/contact/Contact.java
Normal file
75
app/src/main/java/com/ttstd/dialer/contact/Contact.java
Normal file
@@ -0,0 +1,75 @@
|
||||
package com.ttstd.dialer.contact;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
@Entity(tableName = "contacts")
|
||||
public class Contact implements Serializable {
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
private int id;
|
||||
private String name;
|
||||
private String phoneNumber;
|
||||
private String avatar;
|
||||
|
||||
public Contact(String name, String phoneNumber) {
|
||||
this.name = name;
|
||||
this.phoneNumber = phoneNumber;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getPhoneNumber() {
|
||||
return phoneNumber;
|
||||
}
|
||||
|
||||
public void setPhoneNumber(String phoneNumber) {
|
||||
this.phoneNumber = phoneNumber;
|
||||
}
|
||||
|
||||
public String getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public void setAvatar(String avatar) {
|
||||
this.avatar = avatar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (obj instanceof Contact) {
|
||||
return Objects.equals(((Contact) obj).phoneNumber, phoneNumber)
|
||||
|| ((Contact) obj).id == id;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return JsonParser.parseString(new Gson().toJson(this)).getAsJsonObject().toString();
|
||||
}
|
||||
}
|
||||
|
||||
37
app/src/main/java/com/ttstd/dialer/contact/ContactDao.java
Normal file
37
app/src/main/java/com/ttstd/dialer/contact/ContactDao.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package com.ttstd.dialer.contact;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface ContactDao {
|
||||
@Insert
|
||||
void insert(Contact contact);
|
||||
|
||||
@Update
|
||||
void update(Contact contact);
|
||||
|
||||
@Delete
|
||||
void delete(Contact contact);
|
||||
|
||||
@Query("DELETE FROM contacts WHERE id = :id")
|
||||
void deleteById(int id);
|
||||
|
||||
@Query("DELETE FROM contacts")
|
||||
void deleteAll();
|
||||
|
||||
@Query("SELECT * FROM contacts ORDER BY name ASC")
|
||||
List<Contact> getAllContacts();
|
||||
|
||||
@Query("SELECT * FROM contacts WHERE id = :id")
|
||||
Contact getContactById(int id);
|
||||
|
||||
@Query("SELECT * FROM contacts WHERE name LIKE :searchQuery OR phoneNumber LIKE :searchQuery")
|
||||
List<Contact> searchContacts(String searchQuery);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.ttstd.dialer.contact;
|
||||
|
||||
import android.content.Context;
|
||||
import java.util.List;
|
||||
|
||||
public class ContactRepository {
|
||||
private ContactDao mContactDao;
|
||||
private List<Contact> mAllContacts;
|
||||
|
||||
// 构造函数,获取数据库访问对象
|
||||
public ContactRepository(Context context) {
|
||||
AppDatabase db = AppDatabase.getDatabase(context);
|
||||
mContactDao = db.contactDao();
|
||||
mAllContacts = mContactDao.getAllContacts();
|
||||
}
|
||||
|
||||
// 获取所有联系人
|
||||
public List<Contact> getAllContacts() {
|
||||
return mContactDao.getAllContacts();
|
||||
}
|
||||
|
||||
// 根据ID获取联系人
|
||||
public Contact getContactById(int id) {
|
||||
return mContactDao.getContactById(id);
|
||||
}
|
||||
|
||||
// 搜索联系人
|
||||
public List<Contact> searchContacts(String query) {
|
||||
return mContactDao.searchContacts("%" + query + "%");
|
||||
}
|
||||
|
||||
// 添加联系人
|
||||
public void insert(Contact contact) {
|
||||
mContactDao.insert(contact);
|
||||
}
|
||||
|
||||
// 更新联系人
|
||||
public void update(Contact contact) {
|
||||
mContactDao.update(contact);
|
||||
}
|
||||
|
||||
// 删除联系人
|
||||
public void delete(Contact contact) {
|
||||
mContactDao.delete(contact);
|
||||
}
|
||||
|
||||
// 根据ID删除联系人
|
||||
public void deleteById(int id) {
|
||||
mContactDao.deleteById(id);
|
||||
}
|
||||
|
||||
// 删除所有联系人
|
||||
public void deleteAll() {
|
||||
mContactDao.deleteAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.ttstd.dialer.contact;
|
||||
|
||||
import android.app.Application;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import java.util.List;
|
||||
|
||||
public class ContactViewModel extends AndroidViewModel {
|
||||
private ContactRepository mRepository;
|
||||
|
||||
public ContactViewModel(Application application) {
|
||||
super(application);
|
||||
mRepository = new ContactRepository(application);
|
||||
}
|
||||
|
||||
public List<Contact> getAllContacts() {
|
||||
return mRepository.getAllContacts();
|
||||
}
|
||||
|
||||
public Contact getContactById(int id) {
|
||||
return mRepository.getContactById(id);
|
||||
}
|
||||
|
||||
public List<Contact> searchContacts(String query) {
|
||||
return mRepository.searchContacts(query);
|
||||
}
|
||||
|
||||
public void insert(Contact contact) {
|
||||
mRepository.insert(contact);
|
||||
}
|
||||
|
||||
public void update(Contact contact) {
|
||||
mRepository.update(contact);
|
||||
}
|
||||
|
||||
public void delete(Contact contact) {
|
||||
mRepository.delete(contact);
|
||||
}
|
||||
|
||||
public void deleteById(int id) {
|
||||
mRepository.deleteById(id);
|
||||
}
|
||||
|
||||
public void deleteAll() {
|
||||
mRepository.deleteAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.ttstd.dialer.fragment.contact;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.ttstd.dialer.R;
|
||||
import com.ttstd.dialer.base.mvvm.fragment.BaseMvvmFragment;
|
||||
import com.ttstd.dialer.databinding.FragmentContactBinding;
|
||||
|
||||
public class ContactFragment extends BaseMvvmFragment<ContactViewModel, FragmentContactBinding> {
|
||||
private static final String TAG ="ContactFragment";
|
||||
|
||||
private Activity mContext;
|
||||
|
||||
@Override
|
||||
protected int getLayoutId() {
|
||||
return R.layout.fragment_contact;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDataBinding() {
|
||||
mContext = getActivity();
|
||||
mViewModel.setContext(mContext);
|
||||
mViewModel.setVDBinding(mViewDataBinding);
|
||||
mViewModel.setLifecycle(getLifecycleSubject());
|
||||
mViewDataBinding.setClick(new BtnClick());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initView(Bundle bundle) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initData(Bundle savedInstanceState) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchData() {
|
||||
|
||||
}
|
||||
|
||||
public class BtnClick{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.ttstd.dialer.fragment.contact;
|
||||
|
||||
import com.trello.rxlifecycle4.android.FragmentEvent;
|
||||
import com.ttstd.dialer.base.mvvm.BaseViewModel;
|
||||
import com.ttstd.dialer.databinding.FragmentContactBinding;
|
||||
|
||||
public class ContactViewModel extends BaseViewModel<FragmentContactBinding, FragmentEvent> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
package com.ttstd.dialer.fragment.home;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.hjq.toast.Toaster;
|
||||
import com.ttstd.dialer.R;
|
||||
import com.ttstd.dialer.base.BaseFragment;
|
||||
import com.ttstd.dialer.base.mvvm.fragment.BaseMvvmFragment;
|
||||
import com.ttstd.dialer.databinding.FragmentHomeBinding;
|
||||
import com.ttstd.dialer.utils.ApkUtils;
|
||||
import com.ttstd.dialer.utils.DataUtil;
|
||||
import com.ttstd.dialer.utils.LunarCalendarFestivalUtils;
|
||||
import com.ttstd.dialer.utils.TimeUtils;
|
||||
|
||||
/**
|
||||
* A simple {@link Fragment} subclass.
|
||||
* Use the {@link HomeFragment#newInstance} factory method to
|
||||
* create an instance of this fragment.
|
||||
*/
|
||||
public class HomeFragment extends BaseMvvmFragment<HomeViewModel, FragmentHomeBinding> {
|
||||
private static final String TAG = "HomeFragment";
|
||||
|
||||
private Activity mContext;
|
||||
private LunarCalendarFestivalUtils mFestivalUtils;
|
||||
|
||||
// TODO: Rename parameter arguments, choose names that match
|
||||
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
|
||||
private static final String ARG_PARAM1 = "param1";
|
||||
private static final String ARG_PARAM2 = "param2";
|
||||
|
||||
// TODO: Rename and change types of parameters
|
||||
private String mParam1;
|
||||
private String mParam2;
|
||||
|
||||
public HomeFragment() {
|
||||
// Required empty public constructor
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this factory method to create a new instance of
|
||||
* this fragment using the provided parameters.
|
||||
*
|
||||
* @param param1 Parameter 1.
|
||||
* @param param2 Parameter 2.
|
||||
* @return A new instance of fragment HomeFragment.
|
||||
*/
|
||||
// TODO: Rename and change types and number of parameters
|
||||
public static HomeFragment newInstance(String param1, String param2) {
|
||||
HomeFragment fragment = new HomeFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_PARAM1, param1);
|
||||
args.putString(ARG_PARAM2, param2);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (getArguments() != null) {
|
||||
mParam1 = getArguments().getString(ARG_PARAM1);
|
||||
mParam2 = getArguments().getString(ARG_PARAM2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutId() {
|
||||
return R.layout.fragment_home;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDataBinding() {
|
||||
mContext = getActivity();
|
||||
mViewModel.setContext(mContext);
|
||||
mViewModel.setVDBinding(mViewDataBinding);
|
||||
mViewModel.setLifecycle(getLifecycleSubject());
|
||||
mViewDataBinding.setClick(new BtnClick());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initView(Bundle bundle) {
|
||||
mFestivalUtils = new LunarCalendarFestivalUtils();
|
||||
setTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initData(Bundle savedInstanceState) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchData() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
Log.e(TAG, "onStart: ");
|
||||
registerReceivers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
Log.e(TAG, "onStop: ");
|
||||
unregisterReceivers();
|
||||
}
|
||||
|
||||
public void registerReceivers() {
|
||||
registerTimeReceiver();
|
||||
}
|
||||
|
||||
private void unregisterReceivers() {
|
||||
if (null != mTimeReceiver) {
|
||||
mContext.unregisterReceiver(mTimeReceiver);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 时间
|
||||
*/
|
||||
private void registerTimeReceiver() {
|
||||
if (null == mTimeReceiver) {
|
||||
mTimeReceiver = new TimeReceiver();
|
||||
}
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_DATE_CHANGED);
|
||||
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
|
||||
filter.addAction(Intent.ACTION_TIME_CHANGED);
|
||||
filter.addAction(Intent.ACTION_TIME_TICK);
|
||||
mContext.registerReceiver(mTimeReceiver, filter);
|
||||
}
|
||||
|
||||
private TimeReceiver mTimeReceiver;
|
||||
|
||||
private class TimeReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
Log.e("TimeReceiver", "onReceive: " + action);
|
||||
switch (action) {
|
||||
case Intent.ACTION_DATE_CHANGED:
|
||||
case Intent.ACTION_TIMEZONE_CHANGED:
|
||||
case Intent.ACTION_TIME_CHANGED:
|
||||
case Intent.ACTION_TIME_TICK:
|
||||
setTime();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setTime() {
|
||||
if (isAdded()) {
|
||||
mViewDataBinding.tvTime.setText(DataUtil.formatDateHour());
|
||||
// mViewDataBinding.tvDate.setText(DataUtil.formatDateDay());
|
||||
// mViewDataBinding.tvWeek.setText(TimeUtils.getWeek());
|
||||
// mViewDataBinding.tvLunar.setText(mFestivalUtils.getLunarCalendar());
|
||||
}
|
||||
}
|
||||
|
||||
public void openAppStore(String pkg) {
|
||||
Uri uri = Uri.parse("market://details?id=" + pkg);
|
||||
Intent storeIntent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
storeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
try {
|
||||
startActivity(storeIntent);
|
||||
} catch (Exception e1) {
|
||||
Log.e(TAG, "openWeixin storeIntent: " + e1.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public class BtnClick {
|
||||
public void openContact(View view) {
|
||||
Intent intent = new Intent();
|
||||
try {
|
||||
startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "openSettings: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void openSettings(View view) {
|
||||
Intent intent = new Intent(Settings.ACTION_SETTINGS);
|
||||
try {
|
||||
startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "openSettings: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void openDouyin(View view) {
|
||||
if (ApkUtils.isInstalled(mContext, "com.ss.android.ugc.aweme")) {
|
||||
ApkUtils.openPackage(mContext, "com.ss.android.ugc.aweme");
|
||||
} else {
|
||||
Toaster.show("抖音未安装,请安装后使用");
|
||||
}
|
||||
}
|
||||
|
||||
public void openWeixin(View view) {
|
||||
if (ApkUtils.isInstalled(mContext, "com.tencent.mm")) {
|
||||
Intent intent = new Intent();
|
||||
ComponentName cmp = new ComponentName("com.tencent.mm", "com.tencent.mm.ui.LauncherUI");
|
||||
intent.setAction(Intent.ACTION_MAIN);
|
||||
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
intent.setComponent(cmp);
|
||||
try {
|
||||
startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "launchWeChat: " + e.getMessage());
|
||||
Toaster.show("打开微信失败");
|
||||
openAppStore("com.tencent.mm");
|
||||
}
|
||||
} else {
|
||||
Toaster.show("微信未安装,请安装后使用");
|
||||
openAppStore("com.tencent.mm");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.ttstd.dialer.fragment.home;
|
||||
|
||||
import com.trello.rxlifecycle4.android.FragmentEvent;
|
||||
import com.ttstd.dialer.base.mvvm.BaseViewModel;
|
||||
import com.ttstd.dialer.databinding.FragmentHomeBinding;
|
||||
|
||||
public class HomeViewModel extends BaseViewModel<FragmentHomeBinding, FragmentEvent> {
|
||||
|
||||
}
|
||||
86
app/src/main/java/com/ttstd/dialer/utils/ApkUtils.java
Normal file
86
app/src/main/java/com/ttstd/dialer/utils/ApkUtils.java
Normal file
@@ -0,0 +1,86 @@
|
||||
package com.ttstd.dialer.utils;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.hjq.toast.Toaster;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ApkUtils {
|
||||
|
||||
private static final String TAG = "ApkUtils";
|
||||
|
||||
public static boolean isInstalled(Context context, String packageName) {
|
||||
if (TextUtils.isEmpty(packageName)) return false;
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
try {
|
||||
PackageInfo packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
|
||||
return packageInfo != null;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean openPackage(Context context, String packageName) {
|
||||
Context pkgContext = getPackageContext(context, packageName);
|
||||
Intent intent = getAppOpenIntentByPackageName(context, packageName);
|
||||
if (pkgContext != null && intent != null) {
|
||||
pkgContext.startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
Log.e(TAG, "openPackage: can not open " + packageName);
|
||||
Toaster.show("打开失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Context getPackageContext(Context context, String packageName) {
|
||||
Context pkgContext = null;
|
||||
if (context.getPackageName().equals(packageName)) {
|
||||
pkgContext = context;
|
||||
} else {
|
||||
// 创建第三方应用的上下文环境
|
||||
try {
|
||||
pkgContext = context.createPackageContext(packageName,
|
||||
Context.CONTEXT_IGNORE_SECURITY
|
||||
| Context.CONTEXT_INCLUDE_CODE);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return pkgContext;
|
||||
}
|
||||
|
||||
|
||||
public static Intent getAppOpenIntentByPackageName(Context context, String packageName) {
|
||||
//Activity完整名
|
||||
String mainAct = null;
|
||||
//根据包名寻找
|
||||
PackageManager pkgMag = context.getPackageManager();
|
||||
Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
List<ResolveInfo> list = pkgMag.queryIntentActivities(intent,
|
||||
PackageManager.GET_ACTIVITIES);
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
ResolveInfo info = list.get(i);
|
||||
if (info.activityInfo.packageName.equals(packageName)) {
|
||||
mainAct = info.activityInfo.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (TextUtils.isEmpty(mainAct)) {
|
||||
return null;
|
||||
}
|
||||
intent.setComponent(new ComponentName(packageName, mainAct));
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
29
app/src/main/java/com/ttstd/dialer/utils/DataUtil.java
Normal file
29
app/src/main/java/com/ttstd/dialer/utils/DataUtil.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package com.ttstd.dialer.utils;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class DataUtil {
|
||||
private static SimpleDateFormat day = new SimpleDateFormat("M月d日");
|
||||
private static SimpleDateFormat hour = new SimpleDateFormat("HH:mm");
|
||||
private static SimpleDateFormat minute = new SimpleDateFormat("mm");
|
||||
|
||||
/**
|
||||
* 格式化日期(精确到天)
|
||||
*/
|
||||
public static String formatDateDay() {
|
||||
return day.format(new Date());
|
||||
}
|
||||
|
||||
public static String formatDateTime() {
|
||||
return day.format(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期(hour)
|
||||
*/
|
||||
public static String formatDateHour() {
|
||||
return hour.format(new Date());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,600 @@
|
||||
package com.ttstd.dialer.utils;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 获取输入公历日期的生肖、天干地支、农历年、农历月、农历日、公历节日、农历节日、24节气等数据
|
||||
* DATE 2020.08.13
|
||||
* https://www.cnblogs.com/weihbs/p/13955786.html
|
||||
*/
|
||||
public class LunarCalendarFestivalUtils {
|
||||
//生肖年
|
||||
private String animal;
|
||||
//干支年
|
||||
private String ganZhiYear;
|
||||
//阴历年
|
||||
private String lunarYear;
|
||||
//阴历月
|
||||
private String lunarMonth;
|
||||
//阴历日
|
||||
private String lunarDay;
|
||||
//阳历节日
|
||||
private String solarFestival;
|
||||
//阴历节日
|
||||
private String lunarFestival;
|
||||
//节气
|
||||
private String lunarTerm;
|
||||
|
||||
public LunarCalendarFestivalUtils() {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
Date date = new Date(System.currentTimeMillis());
|
||||
String timeStamp = sdf.format(date);
|
||||
initLunarCalendarInfo(timeStamp);
|
||||
}
|
||||
|
||||
public String getLunarCalendar() {
|
||||
return lunarMonth + "月" + lunarDay;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询日期的年份生肖
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getAnimal() {
|
||||
return animal;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询日期年份的天干地支
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getGanZhiYear() {
|
||||
return ganZhiYear;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询日期的农历年份
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getLunarYear() {
|
||||
return lunarYear;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询日期的农历月份
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getLunarMonth() {
|
||||
return lunarMonth;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询日期的农历日
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getLunarDay() {
|
||||
return lunarDay;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询日期的公历节日(不是节日返回空)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getSolarFestival() {
|
||||
return solarFestival;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询日期的农历节日(不是节日返回空)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getLunarFestival() {
|
||||
return lunarFestival;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询日期的节气数据(不是节气返回空)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getLunarTerm() {
|
||||
return lunarTerm;
|
||||
}
|
||||
|
||||
|
||||
final static long[] lunarInfo = new long[]{
|
||||
0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
|
||||
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,
|
||||
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,
|
||||
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,
|
||||
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,
|
||||
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5d0, 0x14573, 0x052d0, 0x0a9a8, 0x0e950, 0x06aa0,
|
||||
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,
|
||||
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b5a0, 0x195a6,
|
||||
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,
|
||||
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0,
|
||||
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,
|
||||
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,
|
||||
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,
|
||||
0x05aa0, 0x076a3, 0x096d0, 0x04bd7, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,
|
||||
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0
|
||||
};
|
||||
//阳历天数
|
||||
final static int[] solarMonths = new int[]{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
//生肖
|
||||
final static String[] animals = new String[]{"鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪"};
|
||||
//天干
|
||||
final static String[] tGan = new String[]{"甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"};
|
||||
//地支
|
||||
final static String[] dZhi = new String[]{"子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"};
|
||||
//二十四节气
|
||||
final static String[] solarTerms = new String[]{"小寒", "大寒", "立春", "雨水", "惊蛰", "春分", "清明", "谷雨", "立夏",
|
||||
"小满", "芒种", "夏至", "小暑", "大暑", "立秋", "处暑", "白露", "秋分", "寒露", "霜降", "立冬", "小雪", "大雪", "冬至"};
|
||||
//二十四节气日期偏移度
|
||||
private static final double D = 0.2422;
|
||||
//特殊年份节气日期偏移
|
||||
private final static Map<Integer, Integer[]> INCREASE_OFFSETMAP = new HashMap<Integer, Integer[]>();//+1偏移
|
||||
private final static Map<Integer, Integer[]> DECREASE_OFFSETMAP = new HashMap<Integer, Integer[]>();//-1偏移
|
||||
|
||||
static {
|
||||
INCREASE_OFFSETMAP.put(0, new Integer[]{1982});//小寒
|
||||
DECREASE_OFFSETMAP.put(0, new Integer[]{2019});//小寒
|
||||
INCREASE_OFFSETMAP.put(1, new Integer[]{2082});//大寒
|
||||
DECREASE_OFFSETMAP.put(3, new Integer[]{2026});//雨水
|
||||
INCREASE_OFFSETMAP.put(5, new Integer[]{2084});//春分
|
||||
INCREASE_OFFSETMAP.put(9, new Integer[]{2008});//小满
|
||||
INCREASE_OFFSETMAP.put(10, new Integer[]{1902});//芒种
|
||||
INCREASE_OFFSETMAP.put(11, new Integer[]{1928});//夏至
|
||||
INCREASE_OFFSETMAP.put(12, new Integer[]{1925, 2016});//小暑
|
||||
INCREASE_OFFSETMAP.put(13, new Integer[]{1922});//大暑
|
||||
INCREASE_OFFSETMAP.put(14, new Integer[]{2002});//立秋
|
||||
INCREASE_OFFSETMAP.put(16, new Integer[]{1927});//白露
|
||||
INCREASE_OFFSETMAP.put(17, new Integer[]{1942});//秋分
|
||||
INCREASE_OFFSETMAP.put(19, new Integer[]{2089});//霜降
|
||||
INCREASE_OFFSETMAP.put(20, new Integer[]{2089});//立冬
|
||||
INCREASE_OFFSETMAP.put(21, new Integer[]{1978});//小雪
|
||||
INCREASE_OFFSETMAP.put(22, new Integer[]{1954});//大雪
|
||||
DECREASE_OFFSETMAP.put(23, new Integer[]{1918, 2021});//冬至
|
||||
}
|
||||
|
||||
//定义一个二维数组,第一维数组存储的是20世纪的节气C值,第二维数组存储的是21世纪的节气C值,0到23个,依次代表立春、雨水...大寒节气的C值
|
||||
private static final double[][] CENTURY_ARRAY = {
|
||||
{6.11, 20.84, 4.6295, 19.4599, 6.3826, 21.4155, 5.59, 20.888, 6.318, 21.86, 6.5, 22.2, 7.928, 23.65, 8.35, 23.95, 8.44, 23.822, 9.098, 24.218, 8.218, 23.08, 7.9, 22.6},
|
||||
{5.4055, 20.12, 3.87, 18.73, 5.63, 20.646, 4.81, 20.1, 5.52, 21.04, 5.678, 21.37, 7.108, 22.83, 7.5, 23.13, 7.646, 23.042, 8.318, 23.438, 7.438, 22.36, 7.18, 21.94}
|
||||
};
|
||||
//农历月份
|
||||
final static String lunarNumber[] = {"一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二"};
|
||||
//农历年
|
||||
final static String[] lunarYears = new String[]{"零", "一", "二", "三", "四", "五", "六", "七", "八", "九"};
|
||||
final static String[] chineseTen = new String[]{"初", "十", "廿", "卅"};
|
||||
//农历节日
|
||||
final static String[] lunarHoliday = new String[]{"0101 春节", "0115 元宵节", "0202 龙头节", "0505 端午节", "0707 七夕节", "0715 中元节",
|
||||
"0815 中秋节", "0909 重阳节", "1001 寒衣节", "1015 下元节", "1208 腊八节", "1223 小年"};
|
||||
//公立节日
|
||||
final static String[] solarHoliday = new String[]{"0101 元旦", "0214 情人节", "0308 妇女节", "0312 植树节", "0315 消费者权益日",
|
||||
"0401 愚人节", "0422 地球日", "0423 读书日", "0501 劳动节", "0504 青年节", "0512 护士节", "0518 博物馆日", "0519 旅游日", "0601 儿童节",
|
||||
"0701 建党节", "0801 建军节", "0910 教师节", "1001 国庆节", "1024 联合国日", "1204 宪法日", "1224 平安夜", "1225 圣诞节"};
|
||||
//格式化日期
|
||||
static SimpleDateFormat chineseDateFormat = new SimpleDateFormat("yyyy年MM月dd日", Locale.CHINA);
|
||||
static SimpleDateFormat solarDateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||
|
||||
/**
|
||||
* 返回农历y年的总天数
|
||||
*
|
||||
* @param y
|
||||
* @return
|
||||
*/
|
||||
private int lunarYearDays(int y) {
|
||||
int i, sum = 348;
|
||||
for (i = 0x8000; i > 0x8; i >>= 1) {
|
||||
sum += ((lunarInfo[y - 1900] & i) != 0 ? 1 : 0);
|
||||
}
|
||||
return (sum + leapDays(y));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回农历y年闰月的天数
|
||||
*/
|
||||
private int leapDays(int y) {
|
||||
if (leapMonth(y) != 0) {
|
||||
return ((lunarInfo[y - 1900] & 0x10000) != 0 ? 30 : 29);
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断y年的农历中那个月是闰月,不是闰月返回0
|
||||
*
|
||||
* @param y
|
||||
* @return
|
||||
*/
|
||||
private int leapMonth(int y) {
|
||||
return (int) (lunarInfo[y - 1900] & 0xf);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回农历y年m月的总天数
|
||||
*
|
||||
* @param y
|
||||
* @param m
|
||||
* @return
|
||||
*/
|
||||
private int monthDays(int y, int m) {
|
||||
return ((lunarInfo[y - 1900] & (0x10000 >> m)) != 0 ? 30 : 29);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取阴历年
|
||||
*
|
||||
* @param year
|
||||
* @return
|
||||
*/
|
||||
private String getLunarYearString(String year) {
|
||||
int y1 = Integer.parseInt(year.charAt(0) + "");
|
||||
int y2 = Integer.parseInt(year.charAt(1) + "");
|
||||
int y3 = Integer.parseInt(year.charAt(2) + "");
|
||||
int y4 = Integer.parseInt(year.charAt(3) + "");
|
||||
return lunarYears[y1] + lunarYears[y2] + lunarYears[y3] + lunarYears[y4];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取阴历日
|
||||
*/
|
||||
private String getLunarDayString(int day) {
|
||||
int n = day % 10 == 0 ? 9 : day % 10 - 1;
|
||||
if (day > 30)
|
||||
return "";
|
||||
if (day == 10)
|
||||
return "初十";
|
||||
else
|
||||
return chineseTen[day / 10] + lunarNumber[n];
|
||||
}
|
||||
|
||||
/**
|
||||
* 特例,特殊的年分的节气偏移量,由于公式并不完善,所以算出的个别节气的第几天数并不准确,在此返回其偏移量
|
||||
*
|
||||
* @param year 年份
|
||||
* @param n 节气编号
|
||||
* @return 返回其偏移量
|
||||
*/
|
||||
private int specialYearOffset(int year, int n) {
|
||||
int offset = 0;
|
||||
offset += getOffset(DECREASE_OFFSETMAP, year, n, -1);
|
||||
offset += getOffset(INCREASE_OFFSETMAP, year, n, 1);
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* 节气偏移量计算
|
||||
*
|
||||
* @param map
|
||||
* @param year
|
||||
* @param n
|
||||
* @param offset
|
||||
* @return
|
||||
*/
|
||||
private int getOffset(Map<Integer, Integer[]> map, int year, int n, int offset) {
|
||||
int off = 0;
|
||||
Integer[] years = map.get(n);
|
||||
if (null != years) {
|
||||
for (int i : years) {
|
||||
if (i == year) {
|
||||
off = offset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return off;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某年的第n个节气为几日(从0小寒起算)
|
||||
*
|
||||
* @param year
|
||||
* @param n
|
||||
* @return
|
||||
*/
|
||||
private int sTerm(int year, int n) {
|
||||
double centuryValue = 0;//节气的世纪值,每个节气的每个世纪值都不同
|
||||
int centuryIndex = -1;
|
||||
if (year >= 1901 && year <= 2000) {//20世纪
|
||||
centuryIndex = 0;
|
||||
} else if (year >= 2001 && year <= 2100) {//21世纪
|
||||
centuryIndex = 1;
|
||||
} else {
|
||||
throw new RuntimeException("不支持此年份:" + year + ",目前只支持1901年到2100年的时间范围");
|
||||
}
|
||||
centuryValue = CENTURY_ARRAY[centuryIndex][n];
|
||||
int dateNum = 0;
|
||||
|
||||
int y = year % 100;//步骤1:取年分的后两位数
|
||||
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) {//闰年
|
||||
if (n == 0 || n == 1 || n == 2 || n == 3) {
|
||||
//注意:凡闰年3月1日前闰年数要减一,即:L=[(Y-1)/4],因为小寒、大寒、立春、雨水这两个节气都小于3月1日,所以 y = y-1
|
||||
y = y - 1;//步骤2
|
||||
}
|
||||
}
|
||||
dateNum = (int) (y * D + centuryValue) - (int) (y / 4);//步骤3,使用公式[Y*D+C]-L计算
|
||||
dateNum += specialYearOffset(year, n);//步骤4,加上特殊的年分的节气偏移量
|
||||
return dateNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* 母亲节和父亲节
|
||||
*
|
||||
* @param year
|
||||
* @param month
|
||||
* @param day
|
||||
* @return
|
||||
*/
|
||||
private String getMotherOrFatherDay(int year, int month, int day) {
|
||||
if (month != 5 && month != 6) return null;
|
||||
if ((month == 5 && (day < 8 || day > 14)) || (month == 6 && (day < 15 || day > 21)))
|
||||
return null;
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.set(year, month - 1, 1);
|
||||
int weekDate = calendar.get(Calendar.DAY_OF_WEEK);
|
||||
weekDate = (weekDate == 1) ? 7 : weekDate - 1;
|
||||
switch (month) {
|
||||
case 5:
|
||||
if (day == 15 - weekDate) {
|
||||
return "母亲节";
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
if (day == 22 - weekDate) {
|
||||
return "父亲节";
|
||||
}
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 感恩节
|
||||
*
|
||||
* @param year
|
||||
* @param month
|
||||
* @param day
|
||||
* @return
|
||||
*/
|
||||
private String thanksgiving(int year, int month, int day) {
|
||||
if (month != 11) return null;
|
||||
if ((month == 11 && (day < 19 || day > 28))) return null;
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.set(year, month - 1, 1);
|
||||
int weekDate = calendar.get(Calendar.DAY_OF_WEEK);
|
||||
weekDate = (weekDate == 1) ? 7 : weekDate - 1;
|
||||
switch (month) {
|
||||
case 11:
|
||||
if (day == 29 - weekDate + 4) {
|
||||
return "感恩节";
|
||||
}
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取复活节
|
||||
*
|
||||
* @param year
|
||||
* @param month
|
||||
* @param day
|
||||
* @return
|
||||
*/
|
||||
private String getEasterDay(int year, int month, int day) {
|
||||
int n = year - 1900;
|
||||
int a = n % 19;
|
||||
int q = n / 4;
|
||||
int b = (7 * a + 1) / 19;
|
||||
int m = (11 * a + 4 - b) % 29;
|
||||
int w = (n + q + 31 - m) % 7;
|
||||
int answer = 25 - m - w;
|
||||
String easterDay = "";
|
||||
if (answer > 0) {
|
||||
easterDay = year + "-" + 4 + "-" + answer;
|
||||
} else {
|
||||
easterDay = year + "-" + 3 + "-" + (31 + answer);
|
||||
}
|
||||
String searchDay = year + "-" + month + "-" + day;
|
||||
if (searchDay.equals(easterDay)) {
|
||||
return "复活节";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入公历日期初始化当前日期的生肖、天干地支、农历年、农历月、农历日、公历节日、农历节日、24节气
|
||||
* 输入日期的格式为(YYYY-MM-DD)
|
||||
*
|
||||
* @param currentDate
|
||||
*/
|
||||
public void initLunarCalendarInfo(String currentDate) {
|
||||
String[] splitDate = currentDate.split("-");
|
||||
//设置生肖
|
||||
int year = Integer.parseInt(splitDate[0]);
|
||||
this.animal = animals[(year - 4) % 12];
|
||||
//设置天干地支
|
||||
int num = year - 1900 + 36;
|
||||
this.ganZhiYear = (tGan[num % 10] + dZhi[num % 12]);
|
||||
///////////设置阴历/////////////////////////////////////////////////////////
|
||||
//基准日期
|
||||
Date baseDate = null;
|
||||
//当前日期
|
||||
Date nowaday = null;
|
||||
try {
|
||||
baseDate = chineseDateFormat.parse("1900年1月31日");
|
||||
nowaday = solarDateFormat.parse(currentDate);
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
// 获取当前日期与1900年1月31日相差的天数
|
||||
int offset = (int) ((nowaday.getTime() - baseDate.getTime()) / 86400000L);
|
||||
|
||||
//用offset减去每农历年的天数,计算当天是农历第几天 iYear最终结果是农历的年份
|
||||
int iYear, daysOfYear = 0;
|
||||
for (iYear = 1900; iYear < 10000 && offset > 0; iYear++) {
|
||||
daysOfYear = lunarYearDays(iYear);
|
||||
offset -= daysOfYear;
|
||||
}
|
||||
if (offset < 0) {
|
||||
offset += daysOfYear;
|
||||
iYear--;
|
||||
}
|
||||
this.lunarYear = getLunarYearString(iYear + "");
|
||||
int leapMonth = leapMonth(iYear); // 闰哪个月,1-12
|
||||
boolean leap = false;
|
||||
|
||||
// 用当年的天数offset,逐个减去每月(农历)的天数,求出当天是本月的第几天
|
||||
int iMonth, daysOfMonth = 0;
|
||||
for (iMonth = 1; iMonth < 13 && offset > 0; iMonth++) {
|
||||
// 闰月
|
||||
if (leapMonth > 0 && iMonth == (leapMonth + 1) && !leap) {
|
||||
--iMonth;
|
||||
leap = true;
|
||||
daysOfMonth = leapDays(iYear);
|
||||
} else
|
||||
daysOfMonth = monthDays(iYear, iMonth);
|
||||
|
||||
offset -= daysOfMonth;
|
||||
// 解除闰月
|
||||
if (leap && iMonth == (leapMonth + 1))
|
||||
leap = false;
|
||||
}
|
||||
// offset为0时,并且刚才计算的月份是闰月,要校正
|
||||
if (offset == 0 && leapMonth > 0 && iMonth == leapMonth + 1) {
|
||||
if (leap) {
|
||||
leap = false;
|
||||
} else {
|
||||
leap = true;
|
||||
--iMonth;
|
||||
}
|
||||
}
|
||||
// offset小于0时,也要校正
|
||||
if (offset < 0) {
|
||||
offset += daysOfMonth;
|
||||
--iMonth;
|
||||
}
|
||||
// 设置对应的阴历月份
|
||||
this.lunarMonth = lunarNumber[iMonth - 1];
|
||||
if ("一".equals(this.lunarMonth)) {
|
||||
this.lunarMonth = "正";
|
||||
}
|
||||
if ("十二".equals(this.lunarMonth)) {
|
||||
this.lunarMonth = "腊";
|
||||
}
|
||||
if (leap) {
|
||||
this.lunarMonth = "闰" + this.lunarMonth;
|
||||
}
|
||||
|
||||
//设置阴历日
|
||||
int iDay = offset + 1;
|
||||
this.lunarDay = getLunarDayString(iDay);
|
||||
|
||||
//设置节气
|
||||
int month = Integer.parseInt(splitDate[1]);
|
||||
int day = Integer.parseInt(splitDate[2]);
|
||||
if (day == sTerm(year, (month - 1) * 2)) {
|
||||
this.lunarTerm = solarTerms[(month - 1) * 2];
|
||||
} else if (day == sTerm(year, (month - 1) * 2 + 1)) {
|
||||
this.lunarTerm = solarTerms[(month - 1) * 2 + 1];
|
||||
} else {
|
||||
this.lunarTerm = "";
|
||||
}
|
||||
|
||||
//设置阳历节日
|
||||
String solarFestival = "";
|
||||
for (int i = 0; i < solarHoliday.length; i++) {
|
||||
// 返回公历节假日名称
|
||||
String sd = solarHoliday[i].split(" ")[0]; // 节假日的日期
|
||||
String sdv = solarHoliday[i].split(" ")[1]; // 节假日的名称
|
||||
String smonth_v = splitDate[1];
|
||||
String sday_v = splitDate[2];
|
||||
String smd = smonth_v + sday_v;
|
||||
if (sd.trim().equals(smd.trim())) {
|
||||
solarFestival = sdv;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//判断节日是否是父亲节或母亲节
|
||||
String motherOrFatherDay = getMotherOrFatherDay(year, month, day);
|
||||
if (motherOrFatherDay != null) {
|
||||
solarFestival = motherOrFatherDay;
|
||||
}
|
||||
//判断节日是否是复活节
|
||||
String easterDay = getEasterDay(year, month, day);
|
||||
if (easterDay != null) {
|
||||
solarFestival = easterDay;
|
||||
}
|
||||
//判断节日是否是感恩节
|
||||
String thanksgiving = thanksgiving(year, month, day);
|
||||
if (thanksgiving != null) {
|
||||
solarFestival = thanksgiving;
|
||||
}
|
||||
this.solarFestival = solarFestival;
|
||||
|
||||
//设置阴历节日
|
||||
String lunarFestival = "";
|
||||
for (int i = 0; i < lunarHoliday.length; i++) {
|
||||
//阴历闰月节日
|
||||
if (leap) {
|
||||
break;
|
||||
}
|
||||
// 返回农历节假日名称
|
||||
String ld = lunarHoliday[i].split(" ")[0]; // 节假日的日期
|
||||
String ldv = lunarHoliday[i].split(" ")[1]; // 节假日的名称
|
||||
String lmonth_v = iMonth + "";
|
||||
String lday_v = iDay + "";
|
||||
String lmd = "";
|
||||
if (iMonth < 10) {
|
||||
lmonth_v = "0" + iMonth;
|
||||
}
|
||||
if (iDay < 10) {
|
||||
lday_v = "0" + iDay;
|
||||
}
|
||||
lmd = lmonth_v + lday_v;
|
||||
if ("12".equals(lmonth_v)) { // 除夕夜需要特殊处理
|
||||
if ((daysOfMonth == 29 && iDay == 29) || (daysOfMonth == 30 && iDay == 30)) {
|
||||
lunarFestival = "除夕";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ld.trim().equals(lmd.trim())) {
|
||||
lunarFestival = ldv;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ("清明".equals(this.lunarTerm)) {
|
||||
lunarFestival = "清明节";
|
||||
}
|
||||
this.lunarFestival = lunarFestival;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 测试方法
|
||||
// * @param args
|
||||
// */
|
||||
// public static void main(String[] args) {
|
||||
// LunarCalendarFestivalUtils festival = new LunarCalendarFestivalUtils();
|
||||
// festival.initLunarCalendarInfo("2021-06-25");
|
||||
// System.out.println("农历"+festival.getLunarYear()+"年"+festival.getLunarMonth()+"月"+festival.getLunarDay()+"日");
|
||||
// System.out.println(festival.getGanZhiYear()+"【"+festival.getAnimal()+"】年");
|
||||
// System.out.println(festival.getLunarTerm());
|
||||
// System.out.println(festival.getSolarFestival());
|
||||
// System.out.println(festival.getLunarFestival());
|
||||
// }
|
||||
}
|
||||
77
app/src/main/java/com/ttstd/dialer/utils/ScreenUtils.java
Normal file
77
app/src/main/java/com/ttstd/dialer/utils/ScreenUtils.java
Normal file
@@ -0,0 +1,77 @@
|
||||
package com.ttstd.dialer.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class ScreenUtils {
|
||||
private static final String TAG = "ScreenUtils";
|
||||
|
||||
/**
|
||||
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
|
||||
*/
|
||||
public static int dip2px(Context context, float dpValue) {
|
||||
final float scale = context.getResources().getDisplayMetrics().density;
|
||||
return (int) (dpValue * scale + 0.5f);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
|
||||
*/
|
||||
public static int px2dip(Context context, float pxValue) {
|
||||
final float scale = context.getResources().getDisplayMetrics().density;
|
||||
return (int) (pxValue / scale + 0.5f);
|
||||
}
|
||||
|
||||
public static int dp2px(Resources resources, float dp) {
|
||||
final float scale = resources.getDisplayMetrics().density;
|
||||
return (int) (dp * scale + 0.5f);
|
||||
}
|
||||
|
||||
public static int sp2px(Resources resources, float sp) {
|
||||
final float scale = resources.getDisplayMetrics().scaledDensity;
|
||||
return (int) (sp * scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用需反射调用
|
||||
*/
|
||||
public static boolean isTablet() {
|
||||
try {
|
||||
// 1. 反射获取 SystemProperties 类
|
||||
Class<?> systemPropertiesClass = Class.forName("android.os.SystemProperties");
|
||||
// 2. 获取 get(String key) 方法
|
||||
Method getMethod = systemPropertiesClass.getDeclaredMethod("get", String.class);
|
||||
// 3. 调用方法获取属性值
|
||||
String characteristics = (String) getMethod.invoke(null, "ro.build.characteristics");
|
||||
Log.e(TAG, "isTablet: " + characteristics);
|
||||
// 4. 判断是否包含 "tablet" 标识
|
||||
return characteristics != null && characteristics.contains("tablet");
|
||||
} catch (Exception e) {
|
||||
// 反射失败时的处理(如属性不存在或权限问题)
|
||||
Log.e(TAG, "Reflection failed: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isTablet(Context context) {
|
||||
boolean isTablet = (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE;
|
||||
Log.e(TAG, "isTablet: " + isTablet);
|
||||
return isTablet;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 是否是平板
|
||||
*
|
||||
* @param context 上下文
|
||||
* @return 是平板则返回true,反之返回false
|
||||
*/
|
||||
public static boolean isPad(Context context) {
|
||||
return (context.getResources().getConfiguration().screenLayout
|
||||
& Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE;
|
||||
}
|
||||
}
|
||||
24
app/src/main/java/com/ttstd/dialer/utils/SystemUtils.java
Normal file
24
app/src/main/java/com/ttstd/dialer/utils/SystemUtils.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package com.ttstd.dialer.utils;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SystemUtils {
|
||||
|
||||
public static boolean isMainProcessName(Context cxt, int pid) {
|
||||
String packageName = cxt.getPackageName();
|
||||
ActivityManager am = (ActivityManager) cxt.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
List<ActivityManager.RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();
|
||||
if (runningApps == null) {
|
||||
return false;
|
||||
}
|
||||
for (ActivityManager.RunningAppProcessInfo procInfo : runningApps) {
|
||||
if (procInfo.pid == pid) {
|
||||
return procInfo.processName.equals(packageName);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
19
app/src/main/java/com/ttstd/dialer/utils/TimeUtils.java
Normal file
19
app/src/main/java/com/ttstd/dialer/utils/TimeUtils.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.ttstd.dialer.utils;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
public class TimeUtils {
|
||||
// 根据日期取得星期几
|
||||
public static String getWeek() {
|
||||
Date date = new Date();
|
||||
String[] weeks = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(date);
|
||||
int weekIndex = cal.get(Calendar.DAY_OF_WEEK) - 1;
|
||||
if (weekIndex < 0) {
|
||||
weekIndex = 0;
|
||||
}
|
||||
return weeks[weekIndex];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package com.ttstd.dialer.view;
|
||||
|
||||
import android.util.SparseArray;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 加载显示Fragment的ViewPagerAdapter基类
|
||||
* 提供可以刷新的方法
|
||||
*
|
||||
* @author Fly
|
||||
* @e-mail 1285760616@qq.com
|
||||
* @time 2018/3/22
|
||||
*/
|
||||
public class BaseFragmentPagerAdapter extends FragmentPagerAdapter {
|
||||
private List<Fragment> mFragmentList;
|
||||
private FragmentManager mFragmentManager;
|
||||
/**下面两个值用来保存Fragment的位置信息,用以判断该位置是否需要更新*/
|
||||
private SparseArray<String> mFragmentPositionMap;
|
||||
private SparseArray<String> mFragmentPositionMapAfterUpdate;
|
||||
|
||||
public BaseFragmentPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
|
||||
super(fm);
|
||||
mFragmentManager = fm;
|
||||
mFragmentList = fragments;
|
||||
mFragmentPositionMap = new SparseArray<>();
|
||||
mFragmentPositionMapAfterUpdate = new SparseArray<>();
|
||||
setFragmentPositionMap();
|
||||
setFragmentPositionMapForUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存更新之前的位置信息,用<hashCode, position>的键值对结构来保存
|
||||
*/
|
||||
private void setFragmentPositionMap() {
|
||||
mFragmentPositionMap.clear();
|
||||
for (int i = 0; i < mFragmentList.size(); i++) {
|
||||
mFragmentPositionMap.put(Long.valueOf(getItemId(i)).intValue(), String.valueOf(i));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存更新之后的位置信息,用<hashCode, position>的键值对结构来保存
|
||||
*/
|
||||
private void setFragmentPositionMapForUpdate() {
|
||||
mFragmentPositionMapAfterUpdate.clear();
|
||||
for (int i = 0; i < mFragmentList.size(); i++) {
|
||||
mFragmentPositionMapAfterUpdate.put(Long.valueOf(getItemId(i)).intValue(), String.valueOf(i));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在此方法中找到需要更新的位置返回POSITION_NONE,否则返回POSITION_UNCHANGED即可
|
||||
*/
|
||||
@Override
|
||||
public int getItemPosition(Object object) {
|
||||
int hashCode = object.hashCode();
|
||||
//查找object在更新后的列表中的位置
|
||||
String position = mFragmentPositionMapAfterUpdate.get(hashCode);
|
||||
//更新后的列表中不存在该object的位置了
|
||||
if (position == null) {
|
||||
return POSITION_NONE;
|
||||
} else {
|
||||
//如果更新后的列表中存在该object的位置, 查找该object之前的位置并判断位置是否发生了变化
|
||||
int size = mFragmentPositionMap.size();
|
||||
for (int i = 0; i < size ; i++) {
|
||||
int key = mFragmentPositionMap.keyAt(i);
|
||||
if (key == hashCode) {
|
||||
String index = mFragmentPositionMap.get(key);
|
||||
if (position.equals(index)) {
|
||||
//位置没变依然返回POSITION_UNCHANGED
|
||||
return POSITION_UNCHANGED;
|
||||
} else {
|
||||
//位置变了
|
||||
return POSITION_NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return POSITION_UNCHANGED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定的Fragment替换/更新为新的Fragment
|
||||
* @param oldFragment 旧Fragment
|
||||
* @param newFragment 新Fragment
|
||||
*/
|
||||
public void replaceFragment(Fragment oldFragment, Fragment newFragment) {
|
||||
int position = mFragmentList.indexOf(oldFragment);
|
||||
if (position == -1) {
|
||||
return;
|
||||
}
|
||||
//从Transaction移除旧的Fragment
|
||||
removeFragmentInternal(oldFragment);
|
||||
//替换List中对应的Fragment
|
||||
mFragmentList.set(position, newFragment);
|
||||
//刷新Adapter
|
||||
notifyItemChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定位置的Fragment替换/更新为新的Fragment,同{@link #replaceFragment(Fragment oldFragment, Fragment newFragment)}
|
||||
* @param position 旧Fragment的位置
|
||||
* @param newFragment 新Fragment
|
||||
*/
|
||||
public void replaceFragment(int position, Fragment newFragment) {
|
||||
Fragment oldFragment = mFragmentList.get(position);
|
||||
removeFragmentInternal(oldFragment);
|
||||
mFragmentList.set(position, newFragment);
|
||||
notifyItemChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定的Fragment
|
||||
* @param fragment 目标Fragment
|
||||
*/
|
||||
public void removeFragment(Fragment fragment) {
|
||||
//先从List中移除
|
||||
mFragmentList.remove(fragment);
|
||||
//然后从Transaction移除
|
||||
removeFragmentInternal(fragment);
|
||||
//最后刷新Adapter
|
||||
notifyItemChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定位置的Fragment,同 {@link #removeFragment(Fragment fragment)}
|
||||
* @param position
|
||||
*/
|
||||
public void removeFragment(int position) {
|
||||
Fragment fragment = mFragmentList.get(position);
|
||||
//然后从List中移除
|
||||
mFragmentList.remove(fragment);
|
||||
//先从Transaction移除
|
||||
removeFragmentInternal(fragment);
|
||||
//最后刷新Adapter
|
||||
notifyItemChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加Fragment
|
||||
* @param fragment 目标Fragment
|
||||
*/
|
||||
public void addFragment(Fragment fragment) {
|
||||
mFragmentList.add(fragment);
|
||||
notifyItemChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在指定位置插入一个Fragment
|
||||
* @param position 插入位置
|
||||
* @param fragment 目标Fragment
|
||||
*/
|
||||
public void insertFragment(int position, Fragment fragment) {
|
||||
mFragmentList.add(position, fragment);
|
||||
notifyItemChanged();
|
||||
}
|
||||
|
||||
public void notifyItemChanged() {
|
||||
//刷新之前重新收集位置信息
|
||||
setFragmentPositionMapForUpdate();
|
||||
notifyDataSetChanged();
|
||||
setFragmentPositionMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Transaction移除Fragment
|
||||
* @param fragment 目标Fragment
|
||||
*/
|
||||
private void removeFragmentInternal(Fragment fragment) {
|
||||
FragmentTransaction transaction = mFragmentManager.beginTransaction();
|
||||
transaction.remove(fragment);
|
||||
transaction.commitAllowingStateLoss();
|
||||
}
|
||||
|
||||
/**
|
||||
* 此方法不用position做返回值即可破解fragment tag异常的错误
|
||||
*/
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
// 获取当前数据的hashCode,其实这里不用hashCode用自定义的可以关联当前Item对象的唯一值也可以,只要不是直接返回position
|
||||
return mFragmentList.get(position).hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
return mFragmentList.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mFragmentList.size();
|
||||
}
|
||||
|
||||
public List<Fragment> getFragments() {
|
||||
return mFragmentList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
package com.ttstd.dialer.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PointF;
|
||||
import android.util.SparseArray;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
|
||||
import net.lucode.hackware.magicindicator.NavigatorHelper;
|
||||
import net.lucode.hackware.magicindicator.abs.IPagerNavigator;
|
||||
import net.lucode.hackware.magicindicator.buildins.ArgbEvaluatorHolder;
|
||||
import net.lucode.hackware.magicindicator.buildins.UIUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
// _oo0oo_
|
||||
// o8888888o
|
||||
// 88" . "88
|
||||
// (| -_- |)
|
||||
// 0\ = /0
|
||||
// ___/`---'\___
|
||||
// .' \\| |// '.
|
||||
// / \\||| : |||// \
|
||||
// / _||||| -:- |||||- \
|
||||
// | | \\\ - /// | |
|
||||
// | \_| ''\---/'' |_/ |
|
||||
// \ .-\__ '-' ___/-. /
|
||||
// ___'. .' /--.--\ `. .'___
|
||||
// ."" '< `.___\_<|>_/___.' >' "".
|
||||
// | | : `- \`.;`\ _ /`;.`/ - ` : | |
|
||||
// \ \ `_. \_ __\ /__ _/ .-` / /
|
||||
// =====`-.____`.___ \_____/___.-`___.-'=====
|
||||
// `=---='
|
||||
//
|
||||
//
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// 佛祖保佑 永无BUG
|
||||
|
||||
/**
|
||||
* 类似CircleIndicator的效果
|
||||
* Created by hackware on 2016/9/3.
|
||||
*/
|
||||
|
||||
public class ScaleCircleNavigator extends View implements IPagerNavigator, NavigatorHelper.OnNavigatorScrollListener {
|
||||
private int mMinRadius;
|
||||
private int mMaxRadius;
|
||||
private int mNormalCircleColor = Color.LTGRAY;
|
||||
private int mSelectedCircleColor = Color.GRAY;
|
||||
private int mCircleSpacing;
|
||||
private int mCircleCount;
|
||||
|
||||
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private List<PointF> mCirclePoints = new ArrayList<PointF>();
|
||||
private SparseArray<Float> mCircleRadiusArray = new SparseArray<Float>();
|
||||
|
||||
// 事件回调
|
||||
private boolean mTouchable;
|
||||
private OnCircleClickListener mCircleClickListener;
|
||||
private float mDownX;
|
||||
private float mDownY;
|
||||
private int mTouchSlop;
|
||||
|
||||
private boolean mFollowTouch = true; // 是否跟随手指滑动
|
||||
private NavigatorHelper mNavigatorHelper = new NavigatorHelper();
|
||||
private Interpolator mStartInterpolator = new LinearInterpolator();
|
||||
|
||||
public ScaleCircleNavigator(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
||||
mMinRadius = UIUtil.dip2px(context, 3);
|
||||
mMaxRadius = UIUtil.dip2px(context, 4);
|
||||
mCircleSpacing = UIUtil.dip2px(context, 8);
|
||||
mNavigatorHelper.setNavigatorScrollListener(this);
|
||||
mNavigatorHelper.setSkimOver(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
|
||||
}
|
||||
|
||||
private int measureWidth(int widthMeasureSpec) {
|
||||
int mode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
int result = 0;
|
||||
switch (mode) {
|
||||
case MeasureSpec.EXACTLY:
|
||||
result = width;
|
||||
break;
|
||||
case MeasureSpec.AT_MOST:
|
||||
case MeasureSpec.UNSPECIFIED:
|
||||
if (mCircleCount <= 0) {
|
||||
result = getPaddingLeft() + getPaddingRight();
|
||||
} else {
|
||||
result = (mCircleCount - 1) * mMinRadius * 2 + mMaxRadius * 2 + (mCircleCount - 1) * mCircleSpacing + getPaddingLeft() + getPaddingRight();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private int measureHeight(int heightMeasureSpec) {
|
||||
int mode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
int result = 0;
|
||||
switch (mode) {
|
||||
case MeasureSpec.EXACTLY:
|
||||
result = height;
|
||||
break;
|
||||
case MeasureSpec.AT_MOST:
|
||||
case MeasureSpec.UNSPECIFIED:
|
||||
result = mMaxRadius * 2 + getPaddingTop() + getPaddingBottom();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
for (int i = 0, j = mCirclePoints.size(); i < j; i++) {
|
||||
PointF point = mCirclePoints.get(i);
|
||||
float radius = mCircleRadiusArray.get(i, (float) mMinRadius);
|
||||
mPaint.setColor(ArgbEvaluatorHolder.eval((radius - mMinRadius) / (mMaxRadius - mMinRadius), mNormalCircleColor, mSelectedCircleColor));
|
||||
canvas.drawCircle(point.x, getHeight() / 2.0f, radius, mPaint);
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareCirclePoints() {
|
||||
mCirclePoints.clear();
|
||||
if (mCircleCount > 0) {
|
||||
int y = Math.round(getHeight() / 2.0f);
|
||||
int centerSpacing = mMinRadius * 2 + mCircleSpacing;
|
||||
int startX = mMaxRadius + getPaddingLeft();
|
||||
for (int i = 0; i < mCircleCount; i++) {
|
||||
PointF pointF = new PointF(startX, y);
|
||||
mCirclePoints.add(pointF);
|
||||
startX += centerSpacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
float x = event.getX();
|
||||
float y = event.getY();
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
if (mTouchable) {
|
||||
mDownX = x;
|
||||
mDownY = y;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
if (mCircleClickListener != null) {
|
||||
if (Math.abs(x - mDownX) <= mTouchSlop && Math.abs(y - mDownY) <= mTouchSlop) {
|
||||
float max = Float.MAX_VALUE;
|
||||
int index = 0;
|
||||
for (int i = 0; i < mCirclePoints.size(); i++) {
|
||||
PointF pointF = mCirclePoints.get(i);
|
||||
float offset = Math.abs(pointF.x - x);
|
||||
if (offset < max) {
|
||||
max = offset;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
mCircleClickListener.onClick(index);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
mNavigatorHelper.onPageScrolled(position, positionOffset, positionOffsetPixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
mNavigatorHelper.onPageSelected(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrollStateChanged(int state) {
|
||||
mNavigatorHelper.onPageScrollStateChanged(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
prepareCirclePoints();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyDataSetChanged() {
|
||||
prepareCirclePoints();
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachToMagicIndicator() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachFromMagicIndicator() {
|
||||
}
|
||||
|
||||
public void setMinRadius(int minRadius) {
|
||||
mMinRadius = minRadius;
|
||||
prepareCirclePoints();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setMaxRadius(int maxRadius) {
|
||||
mMaxRadius = maxRadius;
|
||||
prepareCirclePoints();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setNormalCircleColor(int normalCircleColor) {
|
||||
mNormalCircleColor = normalCircleColor;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setSelectedCircleColor(int selectedCircleColor) {
|
||||
mSelectedCircleColor = selectedCircleColor;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setCircleSpacing(int circleSpacing) {
|
||||
mCircleSpacing = circleSpacing;
|
||||
prepareCirclePoints();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setStartInterpolator(Interpolator startInterpolator) {
|
||||
mStartInterpolator = startInterpolator;
|
||||
if (mStartInterpolator == null) {
|
||||
mStartInterpolator = new LinearInterpolator();
|
||||
}
|
||||
}
|
||||
|
||||
public void setCircleCount(int count) {
|
||||
mCircleCount = count; // 此处不调用invalidate,让外部调用notifyDataSetChanged
|
||||
mNavigatorHelper.setTotalCount(mCircleCount);
|
||||
}
|
||||
|
||||
public void setTouchable(boolean touchable) {
|
||||
mTouchable = touchable;
|
||||
}
|
||||
|
||||
public void setFollowTouch(boolean followTouch) {
|
||||
mFollowTouch = followTouch;
|
||||
}
|
||||
|
||||
public void setSkimOver(boolean skimOver) {
|
||||
mNavigatorHelper.setSkimOver(skimOver);
|
||||
}
|
||||
|
||||
public void setCircleClickListener(OnCircleClickListener circleClickListener) {
|
||||
if (!mTouchable) {
|
||||
mTouchable = true;
|
||||
}
|
||||
mCircleClickListener = circleClickListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnter(int index, int totalCount, float enterPercent, boolean leftToRight) {
|
||||
if (mFollowTouch) {
|
||||
float radius = mMinRadius + (mMaxRadius - mMinRadius) * mStartInterpolator.getInterpolation(enterPercent);
|
||||
mCircleRadiusArray.put(index, radius);
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLeave(int index, int totalCount, float leavePercent, boolean leftToRight) {
|
||||
if (mFollowTouch) {
|
||||
float radius = mMaxRadius + (mMinRadius - mMaxRadius) * mStartInterpolator.getInterpolation(leavePercent);
|
||||
mCircleRadiusArray.put(index, radius);
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelected(int index, int totalCount) {
|
||||
if (!mFollowTouch) {
|
||||
mCircleRadiusArray.put(index, (float) mMaxRadius);
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeselected(int index, int totalCount) {
|
||||
if (!mFollowTouch) {
|
||||
mCircleRadiusArray.put(index, (float) mMinRadius);
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnCircleClickListener {
|
||||
void onClick(int index);
|
||||
}
|
||||
}
|
||||
12
app/src/main/res/drawable/ic_add.xml
Normal file
12
app/src/main/res/drawable/ic_add.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="200dp"
|
||||
android:height="200dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:pathData="M105,480a8,8 0,0 1,8 -8h799a8,8 0,0 1,8 8v64a8,8 0,0 1,-8 8H113a8,8 0,0 1,-8 -8v-64z"
|
||||
android:fillColor="#323338"/>
|
||||
<path
|
||||
android:pathData="M480,920a8,8 0,0 1,-8 -8V112a8,8 0,0 1,8 -8h64a8,8 0,0 1,8 8v800a8,8 0,0 1,-8 8h-64z"
|
||||
android:fillColor="#323338"/>
|
||||
</vector>
|
||||
18
app/src/main/res/drawable/ic_contact.xml
Normal file
18
app/src/main/res/drawable/ic_contact.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="200dp"
|
||||
android:height="200dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#03A9F4"
|
||||
android:pathData="M2.56,271.38m28.16,0l135.67,0q28.16,0 28.16,28.16l0,-0.64q0,28.16 -28.16,28.16l-135.67,0q-28.16,0 -28.16,-28.16l0,0.64q0,-28.16 28.16,-28.16Z"/>
|
||||
<path
|
||||
android:fillColor="#03A9F4"
|
||||
android:pathData="M2.56,494.08m28.16,0l135.67,0q28.16,0 28.16,28.16l0,-0.64q0,28.16 -28.16,28.16l-135.67,0q-28.16,0 -28.16,-28.16l0,0.64q0,-28.16 28.16,-28.16Z"/>
|
||||
<path
|
||||
android:fillColor="#03A9F4"
|
||||
android:pathData="M2.56,717.42m28.16,0l135.67,0q28.16,0 28.16,28.16l0,0q0,28.16 -28.16,28.16l-135.67,0q-28.16,0 -28.16,-28.16l0,0q0,-28.16 28.16,-28.16Z"/>
|
||||
<path
|
||||
android:fillColor="#03A9F4"
|
||||
android:pathData="M935.6,0.04L184.31,0.04A83.19,83.19 0,0 0,98.55 80.04v168.31h63.99a52.48,52.48 0,0 1,54.4 50.56,52.48 52.48,0 0,1 -54.4,51.2h-63.99v120.95h63.99a51.2,51.2 0,1 1,0 102.39h-63.99v120.95h63.99a51.2,51.2 0,1 1,0 102.39h-63.99v147.19A83.19,83.19 0,0 0,184.31 1023.96h751.3a83.19,83.19 0,0 0,85.75 -79.99L1021.36,80.04A83.19,83.19 0,0 0,935.6 0.04zM823.61,790.38h-447.96a37.76,37.76 0,0 1,-32 -45.44A298.22,298.22 0,0 1,546.52 481.92a127.99,127.99 0,1 1,106.23 0,298.22 298.22,0 0,1 202.86,263.02 37.76,37.76 0,0 1,-32 45.44z"/>
|
||||
</vector>
|
||||
18
app/src/main/res/drawable/ic_douyin.xml
Normal file
18
app/src/main/res/drawable/ic_douyin.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="200dp"
|
||||
android:height="200dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:pathData="M0,0m184.32,0l655.36,0q184.32,0 184.32,184.32l0,655.36q0,184.32 -184.32,184.32l-655.36,0q-184.32,0 -184.32,-184.32l0,-655.36q0,-184.32 184.32,-184.32Z"
|
||||
android:fillColor="#111111"/>
|
||||
<path
|
||||
android:pathData="M204.28,670.6a246.25,246.25 0,0 1,245.98 -245.98v147.58a98.5,98.5 0,0 0,-98.39 98.39c0,48.34 26.14,100.35 83.55,100.35 3.82,0 93.55,-0.88 93.55,-77.2V134.36h157.27a133.31,133.31 0,0 0,133.12 133l-0.13,147.31a273.15,273.15 0,0 1,-142.62 -38.91l-0.06,317.98c0,146 -124.24,224.78 -241.14,224.78 -131.75,0.03 -231.12,-106.57 -231.12,-247.92z"
|
||||
android:fillColor="#FF4040"/>
|
||||
<path
|
||||
android:pathData="M164.93,631.23a246.25,246.25 0,0 1,245.98 -245.98v147.58a98.5,98.5 0,0 0,-98.39 98.39c0,48.34 26.14,100.35 83.55,100.35 3.82,0 93.55,-0.88 93.55,-77.2V95h157.27a133.31,133.31 0,0 0,133.12 133l-0.13,147.31a273.15,273.15 0,0 1,-142.62 -38.91l-0.06,317.98c0,146 -124.24,224.78 -241.14,224.78 -131.75,0.03 -231.12,-106.57 -231.12,-247.92z"
|
||||
android:fillColor="#00F5FF"/>
|
||||
<path
|
||||
android:pathData="M410.91,427.58c-158.82,20.15 -284.45,222.72 -154.11,405 120.4,98.48 373.69,41.21 380.7,-171.86l-0.17,-324.15a280.73,280.73 0,0 0,142.89 38.63V261.22a144.99,144.99 0,0 1,-72.81 -54.82,135.24 135.24,0 0,1 -54.7,-72.46h-123.67l-0.08,561.42c-0.11,78.47 -130.97,106.41 -164.19,30.26 -83.19,-39.77 -64.38,-190.92 46.32,-192.57z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
||||
15
app/src/main/res/drawable/ic_settings.xml
Normal file
15
app/src/main/res/drawable/ic_settings.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="200dp"
|
||||
android:height="200dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:pathData="M512.29,432.11c-43.33,0 -78.58,35.71 -78.58,79.61S468.99,591.33 512.29,591.33 590.87,555.62 590.87,511.72s-35.25,-79.61 -78.58,-79.61z"
|
||||
android:fillColor="#5C8EF3"/>
|
||||
<path
|
||||
android:pathData="M640.73,368.34a19.1,19.1 0,0 1,-18.25 -2.65,178.17 178.17,0 0,0 -41.06,-24.21c-5.84,-2.31 -10.11,-7.84 -11.15,-14.43l-9.66,-68.44 -98.87,1.49 -7.9,66.91a19.12,19.12 0,0 1,-11.03 14.41h-0.08a191.95,191.95 0,0 0,-41.06 24.2,17.91 17.91,0 0,1 -18.46,2.62l-63.55,-25.8 -48.12,87.51 53.44,40.76a18,18 0,0 1,6.97 17.08,190.33 190.33,0 0,0 -1.83,23.89 190.33,190.33 0,0 0,1.84 23.94,18.16 18.16,0 0,1 -6.96,17.01l-53.97,42.54 50.87,86.33 61.31,-26.37a18.33,18.33 0,0 1,6.93 -1.28,17.88 17.88,0 0,1 11.49,3.94 186.74,186.74 0,0 0,41 23.89c5.84,2.31 10.11,7.83 11.16,14.43l9.66,68.44 97.63,-1.49 9.39,-66.88a19.1,19.1 0,0 1,11.04 -14.44l0.13,-0.06c13.56,-5.48 27.36,-13.52 41.01,-23.89a17.96,17.96 0,0 1,18.46 -2.62l63.53,25.79 47.85,-87.79L698.88,552.31a19.32,19.32 0,0 1,-6.87 -16.85v-0.09a190.36,190.36 0,0 0,1.84 -23.94,190.33 190.33,0 0,0 -1.84,-23.93 18.17,18.17 0,0 1,7 -17.07l53.95,-42.51L702.13,341.94zM512.29,628.56c-63.76,0 -115.63,-52.33 -115.63,-116.65s51.87,-116.65 115.63,-116.65 115.63,52.37 115.63,116.75 -51.88,116.56 -115.63,116.56z"
|
||||
android:fillColor="#5C8EF3"/>
|
||||
<path
|
||||
android:pathData="M880.36,0L143.64,0C64.64,0 0,64.64 0,143.64v736.71c0,79 64.64,143.64 143.64,143.64h736.71c79,0 143.64,-64.64 143.64,-143.64L1024,143.64c0,-79 -64.64,-143.64 -143.64,-143.64zM731.18,511.63c0,6.58 -0.42,12.64 -0.88,18.31l48.12,38.72c12.35,10.28 15.6,27.55 7.76,41.1l-53.26,93.53a32.25,32.25 0,0 1,-27.77 16.1,30.23 30.23,0 0,1 -11.74,-2.15l-56.94,-23.36a213.64,213.64 0,0 1,-30.88 18.24l-8.7,61.49a32.18,32.18 0,0 1,-31.6 27.46L458.65,801.07a31.79,31.79 0,0 1,-31.6 -27.14l-8.71,-61.71a234.39,234.39 0,0 1,-30.83 -18.25l-56.98,23.1a31.97,31.97 0,0 1,-39.38 -13.65l-53.54,-93.58a32.18,32.18 0,0 1,7.46 -40.78l0.06,-0.04 48.55,-38.5a181.45,181.45 0,0 1,-0.84 -18.33c0,-6.51 0.42,-12.59 0.88,-18.29l-48.31,-38.25a32.15,32.15 0,0 1,-7.8 -41.1l53.65,-93.56c7.52,-13.52 24.76,-19.68 39.09,-13.98l57,23.1a228.49,228.49 0,0 1,30.88 -18.25l8.72,-61.56c2.59,-15.87 15.87,-27.39 31.56,-27.39L565.38,222.91a32.25,32.25 0,0 1,31.6 27.13l8.71,61.53a234.39,234.39 0,0 1,30.84 18.25l56.98,-23.1c15.16,-5.88 31.34,-0.25 39.39,13.65L786.44,413.96a32.18,32.18 0,0 1,-7.46 40.78l-0.06,0.05 -48.55,38.5a181.85,181.85 0,0 1,0.8 18.34z"
|
||||
android:fillColor="#5C8EF3"/>
|
||||
</vector>
|
||||
15
app/src/main/res/drawable/ic_weixin.xml
Normal file
15
app/src/main/res/drawable/ic_weixin.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="200dp"
|
||||
android:height="200dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:pathData="M749.94,518.05c-18.06,0 -33.14,15.03 -33.14,30.07 0,18.11 15.08,33.14 33.14,33.14a33.42,33.42 0,0 0,33.14 -33.14c0,-15.03 -15.08,-30.11 -33.14,-30.11zM584.24,518.05c-18.01,0 -33.05,15.03 -33.05,30.07 0,18.11 15.03,33.14 33.05,33.14a33.42,33.42 0,0 0,33.19 -33.14c0,-15.03 -15.08,-30.11 -33.14,-30.11z"
|
||||
android:fillColor="#3ABF11"/>
|
||||
<path
|
||||
android:pathData="M813.15,0L210.85,0A209.55,209.55 0,0 0,0 210.85v602.29A209.55,209.55 0,0 0,210.85 1024h602.29A209.55,209.55 0,0 0,1024 813.15L1024,210.85A209.55,209.55 0,0 0,813.15 0zM400.62,671.65c-30.11,0 -60.23,-3.03 -87.37,-12.1L307.2,659.55l-102.4,51.2 27.09,-84.29s-2.98,0 -2.98,-3.03c-66.28,-42.17 -108.45,-108.45 -108.45,-183.71 0,-126.51 126.51,-228.91 280.11,-228.91 138.52,0 256,84.34 277.08,195.77L665.6,406.58c-141.54,0 -256,93.37 -256,210.85 0,18.06 3.03,36.12 9.03,54.18h-18.06zM810.21,771.02s-3.03,2.98 0,0l21.08,72.28 -84.34,-42.17h-3.03a303.43,303.43 0,0 1,-75.26 9.03c-129.53,0 -237.94,-87.37 -237.94,-195.77 0,-108.45 105.42,-195.77 237.94,-195.77s234.91,87.37 234.91,195.77c0,66.28 -36.17,120.46 -93.37,156.62z"
|
||||
android:fillColor="#3ABF11"/>
|
||||
<path
|
||||
android:pathData="M304.18,325.26c-21.08,0 -39.1,18.06 -39.1,36.17 0,21.08 18.01,36.12 39.1,36.12 21.08,0 39.14,-18.06 39.14,-36.12 0,-21.08 -18.06,-36.17 -39.1,-36.17zM496.92,397.55c21.08,0 39.19,-18.06 39.19,-36.12 0,-21.08 -18.11,-36.17 -39.19,-36.17 -21.04,0 -39.1,18.06 -39.1,36.17 0,21.08 18.06,36.12 39.1,36.12z"
|
||||
android:fillColor="#3ABF11"/>
|
||||
</vector>
|
||||
@@ -1,18 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".activity.MainActivity">
|
||||
tools:context=".activity.main.MainActivity">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello World!"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="click"
|
||||
type="com.ttstd.dialer.activity.main.MainActivity.BtnClick" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:background="@color/default_background_color"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.viewpager.widget.ViewPager
|
||||
android:id="@+id/viewPager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/magicIndicator"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<net.lucode.hackware.magicindicator.MagicIndicator
|
||||
android:id="@+id/magicIndicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="16dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
||||
22
app/src/main/res/layout/fragment_contact.xml
Normal file
22
app/src/main/res/layout/fragment_contact.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".fragment.contact.ContactFragment">
|
||||
|
||||
<data>
|
||||
<variable
|
||||
name="click"
|
||||
type="com.ttstd.dialer.fragment.contact.ContactFragment.BtnClick" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
||||
251
app/src/main/res/layout/fragment_home.xml
Normal file
251
app/src/main/res/layout/fragment_home.xml
Normal file
@@ -0,0 +1,251 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".fragment.home.HomeFragment">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="click"
|
||||
type="com.ttstd.dialer.fragment.home.HomeFragment.BtnClick" />
|
||||
</data>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:shadowColor="#80000000"
|
||||
android:shadowDx="2"
|
||||
android:shadowDy="2"
|
||||
android:shadowRadius="2"
|
||||
android:text="00:00"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="50sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="00:00" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:onClick="@{click::openContact}"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.shehuan.niv.NiceImageView
|
||||
android:id="@+id/niceImageView1"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/ic_contact"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:text="联系人"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="@dimen/home_item_text_size"
|
||||
app:layout_constraintEnd_toEndOf="@+id/niceImageView1"
|
||||
app:layout_constraintStart_toStartOf="@+id/niceImageView1"
|
||||
app:layout_constraintTop_toBottomOf="@+id/niceImageView1" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:onClick="@{click::openSettings}"
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.shehuan.niv.NiceImageView
|
||||
android:id="@+id/niceImageView2"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/ic_settings"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:text="设置"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="@dimen/home_item_text_size"
|
||||
app:layout_constraintEnd_toEndOf="@+id/niceImageView2"
|
||||
app:layout_constraintStart_toStartOf="@+id/niceImageView2"
|
||||
app:layout_constraintTop_toBottomOf="@+id/niceImageView2" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:onClick="@{click::openDouyin}"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.shehuan.niv.NiceImageView
|
||||
android:id="@+id/niceImageView3"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/ic_douyin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:textStyle="bold"
|
||||
android:text="微信"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="@dimen/home_item_text_size"
|
||||
app:layout_constraintEnd_toEndOf="@+id/niceImageView3"
|
||||
app:layout_constraintStart_toStartOf="@+id/niceImageView3"
|
||||
app:layout_constraintTop_toBottomOf="@+id/niceImageView3" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:onClick="@{click::openWeixin}"
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.shehuan.niv.NiceImageView
|
||||
android:id="@+id/niceImageView4"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:textStyle="bold"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/ic_weixin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:text="抖音"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="@dimen/home_item_text_size"
|
||||
app:layout_constraintEnd_toEndOf="@+id/niceImageView4"
|
||||
app:layout_constraintStart_toStartOf="@+id/niceImageView4"
|
||||
app:layout_constraintTop_toBottomOf="@+id/niceImageView4" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</FrameLayout>
|
||||
</layout>
|
||||
@@ -3,4 +3,252 @@
|
||||
<color name="colorPrimary">#6200EE</color>
|
||||
<color name="colorPrimaryDark">#3700B3</color>
|
||||
<color name="colorAccent">#03DAC5</color>
|
||||
|
||||
<color name="indicator_color_normal">#99bd2f25</color>
|
||||
<color name="indicator_color_selected">#BD2F25</color>
|
||||
|
||||
<color name="default_background_color">#F8CBBD</color>
|
||||
|
||||
|
||||
|
||||
<!--https://www.jianshu.com/p/8dc258dfd189-->
|
||||
<color name="transparent">#00000000</color><!--透明色-->
|
||||
<color name="colorYang">#FFE2C59B</color><!--羊皮纸色-->
|
||||
<color name="ivory">#FFFFFFF0</color><!--象牙色-->
|
||||
<color name="lightYellow">#FFFFFFE0</color><!--亮黄色-->
|
||||
<color name="yellow">#FFFFFF00</color><!--黄色-->
|
||||
<color name="snow">#FFFFFAFA</color><!--雪白色-->
|
||||
<color name="floralWhite">#FFFFFAF0</color><!--花白色-->
|
||||
<color name="lemonChiffon">#FFFFFACD</color><!--柠檬绸色-->
|
||||
<color name="cornSilk">#FFFFF8DC</color><!--米绸色-->
|
||||
<color name="seashell">#FFFFF5EE</color><!--海贝色-->
|
||||
<color name="lavenderBlush">#FFFFF0F5</color><!--淡紫红色-->
|
||||
<color name="papayaWhip">#FFFFEFD5</color><!--番木色-->
|
||||
<color name="blanchedAlmond">#FFFFEBCD</color><!--白杏色-->
|
||||
<color name="mistyRose">#FFFFE4E1</color><!--浅玫瑰色-->
|
||||
<color name="bisque">#FFFFE4C4</color><!--桔黄色-->
|
||||
<color name="moccasin">#FFFFE4B5</color><!--鹿皮色-->
|
||||
<color name="navajoWhite">#FFFFDEAD</color><!--纳瓦白-->
|
||||
<color name="peachPuff">#FFFFDAB9</color><!--桃色-->
|
||||
<color name="gold">#FFFFD700</color><!--金色-->
|
||||
<color name="pink">#FFFFC0CB</color><!--粉红色-->
|
||||
<color name="lightPink">#FFFFB6C1</color><!--亮粉红色-->
|
||||
<color name="orange">#FFFFA500</color><!--橙色-->
|
||||
<color name="lightSalmon">#FFFFA07A</color><!--亮肉色-->
|
||||
<color name="darkOrange">#FFFF8C00</color><!--暗桔黄色-->
|
||||
<color name="coral">#FFFF7F50</color><!--珊瑚色-->
|
||||
<color name="hotPink">#FFFF69B4</color><!--热粉红色-->
|
||||
<color name="tomato">#FFFF6347</color><!--西红柿色-->
|
||||
<color name="orangeRed">#FFFF4500</color><!--红橙色-->
|
||||
<color name="deepPink">#FFFF1493</color><!--深粉红色-->
|
||||
<color name="fuchsia">#FFFF00FF</color><!--紫红色-->
|
||||
<color name="red">#FFFF0000</color><!--红色-->
|
||||
<color name="oldLace">#FFFDF5E6</color><!--老花色-->
|
||||
<color name="lightGoldenrodYellow">#FFFAFAD2</color><!--亮金黄色-->
|
||||
<color name="linen">#FFFAF0E6</color><!--亚麻色-->
|
||||
<color name="antiqueWhite">#FFFAEBD7</color><!--古董白色-->
|
||||
<color name="salmon">#FFFA8072</color><!--鲜肉色-->
|
||||
<color name="ghostWhite">#FFF8F8FF</color><!--幽灵白-->
|
||||
<color name="mintCream">#FFF5FFFA</color><!--薄荷色-->
|
||||
<color name="whiteSmoke">#FFF5F5F5</color><!--烟白色-->
|
||||
<color name="beige">#FFF5F5DC</color><!--米色-->
|
||||
<color name="wheat">#FFF5DEB3</color><!--浅黄色-->
|
||||
<color name="sandyBrown">#FFF4A460</color><!--沙褐色-->
|
||||
<color name="azure">#FFF0FFFF</color><!--天蓝色-->
|
||||
<color name="aliceBlue">#FFF0F8FF</color><!--艾利斯兰色-->
|
||||
<color name="khaki">#FFF0E68C</color><!--黄褐色-->
|
||||
<color name="lightCoral">#FFF08080</color><!--亮珊瑚色-->
|
||||
<color name="paleGoldenrod">#FFEEE8AA</color><!--苍麒麟色-->
|
||||
<color name="violet">#FFEE82EE</color><!--紫罗兰色-->
|
||||
<color name="darkSalmon">#FFE9967A</color><!--暗肉色-->
|
||||
<color name="lavender">#FFE6E6FA</color><!--淡紫色-->
|
||||
<color name="lightCyan">#FFE0FFFF</color><!--亮青色-->
|
||||
<color name="burlyWood">#FFDEB887</color><!--实木色-->
|
||||
<color name="plum">#FFDDA0DD</color><!--洋李色-->
|
||||
<color name="lightGrey">#FFDCDCDC</color><!--淡灰色-->
|
||||
<color name="crimson">#FFDC143C</color><!--暗深红色-->
|
||||
<color name="paleVioletRed">#FFDB7093</color><!--苍紫罗兰色-->
|
||||
<color name="goldenrod">#FFDAA520</color><!--金麒麟色-->
|
||||
<color name="orchid">#FFDA70D6</color><!--淡紫色-->
|
||||
<color name="thistle">#FFD8BFD8</color><!--蓟色-->
|
||||
<color name="lightGray">#FFD3D3D3</color><!--亮灰色-->
|
||||
<color name="tan">#FFD2B48C</color><!--茶色-->
|
||||
<color name="chocolate">#FFD2691E</color><!--巧可力色-->
|
||||
<color name="peru">#FFCD853F</color><!--秘鲁色-->
|
||||
<color name="indianRed">#FFCD5C5C</color><!--印第安红色-->
|
||||
<color name="mediumVioletRed">#FFC71585</color><!--中紫罗兰色-->
|
||||
<color name="silver">#FFC0C0C0</color><!--银色-->
|
||||
<color name="darkKhaki">#FFBDB76B</color><!--暗黄褐色-->
|
||||
<color name="rosyBrown">#FFBC8F8F</color><!--褐玫瑰红色-->
|
||||
<color name="mediumOrchid">#FFBA55D3</color><!--中粉紫色-->
|
||||
<color name="darkGoldenrod">#FFB8860B</color><!--暗金黄色-->
|
||||
<color name="firebrick">#FFB22222</color><!--火砖色-->
|
||||
<color name="powderBlue">#FFB0E0E6</color><!--粉蓝色-->
|
||||
<color name="lightSteelBlue">#FFB0C4DE</color><!--亮钢兰色-->
|
||||
<color name="paleTurquoise">#FFAFEEEE</color><!--苍宝石绿色-->
|
||||
<color name="greenYellow">#FFADFF2F</color><!--黄绿色-->
|
||||
<color name="lightBlue">#FFADD8E6</color><!--亮蓝色-->
|
||||
<color name="darkGray">#FFA9A9A9</color><!--暗灰色-->
|
||||
<color name="brown">#FFA52A2A</color><!--褐色-->
|
||||
<color name="sienna">#FFA0522D</color><!--赭色-->
|
||||
<color name="darkOrchid">#FF9932CC</color><!--暗紫色-->
|
||||
<color name="paleGreen">#FF98FB98</color><!--苍绿色-->
|
||||
<color name="darkViolet">#FF9400D3</color><!--暗紫罗兰色-->
|
||||
<color name="mediumPurple">#FF9370DB</color><!--中紫色-->
|
||||
<color name="lightGreen">#FF90EE90</color><!--亮绿色-->
|
||||
<color name="darkSeaGreen">#FF8FBC8F</color><!--暗海兰色-->
|
||||
<color name="saddleBrown">#FF8B4513</color><!--重褐色-->
|
||||
<color name="darkMagenta">#FF8B008B</color><!--暗洋红色-->
|
||||
<color name="darkRed">#FF8B0000</color><!--暗红色-->
|
||||
<color name="blueViolet">#FF8A2BE2</color><!--紫罗兰蓝色-->
|
||||
<color name="lightSkyBlue">#FF87CEFA</color><!--亮天蓝色-->
|
||||
<color name="skyBlue">#FF87CEEB</color><!--天蓝色-->
|
||||
<color name="gray">#FF808080</color><!--灰色-->
|
||||
<color name="olive">#FF808000</color><!--橄榄色-->
|
||||
<color name="purple">#FF800080</color><!--紫色-->
|
||||
<color name="maroon">#FF800000</color><!--粟色-->
|
||||
<color name="aquamarine">#FF7FFFD4</color><!--碧绿色-->
|
||||
<color name="chartreuse">#FF7FFF00</color><!--黄绿色-->
|
||||
<color name="lawnGreen">#FF7CFC00</color><!--草绿色-->
|
||||
<color name="mediumSlateBlue">#FF7B68EE</color><!--中暗蓝色-->
|
||||
<color name="lightSlateGray">#FF778899</color><!--亮蓝灰色-->
|
||||
<color name="slateGray">#FF708090</color><!--灰石色-->
|
||||
<color name="oliveDrab">#FF6B8E23</color><!--深绿褐色-->
|
||||
<color name="slateBlue">#FF6A5ACD</color><!--石蓝色-->
|
||||
<color name="dimGray">#FF696969</color><!--暗灰色-->
|
||||
<color name="mediumAquamarine">#FF66CDAA</color><!--中绿色-->
|
||||
<color name="cornFlowerBlue">#FF6495ED</color><!--菊兰色-->
|
||||
<color name="cadetBlue">#FF5F9EA0</color><!--军兰色-->
|
||||
<color name="darkOliveGreen">#FF556B2F</color><!--暗橄榄绿色-->
|
||||
<color name="indigo">#FF4B0082</color><!--靛青色-->
|
||||
<color name="mediumTurquoise">#FF48D1CC</color><!--中绿宝石色-->
|
||||
<color name="darkSlateBlue">#FF483D8B</color><!--暗灰蓝色-->
|
||||
<color name="steelBlue">#FF4682B4</color><!--钢兰色-->
|
||||
<color name="royalBlue">#FF4169E1</color><!--皇家蓝色-->
|
||||
<color name="turquoise">#FF40E0D0</color><!--青绿色-->
|
||||
<color name="mediumSeaGreen">#FF3CB371</color><!--中海蓝色-->
|
||||
<color name="limeGreen">#FF32CD32</color><!--橙绿色-->
|
||||
<color name="darkSlateGray">#FF2F4F4F</color><!--暗瓦灰色-->
|
||||
<color name="seaGreen">#FF2E8B57</color><!--海绿色-->
|
||||
<color name="forestGreen">#FF228B22</color><!--森林绿色-->
|
||||
<color name="lightSeaGreen">#FF20B2AA</color><!--亮海蓝色-->
|
||||
<color name="dodgerBlue">#FF1E90FF</color><!--闪兰色-->
|
||||
<color name="midnightBlue">#FF191970</color><!--中灰兰色-->
|
||||
<color name="aqua">#FF00FFFF</color><!--浅绿色-->
|
||||
<color name="springGreen">#FF00FF7F</color><!--春绿色-->
|
||||
<color name="lime">#FF00FF00</color><!--酸橙色-->
|
||||
<color name="mediumSpringGreen">#FF00FA9A</color><!--中春绿色-->
|
||||
<color name="darkTurquoise">#FF00CED1</color><!--暗宝石绿色-->
|
||||
<color name="deepSkyBlue">#FF00BFFF</color><!--深天蓝色-->
|
||||
<color name="darkCyan">#FF008B8B</color><!--暗青色-->
|
||||
<color name="teal">#FF008080</color><!--水鸭色-->
|
||||
<color name="green">#FF008000</color><!--绿色-->
|
||||
<color name="darkGreen">#FF006400</color><!--暗绿色-->
|
||||
<color name="blue">#FF0000FF</color><!--蓝色-->
|
||||
<color name="mediumBlue">#FF0000CD</color><!--中兰色-->
|
||||
<color name="darkBlue">#FF00008B</color><!--暗蓝色-->
|
||||
<color name="navy">#FF000080</color><!--海军色-->
|
||||
<color name="unBlack">#FF2B2B2B</color><!--不知名黑色-->
|
||||
|
||||
<!--https://www.cnblogs.com/elleniou/archive/2012/04/25/2469676.html-->
|
||||
<color name="white">#ffffff</color><!--白色 -->
|
||||
<color name="lightyellow">#ffffe0</color><!--亮黄色 -->
|
||||
<color name="floralwhite">#fffaf0</color><!--花白色 -->
|
||||
<color name="lemonchiffon">#fffacd</color><!--柠檬绸色 -->
|
||||
<color name="cornsilk">#fff8dc</color><!--米绸色 -->
|
||||
<color name="seaShell">#fff5ee</color><!--海贝色 -->
|
||||
<color name="lavenderblush">#fff0f5</color><!--淡紫红 -->
|
||||
<color name="papayawhip">#ffefd5</color><!--番木色 -->
|
||||
<color name="blanchedalmond">#ffebcd</color><!--白杏色 -->
|
||||
<color name="mistyrose">#ffe4e1</color><!--浅玫瑰色 -->
|
||||
<color name="navajowhite">#ffdead</color><!--纳瓦白 -->
|
||||
<color name="peachpuff">#ffdab9</color><!--桃色 -->
|
||||
<color name="lightpink">#ffb6c1</color><!--亮粉红色 -->
|
||||
<color name="lightsalmon">#ffa07a</color><!--亮肉色 -->
|
||||
<color name="darkorange">#ff8c00</color><!--暗桔黄色 -->
|
||||
<color name="hotpink">#ff69b4</color><!--热粉红色 -->
|
||||
<color name="orangered">#ff4500</color><!--红橙色 -->
|
||||
<color name="deeppink">#ff1493</color><!--深粉红色 -->
|
||||
<color name="magenta">#ff00ff</color><!--红紫色 -->
|
||||
<color name="oldlace">#fdf5e6</color><!--老花色 -->
|
||||
<color name="lightgoldenrodyellow">#fafad2</color><!--亮金黄色 -->
|
||||
<color name="antiquewhite">#faebd7</color><!--古董白 -->
|
||||
<color name="ghostwhite">#f8f8ff</color><!--幽灵白 -->
|
||||
<color name="mintcream">#f5fffa</color><!--薄荷色 -->
|
||||
<color name="whitesmoke">#f5f5f5</color><!--烟白色 -->
|
||||
<color name="sandybrown">#f4a460</color><!--沙褐色 -->
|
||||
<color name="honeydew">#f0fff0</color><!--蜜色 -->
|
||||
<color name="aliceblue">#f0f8ff</color><!--艾利斯兰 -->
|
||||
<color name="lightcoral">#f08080</color><!--亮珊瑚色 -->
|
||||
<color name="palegoldenrod">#eee8aa</color><!--苍麒麟色 -->
|
||||
<color name="darksalmon">#e9967a</color><!--暗肉色 -->
|
||||
<color name="lightcyan">#e0ffff</color><!--亮青色 -->
|
||||
<color name="burlywood">#deb887</color><!--实木色 -->
|
||||
<color name="gainsboro">#dcdcdc</color><!--淡灰色 -->
|
||||
<color name="palevioletred">#db7093</color><!--苍紫罗兰色 -->
|
||||
<color name="lightgray">#d3d3d3</color><!--亮灰色 -->
|
||||
<color name="lightgrey">#d3d3d3</color><!--亮灰色 -->
|
||||
<color name="indianred">#cd5c5c</color><!--印第安红 -->
|
||||
<color name="mediumvioletred">#c71585</color><!--中紫罗兰色 -->
|
||||
<color name="darkkhaki">#bdb76b</color><!--暗黄褐色 -->
|
||||
<color name="rosybrown">#bc8f8f</color><!--褐玫瑰红 -->
|
||||
<color name="mediumorchid">#ba55d3</color><!--中粉紫色 -->
|
||||
<color name="darkgoldenrod">#b8860b</color><!--暗金黄色 -->
|
||||
<color name="powderblue">#b0e0e6</color><!--粉蓝色 -->
|
||||
<color name="lightsteelblue">#b0c4de</color><!--亮钢兰色 -->
|
||||
<color name="paleturquoise">#afeeee</color><!--苍宝石绿 -->
|
||||
<color name="greenyellow">#adff2f</color><!--黄绿色 -->
|
||||
<color name="lightblue">#add8e6</color><!--亮蓝色 -->
|
||||
<color name="darkgray">#a9a9a9</color><!--暗灰色 -->
|
||||
<color name="darkgrey">#a9a9a9</color><!--暗灰色 -->
|
||||
<color name="darkorchid">#9932cc</color><!--暗紫色 -->
|
||||
<color name="palegreen">#98fb98</color><!--苍绿色 -->
|
||||
<color name="darkviolet">#9400d3</color><!--暗紫罗兰色 -->
|
||||
<color name="mediumpurple">#9370db</color><!--中紫色 -->
|
||||
<color name="lightgreen">#90ee90</color><!--亮绿色 -->
|
||||
<color name="darkseagreen">#8fbc8f</color><!--暗海兰色 -->
|
||||
<color name="saddlebrown">#8b4513</color><!--重褐色 -->
|
||||
<color name="darkmagenta">#8b008b</color><!--暗洋红 -->
|
||||
<color name="darkred">#8b0000</color><!--暗红色 -->
|
||||
<color name="blueviolet">#8a2be2</color><!--紫罗兰蓝色 -->
|
||||
<color name="lightskyblue">#87cefa</color><!--亮天蓝色 -->
|
||||
<color name="skyblue">#87ceeb</color><!--天蓝色 -->
|
||||
<color name="grey">#808080</color><!--灰色 -->
|
||||
<color name="lawngreen">#7cfc00</color><!--草绿色 -->
|
||||
<color name="mediumslateblue">#7b68ee</color><!--中暗蓝色 -->
|
||||
<color name="lightslategray">#778899</color><!--亮蓝灰 -->
|
||||
<color name="lightslategrey">#778899</color><!--亮蓝灰 -->
|
||||
<color name="slategray">#708090</color><!--灰石色 -->
|
||||
<color name="slategrey">#708090</color><!--灰石色 -->
|
||||
<color name="olivedrab">#6b8e23</color><!--深绿褐色 -->
|
||||
<color name="slateblue">#6a5acd</color><!--石蓝色 -->
|
||||
<color name="dimgray">#696969</color><!--暗灰色 -->
|
||||
<color name="dimgrey">#696969</color><!--暗灰色 -->
|
||||
<color name="mediumaquamarine">#66cdaa</color><!--中绿色 -->
|
||||
<color name="cornflowerblue">#6495ed</color><!--菊兰色 -->
|
||||
<color name="cadetblue">#5f9ea0</color><!--军兰色 -->
|
||||
<color name="darkolivegreen">#556b2f</color><!--暗橄榄绿 -->
|
||||
<color name="mediumturquoise">#48d1cc</color><!--中绿宝石 -->
|
||||
<color name="darkslateblue">#483d8b</color><!--暗灰蓝色 -->
|
||||
<color name="steelblue">#4682b4</color><!--钢兰色 -->
|
||||
<color name="royalblue">#4169e1</color><!--皇家蓝 -->
|
||||
<color name="mediumseagreen">#3cb371</color><!--中海蓝 -->
|
||||
<color name="limegreen">#32cd32</color><!--橙绿色 -->
|
||||
<color name="darkslategray">#2f4f4f</color><!--暗瓦灰色 -->
|
||||
<color name="darkslategrey">#2f4f4f</color><!--暗瓦灰色 -->
|
||||
<color name="seagreen">#2e8b57</color><!--海绿色 -->
|
||||
<color name="forestgreen">#228b22</color><!--森林绿 -->
|
||||
<color name="lightseagreen">#20b2aa</color><!--亮海蓝色 -->
|
||||
<color name="dodgerblue">#1e90ff</color><!--闪兰色 -->
|
||||
<color name="midnightblue">#191970</color><!--中灰兰色 -->
|
||||
<color name="cyan">#00ffff</color><!--青色 -->
|
||||
<color name="springgreen">#00ff7f</color><!--春绿色 -->
|
||||
<color name="mediumspringgreen">#00fa9a</color><!--中春绿色 -->
|
||||
<color name="darkturquoise">#00ced1</color><!--暗宝石绿 -->
|
||||
<color name="deepskyblue">#00bfff</color><!--深天蓝色 -->
|
||||
<color name="darkcyan">#008b8b</color><!--暗青色 -->
|
||||
<color name="darkgreen">#006400</color><!--暗绿色 -->
|
||||
<color name="mediumblue">#0000cd</color><!--中兰色 -->
|
||||
<color name="darkblue">#00008b</color><!--暗蓝色 -->
|
||||
<color name="black">#000000</color><!--黑色 -->
|
||||
</resources>
|
||||
|
||||
4
app/src/main/res/values/dimens.xml
Normal file
4
app/src/main/res/values/dimens.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="home_item_text_size">20sp</dimen>
|
||||
</resources>
|
||||
@@ -1,3 +1,6 @@
|
||||
<resources>
|
||||
<string name="app_name">拨号助手</string>
|
||||
|
||||
<!-- TODO: Remove or change this placeholder text -->
|
||||
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">-->
|
||||
<!-- <!– Customize your theme here. –>-->
|
||||
<!-- <item name="colorPrimary">@color/colorPrimary</item>-->
|
||||
<!-- <item name="colorPrimaryDark">@color/colorPrimaryDark</item>-->
|
||||
<!-- <item name="colorAccent">@color/colorAccent</item>-->
|
||||
<!-- </style>-->
|
||||
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="AppThemeFitsSystemWindows" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
<item name="android:fitsSystemWindows">true</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
18
app/src/main/res/xml/file_paths.xml
Normal file
18
app/src/main/res/xml/file_paths.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<external-path
|
||||
name="external"
|
||||
path="." />
|
||||
<external-files-path
|
||||
name="external_files"
|
||||
path="." />
|
||||
<cache-path
|
||||
name="cache"
|
||||
path="." />
|
||||
<external-cache-path
|
||||
name="external_cache"
|
||||
path="." />
|
||||
<files-path
|
||||
name="files"
|
||||
path="." />
|
||||
</paths>
|
||||
4
app/src/main/res/xml/network.xml
Normal file
4
app/src/main/res/xml/network.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<base-config cleartextTrafficPermitted="true" />
|
||||
</network-security-config>
|
||||
1
iconloader/.gitignore
vendored
Normal file
1
iconloader/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
37
iconloader/build.gradle
Normal file
37
iconloader/build.gradle
Normal file
@@ -0,0 +1,37 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 36
|
||||
// buildToolsVersion "36.0.0"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 36
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles 'consumer-rules.pro'
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||
|
||||
//磁盘缓存
|
||||
implementation 'com.jakewharton:disklrucache:2.0.2'
|
||||
}
|
||||
0
iconloader/consumer-rules.pro
Normal file
0
iconloader/consumer-rules.pro
Normal file
21
iconloader/proguard-rules.pro
vendored
Normal file
21
iconloader/proguard-rules.pro
vendored
Normal file
@@ -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
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.uiui.iconloader;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
|
||||
assertEquals("com.ttstd.iconloader.test", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
1
iconloader/src/main/AndroidManifest.xml
Normal file
1
iconloader/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="com.uiui.iconloader" />
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.uiui.iconloader;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
|
||||
public class CacheKeyGenerator {
|
||||
// 生成ComponentName的标准字符串格式(包名/类名)
|
||||
public static String generateKey(ComponentName componentName) {
|
||||
return componentName.flattenToShortString(); // 如:com.example.app/.MainActivity
|
||||
}
|
||||
|
||||
// 从Intent中提取ComponentName作为键
|
||||
public static String generateKey(Intent intent) {
|
||||
return generateKey(intent.getComponent());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.uiui.iconloader;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.collection.LruCache;
|
||||
|
||||
import com.jakewharton.disklrucache.DiskLruCache;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class IconCacheManager {
|
||||
private static final String TAG = "IconCacheManager";
|
||||
private LruCache<String, Drawable> mMemoryCache;
|
||||
private DiskLruCache mDiskCache;
|
||||
|
||||
public IconCacheManager(Context context) {
|
||||
// 内存缓存(最大8MB)
|
||||
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
|
||||
int cacheSize = maxMemory / 8;
|
||||
mMemoryCache = new LruCache<String, Drawable>(cacheSize) {
|
||||
@Override
|
||||
protected int sizeOf(String key, Drawable icon) {
|
||||
if (icon instanceof BitmapDrawable) {
|
||||
return ((BitmapDrawable) icon).getBitmap().getByteCount() / 1024;
|
||||
}
|
||||
return 1; // 非Bitmap对象按1KB计算
|
||||
}
|
||||
};
|
||||
|
||||
// 磁盘缓存(50MB)
|
||||
File cacheDir = new File(context.getCacheDir(), "icon_cache");
|
||||
try {
|
||||
mDiskCache = DiskLruCache.open(cacheDir, 1, 1, 50 * 1024 * 1024);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "IconCacheManager: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public Drawable getFromMemory(String key) {
|
||||
return mMemoryCache.get(key);
|
||||
}
|
||||
|
||||
public Drawable getFromDisk(String key) {
|
||||
try {
|
||||
DiskLruCache.Snapshot snapshot = mDiskCache.get(key);
|
||||
if (snapshot != null) {
|
||||
InputStream in = snapshot.getInputStream(0);
|
||||
return Drawable.createFromStream(in, null);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e("IconCache", "Disk read error", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void putToMemory(String key, Drawable icon) {
|
||||
mMemoryCache.put(key, icon);
|
||||
}
|
||||
|
||||
|
||||
public void putToDisk(String key, Drawable icon) {
|
||||
try {
|
||||
DiskLruCache.Editor editor = mDiskCache.edit(key);
|
||||
if (icon instanceof BitmapDrawable) {
|
||||
Bitmap bitmap = ((BitmapDrawable) icon).getBitmap();
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, editor.newOutputStream(0));
|
||||
editor.commit();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e("IconCache", "Disk write error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
97
iconloader/src/main/java/com/uiui/iconloader/IconLoader.java
Normal file
97
iconloader/src/main/java/com/uiui/iconloader/IconLoader.java
Normal file
@@ -0,0 +1,97 @@
|
||||
package com.uiui.iconloader;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.AdaptiveIconDrawable;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.loader.content.AsyncTaskLoader;
|
||||
|
||||
public class IconLoader extends AsyncTaskLoader<Drawable> {
|
||||
private final ComponentName mComponentName; // 关键:使用ComponentName标识组件
|
||||
private final IconCacheManager mCache;
|
||||
|
||||
public IconLoader(Context context, ComponentName componentName, IconCacheManager cache) {
|
||||
super(context);
|
||||
mComponentName = componentName;
|
||||
mCache = cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
String key = CacheKeyGenerator.generateKey(mComponentName);
|
||||
// 1. 检查内存缓存
|
||||
Drawable cachedIcon = mCache.getFromMemory(key);
|
||||
if (cachedIcon != null) {
|
||||
deliverResult(cachedIcon); // 直接返回缓存
|
||||
return;
|
||||
}
|
||||
forceLoad(); // 触发后台加载
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable loadInBackground() {
|
||||
String key = CacheKeyGenerator.generateKey(mComponentName);
|
||||
|
||||
// 1. 检查内存缓存
|
||||
Drawable cachedIcon = mCache.getFromMemory(key);
|
||||
if (cachedIcon != null) return cachedIcon;
|
||||
|
||||
// 2. 检查磁盘缓存
|
||||
Drawable diskCached = mCache.getFromDisk(key);
|
||||
if (diskCached != null) {
|
||||
mCache.putToMemory(key, diskCached);
|
||||
return diskCached;
|
||||
}
|
||||
|
||||
// 3. 从PackageManager加载组件专属图标
|
||||
try {
|
||||
PackageManager pm = getContext().getPackageManager();
|
||||
ActivityInfo info = pm.getActivityInfo(mComponentName, 0); // 获取ActivityInfo
|
||||
Drawable rawIcon = info.loadIcon(pm); // 加载该组件的图标
|
||||
|
||||
// 4. 图标处理(压缩/主题适配)
|
||||
Drawable processedIcon = processIcon(rawIcon);
|
||||
|
||||
// 5. 更新缓存
|
||||
mCache.putToMemory(key, processedIcon);
|
||||
mCache.putToDisk(key, processedIcon);
|
||||
return processedIcon;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Drawable processIcon(Drawable icon) {
|
||||
// 自适应图标处理(Android 8.0+)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
||||
&& icon instanceof AdaptiveIconDrawable) {
|
||||
AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) icon;
|
||||
int targetColor = isDarkMode() ? Color.WHITE : Color.BLACK;
|
||||
adaptiveIcon.setColorFilter(targetColor, PorterDuff.Mode.SRC_ATOP); // 根据主题切换单色模式
|
||||
}
|
||||
|
||||
// 图标尺寸压缩(避免内存溢出)
|
||||
if (icon instanceof BitmapDrawable) {
|
||||
Bitmap bitmap = ((BitmapDrawable) icon).getBitmap();
|
||||
int scaledWidth = bitmap.getWidth() / 2;
|
||||
int scaledHeight = bitmap.getHeight() / 2;
|
||||
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, true);
|
||||
return new BitmapDrawable(getContext().getResources(), scaledBitmap);
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
private boolean isDarkMode() {
|
||||
return (getContext().getResources().getConfiguration().uiMode
|
||||
& Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.uiui.iconloader;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
||||
1
niceimageview/.gitignore
vendored
Normal file
1
niceimageview/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
48
niceimageview/build.gradle
Normal file
48
niceimageview/build.gradle
Normal file
@@ -0,0 +1,48 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 28
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
iPlay50PDebug {}
|
||||
iPlay50PRelease {}
|
||||
U807Debug {}
|
||||
U807Release {}
|
||||
iPlay50Debug {}
|
||||
iPlay50Release {}
|
||||
zhanRuiDebug {}
|
||||
zhanRuiRelease {}
|
||||
iPlay50SEDebug {}
|
||||
iPlay50SERelease {}
|
||||
iPlay50ProDebug {}
|
||||
iPlay50ProRelease {}
|
||||
T1102Debug {}
|
||||
T1102Release {}
|
||||
iPlay50miniDebug {}
|
||||
iPlay50miniRelease {}
|
||||
iPlay5013Debug {}
|
||||
iPlay5013Release {}
|
||||
iPlay50miniProDebug {}
|
||||
iPlay50miniProRelease {}
|
||||
XPadDebug {}
|
||||
XPadRelease {}
|
||||
teclast8183Debug {}
|
||||
teclast8183Release {}
|
||||
G10PDebug {}
|
||||
G10PRelease {}
|
||||
debug {}
|
||||
release {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
implementation 'com.android.support:appcompat-v7:28.0.0'
|
||||
}
|
||||
21
niceimageview/proguard-rules.pro
vendored
Normal file
21
niceimageview/proguard-rules.pro
vendored
Normal file
@@ -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 menu 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
|
||||
2
niceimageview/src/main/AndroidManifest.xml
Normal file
2
niceimageview/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.shehuan.niv" />
|
||||
338
niceimageview/src/main/java/com/shehuan/niv/NiceImageView.java
Normal file
338
niceimageview/src/main/java/com/shehuan/niv/NiceImageView.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
11
niceimageview/src/main/java/com/shehuan/niv/Utils.java
Normal file
11
niceimageview/src/main/java/com/shehuan/niv/Utils.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
17
niceimageview/src/main/res/values/attrs.xml
Normal file
17
niceimageview/src/main/res/values/attrs.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<declare-styleable name="NiceImageView">
|
||||
<attr name="is_circle" format="boolean" />
|
||||
<attr name="is_cover_src" format="boolean" />
|
||||
<attr name="corner_radius" format="dimension" />
|
||||
<attr name="corner_top_left_radius" format="dimension" />
|
||||
<attr name="corner_top_right_radius" format="dimension" />
|
||||
<attr name="corner_bottom_left_radius" format="dimension" />
|
||||
<attr name="corner_bottom_right_radius" format="dimension" />
|
||||
<attr name="border_width" format="dimension" />
|
||||
<attr name="border_color" format="color" />
|
||||
<attr name="inner_border_width" format="dimension" />
|
||||
<attr name="inner_border_color" format="color" />
|
||||
<attr name="mask_color" format="color" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
3
niceimageview/src/main/res/values/strings.xml
Normal file
3
niceimageview/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">niceimageview</string>
|
||||
</resources>
|
||||
@@ -1,2 +1,2 @@
|
||||
rootProject.name='拨号助手'
|
||||
include ':app'
|
||||
include ':app', ':niceimageview', ':iconloader'
|
||||
|
||||
Reference in New Issue
Block a user