主页固定显示

This commit is contained in:
2025-10-20 21:30:28 +08:00
parent b1f21fd4f7
commit 68b2e0754c
74 changed files with 4881 additions and 34 deletions

View File

@@ -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'
}

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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 {
}
}

View File

@@ -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> {
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
};
}
}

View 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();
}
}
}
});
}
}

View File

@@ -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;
}
});
}
}
}

View File

@@ -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;
}
}

View 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;
}
}

View File

@@ -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;
}
});
}
}
}

View File

@@ -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();
}

View File

@@ -0,0 +1,8 @@
package com.ttstd.dialer.base.mvp;
@Deprecated
public interface BasePresenter<V extends BaseView> {
void attachView(V view);
void detachView();
}

View File

@@ -0,0 +1,6 @@
package com.ttstd.dialer.base.mvp;
@Deprecated
public interface BaseView {
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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() {
}
}

View File

@@ -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();
}
}

View 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.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();
}
}

View 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();
}
}

View File

@@ -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);
}
}

View File

@@ -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";
}

View 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;
}
}

View 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();
}
}

View 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);
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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{
}
}

View File

@@ -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> {
}

View File

@@ -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");
}
}
}
}

View File

@@ -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> {
}

View 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;
}
}

View 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());
}
}

View File

@@ -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());
// }
}

View 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;
}
}

View 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;
}
}

View 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];
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -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"
app:layout_constraintTop_toTopOf="parent" />
<data>
</androidx.constraintlayout.widget.ConstraintLayout>
<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>

View 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>

View 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>

View File

@@ -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>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="home_item_text_size">20sp</dimen>
</resources>

View File

@@ -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>

View File

@@ -1,11 +1,25 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">-->
<!-- &lt;!&ndash; Customize your theme here. &ndash;&gt;-->
<!-- <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>

View 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>

View 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
View File

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

37
iconloader/build.gradle Normal file
View 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'
}

View File

21
iconloader/proguard-rules.pro vendored Normal file
View 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

View File

@@ -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());
}
}

View File

@@ -0,0 +1 @@
<manifest package="com.uiui.iconloader" />

View File

@@ -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());
}
}

View File

@@ -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);
}
}
}

View 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;
}
}

View File

@@ -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
View File

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

View 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
View 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

View File

@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.shehuan.niv" />

View 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();
}
}

View 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);
}
}

View 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>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">niceimageview</string>
</resources>

View File

@@ -1,2 +1,2 @@
rootProject.name='拨号助手'
include ':app'
include ':app', ':niceimageview', ':iconloader'