89 files added
1 files modified
| | |
| | | # Built application files |
| | | *.apk |
| | | *.ap_ |
| | | |
| | | # Files for the Dalvik VM |
| | | *.dex |
| | | |
| | | # Java class files |
| | | *.class |
| | | |
| | | # Generated files |
| | | bin/ |
| | | gen/ |
| | | |
| | | # Gradle files |
| | | .gradle/ |
| | | build/ |
| | | |
| | | # Local configuration file (sdk path, etc) |
| | | *.iml |
| | | .gradle |
| | | /local.properties |
| | | /.idea/caches |
| | | /.idea/libraries |
| | | /.idea/modules.xml |
| | | /.idea/workspace.xml |
| | | /.idea/navEditor.xml |
| | | /.idea/assetWizardSettings.xml |
| | | .DS_Store |
| | | /build |
| | | /captures |
| | | .externalNativeBuild |
| | | .cxx |
| | | local.properties |
| | | |
| | | # Proguard folder generated by Eclipse |
| | | proguard/ |
| | | |
| | | #Log Files |
| | | *.log |
| | | /.idea/ |
| | | /.idea/ |
New file |
| | |
| | | /build |
| | | /src/androidTest/ |
| | | /src/test/ |
New file |
| | |
| | | plugins { |
| | | id 'com.android.application' |
| | | } |
| | | |
| | | android { |
| | | compileSdk 31 |
| | | |
| | | defaultConfig { |
| | | applicationId "com.runt.open.mvvm" |
| | | minSdk 23 |
| | | targetSdk 31 |
| | | versionCode 1 |
| | | versionName "1.0" |
| | | |
| | | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" |
| | | } |
| | | |
| | | buildTypes { |
| | | debug{ |
| | | minifyEnabled false |
| | | buildConfigField 'String','HOST_IP_ADDR','"http://192.168.100.82:8080/"' |
| | | buildConfigField 'String','ENVIRONMENT','"release"' |
| | | resValue "string", "app_name", "MVVM开源项目测试" |
| | | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' |
| | | } |
| | | release { |
| | | minifyEnabled false |
| | | buildConfigField 'String','HOST_IP_ADDR','"http://192.168.100.82:8080/"' |
| | | buildConfigField 'String','ENVIRONMENT','"release"' |
| | | resValue "string", "app_name", "MVVM开源项目" |
| | | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' |
| | | } |
| | | } |
| | | compileOptions { |
| | | sourceCompatibility JavaVersion.VERSION_1_8 |
| | | targetCompatibility JavaVersion.VERSION_1_8 |
| | | } |
| | | buildFeatures { |
| | | viewBinding true |
| | | } |
| | | } |
| | | |
| | | dependencies { |
| | | |
| | | implementation 'androidx.appcompat:appcompat:1.2.0' |
| | | implementation 'com.google.android.material:material:1.3.0' |
| | | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' |
| | | implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' |
| | | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' |
| | | implementation 'androidx.navigation:navigation-fragment:2.3.5' |
| | | implementation 'androidx.navigation:navigation-ui:2.3.5' |
| | | testImplementation 'junit:junit:4.+' |
| | | androidTestImplementation 'androidx.test.ext:junit:1.1.2' |
| | | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' |
| | | //// 注意:分包之后不会有默认的Header和Footer需要手动添加!还是原来的三种方法! |
| | | implementation 'com.scwang.smart:refresh-layout-kernel:2.0.1' //核心必须依赖 |
| | | implementation 'com.scwang.smart:refresh-header-classics:2.0.1' //经典刷新头 |
| | | implementation 'com.github.d-max:spots-dialog:1.1@aar'//loading view |
| | | implementation 'com.google.code.gson:gson:2.8.6' |
| | | implementation 'com.squareup.okhttp3:okhttp:4.9.0' |
| | | implementation 'com.squareup.retrofit2:retrofit:2.9.0' |
| | | implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0' //RXjava和retrofit结合 |
| | | implementation 'com.permissionx.guolindev:permissionx:1.2.2' //权限依赖让你推广你就发群里?没有别的群了? |
| | | implementation 'com.github.bumptech.glide:glide:4.12.0' |
| | | annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0' |
| | | } |
New file |
| | |
| | | # 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 |
New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
| | | package="com.runt.open.mvvm" > |
| | | |
| | | <application |
| | | android:allowBackup="true" |
| | | android:icon="@mipmap/ic_launcher" |
| | | android:label="@string/app_name" |
| | | android:roundIcon="@mipmap/ic_launcher_round" |
| | | android:supportsRtl="true" |
| | | android:theme="@style/Theme.OpemMvvm" > |
| | | <activity |
| | | android:name=".MainActivity" |
| | | android:exported="true" |
| | | android:label="@string/app_name" > |
| | | <intent-filter> |
| | | <action android:name="android.intent.action.MAIN" /> |
| | | |
| | | <category android:name="android.intent.category.LAUNCHER" /> |
| | | </intent-filter> |
| | | </activity> |
| | | </application> |
| | | |
| | | </manifest> |
New file |
| | |
| | | package com.runt.open.mvvm; |
| | | |
| | | import android.os.Bundle; |
| | | |
| | | import com.google.android.material.bottomnavigation.BottomNavigationView; |
| | | |
| | | import androidx.appcompat.app.AppCompatActivity; |
| | | import androidx.navigation.NavController; |
| | | import androidx.navigation.Navigation; |
| | | import androidx.navigation.ui.AppBarConfiguration; |
| | | import androidx.navigation.ui.NavigationUI; |
| | | |
| | | import com.runt.open.mvvm.databinding.ActivityMainBinding; |
| | | |
| | | public class MainActivity extends AppCompatActivity { |
| | | |
| | | private ActivityMainBinding binding; |
| | | |
| | | @Override |
| | | protected void onCreate(Bundle savedInstanceState) { |
| | | super.onCreate(savedInstanceState); |
| | | |
| | | binding = ActivityMainBinding.inflate(getLayoutInflater()); |
| | | setContentView(binding.getRoot()); |
| | | |
| | | NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main); |
| | | NavigationUI.setupWithNavController(binding.navView, navController); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm; |
| | | |
| | | import android.app.Activity; |
| | | import android.app.Application; |
| | | import android.content.Context; |
| | | import android.os.Bundle; |
| | | |
| | | import androidx.annotation.NonNull; |
| | | import androidx.annotation.Nullable; |
| | | |
| | | import com.runt.open.mvvm.util.MyLog; |
| | | import com.scwang.smart.refresh.footer.ClassicsFooter; |
| | | import com.scwang.smart.refresh.header.ClassicsHeader; |
| | | import com.scwang.smart.refresh.layout.SmartRefreshLayout; |
| | | import com.scwang.smart.refresh.layout.api.RefreshFooter; |
| | | import com.scwang.smart.refresh.layout.api.RefreshHeader; |
| | | import com.scwang.smart.refresh.layout.api.RefreshLayout; |
| | | import com.scwang.smart.refresh.layout.listener.DefaultRefreshFooterCreator; |
| | | import com.scwang.smart.refresh.layout.listener.DefaultRefreshHeaderCreator; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * Created by Administrator on 2021/10/28 0028. |
| | | */ |
| | | public class MyApplication extends Application { |
| | | |
| | | final String TAG = "MyApplication"; |
| | | List<Activity> activities = new ArrayList<>(); |
| | | private Activity currentActivity;//当前activity |
| | | private boolean isInfront; //是否前台运行 |
| | | private static MyApplication application; |
| | | public static MyApplication getApplication() { |
| | | return application; |
| | | } |
| | | |
| | | public Activity getCurrentActivity() { |
| | | return currentActivity; |
| | | } |
| | | |
| | | @Override |
| | | public void onCreate() { |
| | | super.onCreate(); |
| | | application = this; |
| | | //CrashReport.initCrashReport(getApplicationContext(), "8d88679ae9", false);//注册bugly |
| | | //设置全局的Header构建器 |
| | | SmartRefreshLayout.setDefaultRefreshHeaderCreator(new DefaultRefreshHeaderCreator() { |
| | | @Override |
| | | public RefreshHeader createRefreshHeader(Context context, RefreshLayout layout) { |
| | | //layout.setPrimaryColorsId(R.color.colorPrimary, android.R.color.white);//全局设置主题颜色 |
| | | return new ClassicsHeader(context);//.setTimeFormat(new DynamicTimeFormat("更新于 %s"));//指定为经典Header,默认是 贝塞尔雷达Header |
| | | } |
| | | }); |
| | | //设置全局的Footer构建器 |
| | | SmartRefreshLayout.setDefaultRefreshFooterCreator(new DefaultRefreshFooterCreator() { |
| | | @Override |
| | | public RefreshFooter createRefreshFooter(Context context, RefreshLayout layout) { |
| | | //指定为经典Footer,默认是 BallPulseFooter |
| | | return new ClassicsFooter(context).setDrawableSize(20); |
| | | } |
| | | }); |
| | | registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { |
| | | @Override |
| | | public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { |
| | | MyLog.d(TAG,"onActivityCreated "+activity.getClass().getSimpleName()); |
| | | if(!activities.contains(activity)){ |
| | | activities.add(activity); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onActivityStarted(@NonNull Activity activity) { |
| | | MyLog.d(TAG,"onActivityStarted "+activity.getClass().getSimpleName()); |
| | | currentActivity = activity; |
| | | } |
| | | |
| | | @Override |
| | | public void onActivityResumed(@NonNull Activity activity) { |
| | | MyLog.d(TAG,"onActivityResumed "+activity.getClass().getSimpleName()); |
| | | currentActivity = activity; |
| | | isInfront = true; |
| | | } |
| | | |
| | | @Override |
| | | public void onActivityPaused(@NonNull Activity activity) { |
| | | MyLog.d(TAG,"onActivityPaused "+activity.getClass().getSimpleName()); |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public void onActivityStopped(@NonNull Activity activity) { |
| | | MyLog.d(TAG,"onActivityStopped "+activity.getClass().getSimpleName()); |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle bundle) { |
| | | MyLog.d(TAG,"onActivitySaveInstanceState "+activity.getClass().getSimpleName()); |
| | | isInfront = false; |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public void onActivityDestroyed(@NonNull Activity activity) { |
| | | MyLog.d(TAG,"onActivityDestroyed "+activity.getClass().getSimpleName()); |
| | | if(activities.contains(activity)){ |
| | | activities.remove(activity); |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * 退出程序 |
| | | */ |
| | | public void quitApp(){ |
| | | for(Activity activity:activities){ |
| | | activity.finish(); |
| | | } |
| | | System.exit(0); |
| | | } |
| | | |
| | | public void clearActivities(){ |
| | | for(Activity activity:activities){ |
| | | /*if(activity instanceof LoginActivity){ |
| | | continue; |
| | | }*/ |
| | | activity.finish(); |
| | | } |
| | | } |
| | | |
| | | public boolean isInfront(){ |
| | | return isInfront; |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.base.activities; |
| | | |
| | | import android.Manifest; |
| | | import android.app.AlertDialog; |
| | | import android.app.ProgressDialog; |
| | | import android.content.Context; |
| | | import android.content.DialogInterface; |
| | | import android.content.Intent; |
| | | import android.content.pm.ActivityInfo; |
| | | import android.net.Uri; |
| | | import android.os.Build; |
| | | import android.os.Bundle; |
| | | import android.os.Environment; |
| | | import android.provider.Settings; |
| | | import android.util.Log; |
| | | import android.view.LayoutInflater; |
| | | import android.view.MotionEvent; |
| | | import android.view.View; |
| | | import android.view.WindowManager; |
| | | import android.view.inputmethod.InputMethodManager; |
| | | import android.widget.EditText; |
| | | import android.widget.Toast; |
| | | |
| | | import androidx.annotation.ColorRes; |
| | | import androidx.annotation.Nullable; |
| | | import androidx.annotation.StringRes; |
| | | import androidx.appcompat.app.AppCompatActivity; |
| | | import androidx.core.content.FileProvider; |
| | | import androidx.lifecycle.ViewModelProvider; |
| | | import androidx.viewbinding.ViewBinding; |
| | | |
| | | import com.runt.open.mvvm.MyApplication; |
| | | import com.runt.open.mvvm.R; |
| | | import com.runt.open.mvvm.base.model.BaseViewModel; |
| | | import com.runt.open.mvvm.base.model.ViewModelFactory; |
| | | import com.runt.open.mvvm.data.ApkUpGradeResult; |
| | | import com.runt.open.mvvm.util.MyLog; |
| | | import com.runt.open.mvvm.util.SpUtils; |
| | | import com.permissionx.guolindev.PermissionX; |
| | | import com.permissionx.guolindev.callback.ExplainReasonCallbackWithBeforeParam; |
| | | import com.permissionx.guolindev.callback.ForwardToSettingsCallback; |
| | | import com.permissionx.guolindev.callback.RequestCallback; |
| | | import com.permissionx.guolindev.request.ExplainScope; |
| | | import com.permissionx.guolindev.request.ForwardScope; |
| | | |
| | | import java.io.File; |
| | | import java.io.FileOutputStream; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.lang.reflect.Method; |
| | | import java.lang.reflect.ParameterizedType; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | |
| | | import dmax.dialog.SpotsDialog; |
| | | import okhttp3.Call; |
| | | import okhttp3.Callback; |
| | | import okhttp3.OkHttpClient; |
| | | import okhttp3.Request; |
| | | import okhttp3.Response; |
| | | import okhttp3.ResponseBody; |
| | | |
| | | /** |
| | | * activity 封装 |
| | | * Created by Administrator on 2021/10/27 0027. |
| | | */ |
| | | public abstract class BaseActivity<B extends ViewBinding,VM extends BaseViewModel> extends AppCompatActivity { |
| | | |
| | | protected B binding; |
| | | protected VM viewModel; |
| | | protected String TAG ; |
| | | public final String[] FILE_PERMISSIONS = new String []{Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE}; |
| | | public final String[] LOCATION_PERMISSIONS = new String []{Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION}; |
| | | public final String[] CAMERA_PERMISSIONS = new String[]{ FILE_PERMISSIONS[0],FILE_PERMISSIONS[1], Manifest.permission.CAMERA}; |
| | | public final String[] CAMERA_RECORD_PERMISSIONS = new String[]{ FILE_PERMISSIONS[0],FILE_PERMISSIONS[1], Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO}; |
| | | |
| | | public static final int RESULT_LOGIN = 100,RESULT_LOGIN_RECREATE = 103,RESULT_BIND = 101,RESULT_SENDEDFILES = 105,RESULT_DISSCONNECT = 104, |
| | | RESULT_UPDATEUSER = 115,RESULT_LOGOUT = 113, REQUEST_CODE_ACTIVITY = 333; |
| | | public static final int REQUEST_CODE_LOGOUT = 20009,/*退出*/ |
| | | REQUEST_CODE_ORDER = 10010,/*订单详情*/ |
| | | REQUEST_CODE_LOGIN = 20010,/*登录*/ |
| | | REQUEST_CODE_SCAN = 20011/*扫描请求*/, |
| | | REQUEST_CODE_PIC = 20012,/*选择图片*/ |
| | | REQUEST_CODE_PAYPASS = 20112,/*支付验证*/ |
| | | REQUEST_CODE_PAYPASS_FOR_ALIPAY = 20110,/*支付宝修改支付验证*/ |
| | | REQUEST_CODE_PAYPASS_FOR_REALNAME = 20111,/*支付宝修改支付验证*/ |
| | | REQUEST_CODE_PERMISSION = 20013,/*请求权限*/ |
| | | REQUEST_VERSION_PERMISSION = 20013,/*检查更新*/ |
| | | REQUEST_INSTALL_PERMISSION = 20014,/*请求安装权限*/ |
| | | REQUEST_CODE_MANAGE_GROUP = 20015,/*选择分组*/ |
| | | REQUEST_CODE_MANAGE_DEL = 20016,/*选择删除*/ |
| | | REQUEST_CODE_MANAGE_EDIT = 20017,/*选择编辑*/ |
| | | REQUEST_CODE_MANAGE_ADD = 20018,/*选择添加*/ |
| | | REQUEST_CODE_MANAGE_DRAFT = 20019,/*选择草稿编辑*/ |
| | | REQUEST_CODE_WEPAY = 20020,/*微信支付*/ |
| | | REQUEST_CODE_ALIPAY = 20021,/*支付宝*/ |
| | | REQUEST_CODE_PINKAGE = 20022,/*包邮设置*/ |
| | | REQUEST_INSTALL_APK = 20200,/*请求安装权限*/ |
| | | REQUEST_CODE_COMMENT_REFRESH = 20222,/*请求安装权限*/ |
| | | RESULT_CODE_DRAFT = 4041/*草稿*/, |
| | | RESULT_CODE_UPDATED = 4042/*更新*/, |
| | | RESULT_CODE_FAILD = 4044/*失败*/, |
| | | RESULT_CODE_SUCESS = 4046/*成功*/, |
| | | RESULT_CODE_CANCEL = 4043/*取消*/; |
| | | protected Context mContext; |
| | | |
| | | |
| | | @Override |
| | | protected void onCreate(@Nullable Bundle savedInstanceState) { |
| | | super.onCreate(savedInstanceState); |
| | | // get genericity "B" |
| | | setStatusBarBgColor(R.color.white); |
| | | setStatusBarTextColor(true); |
| | | final ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass(); |
| | | try { |
| | | Class<B> entityClass = (Class<B>) type.getActualTypeArguments()[0]; |
| | | Method method = entityClass.getMethod("inflate", LayoutInflater.class);//get method from name "inflate"; |
| | | binding = (B) method.invoke(entityClass,getLayoutInflater());//execute method to create a objct of viewbind; |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | Class<VM> vmClass = (Class<VM>) type.getActualTypeArguments()[1]; |
| | | viewModel = new ViewModelProvider(this,getViewModelFactory()).get(vmClass); |
| | | setContentView(binding.getRoot()); |
| | | mContext = this; |
| | | try { |
| | | //设置坚屏 一定要放到try catch里面,否则会崩溃 |
| | | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); |
| | | } catch (Exception e) { |
| | | } |
| | | TAG = this.getClass().getSimpleName(); |
| | | initViews(); |
| | | } |
| | | |
| | | public abstract void initViews(); |
| | | |
| | | AlertDialog loadingDialog; |
| | | /** |
| | | * 显示加载弹框 |
| | | * @param msg |
| | | */ |
| | | public void showLoadingDialog(String msg){ |
| | | if(loadingDialog != null && loadingDialog.isShowing()){ |
| | | loadingDialog.dismiss(); |
| | | } |
| | | loadingDialog = new SpotsDialog.Builder().setContext(mContext).build(); |
| | | loadingDialog.setMessage(msg); |
| | | loadingDialog.setCancelable(false); |
| | | loadingDialog.show(); |
| | | } |
| | | |
| | | /** |
| | | * 清除弹框 |
| | | */ |
| | | public void dissmissLoadingDialog(){ |
| | | if(loadingDialog != null && loadingDialog.isShowing()){ |
| | | loadingDialog.dismiss(); |
| | | } |
| | | } |
| | | |
| | | public ViewModelProvider.Factory getViewModelFactory(){ |
| | | return ViewModelFactory.getInstance(); |
| | | } |
| | | |
| | | public void setStatusBarTransparent(boolean isBlack){ |
| | | //透明状态栏 |
| | | getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); |
| | | //透明导航栏 |
| | | getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); |
| | | } |
| | | |
| | | /** |
| | | * 修改状态栏颜色,支持4.4以上版本 |
| | | * @param colorId |
| | | */ |
| | | public void setStatusBarBgColor(@ColorRes int colorId) { |
| | | getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); |
| | | getWindow().setStatusBarColor(getResources().getColor(colorId)); |
| | | } |
| | | |
| | | /** |
| | | * 修改状态栏文本颜色 |
| | | * @param isBlack |
| | | */ |
| | | public void setStatusBarTextColor(boolean isBlack){ |
| | | View decor = getWindow().getDecorView(); |
| | | if (isBlack) { |
| | | decor.setSystemUiVisibility( View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); |
| | | } else { |
| | | decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 隐藏虚拟按键 |
| | | */ |
| | | public void hideBottomUIMenu() { |
| | | //隐藏虚拟按键 |
| | | if (Build.VERSION.SDK_INT > 11 && Build.VERSION.SDK_INT < 19) { // lower api |
| | | View v = getWindow().getDecorView(); |
| | | v.setSystemUiVisibility(View.GONE); |
| | | } else if (Build.VERSION.SDK_INT >= 19) { |
| | | //for new api versions. |
| | | View decorView = getWindow().getDecorView(); |
| | | int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
| | | | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY ; |
| | | decorView.setSystemUiVisibility(uiOptions); |
| | | } |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public boolean dispatchTouchEvent(MotionEvent ev) { |
| | | if (ev.getAction() == MotionEvent.ACTION_DOWN) { //把操作放在用户点击的时候 |
| | | View v = getCurrentFocus(); //得到当前页面的焦点,ps:有输入框的页面焦点一般会被输入框占据 |
| | | if (isShouldHideKeyboard(v, ev)) { //判断用户点击的是否是输入框以外的区域 |
| | | hideSoftKeyboard (); //收起键盘 |
| | | } |
| | | } |
| | | return super.dispatchTouchEvent(ev); |
| | | } |
| | | |
| | | /** |
| | | * 根据EditText所在坐标和用户点击的坐标相对比,来判断是否隐藏键盘,因为当用户点击EditText时则不能隐藏 |
| | | * |
| | | * @param v |
| | | * @param event |
| | | * @return |
| | | */ |
| | | private boolean isShouldHideKeyboard(View v, MotionEvent event) { |
| | | if (v != null && (v instanceof EditText)) { //判断得到的焦点控件是否包含EditText |
| | | int[] l = {0, 0}; |
| | | v.getLocationInWindow(l); |
| | | int left = l[0], //得到输入框在屏幕中上下左右的位置 |
| | | top = l[1], |
| | | bottom = top + v.getHeight(), |
| | | right = left + v.getWidth(); |
| | | if (event.getX() > left && event.getX() < right |
| | | && event.getY() > top && event.getY() < bottom) { |
| | | // 点击位置如果是EditText的区域,忽略它,不收起键盘。 |
| | | return false; |
| | | } else { |
| | | return true; |
| | | } |
| | | } |
| | | // 如果焦点不是EditText则忽略 |
| | | return false; |
| | | } |
| | | /** |
| | | * 判断软键盘输入法是否弹出 |
| | | */ |
| | | public boolean isKeyboardVisbility(Context context, View v) { |
| | | InputMethodManager imm = (InputMethodManager) context.getSystemService(context.INPUT_METHOD_SERVICE); |
| | | if (imm.hideSoftInputFromWindow(v.getWindowToken(), 0)) { |
| | | imm.showSoftInput(v, 0); |
| | | return true;//键盘显示中 |
| | | } else { |
| | | return false;//键盘未显示 |
| | | } |
| | | } |
| | | protected void hideSoftKeyboard() { |
| | | if (getWindow().getAttributes().softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) { |
| | | if (getCurrentFocus() != null) |
| | | ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), |
| | | InputMethodManager.HIDE_NOT_ALWAYS); |
| | | } |
| | | } |
| | | /** |
| | | * 状态栏高度 |
| | | * @return |
| | | */ |
| | | public int getStatusBarHeight() { |
| | | int result = 0; |
| | | int resId = getResources().getIdentifier("status_bar_height", "dimen", "android"); |
| | | if (resId > 0) { |
| | | result = getResources().getDimensionPixelOffset(resId); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | |
| | | long mExitTime= 0 ; |
| | | /** |
| | | * 返回键退出程序 |
| | | */ |
| | | public void backExit() { |
| | | if ((System.currentTimeMillis() - mExitTime) > 2000) { |
| | | Toast.makeText(this, "再按一次退出", Toast.LENGTH_SHORT).show(); |
| | | mExitTime = System.currentTimeMillis(); |
| | | } else { |
| | | //此方法导致app关闭后重启 |
| | | MyApplication.getApplication().quitApp(); |
| | | System.exit(0); |
| | | //quitApp(); |
| | | } |
| | | } |
| | | public void showToast(String message){ |
| | | Toast.makeText(this,message,Toast.LENGTH_SHORT).show(); |
| | | } |
| | | |
| | | public void showToast(@StringRes int msg){ |
| | | showToast(getString(msg)); |
| | | } |
| | | |
| | | /** |
| | | * 获取文件保存路径 sdcard根目录/download/文件名称 |
| | | * @param fileUrl |
| | | * @return |
| | | */ |
| | | public static String getSaveFilePath(String fileUrl){ |
| | | String fileName=fileUrl.substring(fileUrl.lastIndexOf("/")+1,fileUrl.length());//获取文件名称 |
| | | String storePath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "Download"; |
| | | File appDir = new File(storePath); |
| | | if (!appDir.exists()) { |
| | | appDir.mkdirs(); |
| | | } |
| | | return storePath + File.separator +fileName; |
| | | } |
| | | |
| | | |
| | | protected boolean onBackKeyDown() { |
| | | onBackPressed(); |
| | | return false; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.base.activities; |
| | | |
| | | import android.os.Bundle; |
| | | |
| | | import androidx.annotation.Nullable; |
| | | import androidx.recyclerview.widget.LinearLayoutManager; |
| | | import androidx.recyclerview.widget.RecyclerView; |
| | | import androidx.viewbinding.ViewBinding; |
| | | |
| | | import com.runt.open.mvvm.base.adapter.BaseAdapter; |
| | | import com.runt.open.mvvm.base.model.BaseLoadPageViewModel; |
| | | import com.runt.open.mvvm.data.BasePageResult; |
| | | import com.runt.open.mvvm.databinding.RefreshRecyclerBinding; |
| | | import com.scwang.smart.refresh.footer.ClassicsFooter; |
| | | import com.scwang.smart.refresh.header.ClassicsHeader; |
| | | import com.scwang.smart.refresh.layout.SmartRefreshLayout; |
| | | import com.scwang.smart.refresh.layout.listener.OnRefreshLoadMoreListener; |
| | | |
| | | import java.lang.reflect.ParameterizedType; |
| | | import java.util.HashMap; |
| | | |
| | | /** |
| | | * Created by Administrator on 2021/11/4 0004. |
| | | */ |
| | | public abstract class BaseLoadPageActivity<B extends ViewBinding,VM extends BaseLoadPageViewModel,A extends BaseAdapter,D extends BasePageResult> |
| | | extends BaseTitleBarActivity<B,VM> implements OnRefreshLoadMoreListener { |
| | | |
| | | protected SmartRefreshLayout smartRefresh; |
| | | protected RecyclerView recycler; |
| | | final String TAG = "RecyclerFragment"; |
| | | protected A adapter;//适配器 |
| | | protected String url;//请求地址 |
| | | protected HashMap param;//参数 |
| | | protected int page=1; |
| | | protected final int SIZE = 10; |
| | | protected boolean isRefresh = false;//是否正在刷新 |
| | | |
| | | @Override |
| | | protected void onCreate(@Nullable Bundle savedInstanceState) { |
| | | super.onCreate(savedInstanceState); |
| | | try { |
| | | Class<A> entityClass = (Class<A>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[2]; |
| | | this.adapter = entityClass.newInstance();//实例化泛型 |
| | | String smartStr = "smartRefresh"; |
| | | smartRefresh = (SmartRefreshLayout) binding.getClass().getField(smartStr).get(binding); |
| | | recycler = (RecyclerView) binding.getClass().getDeclaredField("recycler").get(binding); |
| | | } catch (NoSuchFieldException e) { |
| | | try { |
| | | RefreshRecyclerBinding includeRefreshRecycler = (RefreshRecyclerBinding) binding.getClass().getDeclaredField ("includeRefreshRecycler").get(binding); |
| | | smartRefresh = includeRefreshRecycler.smartRefresh; |
| | | recycler = includeRefreshRecycler.recycler; |
| | | } catch (Exception e2) { |
| | | e2.printStackTrace(); |
| | | } |
| | | } catch (IllegalAccessException e) { |
| | | e.printStackTrace(); |
| | | } catch (InstantiationException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | smartRefresh.setRefreshHeader(new ClassicsHeader(mContext)); |
| | | smartRefresh.setRefreshFooter(new ClassicsFooter(mContext)); |
| | | smartRefresh.setOnRefreshLoadMoreListener(this); |
| | | recycler.setLayoutManager(new LinearLayoutManager(mContext)); |
| | | recycler.setAdapter(adapter); |
| | | } |
| | | |
| | | private void finishFreshLoadmore(D result){ |
| | | if(result.code == 200){ |
| | | |
| | | smartRefresh.setEnableRefresh(true); |
| | | smartRefresh.finishRefresh(); |
| | | if(page == 1){ |
| | | adapter.getData().clear(); |
| | | adapter.setData(result.rows); |
| | | }else{ |
| | | adapter.getData().addAll(result.rows); |
| | | adapter.notifyDataSetChanged(); |
| | | } |
| | | if(result.total <= adapter.getData().size()// 总数是否已经加载完 |
| | | || result.rows.size() < SIZE // 最后一页数据的数量一般不满size |
| | | ){//判断是否没有数据了 |
| | | smartRefresh.finishLoadMoreWithNoMoreData(); |
| | | }else { |
| | | smartRefresh.finishLoadMore(); |
| | | } |
| | | }else{ |
| | | smartRefresh.setEnableRefresh(true); |
| | | smartRefresh.finishRefresh(); |
| | | smartRefresh.finishLoadMore(); |
| | | } |
| | | } |
| | | |
| | | public A getAdapter() { |
| | | return adapter; |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.base.activities; |
| | | |
| | | import android.os.Bundle; |
| | | |
| | | import androidx.annotation.Nullable; |
| | | import androidx.viewbinding.ViewBinding; |
| | | import androidx.viewpager2.widget.ViewPager2; |
| | | |
| | | import com.runt.open.mvvm.base.adapter.FragmentAdapter; |
| | | import com.runt.open.mvvm.base.fragments.BaseFragment; |
| | | import com.runt.open.mvvm.base.model.BaseViewModel; |
| | | import com.google.android.material.tabs.TabLayout; |
| | | import com.google.android.material.tabs.TabLayoutMediator; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * Created by Administrator on 2021/11/4 0004. |
| | | */ |
| | | public abstract class BaseTabActivity<B extends ViewBinding,VM extends BaseViewModel> extends BaseActivity<B,VM> { |
| | | |
| | | TabLayout tabLayout; |
| | | FragmentAdapter fragmentAdapter; |
| | | List<String> tabTitles = new ArrayList<>(); |
| | | ViewPager2 viewPager2; |
| | | |
| | | @Override |
| | | protected void onCreate(@Nullable Bundle savedInstanceState) { |
| | | super.onCreate(savedInstanceState); |
| | | fragmentAdapter = new FragmentAdapter(this); |
| | | fragmentAdapter.setFragments(initFragments()); |
| | | setTabTitles(initTabTitles()); |
| | | //设置当前可见Item左右可见page数,次范围内不会被销毁 |
| | | //禁用预加载 |
| | | try { |
| | | viewPager2 = (ViewPager2) binding.getClass().getDeclaredField("viewPager2").get(binding); |
| | | tabLayout = (TabLayout) binding.getClass().getDeclaredField("tabLayout").get(binding); |
| | | } catch (IllegalAccessException e) { |
| | | e.printStackTrace(); |
| | | } catch (NoSuchFieldException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | ; |
| | | viewPager2.setOffscreenPageLimit(ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT); |
| | | viewPager2.setAdapter(fragmentAdapter); |
| | | viewPager2.setCurrentItem(0); |
| | | viewPager2.setUserInputEnabled(false); //true:滑动,false:禁止滑动 |
| | | new TabLayoutMediator(tabLayout, viewPager2, (tab, position) -> tab.setText(tabTitles.get(position))).attach(); |
| | | } |
| | | |
| | | |
| | | protected abstract List<String> initTabTitles(); |
| | | |
| | | protected abstract List<BaseFragment> initFragments(); |
| | | |
| | | protected List<String> getTabTitles(){ |
| | | return tabTitles; |
| | | } |
| | | |
| | | public FragmentAdapter getFragmentAdapter() { |
| | | return fragmentAdapter; |
| | | } |
| | | |
| | | public void setTabTitles(List<String> tabTitles) { |
| | | if(tabTitles == null){ |
| | | this.tabTitles.clear(); |
| | | } |
| | | this.tabTitles = tabTitles; |
| | | } |
| | | public void setTabTitles(String[] tabTitles) { |
| | | if(tabTitles == null){ |
| | | this.tabTitles.clear(); |
| | | } |
| | | setTabTitles(new ArrayList<>(Arrays.asList(tabTitles))); |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.base.activities; |
| | | |
| | | import android.graphics.drawable.Drawable; |
| | | import android.os.Bundle; |
| | | import android.view.ViewGroup; |
| | | |
| | | import androidx.annotation.Nullable; |
| | | import androidx.viewbinding.ViewBinding; |
| | | |
| | | import com.runt.open.mvvm.base.model.BaseViewModel; |
| | | import com.runt.open.mvvm.widgets.TitleBarView; |
| | | |
| | | /** |
| | | * 带有标题栏的activity封装 |
| | | * Created by Administrator on 2021/11/2 0002. |
| | | */ |
| | | public abstract class BaseTitleBarActivity<B extends ViewBinding,VM extends BaseViewModel> extends BaseActivity<B,VM> { |
| | | TitleBarView titleBarView; |
| | | |
| | | @Override |
| | | protected void onCreate(@Nullable Bundle savedInstanceState) { |
| | | super.onCreate(savedInstanceState); |
| | | |
| | | try { |
| | | titleBarView = (TitleBarView) binding.getClass().getDeclaredField("titleBar").get(binding); |
| | | titleBarView.setLeftClick(v -> onTitleLeftClick()); |
| | | } catch (IllegalAccessException e) { |
| | | e.printStackTrace(); |
| | | } catch (NoSuchFieldException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | protected void setTitle(String text){ |
| | | titleBarView.setTitleText(text); |
| | | } |
| | | |
| | | protected void onTitleLeftClick(){ |
| | | onBackKeyDown(); |
| | | } |
| | | |
| | | protected void setTitleRight(String text){ |
| | | titleBarView.setRightText(text); |
| | | titleBarView.setRightDra(null); |
| | | } |
| | | |
| | | protected void setTitleRight(Drawable drawable){ |
| | | titleBarView.setRightText(null); |
| | | titleBarView.setRightDra(drawable); |
| | | } |
| | | |
| | | @Override |
| | | public void setStatusBarTransparent(boolean isBlack) { |
| | | super.setStatusBarTransparent(isBlack); |
| | | final ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) titleBarView.getLayoutParams(); |
| | | layoutParams.topMargin = layoutParams.topMargin+getStatusBarHeight(); |
| | | titleBarView.setLayoutParams(layoutParams); |
| | | |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.base.adapter; |
| | | |
| | | import android.graphics.drawable.Drawable; |
| | | import android.view.LayoutInflater; |
| | | import android.view.ViewGroup; |
| | | |
| | | import androidx.annotation.NonNull; |
| | | import androidx.recyclerview.widget.RecyclerView; |
| | | import androidx.viewbinding.ViewBinding; |
| | | |
| | | import com.runt.open.mvvm.base.activities.BaseActivity; |
| | | import com.runt.open.mvvm.databinding.LayoutNullBinding; |
| | | import com.runt.open.mvvm.util.DeviceUtil; |
| | | |
| | | import java.lang.reflect.InvocationTargetException; |
| | | import java.lang.reflect.Method; |
| | | import java.lang.reflect.ParameterizedType; |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * Created by Administrator on 2021/10/27 0027. |
| | | * T 数据类型 |
| | | * V 适配器视图 |
| | | */ |
| | | public abstract class BaseAdapter<B extends ViewBinding,T> extends RecyclerView.Adapter { |
| | | |
| | | protected List<T> mData = new ArrayList<>(); |
| | | |
| | | protected Drawable nullDrawable; |
| | | protected String nullTxt="暂无数据"; |
| | | protected String TAG = "BaseAdapter"; |
| | | protected BaseActivity activity; |
| | | |
| | | public BaseAdapter(){ |
| | | } |
| | | |
| | | public BaseAdapter(@NonNull List<T> data){ |
| | | mData = data; |
| | | } |
| | | |
| | | public List<T> getData() { |
| | | return mData; |
| | | } |
| | | |
| | | public void setData(@NonNull List<T> data){ |
| | | mData = data; |
| | | notifyDataSetChanged(); |
| | | } |
| | | |
| | | @NonNull |
| | | @Override |
| | | public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { |
| | | if(viewType == 1 ){ |
| | | // get genericity "B" |
| | | Class<B> entityClass = (Class<B>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]; |
| | | try { |
| | | /* for(Method method: entityClass.getMethods()){ |
| | | StringBuilder sb = new StringBuilder(); |
| | | for(Class type : method.getParameterTypes()){ |
| | | sb.append(type.getSimpleName()+","); |
| | | } |
| | | Log.e(TAG,String.format("method:%s,return:%s,param:%s",method.getName(),method.getReturnType().getSimpleName(),sb.toString())); |
| | | }*/ |
| | | Method method = entityClass.getMethod("inflate", LayoutInflater.class,ViewGroup.class,boolean.class);//get method from name "inflate"; |
| | | B vBind = (B) method.invoke(entityClass,LayoutInflater.from(parent.getContext()),parent,false);//execute method to create a objct of viewbind; |
| | | return new ViewBindHolder(vBind); |
| | | } catch (SecurityException e) { |
| | | e.printStackTrace(); |
| | | } catch (NoSuchMethodException e) { |
| | | e.printStackTrace(); |
| | | } catch (IllegalArgumentException e) { |
| | | e.printStackTrace(); |
| | | } catch (IllegalAccessException e) { |
| | | e.printStackTrace(); |
| | | } catch (InvocationTargetException e) { |
| | | Throwable t = e.getTargetException();// 获取目标异常 |
| | | t.printStackTrace(); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | return new NullViewHolder( LayoutNullBinding.inflate(LayoutInflater.from(parent.getContext()),parent,false)); |
| | | } |
| | | |
| | | @Override |
| | | public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { |
| | | //MyLog.i(TAG,"onBindViewHolder position:"+position+" "+mData.size()+" "+getItemViewType(position)); |
| | | if(activity == null){ |
| | | activity = (BaseActivity) holder.itemView.getContext(); |
| | | } |
| | | if(getItemViewType(position)==0){ |
| | | bindView((NullViewHolder) holder); |
| | | }else { |
| | | bindView((ViewBindHolder) holder, mData.size() == 0 ? null : mData.get(position), position); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 设置最后一个底部间隔 |
| | | * @param holder |
| | | * @param position |
| | | */ |
| | | protected void setBottomMargin(ViewBindHolder holder, int position){ |
| | | setBottomMargin(holder,position,23); |
| | | } |
| | | |
| | | /** |
| | | * 设置最后一个底部间隔 |
| | | * @param holder |
| | | * @param position 位置 |
| | | * @param dp 间距 |
| | | */ |
| | | protected void setBottomMargin(RecyclerView.ViewHolder holder, int position,int dp){ |
| | | setBottomMargin(holder,position,dp,0); |
| | | } |
| | | protected void setBottomMargin(RecyclerView.ViewHolder holder, int position, int dp, int defaultDp){ |
| | | ViewGroup.MarginLayoutParams params1 = (ViewGroup.MarginLayoutParams) holder.itemView.getLayoutParams(); |
| | | if(position == mData.size() -1){ |
| | | params1.setMargins(params1.leftMargin, params1.topMargin, params1.rightMargin, DeviceUtil.convertDpToPixel(dp,holder.itemView.getContext())); |
| | | }else{ |
| | | params1.setMargins(params1.leftMargin, params1.topMargin, params1.rightMargin, DeviceUtil.convertDpToPixel(defaultDp,holder.itemView.getContext())); |
| | | } |
| | | } |
| | | protected abstract void bindView(ViewBindHolder holder,T data,int position); |
| | | |
| | | |
| | | protected void bindView(NullViewHolder holder){ |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public int getItemCount() { |
| | | //默认显示空视图,若不显示空视图则重写该方法,返回mData.size() |
| | | return mData == null || mData.size() == 0 ?1:mData.size(); |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public int getItemViewType(int position) { |
| | | //当下标为0,数据集合为0 返回0(意味当前应显示空数据视图)) |
| | | //MyLog.i(TAG,"getItemViewType position:"+position+" mdata:"+mData.size()+" "+(position ==0 && mData.size()==0)); |
| | | return position == 0 && mData.size()==0?0:1; |
| | | } |
| | | |
| | | public class ViewBindHolder extends RecyclerView.ViewHolder{ |
| | | public B binding; |
| | | public ViewBindHolder( B binding) { |
| | | super(binding.getRoot()); |
| | | this.binding = binding; |
| | | } |
| | | } |
| | | /** |
| | | * 空数据显示 |
| | | * Created by Administrator on 2021/10/28 0028. |
| | | */ |
| | | public class NullViewHolder extends RecyclerView.ViewHolder { |
| | | LayoutNullBinding binding; |
| | | |
| | | public NullViewHolder(LayoutNullBinding binding) { |
| | | super(binding.getRoot()); |
| | | this.binding = binding; |
| | | } |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.base.adapter; |
| | | |
| | | import androidx.annotation.NonNull; |
| | | import androidx.fragment.app.Fragment; |
| | | import androidx.fragment.app.FragmentActivity; |
| | | import androidx.viewpager2.adapter.FragmentStateAdapter; |
| | | |
| | | import com.runt.open.mvvm.base.fragments.BaseFragment; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * Created by Administrator on 2021/11/3 0003. |
| | | */ |
| | | public class FragmentAdapter extends FragmentStateAdapter { |
| | | |
| | | private List<BaseFragment> fragments = new ArrayList<>(); |
| | | |
| | | public FragmentAdapter(@NonNull FragmentActivity fragmentActivity) { |
| | | super(fragmentActivity); |
| | | } |
| | | |
| | | public void setFragments(List<BaseFragment> fragments) { |
| | | this.fragments = fragments; |
| | | } |
| | | |
| | | public void removeFragment(BaseFragment fragment){ |
| | | this.fragments.remove(fragment); |
| | | } |
| | | |
| | | public void removeFragment(int index){ |
| | | this.fragments.remove(index); |
| | | } |
| | | |
| | | public void addFragment(BaseFragment fragment){ |
| | | fragments.add(fragment); |
| | | } |
| | | |
| | | |
| | | @NonNull |
| | | @Override |
| | | public Fragment createFragment(int position) { |
| | | return fragments.get(position); |
| | | } |
| | | |
| | | @Override |
| | | public int getItemCount() { |
| | | return fragments.size(); |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.base.fragments; |
| | | |
| | | import android.os.Bundle; |
| | | import android.view.LayoutInflater; |
| | | import android.view.View; |
| | | import android.view.ViewGroup; |
| | | |
| | | import androidx.annotation.Nullable; |
| | | import androidx.fragment.app.Fragment; |
| | | import androidx.lifecycle.ViewModel; |
| | | import androidx.lifecycle.ViewModelProvider; |
| | | import androidx.viewbinding.ViewBinding; |
| | | |
| | | import com.runt.open.mvvm.base.activities.BaseActivity; |
| | | import com.runt.open.mvvm.base.model.ViewModelFactory; |
| | | |
| | | import java.lang.reflect.Method; |
| | | import java.lang.reflect.ParameterizedType; |
| | | |
| | | /** |
| | | * fragment 封装 |
| | | * Created by Administrator on 2021/10/28 0028. |
| | | */ |
| | | public abstract class BaseFragment<B extends ViewBinding,VM extends ViewModel> extends Fragment { |
| | | |
| | | protected BaseActivity activity; |
| | | protected B binding; |
| | | protected VM viewModel; |
| | | |
| | | @Nullable |
| | | @Override |
| | | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { |
| | | // get genericity "B" |
| | | try { |
| | | final ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass(); |
| | | Class<B> entityClass = (Class<B>) type.getActualTypeArguments()[0]; |
| | | Method method = entityClass.getMethod("inflate", LayoutInflater.class,ViewGroup.class,boolean.class);//get method from name "inflate"; |
| | | binding = (B) method.invoke(entityClass,inflater,container,false);//execute method to create a objct of viewbind; |
| | | Class<VM> vmClass = (Class<VM>) type.getActualTypeArguments()[1]; |
| | | viewModel = new ViewModelProvider(this,getViewModelFactory()).get(vmClass); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | return binding.getRoot(); |
| | | } |
| | | |
| | | public ViewModelProvider.Factory getViewModelFactory(){ |
| | | return ViewModelFactory.getInstance(); |
| | | } |
| | | |
| | | @Override |
| | | public void onActivityCreated(@Nullable Bundle savedInstanceState) { |
| | | super.onActivityCreated(savedInstanceState); |
| | | activity = (BaseActivity) getActivity(); |
| | | initViews(); |
| | | } |
| | | |
| | | public abstract void initViews(); |
| | | |
| | | @Override |
| | | public void onDestroyView() { |
| | | super.onDestroyView(); |
| | | binding = null; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.base.fragments; |
| | | |
| | | import androidx.annotation.NonNull; |
| | | import androidx.recyclerview.widget.LinearLayoutManager; |
| | | import androidx.recyclerview.widget.RecyclerView; |
| | | import androidx.viewbinding.ViewBinding; |
| | | |
| | | import com.runt.open.mvvm.base.adapter.BaseAdapter; |
| | | import com.runt.open.mvvm.base.model.BaseLoadPageViewModel; |
| | | import com.runt.open.mvvm.data.BasePageResult; |
| | | import com.runt.open.mvvm.databinding.RefreshRecyclerBinding; |
| | | import com.scwang.smart.refresh.footer.ClassicsFooter; |
| | | import com.scwang.smart.refresh.header.ClassicsHeader; |
| | | import com.scwang.smart.refresh.layout.SmartRefreshLayout; |
| | | import com.scwang.smart.refresh.layout.api.RefreshLayout; |
| | | import com.scwang.smart.refresh.layout.listener.OnRefreshLoadMoreListener; |
| | | |
| | | import java.lang.reflect.ParameterizedType; |
| | | import java.util.HashMap; |
| | | |
| | | /** |
| | | * 分页fragment 封装 |
| | | * Created by Administrator on 2021/11/3 0003. |
| | | */ |
| | | public abstract class BaseLoadPageFragment<B extends ViewBinding,VM extends BaseLoadPageViewModel,A extends BaseAdapter,D extends BasePageResult> extends BaseFragment<B,VM> |
| | | implements OnRefreshLoadMoreListener { |
| | | protected SmartRefreshLayout smartRefresh; |
| | | protected RecyclerView recycler; |
| | | final String TAG = "RecyclerFragment"; |
| | | protected A adapter;//适配器 |
| | | protected String url;//请求地址 |
| | | protected HashMap param;//参数 |
| | | protected int page=1; |
| | | protected final int SIZE = 10; |
| | | protected boolean isRefresh = false;//是否正在刷新 |
| | | |
| | | @Override |
| | | public void initViews() { |
| | | try { |
| | | Class<A> entityClass = (Class<A>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[2]; |
| | | this.adapter = entityClass.newInstance();//实例化泛型 |
| | | String smartStr = "smartRefresh"; |
| | | smartRefresh = (SmartRefreshLayout) binding.getClass().getField(smartStr).get(binding); |
| | | recycler = (RecyclerView) binding.getClass().getDeclaredField("recycler").get(binding); |
| | | } catch (NoSuchFieldException e) { |
| | | try { |
| | | RefreshRecyclerBinding includeRefreshRecycler = (RefreshRecyclerBinding) binding.getClass().getDeclaredField ("includeRefreshRecycler").get(binding); |
| | | smartRefresh = includeRefreshRecycler.smartRefresh; |
| | | recycler = includeRefreshRecycler.recycler; |
| | | } catch (Exception e2) { |
| | | e2.printStackTrace(); |
| | | } |
| | | } catch (IllegalAccessException e) { |
| | | e.printStackTrace(); |
| | | } catch (java.lang.InstantiationException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | smartRefresh.setRefreshHeader(new ClassicsHeader(getContext())); |
| | | smartRefresh.setRefreshFooter(new ClassicsFooter(getContext())); |
| | | smartRefresh.setOnRefreshLoadMoreListener(this); |
| | | recycler.setLayoutManager(new LinearLayoutManager(getContext())); |
| | | recycler.setAdapter(adapter); |
| | | } |
| | | |
| | | @Override |
| | | public void onLoadMore(@NonNull RefreshLayout refreshLayout) { |
| | | page++; |
| | | viewModel.onLoadMore(); |
| | | } |
| | | |
| | | @Override |
| | | public void onRefresh(@NonNull RefreshLayout refreshLayout) { |
| | | page = 1; |
| | | viewModel.onRefresh(); |
| | | } |
| | | |
| | | private void finishFreshLoadmore(D result){ |
| | | if(result.code == 200){ |
| | | |
| | | smartRefresh.setEnableRefresh(true); |
| | | smartRefresh.finishRefresh(); |
| | | if(page == 1){ |
| | | adapter.getData().clear(); |
| | | adapter.setData(result.rows); |
| | | }else{ |
| | | adapter.getData().addAll(result.rows); |
| | | adapter.notifyDataSetChanged(); |
| | | } |
| | | if(result.total <= adapter.getData().size()// 总数是否已经加载完 |
| | | || result.rows.size() < SIZE // 最后一页数据的数量一般不满size |
| | | ){//判断是否没有数据了 |
| | | smartRefresh.finishLoadMoreWithNoMoreData(); |
| | | }else { |
| | | smartRefresh.finishLoadMore(); |
| | | } |
| | | }else{ |
| | | smartRefresh.setEnableRefresh(true); |
| | | smartRefresh.finishRefresh(); |
| | | smartRefresh.finishLoadMore(); |
| | | } |
| | | } |
| | | public A getAdapter() { |
| | | return adapter; |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.base.fragments; |
| | | |
| | | import androidx.lifecycle.ViewModel; |
| | | import androidx.viewbinding.ViewBinding; |
| | | import androidx.viewpager2.widget.ViewPager2; |
| | | |
| | | import com.google.android.material.tabs.TabLayout; |
| | | import com.google.android.material.tabs.TabLayoutMediator; |
| | | import com.runt.open.mvvm.base.adapter.FragmentAdapter; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * 带有tablayout fragment封装 |
| | | * Created by Administrator on 2021/11/3 0003. |
| | | */ |
| | | public abstract class BaseTabFragment<B extends ViewBinding,VM extends ViewModel> extends BaseFragment<B,VM> { |
| | | |
| | | TabLayout tabLayout; |
| | | FragmentAdapter fragmentAdapter; |
| | | List<String> tabTitles = new ArrayList<>(); |
| | | ViewPager2 viewPager2; |
| | | |
| | | @Override |
| | | public void initViews() { |
| | | |
| | | fragmentAdapter = new FragmentAdapter(activity); |
| | | fragmentAdapter.setFragments(initFragments()); |
| | | setTabTitles(initTabTitles()); |
| | | //设置当前可见Item左右可见page数,次范围内不会被销毁 |
| | | //禁用预加载 |
| | | try { |
| | | viewPager2 = (ViewPager2) binding.getClass().getDeclaredField("viewPager2").get(binding); |
| | | tabLayout = (TabLayout) binding.getClass().getDeclaredField("tabLayout").get(binding); |
| | | } catch (IllegalAccessException e) { |
| | | e.printStackTrace(); |
| | | } catch (NoSuchFieldException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | ; |
| | | viewPager2.setOffscreenPageLimit(ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT); |
| | | viewPager2.setAdapter(fragmentAdapter); |
| | | viewPager2.setCurrentItem(0); |
| | | viewPager2.setUserInputEnabled(false); //true:滑动,false:禁止滑动 |
| | | new TabLayoutMediator(tabLayout, viewPager2, (tab, position) -> tab.setText(tabTitles.get(position))).attach(); |
| | | } |
| | | |
| | | protected abstract List<String> initTabTitles(); |
| | | |
| | | protected abstract List<BaseFragment> initFragments(); |
| | | |
| | | protected List<String> getTabTitles(){ |
| | | return tabTitles; |
| | | } |
| | | |
| | | public FragmentAdapter getFragmentAdapter() { |
| | | return fragmentAdapter; |
| | | } |
| | | |
| | | public void setTabTitles(List<String> tabTitles) { |
| | | if(tabTitles == null){ |
| | | this.tabTitles.clear(); |
| | | } |
| | | this.tabTitles = tabTitles; |
| | | } |
| | | public void setTabTitles(String[] tabTitles) { |
| | | if(tabTitles == null){ |
| | | this.tabTitles.clear(); |
| | | } |
| | | setTabTitles(new ArrayList<>(Arrays.asList(tabTitles))); |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.base.model; |
| | | |
| | | /** |
| | | * 分页 |
| | | * Created by Administrator on 2021/11/3 0003. |
| | | */ |
| | | public abstract class BaseLoadPageViewModel extends BaseViewModel { |
| | | |
| | | public abstract void onRefresh(); |
| | | |
| | | public abstract void onLoadMore(); |
| | | |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.base.model; |
| | | |
| | | import androidx.lifecycle.ViewModel; |
| | | |
| | | import com.runt.open.mvvm.retrofit.observable.HttpObserver; |
| | | import com.runt.open.mvvm.retrofit.AndroidScheduler; |
| | | |
| | | import io.reactivex.Observable; |
| | | import io.reactivex.schedulers.Schedulers; |
| | | |
| | | /** |
| | | * Created by Administrator on 2021/11/11 0011. |
| | | */ |
| | | public class BaseViewModel extends ViewModel { |
| | | |
| | | |
| | | /** |
| | | * 网络请求观察 |
| | | * @param observable |
| | | * @param <T> |
| | | * @return |
| | | */ |
| | | public <T> void httpObserverOn(Observable<T> observable, HttpObserver observer){ |
| | | observable.subscribeOn(Schedulers.io())//指定网络请求在io后台线程中进行 |
| | | .observeOn(AndroidScheduler.mainThread()) |
| | | .subscribe(observer); |
| | | } |
| | | |
| | | |
| | | |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.base.model; |
| | | |
| | | import androidx.lifecycle.MutableLiveData; |
| | | |
| | | /** |
| | | * Created by Administrator on 2021/11/5 0005. |
| | | */ |
| | | public class ItemViewModel<T> extends BaseViewModel { |
| | | |
| | | MutableLiveData<T> liveData = new MutableLiveData<>(); |
| | | |
| | | public MutableLiveData<T> getLiveData() { |
| | | return liveData; |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.base.model; |
| | | |
| | | import androidx.annotation.NonNull; |
| | | import androidx.lifecycle.ViewModel; |
| | | import androidx.lifecycle.ViewModelProvider; |
| | | |
| | | /** |
| | | * Created by Administrator on 2021/11/4 0004. |
| | | */ |
| | | public class ViewModelFactory implements ViewModelProvider.Factory { |
| | | |
| | | static ViewModelFactory sInstance; |
| | | |
| | | public static ViewModelFactory getInstance() { |
| | | if (sInstance == null) { |
| | | sInstance = new ViewModelFactory(); |
| | | } |
| | | return sInstance; |
| | | } |
| | | |
| | | @Override |
| | | public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { |
| | | //noinspection TryWithIdenticalCatches |
| | | try { |
| | | return modelClass.newInstance(); |
| | | } catch (InstantiationException e) { |
| | | throw new RuntimeException("(创建实例失败,没有对应的构造方法,请重写一个实现类)Cannot create an instance of " + modelClass, e); |
| | | } catch (IllegalAccessException e) { |
| | | throw new RuntimeException("Cannot create an instance of " + modelClass, e); |
| | | } |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.data; |
| | | |
| | | /** |
| | | * Created by Administrator on 2021/11/15 0015. |
| | | */ |
| | | public class ApkUpGradeResult extends BaseApiResult<ApkUpGradeResult.AppInfo>{ |
| | | |
| | | public class AppInfo { |
| | | //以下为声明的参数 |
| | | public int type, isEnable, isImportant, id, code; |
| | | public String url,version, content, udate, remark; |
| | | |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "ApkUpGrade{" + |
| | | "type=" + type + "," + |
| | | "isEnable=" + isEnable + "," + |
| | | "isImportant=" + isImportant + "," + |
| | | "id=" + id + "," + |
| | | "url=" + url + "," + |
| | | "version=" + version + "," + |
| | | "content=" + content + "," + |
| | | "udate=" + udate + "," + |
| | | '}'; |
| | | } |
| | | |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.data; |
| | | |
| | | import java.io.Serializable; |
| | | |
| | | /** |
| | | * Created by Administrator on 2021/10/28 0028. |
| | | */ |
| | | public class BaseApiResult<D extends Object> implements Serializable { |
| | | public String msg; |
| | | public int code = 200; |
| | | public D data; |
| | | |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "BaseApiData{" + |
| | | "msg='" + msg + '\'' + |
| | | ", code=" + code + |
| | | ", data=" + data + |
| | | '}'; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.data; |
| | | |
| | | import java.util.ArrayList; |
| | | |
| | | /** |
| | | * Created by Administrator on 2021/10/28 0028. |
| | | */ |
| | | public class BasePageResult<T> extends BaseApiResult<String>{ |
| | | public int pages; |
| | | public int total; |
| | | public int pageNum; |
| | | public ArrayList<T> rows; |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "PageBean{" + |
| | | ", total=" + total + |
| | | ", rows=" + rows + |
| | | '}'; |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.retrofit; |
| | | |
| | | import android.os.Handler; |
| | | import android.os.Looper; |
| | | |
| | | import androidx.annotation.NonNull; |
| | | |
| | | import java.util.concurrent.Executor; |
| | | |
| | | import io.reactivex.Scheduler; |
| | | import io.reactivex.schedulers.Schedulers; |
| | | |
| | | /** |
| | | * Created by Administrator on 2021/11/8 0008. |
| | | */ |
| | | public class AndroidScheduler implements Executor { |
| | | private static AndroidScheduler instance; |
| | | |
| | | private final Scheduler mMainScheduler; |
| | | private final Handler mHandler; |
| | | |
| | | private AndroidScheduler() { |
| | | mHandler = new Handler(Looper.myLooper()); |
| | | mMainScheduler = Schedulers.from(this); |
| | | } |
| | | |
| | | public static synchronized Scheduler mainThread() { |
| | | if (instance == null) { |
| | | instance = new AndroidScheduler(); |
| | | } |
| | | return instance.mMainScheduler; |
| | | } |
| | | |
| | | @Override |
| | | public void execute(@NonNull Runnable command) { |
| | | mHandler.post(command); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.retrofit.Interceptor; |
| | | |
| | | |
| | | import com.runt.open.mvvm.retrofit.utils.RSAUtils; |
| | | |
| | | import org.json.JSONObject; |
| | | |
| | | import java.io.IOException; |
| | | import java.net.URLDecoder; |
| | | import java.nio.charset.Charset; |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | |
| | | import okhttp3.FormBody; |
| | | import okhttp3.Headers; |
| | | import okhttp3.Interceptor; |
| | | import okhttp3.MediaType; |
| | | import okhttp3.MultipartBody; |
| | | import okhttp3.Request; |
| | | import okhttp3.RequestBody; |
| | | import okhttp3.Response; |
| | | import okio.Buffer; |
| | | |
| | | /** |
| | | * My father is Object, ites purpose of 加密拦截器 |
| | | * |
| | | * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-10-8. |
| | | */ |
| | | |
| | | public class EncryptInterceptor implements Interceptor { |
| | | |
| | | protected static final Charset UTF8 = Charset.forName("UTF-8"); |
| | | private final String ENCRYPT = "encrypt"; |
| | | |
| | | @Override |
| | | public Response intercept(Chain chain) throws IOException { |
| | | return chain.proceed(encryptRequest(chain.request())); |
| | | } |
| | | |
| | | |
| | | //加密 |
| | | protected Request encryptRequest(Request request) throws IOException { |
| | | Headers headers = request.headers(); |
| | | RequestBody requestBody = request.body(); |
| | | Request.Builder builder = request.newBuilder(); |
| | | for(int i = 0 ; i < headers.size() ; i ++){ |
| | | builder.addHeader(headers.name(i),headers.value(i)); |
| | | } |
| | | if(requestBody != null){ |
| | | Charset charset = UTF8; |
| | | MediaType contentType = requestBody.contentType(); |
| | | if (contentType != null) { |
| | | charset = contentType.charset(UTF8); |
| | | } |
| | | HashMap param = new HashMap(); |
| | | if(requestBody instanceof MultipartBody){ |
| | | MultipartBody body = (MultipartBody) requestBody; |
| | | for(MultipartBody.Part part:body.parts()){ |
| | | Buffer buffer1 = new Buffer(); |
| | | part.body().writeTo(buffer1); |
| | | String str=buffer1.readString(charset).replaceAll("%(?![0-9a-fA-F]{2})","%25"); |
| | | param.put(part.headers().get(part.headers().name(0)), URLDecoder.decode(str, "UTF-8")); |
| | | } |
| | | MultipartBody.Builder mbuilder = new MultipartBody.Builder().setType(MultipartBody.FORM); |
| | | mbuilder.addFormDataPart(ENCRYPT,encryptParam(param)); |
| | | builder.post(mbuilder.build()); |
| | | }else if(requestBody instanceof FormBody){ |
| | | FormBody body = (FormBody) requestBody; |
| | | for(int i = 0 ; i < body.size() ; i ++ ){ |
| | | param.put(body.name(i),body.value(i)); |
| | | } |
| | | FormBody.Builder formBuild = new FormBody.Builder(); |
| | | formBuild.add(ENCRYPT,encryptParam(param)); |
| | | builder.post(formBuild.build()); |
| | | }else{ |
| | | Buffer buffer = new Buffer(); |
| | | requestBody.writeTo(buffer); |
| | | String str = buffer.readString(charset); |
| | | String encrypt = encryptJson(str); |
| | | param.put(ENCRYPT,encrypt); |
| | | builder.post(RequestBody.create(MediaType.parse("application/json;charset=utf-8"), new JSONObject(param).toString())); |
| | | } |
| | | } |
| | | return builder.build(); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 加密传递的参数 |
| | | * @param params |
| | | * @return |
| | | */ |
| | | public static String encryptParam(Map<String, Object> params){ |
| | | return encryptJson(new JSONObject(params).toString()); |
| | | } |
| | | public static String encryptJson(String json){ |
| | | try { |
| | | return RSAUtils.encrypt(json,RSAUtils.getPublicKey(RSAUtils.PUBLIC_KEY)); |
| | | }catch (Exception e){ |
| | | e.printStackTrace(); |
| | | return e.getMessage(); |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.retrofit.Interceptor; |
| | | |
| | | import android.util.Log; |
| | | |
| | | import com.runt.open.mvvm.retrofit.net.NetWorkCost; |
| | | import com.runt.open.mvvm.retrofit.net.NetWorkListenear; |
| | | import com.runt.open.mvvm.retrofit.utils.HttpPrintUtils; |
| | | import com.runt.open.mvvm.util.GsonUtils; |
| | | |
| | | import org.json.JSONObject; |
| | | |
| | | import java.io.EOFException; |
| | | import java.io.IOException; |
| | | import java.net.URLDecoder; |
| | | import java.nio.charset.Charset; |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | |
| | | import okhttp3.FormBody; |
| | | import okhttp3.Headers; |
| | | import okhttp3.MediaType; |
| | | import okhttp3.MultipartBody; |
| | | import okhttp3.Request; |
| | | import okhttp3.RequestBody; |
| | | import okhttp3.Response; |
| | | import okhttp3.ResponseBody; |
| | | import okhttp3.internal.http.HttpHeaders; |
| | | import okio.Buffer; |
| | | import okio.BufferedSource; |
| | | |
| | | /** |
| | | * My father is Object, ites purpose of log打印 |
| | | * |
| | | * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-10-21. |
| | | */ |
| | | |
| | | public class HttpLoggingInterceptor extends EncryptInterceptor { |
| | | |
| | | final String TAG = "HttpLogging"; |
| | | |
| | | public HttpLoggingInterceptor() { |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public Response intercept(Chain chain) throws IOException { |
| | | |
| | | Request request = chain.request(); |
| | | int hashCode = request.hashCode(); |
| | | ArrayList<String> logArrays = getRequestLog(request); |
| | | int position = logArrays.size() +2; |
| | | Response response; |
| | | try { |
| | | //request = encryptRequest(request);//加密 |
| | | response = chain.proceed(request); |
| | | logArrays.addAll(getResponseLog(response)); |
| | | Log.d(TAG,"hashcode:"+hashCode); |
| | | NetWorkCost netWorkCost = NetWorkListenear.workCostMap.get(hashCode); |
| | | if(netWorkCost != null) { |
| | | String cost = String.format("dns:%s,secure:%s,connect:%s,requestH:%s,requestB:%s,responseH:%s,responseB:%s", convertTimes(netWorkCost.dns), convertTimes(netWorkCost.secure), convertTimes(netWorkCost.connect), convertTimes(netWorkCost.requestHeader), convertTimes(netWorkCost.requestBody), convertTimes(netWorkCost.resposeHeader), convertTimes(netWorkCost.resposeBody)); |
| | | logArrays.add(position, "<-- costtimes : " + convertTimes(netWorkCost.total) + " (" + cost + ')'); |
| | | } |
| | | NetWorkListenear.workCostMap.remove(hashCode); |
| | | new Thread(){ |
| | | @Override |
| | | public void run() { |
| | | HttpPrintUtils.getInstance().printLog(logArrays, true);//线程安全方法,需在新线程执行,避免阻塞当前线程,导致程序无响应 |
| | | } |
| | | }.start(); |
| | | } catch (Exception e) { |
| | | logArrays.add("<-- response url:" + URLDecoder.decode(request.url().toString(), "UTF-8")); |
| | | NetWorkCost netWorkCost = NetWorkListenear.workCostMap.get(hashCode); |
| | | String cost = String.format("dns:%s,secure:%s,connect:%s,requestH:%s,requestB:%s,responseH:%s,responseB:%s", convertTimes(netWorkCost.dns) ,convertTimes(netWorkCost.secure) , convertTimes(netWorkCost.connect),convertTimes(netWorkCost.requestHeader),convertTimes(netWorkCost.requestBody) ,convertTimes(netWorkCost.resposeHeader),convertTimes(netWorkCost.resposeBody) ); |
| | | logArrays.add("<-- costtimes : "+convertTimes(netWorkCost.total)+" (" +cost + ')'); |
| | | NetWorkListenear.workCostMap.remove(hashCode); |
| | | logArrays.add("<-- response failed " + e.getLocalizedMessage()); |
| | | logArrays.add("<-- " + e.toString()); |
| | | new Thread(){ |
| | | @Override |
| | | public void run() { |
| | | HttpPrintUtils.getInstance().printLog(logArrays, false);//线程安全方法,需在新线程执行,避免阻塞当前线程,导致程序无响应 |
| | | } |
| | | }.start(); |
| | | throw e;//抛出异常,用于请求接收信息 |
| | | } |
| | | return response; |
| | | } |
| | | |
| | | private ArrayList<String> getRequestLog(Request request) throws IOException { |
| | | RequestBody requestBody = request.body(); |
| | | ArrayList<String> logArrays = new ArrayList<>(); |
| | | String requestStartMessage = "--> " + request.method() + ' ' + URLDecoder.decode(request.url().toString() ,"UTF-8")+ ' ' ; |
| | | if ( requestBody != null) { |
| | | requestStartMessage += " (" + requestBody.contentLength() + "-byte body)"; |
| | | } |
| | | logArrays.add(requestStartMessage); |
| | | Headers headers = request.headers(); |
| | | logArrays.add("---------->REQUEST HEADER<----------"); |
| | | for (int i = 0, count = headers.size(); i < count; i++) { |
| | | logArrays.add(headers.name(i) + ": " + headers.value(i)); |
| | | } |
| | | if (requestBody == null) { |
| | | logArrays.add("--> END " + request.method()); |
| | | } else if (bodyEncoded(request.headers())) { |
| | | logArrays.add("--> END " + request.method() + " (encoded body omitted)"); |
| | | } else { |
| | | |
| | | Charset charset = UTF8; |
| | | MediaType contentType = requestBody.contentType(); |
| | | if (contentType != null) { |
| | | charset = contentType.charset(UTF8); |
| | | } |
| | | HashMap param = new HashMap(); |
| | | if(requestBody instanceof MultipartBody){ |
| | | logArrays.add("---------->REQUEST BODY[MultipartBody]<----------"); |
| | | MultipartBody body = (MultipartBody) requestBody; |
| | | for(MultipartBody.Part part:body.parts()){ |
| | | Buffer buffer1 = new Buffer(); |
| | | part.body().writeTo(buffer1); |
| | | String str=buffer1.readString(charset).replaceAll("%(?![0-9a-fA-F]{2})","%25"); |
| | | param.put(part.headers().get(part.headers().name(0)),URLDecoder.decode(str, "UTF-8")); |
| | | } |
| | | logArrays.add(GsonUtils.retractJson(new JSONObject(param).toString())); |
| | | }else if(requestBody instanceof FormBody){ |
| | | logArrays.add("---------->REQUEST BODY[FormBody]<----------"); |
| | | FormBody body = (FormBody) requestBody; |
| | | for(int i = 0 ; i < body.size() ; i ++ ){ |
| | | param.put(body.name(i),body.value(i)); |
| | | } |
| | | logArrays.add(GsonUtils.retractJson(new JSONObject(param).toString())); |
| | | }else{ |
| | | Buffer buffer = new Buffer(); |
| | | requestBody.writeTo(buffer); |
| | | logArrays.add("---------->REQUEST BODY<----------"); |
| | | String str = buffer.readString(charset); |
| | | try{ |
| | | logArrays.add(GsonUtils.retractJson(URLDecoder.decode(str, "UTF-8"))); |
| | | }catch (Exception e){ |
| | | logArrays.add(str); |
| | | } |
| | | } |
| | | logArrays.add("--> END " + request.method() + " " + contentType + " ( " |
| | | + requestBody.contentLength() + "-byte body )"); |
| | | } |
| | | return logArrays; |
| | | |
| | | } |
| | | |
| | | |
| | | private ArrayList<String> getResponseLog(Response response) throws IOException { |
| | | ArrayList<String> logArrays = new ArrayList<>(); |
| | | ResponseBody responseBody = response.body(); |
| | | long contentLength = responseBody.contentLength(); |
| | | String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length"; |
| | | logArrays.add("<-- response code:" + response.code() + " message:" + response.message()+" contentlength:"+bodySize ); |
| | | logArrays.add("<-- response url:"+URLDecoder.decode(response.request().url().toString(),"UTF-8") ); |
| | | |
| | | if ( !HttpHeaders.hasBody(response)) { |
| | | logArrays.add("<-- END HTTP"); |
| | | } else if (bodyEncoded(response.headers())) { |
| | | logArrays.add("<-- END HTTP (encoded body omitted)"); |
| | | } else { |
| | | BufferedSource source = responseBody.source(); |
| | | source.request(Long.MAX_VALUE); // Buffer the entire body. |
| | | Buffer buffer = source.buffer(); |
| | | |
| | | Charset charset = UTF8; |
| | | MediaType contentType = responseBody.contentType(); |
| | | if (contentType != null) { |
| | | charset = contentType.charset(UTF8); |
| | | } |
| | | |
| | | if (isPlaintext(buffer)) { |
| | | logArrays.add("---------->RESPONSE BODY<----------"); |
| | | if (contentLength != 0) { |
| | | logArrays.add(retractJson(buffer.clone().readString(charset))); |
| | | } |
| | | |
| | | logArrays.add("<-- END HTTP (" + buffer.size() + "-byte body)"); |
| | | } |
| | | |
| | | } |
| | | return logArrays; |
| | | } |
| | | |
| | | /** |
| | | * 字符串缩进 |
| | | * @param json |
| | | * @return |
| | | */ |
| | | private String retractJson(String json){ |
| | | int level = 0 ; |
| | | StringBuffer jsonForMatStr = new StringBuffer(); |
| | | for(int index=0;index<json.length();index++)//将字符串中的字符逐个按行输出 |
| | | { |
| | | //获取s中的每个字符 |
| | | char c = json.charAt(index); |
| | | // System.out.println(s.charAt(index)); |
| | | |
| | | //level大于0并且jsonForMatStr中的最后一个字符为\n,jsonForMatStr加入\t |
| | | if (level > 0 && '\n' == jsonForMatStr.charAt(jsonForMatStr.length() - 1)) { |
| | | jsonForMatStr.append(getLevelStr(level)); |
| | | // System.out.println("123"+jsonForMatStr); |
| | | } |
| | | //遇到"{"和"["要增加空格和换行,遇到"}"和"]"要减少空格,以对应,遇到","要换行 |
| | | switch (c) { |
| | | case '{': |
| | | case '[': |
| | | jsonForMatStr.append(c + "\n"); |
| | | level++; |
| | | break; |
| | | case ',': |
| | | jsonForMatStr.append(c + "\n"); |
| | | break; |
| | | case '}': |
| | | case ']': |
| | | jsonForMatStr.append("\n"); |
| | | level--; |
| | | jsonForMatStr.append(getLevelStr(level)); |
| | | jsonForMatStr.append(c); |
| | | break; |
| | | default: |
| | | jsonForMatStr.append(c); |
| | | break; |
| | | } |
| | | } |
| | | return jsonForMatStr.toString(); |
| | | } |
| | | |
| | | private String getLevelStr(int level) { |
| | | StringBuffer levelStr = new StringBuffer(); |
| | | for (int levelI = 0; levelI < level; levelI++) { |
| | | levelStr.append("\t");//\t或空格 |
| | | } |
| | | return levelStr.toString(); |
| | | } |
| | | |
| | | /** |
| | | * Returns true if the body in question probably contains human readable text. Uses a small sample |
| | | * of code points to detect unicode control characters commonly used in binary file signatures. |
| | | */ |
| | | static boolean isPlaintext(Buffer buffer) { |
| | | try { |
| | | Buffer prefix = new Buffer(); |
| | | long byteCount = buffer.size() < 64 ? buffer.size() : 64; |
| | | buffer.copyTo(prefix, 0, byteCount); |
| | | for (int i = 0; i < 16; i++) { |
| | | if (prefix.exhausted()) { |
| | | break; |
| | | } |
| | | int codePoint = prefix.readUtf8CodePoint(); |
| | | if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) { |
| | | return false; |
| | | } |
| | | } |
| | | return true; |
| | | } catch (EOFException e) { |
| | | return false; // Truncated UTF-8 sequence. |
| | | } |
| | | } |
| | | |
| | | private boolean bodyEncoded(Headers headers) { |
| | | String contentEncoding = headers.get("Content-Encoding"); |
| | | return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity"); |
| | | } |
| | | |
| | | private String convertTimes(long ms){ |
| | | String m = null,s=null; |
| | | final int utilS = 1000; |
| | | final int utilM = utilS*60; |
| | | if(ms/utilM>0){ |
| | | m = ms/utilM+"m"; |
| | | } |
| | | if(ms%utilM/utilS>0){ |
| | | s = ms%utilM/utilS+"s"; |
| | | } |
| | | return (m!=null?m:"")+(s!=null?s:"")+ms%utilS+"ms"; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.retrofit.api; |
| | | |
| | | |
| | | |
| | | import com.runt.open.mvvm.data.ApkUpGradeResult; |
| | | |
| | | import java.util.Map; |
| | | |
| | | import io.reactivex.Observable; |
| | | import retrofit2.http.Field; |
| | | import retrofit2.http.FieldMap; |
| | | import retrofit2.http.FormUrlEncoded; |
| | | import retrofit2.http.GET; |
| | | import retrofit2.http.POST; |
| | | import retrofit2.http.Query; |
| | | import retrofit2.http.QueryMap; |
| | | import retrofit2.http.Url; |
| | | |
| | | /** |
| | | * My father is Object, ites purpose of 常用接口 |
| | | * |
| | | * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-7-21. |
| | | */ |
| | | |
| | | public interface CommonApiCenter { |
| | | |
| | | @GET |
| | | Observable<Object> getData(@Url String url, @QueryMap Map<String,String> param); |
| | | |
| | | @FormUrlEncoded |
| | | @POST |
| | | Observable<Object> postData(@Url String url, @FieldMap Map<String,String> param); |
| | | |
| | | |
| | | /** |
| | | * 分页数据 |
| | | * @param url 请求地址 |
| | | * @param pageNum 页数 |
| | | * @param pageSize 每页数量 |
| | | * @param param 其他参数 |
| | | * @return |
| | | */ |
| | | @GET |
| | | Observable<Object> getPageData(@Url String url, @Query("pageNum") int pageNum, @Query("pageSize") int pageSize, @QueryMap Map<String,String> param); |
| | | |
| | | /** |
| | | * 分页数据 |
| | | * @param url 请求地址 |
| | | * @param pageNum 页数 |
| | | * @param pageSize 每页数量 |
| | | * @param param 其他参数 |
| | | * @return |
| | | */ |
| | | @FormUrlEncoded |
| | | @POST |
| | | Observable<Object> postPageData(@Url String url, @Field("pageNum") int pageNum, @Field("pageSize") int pageSize, @FieldMap Map<String,String> param); |
| | | |
| | | /** |
| | | * app更新 |
| | | * @return |
| | | */ |
| | | @GET("system/appupgrade/tourist/get/2") |
| | | Observable<ApkUpGradeResult> getAppUpdate(); |
| | | |
| | | |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.retrofit.converter; |
| | | |
| | | import com.google.gson.Gson; |
| | | import com.google.gson.TypeAdapter; |
| | | import com.google.gson.reflect.TypeToken; |
| | | |
| | | import java.lang.annotation.Annotation; |
| | | import java.lang.reflect.Type; |
| | | |
| | | import okhttp3.RequestBody; |
| | | import okhttp3.ResponseBody; |
| | | import retrofit2.Converter; |
| | | import retrofit2.Retrofit; |
| | | |
| | | /** |
| | | * My father is Object, ites purpose of 解密gson转换 |
| | | * |
| | | * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-7-22. |
| | | */ |
| | | public class DecryptGsonConverterFactory extends Converter.Factory { |
| | | |
| | | public static DecryptGsonConverterFactory create() { |
| | | return create(new Gson(),false); |
| | | } |
| | | |
| | | public static DecryptGsonConverterFactory create(boolean transHump) { |
| | | return create(new Gson(),transHump); |
| | | } |
| | | |
| | | |
| | | public static DecryptGsonConverterFactory create(Gson gson,boolean transHump) { |
| | | return new DecryptGsonConverterFactory(gson,transHump); |
| | | } |
| | | |
| | | private final Gson gson; |
| | | |
| | | private final boolean transHump; |
| | | |
| | | public DecryptGsonConverterFactory(Gson gson, boolean transHump) { |
| | | if (gson == null) throw new NullPointerException("gson == null"); |
| | | this.gson = gson; |
| | | this.transHump = transHump; |
| | | } |
| | | |
| | | @Override |
| | | public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, |
| | | Retrofit retrofit) { |
| | | TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)); |
| | | return new DecryptGsonResponseBodyConverter<>(gson, adapter,transHump); |
| | | } |
| | | |
| | | @Override |
| | | public Converter<?, RequestBody> requestBodyConverter(Type type, |
| | | Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { |
| | | TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)); |
| | | return new GsonRequestBodyConverter<>(gson, adapter); |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.retrofit.converter; |
| | | |
| | | import android.util.Log; |
| | | |
| | | import com.runt.open.mvvm.data.BaseApiResult; |
| | | import com.runt.open.mvvm.util.GsonUtils; |
| | | import com.google.gson.Gson; |
| | | import com.google.gson.JsonIOException; |
| | | import com.google.gson.TypeAdapter; |
| | | import com.google.gson.stream.JsonReader; |
| | | import com.google.gson.stream.JsonToken; |
| | | |
| | | import org.json.JSONException; |
| | | import org.json.JSONObject; |
| | | |
| | | import java.io.ByteArrayInputStream; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.io.InputStreamReader; |
| | | import java.nio.charset.Charset; |
| | | import java.security.NoSuchAlgorithmException; |
| | | import java.security.spec.InvalidKeySpecException; |
| | | |
| | | import okhttp3.ResponseBody; |
| | | import retrofit2.Converter; |
| | | |
| | | /** |
| | | * My father is Object, ites purpose of 解密gson转换器 |
| | | * |
| | | * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-7-22. |
| | | */ |
| | | |
| | | public class DecryptGsonResponseBodyConverter<T> implements Converter<ResponseBody, T> { |
| | | private final Gson gson; |
| | | private final TypeAdapter<T> adapter; |
| | | private final Charset UTF_8 = Charset.forName("UTF-8"); |
| | | private final boolean transHump;//驼峰转换 |
| | | private final String ENCRYPT = "encrypt"; |
| | | |
| | | public DecryptGsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter, boolean transHump) { |
| | | this.gson = gson; |
| | | this.adapter = adapter; |
| | | this.transHump = transHump; |
| | | } |
| | | |
| | | @Override |
| | | public T convert(ResponseBody value) throws IOException { |
| | | String response = null; |
| | | try { |
| | | String val = new String(value.bytes(),UTF_8); |
| | | response = decryptJsonStr(val);//解密 |
| | | } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { |
| | | e.printStackTrace(); |
| | | BaseApiResult apiResult = new BaseApiResult<>(); |
| | | apiResult.code = 412; |
| | | apiResult.msg = "解密数据出错"+e.getMessage(); |
| | | response = new Gson().toJson(apiResult); |
| | | } catch (JSONException e) { |
| | | e.printStackTrace(); |
| | | BaseApiResult apiResult = new BaseApiResult<>(); |
| | | apiResult.code = 414; |
| | | apiResult.msg = "非标准json"; |
| | | response = new Gson().toJson(apiResult); |
| | | }catch (Exception e){ |
| | | JsonReader jsonReader = gson.newJsonReader(value.charStream()); |
| | | return adapter.read(jsonReader); |
| | | } finally { |
| | | InputStream inputStream = new ByteArrayInputStream(response.getBytes()); |
| | | JsonReader jsonReader = gson.newJsonReader(new InputStreamReader(inputStream, UTF_8)); |
| | | T result = adapter.read(jsonReader); |
| | | if (jsonReader.peek() != JsonToken.END_DOCUMENT) { |
| | | throw new JsonIOException("JSON document was not fully consumed."); |
| | | } |
| | | value.close(); |
| | | return result; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 解密json |
| | | * @param body |
| | | * @return |
| | | * @throws Exception |
| | | */ |
| | | protected String decryptJsonStr(String body) throws Exception { |
| | | Log.e("Converter","decryptJsonStr body:"+body); |
| | | if(body.indexOf("{") == 0) { |
| | | JSONObject json = new JSONObject(body); |
| | | body = json.toString(); |
| | | //body = RSAUtils.decrypt(json.getString(ENCRYPT), RSAUtils.getPublicKey(RSAUtils.PUBLIC_KEY));// |
| | | } |
| | | return transHump? GsonUtils.toHumpJson(body):body; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.retrofit.converter; |
| | | |
| | | import com.google.gson.Gson; |
| | | import com.google.gson.TypeAdapter; |
| | | import com.google.gson.stream.JsonWriter; |
| | | |
| | | import java.io.IOException; |
| | | import java.io.OutputStreamWriter; |
| | | import java.io.Writer; |
| | | import java.nio.charset.Charset; |
| | | |
| | | import okhttp3.MediaType; |
| | | import okhttp3.RequestBody; |
| | | import okio.Buffer; |
| | | import retrofit2.Converter; |
| | | |
| | | /** |
| | | * My father is Object, ites purpose of |
| | | * |
| | | * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-7-15. |
| | | */ |
| | | |
| | | final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> { |
| | | private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8"); |
| | | private static final Charset UTF_8 = Charset.forName("UTF-8"); |
| | | |
| | | private final Gson gson; |
| | | private final TypeAdapter<T> adapter; |
| | | |
| | | GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) { |
| | | this.gson = gson; |
| | | this.adapter = adapter; |
| | | } |
| | | |
| | | @Override |
| | | public RequestBody convert(T value) throws IOException { |
| | | Buffer buffer = new Buffer(); |
| | | Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8); |
| | | JsonWriter jsonWriter = gson.newJsonWriter(writer); |
| | | adapter.write(jsonWriter, value); |
| | | jsonWriter.close(); |
| | | return RequestBody.create(MEDIA_TYPE, buffer.readByteString()); |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.retrofit.net; |
| | | |
| | | |
| | | /** |
| | | * My father is Object, ites purpose of 网络消耗 |
| | | * |
| | | * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-3-12. |
| | | */ |
| | | |
| | | public class NetWorkCost { |
| | | |
| | | //网络消耗时间 |
| | | public long dns,connect,total,secure,requestHeader,requestBody,resposeHeader,resposeBody; |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.retrofit.net; |
| | | |
| | | import androidx.annotation.Nullable; |
| | | |
| | | import org.jetbrains.annotations.NotNull; |
| | | |
| | | import java.io.IOException; |
| | | import java.net.InetAddress; |
| | | import java.net.InetSocketAddress; |
| | | import java.net.Proxy; |
| | | import java.nio.charset.Charset; |
| | | import java.util.Date; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | import okhttp3.Call; |
| | | import okhttp3.EventListener; |
| | | import okhttp3.Handshake; |
| | | import okhttp3.Protocol; |
| | | import okhttp3.Request; |
| | | import okhttp3.Response; |
| | | |
| | | /** |
| | | * My father is Object, ites purpose of 接口请求耗时监听 |
| | | * |
| | | * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-7-9. |
| | | */ |
| | | |
| | | public class NetWorkListenear extends EventListener { |
| | | |
| | | private static final String TAG = "NetworkEventListener"; |
| | | final Charset UTF8 = Charset.forName("UTF-8"); |
| | | public static Map<Integer, NetWorkCost> workCostMap = new HashMap<>(); |
| | | |
| | | public static Factory get(){ |
| | | Factory factory = new Factory() { |
| | | @NotNull |
| | | @Override |
| | | public EventListener create(@NotNull Call call) { |
| | | return new NetWorkListenear(); |
| | | } |
| | | }; |
| | | return factory; |
| | | } |
| | | |
| | | @Override |
| | | public void callStart(@NotNull Call call) { |
| | | super.callStart(call); |
| | | //mRequestId = mNextRequestId.getAndIncrement() + ""; |
| | | //getAndAdd,在多线程下使用cas保证原子性 |
| | | NetWorkCost netWorkCost = new NetWorkCost(); |
| | | netWorkCost.total = new Date().getTime(); |
| | | workCostMap.put(call.request().hashCode(),netWorkCost); |
| | | } |
| | | |
| | | @Override |
| | | public void dnsStart(@NotNull Call call, @NotNull String domainName) { |
| | | super.dnsStart(call, domainName); |
| | | //Log.d(TAG, "dnsStart"); |
| | | workCostMap.get(call.request().hashCode()).dns = new Date().getTime(); |
| | | } |
| | | |
| | | @Override |
| | | public void dnsEnd(@NotNull Call call, @NotNull String domainName, @NotNull List<InetAddress> inetAddressList) { |
| | | super.dnsEnd(call, domainName, inetAddressList); |
| | | //Log.d(TAG, "dnsEnd"); |
| | | workCostMap.get(call.request().hashCode()).dns = new Date().getTime() - workCostMap.get(call.request().hashCode()).dns; |
| | | } |
| | | |
| | | @Override |
| | | public void connectStart(@NotNull Call call, @NotNull InetSocketAddress inetSocketAddress, @NotNull Proxy proxy) { |
| | | super.connectStart(call, inetSocketAddress, proxy); |
| | | //Log.d(TAG, "connectStart"); |
| | | workCostMap.get(call.request().hashCode()).connect = new Date().getTime(); |
| | | } |
| | | |
| | | @Override |
| | | public void secureConnectStart(@NotNull Call call) { |
| | | super.secureConnectStart(call); |
| | | //Log.d(TAG, "secureConnectStart"); |
| | | workCostMap.get(call.request().hashCode()).secure = new Date().getTime(); |
| | | } |
| | | |
| | | @Override |
| | | public void secureConnectEnd(@NotNull Call call, @Nullable Handshake handshake) { |
| | | super.secureConnectEnd(call, handshake); |
| | | //Log.d(TAG, "secureConnectEnd"); |
| | | workCostMap.get(call.request().hashCode()).secure = new Date().getTime() - workCostMap.get(call.request().hashCode()).secure; |
| | | } |
| | | |
| | | @Override |
| | | public void connectEnd(@NotNull Call call, @NotNull InetSocketAddress inetSocketAddress, |
| | | @NotNull Proxy proxy, @Nullable Protocol protocol) { |
| | | super.connectEnd(call, inetSocketAddress, proxy, protocol); |
| | | //Log.d(TAG, "connectEnd"); |
| | | workCostMap.get(call.request().hashCode()).connect = new Date().getTime() - workCostMap.get(call.request().hashCode()).connect; |
| | | } |
| | | |
| | | @Override |
| | | public void connectFailed(@NotNull Call call, @NotNull InetSocketAddress inetSocketAddress, @NotNull Proxy proxy, @Nullable Protocol protocol, @NotNull IOException ioe) { |
| | | super.connectFailed(call, inetSocketAddress, proxy, protocol, ioe); |
| | | workCostMap.get(call.request().hashCode()).connect = new Date().getTime() - workCostMap.get(call.request().hashCode()).connect; |
| | | workCostMap.get(call.request().hashCode()).total = new Date().getTime() - workCostMap.get(call.request().hashCode()).total; |
| | | //Log.d(TAG, "connectFailed"); |
| | | } |
| | | |
| | | @Override |
| | | public void requestHeadersStart(@NotNull Call call) { |
| | | super.requestHeadersStart(call); |
| | | //Log.d(TAG, "requestHeadersStart"); |
| | | workCostMap.get(call.request().hashCode()).requestHeader = new Date().getTime(); |
| | | } |
| | | |
| | | @Override |
| | | public void requestHeadersEnd(@NotNull Call call, @NotNull Request request) { |
| | | super.requestHeadersEnd(call, request); |
| | | //Log.d(TAG, "requestHeadersEnd"); |
| | | workCostMap.get(call.request().hashCode()).requestHeader = new Date().getTime() - workCostMap.get(call.request().hashCode()).requestHeader; |
| | | } |
| | | |
| | | @Override |
| | | public void requestBodyStart(@NotNull Call call) { |
| | | super.requestBodyStart(call); |
| | | //Log.d(TAG, "requestBodyStart"); |
| | | workCostMap.get(call.request().hashCode()).requestBody = new Date().getTime(); |
| | | } |
| | | |
| | | @Override |
| | | public void requestBodyEnd(@NotNull Call call, long byteCount) { |
| | | super.requestBodyEnd(call, byteCount); |
| | | //Log.d(TAG, "requestBodyEnd"); |
| | | workCostMap.get(call.request().hashCode()).requestBody = new Date().getTime() - workCostMap.get(call.request().hashCode()).requestBody; |
| | | } |
| | | |
| | | @Override |
| | | public void responseHeadersStart(@NotNull Call call) { |
| | | super.responseHeadersStart(call); |
| | | //Log.d(TAG, "responseHeadersStart"); |
| | | workCostMap.get(call.request().hashCode()).resposeHeader = new Date().getTime(); |
| | | } |
| | | |
| | | @Override |
| | | public void responseHeadersEnd(@NotNull Call call, @NotNull Response response) { |
| | | super.responseHeadersEnd(call, response); |
| | | //Log.d(TAG, "responseHeadersEnd"); |
| | | workCostMap.get(call.request().hashCode()).resposeHeader = new Date().getTime() - workCostMap.get(call.request().hashCode()).resposeHeader; |
| | | } |
| | | |
| | | @Override |
| | | public void responseBodyStart(@NotNull Call call) { |
| | | super.responseBodyStart(call); |
| | | //Log.d(TAG, "responseBodyStart"); |
| | | workCostMap.get(call.request().hashCode()).resposeBody = new Date().getTime(); |
| | | } |
| | | |
| | | @Override |
| | | public void responseBodyEnd(@NotNull Call call, long byteCount) { |
| | | super.responseBodyEnd(call, byteCount); |
| | | //Log.d(TAG, "responseBodyEnd"); |
| | | workCostMap.get(call.request().hashCode()).resposeBody = new Date().getTime() - workCostMap.get(call.request().hashCode()).resposeBody; |
| | | workCostMap.get(call.request().hashCode()).total = new Date().getTime() - workCostMap.get(call.request().hashCode()).total; |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public void callFailed(@NotNull Call call, @NotNull IOException ioe) { |
| | | super.callFailed(call, ioe); |
| | | workCostMap.get(call.request().hashCode()).total = new Date().getTime() - workCostMap.get(call.request().hashCode()).total; |
| | | //Log.d(TAG, "callFailed"); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.retrofit.observable; |
| | | |
| | | import android.util.Log; |
| | | |
| | | import androidx.lifecycle.MutableLiveData; |
| | | |
| | | |
| | | import com.runt.open.mvvm.data.BaseApiResult; |
| | | |
| | | import java.lang.reflect.ParameterizedType; |
| | | import java.net.SocketTimeoutException; |
| | | |
| | | import io.reactivex.observers.DisposableObserver; |
| | | |
| | | /** |
| | | * 网络请求观察 |
| | | * Created by Administrator on 2021/11/11 0011. |
| | | */ |
| | | public abstract class HttpObserver<T extends BaseApiResult> extends DisposableObserver<T>{ |
| | | |
| | | final String TAG = "HttpObserver"; |
| | | |
| | | MutableLiveData<T> resultLive; |
| | | |
| | | public HttpObserver(MutableLiveData<T> resultLive) { |
| | | this.resultLive = resultLive; |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public void onNext(T value) { |
| | | resultLive.setValue(value); |
| | | } |
| | | |
| | | @Override |
| | | public void onError(Throwable throwable) { |
| | | Log.i("subscribe","onError"); |
| | | |
| | | try { |
| | | Log.e(TAG,this.getClass().getSimpleName()+" "+throwable.getMessage()); |
| | | Class<T> entityClass = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]; |
| | | T t = entityClass.newInstance();//实例化一个泛型 |
| | | t.code = 410; |
| | | if( throwable instanceof SocketTimeoutException){ |
| | | t.msg = "服务请求超时,请稍候再试";//设置错误信息 |
| | | }else{ |
| | | t.msg = "网络连接不畅,请检查您的网络设置";//设置错误信息 |
| | | } |
| | | resultLive.setValue(t); |
| | | } catch (ClassCastException e) { |
| | | e.printStackTrace(); |
| | | T t = (T) new BaseApiResult<String>(); |
| | | t.code = 409; |
| | | t.msg = "实例化对象未指定泛型实体类"; |
| | | resultLive.setValue(t); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | T t = (T) new BaseApiResult<String>(); |
| | | t.code = 409; |
| | | t.msg = e.getMessage(); |
| | | resultLive.setValue(t); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onComplete() { |
| | | Log.i("subscribe","onComplete"); |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.retrofit.utils; |
| | | |
| | | import com.runt.open.mvvm.BuildConfig; |
| | | import com.runt.open.mvvm.util.GsonUtils; |
| | | import com.runt.open.mvvm.util.MyLog; |
| | | |
| | | import java.io.EOFException; |
| | | import java.util.ArrayList; |
| | | |
| | | import okio.Buffer; |
| | | |
| | | /** |
| | | * My father is Object, ites purpose of 单例模式 保证synchronized方法的线程安全性 |
| | | * |
| | | * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-5-13. |
| | | */ |
| | | |
| | | public class HttpPrintUtils { |
| | | String TAG = "HttpPrintUtils"; |
| | | static HttpPrintUtils instance; |
| | | public static HttpPrintUtils getInstance(){ |
| | | if(instance == null){ |
| | | instance = new HttpPrintUtils(); |
| | | } |
| | | return instance; |
| | | } |
| | | |
| | | /** |
| | | * 打印log |
| | | * @param list |
| | | */ |
| | | public synchronized void printLog(ArrayList<String> list, boolean info){ |
| | | int length = 0 ;//计算每行最长的长度 |
| | | ArrayList<String> logArrays = new ArrayList<>(); |
| | | for(String str : list){ |
| | | if(str.indexOf("\n")>-1){//有换行的拆分处理 |
| | | String[] split = str.split("\n"); |
| | | for(String s : split){ |
| | | s = s.replace("\t"," ");//缩进替换空格 |
| | | if(length<s.length()){ |
| | | length = s.length(); |
| | | } |
| | | } |
| | | }else{ |
| | | if(length<str.length()){ |
| | | length = str.length(); |
| | | } |
| | | } |
| | | } |
| | | length+=14;//左右间距 |
| | | if(length>300){ |
| | | length = 300; |
| | | } |
| | | String head = "HTTP REQUEST START"; |
| | | logArrays.add(" \n\n\n"+"\n"); |
| | | //打印头部 |
| | | String logHead = "┏"+getEmptyStr((length-head.length())/2,"━")+head+getEmptyStr((length-head.length())/2,"━")+"┓"; |
| | | logArrays.add(logHead+"\n"); |
| | | //打印内容 |
| | | for(String str : list){ |
| | | String logStr = ""; |
| | | if(str.indexOf("\n")>-1){//内部换行替换 |
| | | splitStr(str,logHead.length(),logArrays); |
| | | }else{ |
| | | if(str.length()> logHead.length()){ |
| | | outOflength(str,logHead.length(),logArrays); |
| | | }else { |
| | | logStr = "┃ " + str + getEmptyStr((length - 14 - str.length()), " "); |
| | | //处理中文间距,保证打印无偏差 |
| | | logArrays.add(logStr + getEmptyStr((logHead.length() - logStr.length() - 1 - hasCNchar(logStr)), " ") + "┃ \n"); |
| | | } |
| | | } |
| | | } |
| | | String end = "HTTP REQUEST END"; |
| | | //打印结尾 |
| | | logArrays.add("┗"+getEmptyStr((length-end.length())/2,"━")+end+getEmptyStr((length-end.length())/2,"━")+"┛\n"); |
| | | logArrays.add(" \n\n\n"); |
| | | //Logger.DEFAULT.log(sb.toString());//打印log,避免多个log语句,导致log输出时其他线程的log输出切入此输出阵列内 |
| | | if(BuildConfig.DEBUG) { |
| | | for(int i = 0 ; i < logArrays.size() ; i ++ ){ |
| | | String str = logArrays.get(i); |
| | | if (info) { |
| | | MyLog.i(TAG , str.replace("\n","")+" "+logArrays.size()+" "+i); |
| | | } else { |
| | | MyLog.e(TAG , str.replace("\n","")+" "+logArrays.size()+" "+i); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 拆分 |
| | | * @param str |
| | | * @param totalLength |
| | | * @param list |
| | | */ |
| | | private void splitStr(String str,int totalLength,ArrayList<String> list){ |
| | | String logStr = ""; |
| | | String[] split = str.split("\n"); |
| | | for(String s : split){ |
| | | if(s.length()/totalLength>3){ |
| | | s = s.substring(0,totalLength*3)+"..."; |
| | | } |
| | | s = s.replace("\t"," ");//缩进替换空格 |
| | | if(s.indexOf("\":{\"")>-1 || s.indexOf("\":[{\"")>-1 || s.indexOf("\":[[")>-1){//内容非校正缩进,且为json字符规范 |
| | | splitStr(s.substring(0,s.indexOf("\":")+2)+ GsonUtils.retractJson(s.substring(s.indexOf("\":")+2)),totalLength,list); |
| | | }else { |
| | | if(s.length()> totalLength){ |
| | | outOflength(s,totalLength,list); |
| | | }else { |
| | | logStr = "┃ " + s + getEmptyStr((totalLength - 16 - s.length()), " "); |
| | | //处理中文间距,保证打印无偏差 |
| | | list.add(logStr + getEmptyStr((totalLength - logStr.length() - 1 - hasCNchar(logStr)), " ") + "┃ "/*+logStr.length()+" "+logStr.getBytes().length+" "+(" ").getBytes().length +" "+hasCNchar(s)*/ + "\n"); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 超长字符拆分 |
| | | * @param str |
| | | * @param total |
| | | * @param list |
| | | */ |
| | | private void outOflength(String str,int total,ArrayList<String> list){ |
| | | String logStr = ""; |
| | | //缩进空间 |
| | | String space = getEmptyStr(str.length() - str.trim().length()+4," "); |
| | | //要拆分的实际长度 |
| | | int length = (str.length()-space.length()); |
| | | //每行数量 |
| | | int count = total-16-space.length();//总长度-间距-缩进空间是每行的数量 |
| | | //最终拆分数量 |
| | | int lines = (length/count) + (length%(count)>0?1:0); |
| | | |
| | | for(int i = 0 ; i < lines ; i ++){ |
| | | int start = space.length() + (count * (i+1));//起始位 |
| | | int end = start+count;//结束位 |
| | | String s = ""; |
| | | if(start > str.length() && i > 0){ |
| | | break; |
| | | } else if(end > str.length() && i > 0 || i == lines-1){ |
| | | s = str.substring(start); |
| | | } else if(i == 0 ){ |
| | | s = str.substring(0, start); |
| | | } else { |
| | | s = str.substring(start, end); |
| | | } |
| | | if(i>0) { |
| | | s = space + s; |
| | | } |
| | | logStr = "┃ " + s + getEmptyStr((total - 16 - s.length()), " "); |
| | | list.add(logStr + getEmptyStr((total - logStr.length() - 1 - hasCNchar(logStr)), " ") + "┃ \n"); |
| | | } |
| | | } |
| | | |
| | | |
| | | //返回包含中文数量, |
| | | private int hasCNchar(String str){ |
| | | str = str.replace("┃",""); |
| | | int size = 0 ; |
| | | for(int i = 0 ; i < str.length() ; i ++){ |
| | | char c = str.charAt(i); |
| | | |
| | | if((c >= 0x0391 && c <= 0xFFE5)) { //中文字符 |
| | | size++; |
| | | } |
| | | } |
| | | return size>0?(int)(size/3.0*2):0;//+1为修正在log中与英文字符短一位 |
| | | } |
| | | |
| | | /** |
| | | * 占位符填充 |
| | | * @param length 占位数量 |
| | | * @param space 占位符 |
| | | * @return |
| | | */ |
| | | private String getEmptyStr(int length,String space){ |
| | | StringBuilder sb = new StringBuilder(); |
| | | for(int i = 0 ; i < length ; i ++){ |
| | | sb.append(space); |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | /** |
| | | * Returns true if the body in question probably contains human readable text. Uses a small sample |
| | | * of code points to detect unicode control characters commonly used in binary file signatures. |
| | | */ |
| | | static boolean isPlaintext(Buffer buffer) { |
| | | try { |
| | | Buffer prefix = new Buffer(); |
| | | long byteCount = buffer.size() < 64 ? buffer.size() : 64; |
| | | buffer.copyTo(prefix, 0, byteCount); |
| | | for (int i = 0; i < 16; i++) { |
| | | if (prefix.exhausted()) { |
| | | break; |
| | | } |
| | | int codePoint = prefix.readUtf8CodePoint(); |
| | | if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) { |
| | | return false; |
| | | } |
| | | } |
| | | return true; |
| | | } catch (EOFException e) { |
| | | return false; // Truncated UTF-8 sequence. |
| | | } |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.retrofit.utils; |
| | | |
| | | import android.util.Base64; |
| | | |
| | | import java.io.ByteArrayOutputStream; |
| | | import java.security.Key; |
| | | import java.security.KeyFactory; |
| | | import java.security.KeyPair; |
| | | import java.security.KeyPairGenerator; |
| | | import java.security.PrivateKey; |
| | | import java.security.PublicKey; |
| | | import java.security.Signature; |
| | | import java.security.spec.PKCS8EncodedKeySpec; |
| | | import java.security.spec.X509EncodedKeySpec; |
| | | |
| | | import javax.crypto.Cipher; |
| | | |
| | | |
| | | public class RSAUtils { |
| | | |
| | | public static final String PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALBGei0scHoOjTLImPHvASaGqYNrdLie0ckWp74Nkqv7FVeXPOvWEG8_jRJCVjJ1grr8SGd9sVY2sxn5XIz7fUEBfx7Vm8m0DaCNBWJpFLGw9xiaVZ2AUKoNyTD4NgZobbwZbt6ZNB6_fggPrGF18pq6GPyCndX1JW8ZiZKj33VBAgMBAAECgYB0q-EX3y7_CnyYXT8l-mxHhJ_T9R6HR89QimcyGqe2nvRMSjSvX7r29xg3OqL0uORzQKHnpcDncELw8SQ5yAbpENeIsD0dvdFlkoyFYU4ljeUbJ46binwwg20TNARjTbpNos9zbhTPh8qixdblxppXA1WC18HtXhixgca5bNG9lQJBAPQfNdpNdDL9l8Tw4hYVuDMszcFuZYbHbm0S4xcwqj-dXNWBztNf5W_K92-N5GIoHbOypkGzjlBjSZi_oKA0HusCQQC42irhw682CG44mKdP6YRDxy6OaauVX4yE9WnsbO8JFSSc9ZCKMMD0F3NGtytDrVMAJxG1iPWXa4ptEdtgwCmDAkAUW1npR1YuPllekdu4jb0bf1v1ClirAYxiyhVnxKYdweiQ4U827yM5zEoP4lwuFzxK1NXqWqe-alkjxK8HTPFbAkAviQLf_adP2MknSrIzzZQSreTeAHR8PA7xnf54KucpScOZjVh3AOSNoH4nYDEC_U5LysA2E5s8Lg5xz9a_QYsrAkEAwV6gNED7_SYDsYyEWimQ6znUb_QSY-sSChnSCY-ILG1wpynBHw_t1Oi3ljl6gL_cYKG1O3uwOtvZtb-Vr1bNkQ"; |
| | | |
| | | public static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwRnotLHB6Do0yyJjx7wEmhqmDa3S4ntHJFqe-DZKr-xVXlzzr1hBvP40SQlYydYK6_EhnfbFWNrMZ-VyM-31BAX8e1ZvJtA2gjQViaRSxsPcYmlWdgFCqDckw-DYGaG28GW7emTQev34ID6xhdfKauhj8gp3V9SVvGYmSo991QQIDAQAB"; |
| | | |
| | | |
| | | /** |
| | | * RSA最大加密明文大小 |
| | | */ |
| | | private static final int MAX_ENCRYPT_BLOCK = 117; |
| | | |
| | | /** |
| | | * RSA最大解密密文大小 |
| | | */ |
| | | private static final int MAX_DECRYPT_BLOCK = 128; |
| | | |
| | | static final String KEY_RSA = "RSA"; //android标准 “RSA/ECB/PKCS1Padding” 服务端标准 “RSA” |
| | | |
| | | /** |
| | | * 获取密钥对 |
| | | * |
| | | * @return 密钥对 |
| | | */ |
| | | public static KeyPair getKeyPair() throws Exception { |
| | | KeyPairGenerator generator = KeyPairGenerator.getInstance(KEY_RSA); |
| | | generator.initialize(1024); |
| | | return generator.generateKeyPair(); |
| | | } |
| | | |
| | | /** |
| | | * 获取私钥 |
| | | * |
| | | * @param privateKey 私钥字符串 |
| | | * @return |
| | | */ |
| | | public static PrivateKey getPrivateKey(String privateKey) throws Exception { |
| | | KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA); |
| | | byte[] decodedKey = Base64.decode(privateKey.getBytes(), Base64.URL_SAFE); |
| | | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey); |
| | | return keyFactory.generatePrivate(keySpec); |
| | | } |
| | | |
| | | /** |
| | | * 获取公钥 |
| | | * |
| | | * @param publicKey 公钥字符串 |
| | | * @return |
| | | */ |
| | | public static PublicKey getPublicKey(String publicKey) throws Exception { |
| | | KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA); |
| | | byte[] decodedKey = Base64.decode(publicKey.getBytes(), Base64.URL_SAFE); |
| | | X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey); |
| | | return keyFactory.generatePublic(keySpec); |
| | | } |
| | | |
| | | /** |
| | | * RSA加密 |
| | | * |
| | | * @param data 待加密数据 |
| | | * @param key 密钥 |
| | | * @return |
| | | */ |
| | | public static String encrypt(String data, Key key) throws Exception { |
| | | Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding"); |
| | | cipher.init(Cipher.ENCRYPT_MODE, key); |
| | | int inputLen = data.getBytes().length; |
| | | ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| | | int offset = 0; |
| | | byte[] cache; |
| | | int i = 0; |
| | | // 对数据分段加密 |
| | | while (inputLen - offset > 0) { |
| | | if (inputLen - offset > MAX_ENCRYPT_BLOCK) { |
| | | cache = cipher.doFinal(data.getBytes(), offset, MAX_ENCRYPT_BLOCK); |
| | | } else { |
| | | cache = cipher.doFinal(data.getBytes(), offset, inputLen - offset); |
| | | } |
| | | out.write(cache, 0, cache.length); |
| | | i++; |
| | | offset = i * MAX_ENCRYPT_BLOCK; |
| | | } |
| | | byte[] encryptedData = out.toByteArray(); |
| | | out.close(); |
| | | // 获取加密内容使用base64进行编码,并以UTF-8为标准转化成字符串 |
| | | // 加密后的字符串 |
| | | return Base64.encodeToString(encryptedData,Base64.URL_SAFE | Base64.NO_WRAP); |
| | | } |
| | | |
| | | /** |
| | | * RSA解密 |
| | | * |
| | | * @param data 待解密数据 |
| | | * @param key 密钥 |
| | | * @return |
| | | */ |
| | | public static String decrypt(String data, Key key) throws Exception { |
| | | Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding"); |
| | | cipher.init(Cipher.DECRYPT_MODE, key); |
| | | byte[] dataBytes = Base64.decode(data.getBytes(), Base64.URL_SAFE); |
| | | int inputLen = dataBytes.length; |
| | | ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| | | int offset = 0; |
| | | byte[] cache; |
| | | int i = 0; |
| | | // 对数据分段解密 |
| | | while (inputLen - offset > 0) { |
| | | if (inputLen - offset > MAX_DECRYPT_BLOCK) { |
| | | cache = cipher.doFinal(dataBytes, offset, MAX_DECRYPT_BLOCK); |
| | | } else { |
| | | cache = cipher.doFinal(dataBytes, offset, inputLen - offset); |
| | | } |
| | | out.write(cache, 0, cache.length); |
| | | i++; |
| | | offset = i * MAX_DECRYPT_BLOCK; |
| | | } |
| | | byte[] decryptedData = out.toByteArray(); |
| | | out.close(); |
| | | // 解密后的内容 |
| | | return new String(decryptedData, "UTF-8"); |
| | | } |
| | | |
| | | /** |
| | | * 签名 |
| | | * |
| | | * @param data 待签名数据 |
| | | * @param privateKey 私钥 |
| | | * @return 签名 |
| | | */ |
| | | public static String sign(String data, PrivateKey privateKey) throws Exception { |
| | | byte[] keyBytes = privateKey.getEncoded(); |
| | | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); |
| | | KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA); |
| | | PrivateKey key = keyFactory.generatePrivate(keySpec); |
| | | Signature signature = Signature.getInstance("MD5withRSA"); |
| | | signature.initSign(key); |
| | | signature.update(data.getBytes()); |
| | | return Base64.encodeToString(signature.sign(),Base64.DEFAULT); |
| | | } |
| | | |
| | | /** |
| | | * 验签 |
| | | * |
| | | * @param srcData 原始字符串 |
| | | * @param publicKey 公钥 |
| | | * @param sign 签名 |
| | | * @return 是否验签通过 |
| | | */ |
| | | public static boolean verify(String srcData, PublicKey publicKey, String sign) throws Exception { |
| | | byte[] keyBytes = publicKey.getEncoded(); |
| | | X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); |
| | | KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA); |
| | | PublicKey key = keyFactory.generatePublic(keySpec); |
| | | Signature signature = Signature.getInstance("MD5withRSA"); |
| | | signature.initVerify(key); |
| | | signature.update(srcData.getBytes()); |
| | | return signature.verify(Base64.decode(sign.getBytes(),Base64.DEFAULT)); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.retrofit.utils; |
| | | |
| | | |
| | | import com.runt.open.mvvm.BuildConfig; |
| | | import com.runt.open.mvvm.retrofit.Interceptor.EncryptInterceptor; |
| | | import com.runt.open.mvvm.retrofit.Interceptor.HttpLoggingInterceptor; |
| | | import com.runt.open.mvvm.retrofit.api.CommonApiCenter; |
| | | import com.runt.open.mvvm.retrofit.converter.DecryptGsonConverterFactory; |
| | | import com.runt.open.mvvm.retrofit.net.NetWorkListenear; |
| | | |
| | | import java.util.Collections; |
| | | import java.util.concurrent.TimeUnit; |
| | | |
| | | import okhttp3.OkHttpClient; |
| | | import okhttp3.Protocol; |
| | | import retrofit2.Retrofit; |
| | | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; |
| | | |
| | | /** |
| | | * My father is Object, ites purpose of |
| | | * |
| | | * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-7-9. |
| | | */ |
| | | |
| | | public class RetrofitUtils { |
| | | public static String HOST_IP_ADDR; |
| | | static RetrofitUtils instance; |
| | | Retrofit retrofit/*log输出,驼峰转换*/,unHumpRetrofit/*log输出,不强制驼峰转换*/, |
| | | unLogRetrofit/*log不输出,驼峰转换*/,unLogHumpRetorfit/*log不输出,不强制驼峰转换*/; |
| | | CommonApiCenter commonApi;//常用接口 |
| | | |
| | | OkHttpClient.Builder builder = new OkHttpClient.Builder() |
| | | .addInterceptor(new EncryptInterceptor()); |
| | | OkHttpClient.Builder logBuilder = new OkHttpClient.Builder() |
| | | .addInterceptor(new HttpLoggingInterceptor());//log打印拦截器 |
| | | |
| | | public static RetrofitUtils getInstance() { |
| | | if(instance == null){ |
| | | instance = new RetrofitUtils(); |
| | | } |
| | | return instance; |
| | | } |
| | | |
| | | /** |
| | | * log输出,gson驼峰转换 |
| | | * @return |
| | | */ |
| | | public <T> T getRetrofit(Class<T> clas) { |
| | | if(retrofit == null){ |
| | | retrofit = getRetrofit(getOkHttpClient(logBuilder), |
| | | new Retrofit.Builder().addConverterFactory(DecryptGsonConverterFactory.create(true))) ; |
| | | } |
| | | if(!BuildConfig.DEBUG){//正式版 不打印log |
| | | return getUnLogRetrofit(clas); |
| | | } |
| | | return retrofit.create(clas); |
| | | } |
| | | |
| | | /** |
| | | * log输出,gson不转换驼峰 |
| | | * @return |
| | | */ |
| | | public <T> T getUnHumpRetrofit(Class<T> clas) { |
| | | if(unHumpRetrofit == null){ |
| | | unHumpRetrofit = getRetrofit(getOkHttpClient(logBuilder), |
| | | new Retrofit.Builder().addConverterFactory(DecryptGsonConverterFactory.create())) ; |
| | | } |
| | | if(!BuildConfig.DEBUG){//正式版 不打印log |
| | | return getUnLogHumpRetorfit(clas); |
| | | } |
| | | return unHumpRetrofit.create(clas); |
| | | } |
| | | |
| | | /** |
| | | * log不输出,gson驼峰转换 |
| | | * @return |
| | | */ |
| | | public <T> T getUnLogRetrofit(Class<T> clas) { |
| | | if(unLogRetrofit == null){ |
| | | unLogRetrofit = getRetrofit(getOkHttpClient(builder), |
| | | new Retrofit.Builder().addConverterFactory(DecryptGsonConverterFactory.create(true))) ; |
| | | } |
| | | return unLogRetrofit.create(clas); |
| | | } |
| | | |
| | | /** |
| | | * log不输出,gson不转换驼峰 |
| | | * @return |
| | | */ |
| | | public <T> T getUnLogHumpRetorfit(Class<T> clas) { |
| | | if(unLogHumpRetorfit == null){ |
| | | unLogHumpRetorfit = getRetrofit(getOkHttpClient(builder), |
| | | new Retrofit.Builder().addConverterFactory(DecryptGsonConverterFactory.create())) ; |
| | | } |
| | | return unLogHumpRetorfit.create(clas); |
| | | } |
| | | |
| | | private OkHttpClient getOkHttpClient(OkHttpClient.Builder builder){ |
| | | return builder.connectTimeout(10, TimeUnit.SECONDS)//设置连接超时时间 |
| | | .readTimeout(30, TimeUnit.SECONDS)//设置读取超时时间 |
| | | .protocols(Collections.singletonList(Protocol.HTTP_1_1)) |
| | | .eventListenerFactory(NetWorkListenear.get()) |
| | | .build(); |
| | | } |
| | | |
| | | private Retrofit getRetrofit(OkHttpClient client,Retrofit.Builder builder){ |
| | | return builder |
| | | //设置OKHttpClient |
| | | .client(client) |
| | | //设置baseUrl,注意,baseUrl必须后缀"/" |
| | | .baseUrl(BuildConfig.ENVIRONMENT.equals("develop")?HOST_IP_ADDR:BuildConfig.HOST_IP_ADDR) |
| | | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) |
| | | .build(); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 常用接口 |
| | | * @return |
| | | */ |
| | | public CommonApiCenter getCommonApi(){ |
| | | if(commonApi == null){ |
| | | commonApi = getRetrofit(CommonApiCenter.class); |
| | | } |
| | | return commonApi; |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.ui.dashboard; |
| | | |
| | | import android.os.Bundle; |
| | | import android.view.LayoutInflater; |
| | | import android.view.View; |
| | | import android.view.ViewGroup; |
| | | import android.widget.TextView; |
| | | import androidx.annotation.NonNull; |
| | | import androidx.annotation.Nullable; |
| | | import androidx.fragment.app.Fragment; |
| | | import androidx.lifecycle.Observer; |
| | | import androidx.lifecycle.ViewModelProvider; |
| | | import com.runt.open.mvvm.R; |
| | | import com.runt.open.mvvm.databinding.FragmentDashboardBinding; |
| | | |
| | | public class DashboardFragment extends Fragment { |
| | | |
| | | private DashboardViewModel dashboardViewModel; |
| | | private FragmentDashboardBinding binding; |
| | | |
| | | public View onCreateView(@NonNull LayoutInflater inflater, |
| | | ViewGroup container, Bundle savedInstanceState) { |
| | | dashboardViewModel = |
| | | new ViewModelProvider(this).get(DashboardViewModel.class); |
| | | |
| | | binding = FragmentDashboardBinding.inflate(inflater, container, false); |
| | | View root = binding.getRoot(); |
| | | |
| | | final TextView textView = binding.textDashboard; |
| | | dashboardViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() { |
| | | @Override |
| | | public void onChanged(@Nullable String s) { |
| | | textView.setText(s); |
| | | } |
| | | }); |
| | | return root; |
| | | } |
| | | |
| | | @Override |
| | | public void onDestroyView() { |
| | | super.onDestroyView(); |
| | | binding = null; |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.ui.dashboard; |
| | | |
| | | import androidx.lifecycle.LiveData; |
| | | import androidx.lifecycle.MutableLiveData; |
| | | import androidx.lifecycle.ViewModel; |
| | | |
| | | public class DashboardViewModel extends ViewModel { |
| | | |
| | | private MutableLiveData<String> mText; |
| | | |
| | | public DashboardViewModel() { |
| | | mText = new MutableLiveData<>(); |
| | | mText.setValue("This is dashboard fragment"); |
| | | } |
| | | |
| | | public LiveData<String> getText() { |
| | | return mText; |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.ui.home; |
| | | |
| | | import android.os.Bundle; |
| | | import android.view.LayoutInflater; |
| | | import android.view.View; |
| | | import android.view.ViewGroup; |
| | | import android.widget.TextView; |
| | | import androidx.annotation.NonNull; |
| | | import androidx.annotation.Nullable; |
| | | import androidx.fragment.app.Fragment; |
| | | import androidx.lifecycle.Observer; |
| | | import androidx.lifecycle.ViewModelProvider; |
| | | import com.runt.open.mvvm.R; |
| | | import com.runt.open.mvvm.databinding.FragmentHomeBinding; |
| | | |
| | | public class HomeFragment extends Fragment { |
| | | |
| | | private HomeViewModel homeViewModel; |
| | | private FragmentHomeBinding binding; |
| | | |
| | | public View onCreateView(@NonNull LayoutInflater inflater, |
| | | ViewGroup container, Bundle savedInstanceState) { |
| | | homeViewModel = |
| | | new ViewModelProvider(this).get(HomeViewModel.class); |
| | | |
| | | binding = FragmentHomeBinding.inflate(inflater, container, false); |
| | | View root = binding.getRoot(); |
| | | |
| | | final TextView textView = binding.textHome; |
| | | homeViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() { |
| | | @Override |
| | | public void onChanged(@Nullable String s) { |
| | | textView.setText(s); |
| | | } |
| | | }); |
| | | return root; |
| | | } |
| | | |
| | | @Override |
| | | public void onDestroyView() { |
| | | super.onDestroyView(); |
| | | binding = null; |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.ui.home; |
| | | |
| | | import androidx.lifecycle.LiveData; |
| | | import androidx.lifecycle.MutableLiveData; |
| | | import androidx.lifecycle.ViewModel; |
| | | |
| | | public class HomeViewModel extends ViewModel { |
| | | |
| | | private MutableLiveData<String> mText; |
| | | |
| | | public HomeViewModel() { |
| | | mText = new MutableLiveData<>(); |
| | | mText.setValue("This is home fragment"); |
| | | } |
| | | |
| | | public LiveData<String> getText() { |
| | | return mText; |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.ui.notifications; |
| | | |
| | | import android.os.Bundle; |
| | | import android.view.LayoutInflater; |
| | | import android.view.View; |
| | | import android.view.ViewGroup; |
| | | import android.widget.TextView; |
| | | import androidx.annotation.NonNull; |
| | | import androidx.annotation.Nullable; |
| | | import androidx.fragment.app.Fragment; |
| | | import androidx.lifecycle.Observer; |
| | | import androidx.lifecycle.ViewModelProvider; |
| | | import com.runt.open.mvvm.R; |
| | | import com.runt.open.mvvm.databinding.FragmentNotificationsBinding; |
| | | |
| | | public class NotificationsFragment extends Fragment { |
| | | |
| | | private NotificationsViewModel notificationsViewModel; |
| | | private FragmentNotificationsBinding binding; |
| | | |
| | | public View onCreateView(@NonNull LayoutInflater inflater, |
| | | ViewGroup container, Bundle savedInstanceState) { |
| | | notificationsViewModel = |
| | | new ViewModelProvider(this).get(NotificationsViewModel.class); |
| | | |
| | | binding = FragmentNotificationsBinding.inflate(inflater, container, false); |
| | | View root = binding.getRoot(); |
| | | |
| | | final TextView textView = binding.textNotifications; |
| | | notificationsViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() { |
| | | @Override |
| | | public void onChanged(@Nullable String s) { |
| | | textView.setText(s); |
| | | } |
| | | }); |
| | | return root; |
| | | } |
| | | |
| | | @Override |
| | | public void onDestroyView() { |
| | | super.onDestroyView(); |
| | | binding = null; |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.ui.notifications; |
| | | |
| | | import androidx.lifecycle.LiveData; |
| | | import androidx.lifecycle.MutableLiveData; |
| | | import androidx.lifecycle.ViewModel; |
| | | |
| | | public class NotificationsViewModel extends ViewModel { |
| | | |
| | | private MutableLiveData<String> mText; |
| | | |
| | | public NotificationsViewModel() { |
| | | mText = new MutableLiveData<>(); |
| | | mText.setValue("This is notifications fragment"); |
| | | } |
| | | |
| | | public LiveData<String> getText() { |
| | | return mText; |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.util; |
| | | |
| | | import android.Manifest; |
| | | import android.annotation.SuppressLint; |
| | | import android.app.Activity; |
| | | import android.content.Context; |
| | | import android.content.pm.PackageManager; |
| | | import android.graphics.Point; |
| | | import android.os.Build; |
| | | import android.provider.Settings; |
| | | import android.telephony.TelephonyManager; |
| | | import android.text.TextUtils; |
| | | import android.util.DisplayMetrics; |
| | | import android.util.Log; |
| | | import android.view.Display; |
| | | import android.view.WindowManager; |
| | | |
| | | import androidx.core.app.ActivityCompat; |
| | | |
| | | import java.lang.reflect.Field; |
| | | import java.lang.reflect.Method; |
| | | import java.math.BigDecimal; |
| | | |
| | | |
| | | /** |
| | | * Created by EDZ on 2018/1/30. |
| | | */ |
| | | public class DeviceUtil { |
| | | public static final String TAG = "DeviceUtil"; |
| | | |
| | | /** |
| | | * 设备信息 |
| | | * |
| | | * @param context |
| | | */ |
| | | public static void getDisplay(Context context) { |
| | | StringBuilder sb = new StringBuilder(); |
| | | sb.append("Version code is \n"); |
| | | sb.append("设备的Android版本号:");//设备的Android版本号 |
| | | sb.append(getSDKInt() + " " + getSDKVersion() + "\t");//设备的Android版本号 |
| | | sb.append("设备型号:");//设备型号 |
| | | sb.append(getDeviceModel() + "\t");//设备型号 |
| | | sb.append("设备厂商:");//设备型号 |
| | | sb.append(getDeviceBrand() + "\t");//设备型号 |
| | | sb.append("程序版本号:" + getAppVersionCode(context) + " " + getAppVersionName(context) + "\t");//程序版本号 |
| | | sb.append("设备唯一标识符:" + getSerialNumber(context)); |
| | | sb.append("\n设备imei:" + getIMEI(context)); |
| | | String str = sb.toString() + " \n"; |
| | | str += getDisplayInfomation(context) + " \n"; |
| | | str += getDensity(context) + " \n"; |
| | | str += "屏幕大小:" + getScreenInch(context) + "英寸 \n"; |
| | | str += getAndroiodScreenProperty(context) + "\n"; |
| | | Log.i(TAG, str); |
| | | } |
| | | |
| | | /** |
| | | * Double类型保留指定位数的小数,返回double类型(四舍五入) |
| | | * newScale 为指定的位数 |
| | | */ |
| | | private static double formatDouble(double d, int newScale) { |
| | | BigDecimal bd = new BigDecimal(d); |
| | | return bd.setScale(newScale, BigDecimal.ROUND_HALF_UP).doubleValue(); |
| | | } |
| | | |
| | | /** |
| | | * 设备型号 |
| | | * @return |
| | | */ |
| | | public static String getDeviceModel() { |
| | | return Build.MODEL; |
| | | } |
| | | |
| | | /** |
| | | * 设备厂商 |
| | | * @return |
| | | */ |
| | | public static String getDeviceBrand() { |
| | | return Build.BRAND; |
| | | } |
| | | |
| | | /** |
| | | * 设备的Android版本号 |
| | | * @return |
| | | */ |
| | | public static int getSDKInt() { |
| | | return Build.VERSION.SDK_INT; |
| | | } |
| | | |
| | | /** |
| | | * 设备的Android版本号 |
| | | * @return |
| | | */ |
| | | public static String getSDKVersion() { |
| | | return Build.VERSION.RELEASE; |
| | | } |
| | | |
| | | /** |
| | | * 程序版本号 |
| | | * @param context |
| | | * @return |
| | | */ |
| | | public static int getAppVersionCode(Context context) { |
| | | try { |
| | | return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode; |
| | | } catch (PackageManager.NameNotFoundException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | return 1; |
| | | } |
| | | |
| | | /** |
| | | * 程序版本号 |
| | | * |
| | | * @param context |
| | | * @return |
| | | */ |
| | | public static String getAppVersionName(Context context) { |
| | | try { |
| | | return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName; |
| | | } catch (PackageManager.NameNotFoundException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | return ""; |
| | | } |
| | | |
| | | /** |
| | | * 屏幕像素px |
| | | */ |
| | | @SuppressLint("NewApi") |
| | | public static String getDisplayInfomation(Context context) { |
| | | Point point = new Point(); |
| | | ((Activity) context).getWindowManager().getDefaultDisplay().getSize(point); |
| | | Log.d(TAG, "the screen size is " + point.toString()); |
| | | ((Activity) context).getWindowManager().getDefaultDisplay().getRealSize(point); |
| | | Log.d(TAG, "the screen real size is " + point.toString()); |
| | | return point.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 屏幕信息 |
| | | * @param context |
| | | * @return |
| | | */ |
| | | public static String getAndroiodScreenProperty(Context context) { |
| | | int width = getScreenPixel(context).widthPixels; // 屏幕宽度(像素) |
| | | int height = getScreenPixel(context).heightPixels; // 屏幕高度(像素) |
| | | float density = getDensity(getScreenPixel(context)); // 屏幕密度(0.75 / 1.0 / 1.5) |
| | | int densityDpi = getDpi(context); // 屏幕密度dpi(120 / 160 / 240) |
| | | // 屏幕宽度算法:屏幕宽度(像素)/屏幕密度 |
| | | int screenWidth = getDp(context)[0]; // 屏幕宽度(dp) |
| | | int screenHeight = getDp(context)[1];// 屏幕高度(dp) |
| | | |
| | | |
| | | Log.d("h_bl", "屏幕宽度(像素):" + width); |
| | | Log.d("h_bl", "屏幕高度(像素):" + height); |
| | | Log.d("h_bl", "屏幕密度(0.75 / 1.0 / 1.5):" + density); |
| | | Log.d("h_bl", "屏幕密度dpi(120 / 160 / 240):" + densityDpi); |
| | | Log.d("h_bl", "屏幕宽度(dp):" + screenWidth); // 屏幕适配文件夹(例:layout-sw300dp),是以该属性为准则 |
| | | Log.d("h_bl", "屏幕高度(dp):" + screenHeight); |
| | | |
| | | StringBuilder sb = new StringBuilder(); |
| | | sb.append("屏幕宽度(像素):" + width + "\n"); |
| | | sb.append("屏幕高度(像素):" + height + "\n"); |
| | | sb.append("屏幕密度(0.75 / 1.0 / 1.5):" + density + "\n"); |
| | | sb.append("屏幕密度dpi(120 / 160 / 240):" + densityDpi + "\n"); |
| | | sb.append("屏幕宽度(dp):" + screenWidth + "\n"); |
| | | sb.append("屏幕高度(dp):" + screenHeight + "\n"); |
| | | return sb.toString(); |
| | | |
| | | } |
| | | |
| | | /** |
| | | * 通知栏高度 |
| | | * @param context |
| | | * @return |
| | | */ |
| | | public static int getStatusBarHeight(Context context) { |
| | | Class<?> c = null; |
| | | Object obj = null; |
| | | Field field = null; |
| | | int x = 0, statusBarHeight = 0; |
| | | try { |
| | | c = Class.forName("com.android.internal.R$dimen"); |
| | | obj = c.newInstance(); |
| | | field = c.getField("status_bar_height"); |
| | | x = Integer.parseInt(field.get(obj).toString()); |
| | | statusBarHeight = context.getResources().getDimensionPixelSize(x); |
| | | } catch (Exception e1) { |
| | | e1.printStackTrace(); |
| | | } |
| | | return statusBarHeight; |
| | | } |
| | | |
| | | /** |
| | | * 屏幕密度dpi |
| | | */ |
| | | public static int getDpi(Context context) { |
| | | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); |
| | | DisplayMetrics dm = new DisplayMetrics(); |
| | | wm.getDefaultDisplay().getMetrics(dm); |
| | | return dm.densityDpi; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 屏幕像素px |
| | | */ |
| | | @SuppressLint("NewApi") |
| | | public static Point getDisplayPixel(Context context) { |
| | | Point point = new Point(); |
| | | ((Activity) context).getWindowManager().getDefaultDisplay().getSize(point); |
| | | Log.d(TAG, "the screen size is " + point.toString()); |
| | | ((Activity) context).getWindowManager().getDefaultDisplay().getRealSize(point); |
| | | Log.d(TAG, "the screen real size is " + point.toString()); |
| | | return point; |
| | | } |
| | | |
| | | /** |
| | | * 屏幕像素px |
| | | */ |
| | | public static DisplayMetrics getScreenPixel(Context context) { |
| | | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); |
| | | DisplayMetrics dm = new DisplayMetrics(); |
| | | wm.getDefaultDisplay().getMetrics(dm); |
| | | return dm; |
| | | } |
| | | |
| | | /** |
| | | * 屏幕dp |
| | | */ |
| | | public static int[] getDp(Context context) { |
| | | int width = getScreenPixel(context).widthPixels; // 屏幕宽度(像素) |
| | | int height = getScreenPixel(context).heightPixels; // 屏幕高度(像素) |
| | | float density = getDensity(getScreenPixel(context)); // 屏幕密度(0.75 / 1.0 / 1.5) |
| | | // 屏幕宽度算法:屏幕宽度(像素)/屏幕密度 |
| | | int screenWidth = (int) (width / density); // 屏幕宽度(dp) |
| | | int screenHeight = (int) (height / density);// 屏幕高度(dp) |
| | | return new int[]{screenWidth, screenHeight}; |
| | | } |
| | | |
| | | /** |
| | | * 屏幕密度 |
| | | */ |
| | | public static float getDensity(DisplayMetrics dm) { |
| | | return dm.density; // 屏幕密度(0.75 / 1.0 / 1.5); |
| | | } |
| | | |
| | | /** |
| | | * 屏幕密度dpi |
| | | */ |
| | | public static String getDensity(Context context) { |
| | | DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); |
| | | Log.d(TAG, "Density is " + displayMetrics.density + " densityDpi is " + displayMetrics.densityDpi + " height: " + displayMetrics.heightPixels + |
| | | " width: " + displayMetrics.widthPixels); |
| | | return "Density is " + displayMetrics.density + " densityDpi is " + displayMetrics.densityDpi + " height: " + displayMetrics.heightPixels + |
| | | " width: " + displayMetrics.widthPixels; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 屏幕尺寸inch |
| | | */ |
| | | public static double getScreenInch(Context context) { |
| | | |
| | | try { |
| | | int realWidth = 0, realHeight = 0; |
| | | Display display = ((Activity) context).getWindowManager().getDefaultDisplay(); |
| | | DisplayMetrics metrics = new DisplayMetrics(); |
| | | display.getMetrics(metrics); |
| | | if (Build.VERSION.SDK_INT >= 17) { |
| | | Point size = new Point(); |
| | | display.getRealSize(size); |
| | | realWidth = size.x; |
| | | realHeight = size.y; |
| | | } else if (Build.VERSION.SDK_INT < 17 |
| | | && Build.VERSION.SDK_INT >= 14) { |
| | | Method mGetRawH = Display.class.getMethod("getRawHeight"); |
| | | Method mGetRawW = Display.class.getMethod("getRawWidth"); |
| | | realWidth = (Integer) mGetRawW.invoke(display); |
| | | realHeight = (Integer) mGetRawH.invoke(display); |
| | | } else { |
| | | realWidth = metrics.widthPixels; |
| | | realHeight = metrics.heightPixels; |
| | | } |
| | | |
| | | return formatDouble(Math.sqrt((realWidth / metrics.xdpi) * (realWidth / metrics.xdpi) + (realHeight / metrics.ydpi) * (realHeight / metrics.ydpi)), 1); |
| | | |
| | | |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * dp获取dip |
| | | * @param dp |
| | | * @return |
| | | */ |
| | | public static int convertDpToPixel(float dp, Context context) { |
| | | DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); |
| | | return (int) (dp * displayMetrics.density); |
| | | } |
| | | |
| | | /*** |
| | | * px获取dip |
| | | * @param pixel |
| | | * @return |
| | | */ |
| | | public static int convertPixelToDp(int pixel, Context context) { |
| | | DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); |
| | | return (int) (pixel / displayMetrics.density); |
| | | } |
| | | |
| | | public static String getSerialNumber(Context context) { |
| | | String serial = ""; |
| | | try { |
| | | if (Build.VERSION.SDK_INT >= 28) {//9.0+ |
| | | if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { |
| | | Log.i(TAG, "getMEID meid: READ_PHONE_STATE" ); |
| | | ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.READ_PHONE_STATE}, 1567); |
| | | } else { |
| | | serial = Build.getSerial(); |
| | | } |
| | | } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {//8.0+ |
| | | serial = Build.SERIAL; |
| | | } else {//8.0- |
| | | Class<?> c = Class.forName("android.os.SystemProperties"); |
| | | Method get = c.getMethod("get", String.class); |
| | | serial = (String) get.invoke(c, "ro.serialno"); |
| | | } |
| | | } catch (Exception e) { |
| | | Log.e("e", "读取设备序列号异常:" + e.toString()); |
| | | } |
| | | return serial; |
| | | } |
| | | |
| | | public static String getIMEI(Context context) { |
| | | String deviceId = ""; |
| | | TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); |
| | | if (null != tm) { |
| | | if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { |
| | | Log.i(TAG, "getMEID meid: READ_PHONE_STATE" ); |
| | | ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.READ_PHONE_STATE}, 1567); |
| | | } else { |
| | | if (tm.getDeviceId() != null) { |
| | | deviceId = tm.getDeviceId(); |
| | | } else { |
| | | deviceId = Settings.Secure.getString(context.getApplicationContext().getContentResolver(), Settings.Secure.ANDROID_ID); |
| | | } |
| | | } |
| | | Log.d("deviceId--->", deviceId); |
| | | } |
| | | return ""; |
| | | } |
| | | |
| | | public static String getMEID() { |
| | | try { |
| | | Class clazz = Class.forName("android.os.SystemProperties"); |
| | | Method method = clazz.getMethod("get", String.class, String.class); |
| | | |
| | | String meid = (String) method.invoke(null, "ril.cdma.meid", ""); |
| | | if (!TextUtils.isEmpty(meid)) { |
| | | Log.d(TAG, "getMEID meid: " + meid); |
| | | return meid; |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | Log.w(TAG, "getMEID error : " + e.getMessage()); |
| | | } |
| | | return ""; |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.util; |
| | | |
| | | import android.content.Context; |
| | | import android.util.DisplayMetrics; |
| | | |
| | | /** |
| | | * Created by Administrator on 2021/11/2 0002. |
| | | */ |
| | | public class DimensionUtils { |
| | | /** |
| | | * dp获取dip |
| | | * @param dp |
| | | * @return |
| | | */ |
| | | public int convertDpToPixel(float dp,Context context) { |
| | | DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); |
| | | return (int) (dp * displayMetrics.density); |
| | | } |
| | | |
| | | /*** |
| | | * px获取dip |
| | | * @param pixel |
| | | * @return |
| | | */ |
| | | public int convertPixelToDp(int pixel,Context context) { |
| | | DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); |
| | | return (int) (pixel / displayMetrics.density); |
| | | } |
| | | /** |
| | | * 把pix值转换为sp |
| | | * |
| | | * @return |
| | | */ |
| | | public static float convertPixelToSp( float pixValue,Context context) { |
| | | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; |
| | | return pixValue / fontScale + 0.5f; |
| | | } |
| | | |
| | | /** |
| | | * 将sp值转换为px值,保证文字大小不变 |
| | | * |
| | | * @param spValue |
| | | * @param context |
| | | * (DisplayMetrics类中属性scaledDensity) |
| | | * @return |
| | | */ |
| | | public static int convertSpToPixel(Context context, float spValue) { |
| | | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; |
| | | return (int) (spValue * fontScale + 0.5f); |
| | | } |
| | | } |
New file |
| | |
| | | /* |
| | | * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. |
| | | */ |
| | | package com.runt.open.mvvm.util; |
| | | |
| | | import com.google.gson.Gson; |
| | | import com.google.gson.GsonBuilder; |
| | | import com.google.gson.JsonParseException; |
| | | |
| | | import org.json.JSONArray; |
| | | import org.json.JSONException; |
| | | import org.json.JSONObject; |
| | | |
| | | import java.lang.reflect.Type; |
| | | import java.util.ArrayList; |
| | | import java.util.Collection; |
| | | import java.util.Iterator; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.TreeMap; |
| | | |
| | | /** |
| | | * Json工具类. |
| | | */ |
| | | public class GsonUtils { |
| | | private static Gson gson = new GsonBuilder().create(); |
| | | |
| | | public static String toJson(Object value) { |
| | | return gson.toJson(value); |
| | | } |
| | | |
| | | public static <T> T fromJson(String json, Class<T> classOfT) throws JsonParseException { |
| | | return gson.fromJson(json, classOfT); |
| | | } |
| | | |
| | | public static <T> T fromJson(String json, Type typeOfT) throws JsonParseException { |
| | | return (T) gson.fromJson(json, typeOfT); |
| | | } |
| | | |
| | | /** |
| | | * 将对象转换为驼峰命名的json |
| | | * @param value |
| | | * @return |
| | | */ |
| | | public static String toHumpJson(Object value) { |
| | | try { |
| | | if(value instanceof Collection){ |
| | | return convertToHumpJsonArray(new JSONArray(gson.toJson(value)) ).toString(); |
| | | }else { |
| | | return convertToHumpJsonObj(new JSONObject(gson.toJson(value)) ).toString(); |
| | | } |
| | | } catch (JSONException e) { |
| | | e.printStackTrace(); |
| | | return gson.toJson(value); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * |
| | | * 将json转换为驼峰命名的json |
| | | * @param json |
| | | * @return |
| | | */ |
| | | public static String toHumpJson(String json) throws JSONException { |
| | | if(json.indexOf("[") == 0){ |
| | | return convertToHumpJsonArray(new JSONArray(json) ).toString(); |
| | | }else { |
| | | return convertToHumpJsonObj(new JSONObject(json) ).toString(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 驼峰命名转换 |
| | | * @param json |
| | | * @param classOfT |
| | | * @param <T> |
| | | * @return |
| | | * @throws JsonParseException |
| | | */ |
| | | public static <T> T fromJsonToHump(String json, Class<T> classOfT) throws JsonParseException, JSONException { |
| | | return gson.fromJson(toHumpJson(json), classOfT); |
| | | } |
| | | |
| | | /** |
| | | * 驼峰命名转换 |
| | | * @param json |
| | | * @param typeOfT |
| | | * @param <T> |
| | | * @return |
| | | * @throws JsonParseException |
| | | */ |
| | | public static <T> T fromJsonToHump(String json, Type typeOfT) throws JsonParseException, JSONException { |
| | | return (T) gson.fromJson(toHumpJson(json), typeOfT); |
| | | } |
| | | |
| | | /** |
| | | * 转换驼峰命名 |
| | | * @param jsonObject |
| | | * @return |
| | | */ |
| | | public static JSONObject convertToHumpJsonObj(JSONObject jsonObject){ |
| | | JSONObject temp = new JSONObject(); |
| | | Iterator<String> it = jsonObject.keys(); |
| | | while ( it.hasNext()){ |
| | | String key = it.next(); |
| | | String humpKey = humpName(key); |
| | | try { |
| | | if(jsonObject.get(key) instanceof JSONObject){ |
| | | temp.put(humpKey,convertToHumpJsonObj(jsonObject.getJSONObject(key))); |
| | | }else if(jsonObject.get(key) instanceof JSONArray){ |
| | | temp.put(humpKey,convertToHumpJsonArray(jsonObject.getJSONArray(key))); |
| | | }else { |
| | | temp.put(humpKey,jsonObject.get(key)); |
| | | } |
| | | } catch (JSONException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | return temp; |
| | | |
| | | } |
| | | |
| | | public static JSONArray convertToHumpJsonArray(JSONArray array){ |
| | | JSONArray jsons = new JSONArray(); |
| | | for(int i = 0 ; i < array.length() ; i ++){ |
| | | try { |
| | | if(array.get(i) instanceof JSONObject){ |
| | | jsons.put(convertToHumpJsonObj(array.getJSONObject(i))); |
| | | }else if(array.get(i) instanceof JSONArray){ |
| | | jsons.put(convertToHumpJsonArray(array.getJSONArray(i))); |
| | | }else { |
| | | jsons.put(array.get(i)); |
| | | } |
| | | } catch (JSONException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | return jsons; |
| | | |
| | | } |
| | | /** |
| | | * 将key转换为驼峰 |
| | | * @param param |
| | | * @return |
| | | */ |
| | | public static Map convertToHumpMap(Map<String, Object> param){ |
| | | Map temp = new TreeMap(); |
| | | for(String key: param.keySet()){ |
| | | String humpKey = humpName(key); |
| | | if(param.get(key) instanceof Map){ |
| | | temp.put(humpKey,convertToHumpMap((Map<String, Object>) param.get(key))); |
| | | }else if(param.get(key) instanceof List){ |
| | | temp.put(humpKey,convertToHumpList((List)param.get(key))); |
| | | }else { |
| | | temp.put(humpKey,param.get(key)); |
| | | } |
| | | } |
| | | return temp; |
| | | } |
| | | |
| | | |
| | | public static List convertToHumpList(List list){ |
| | | List ars = new ArrayList(); |
| | | for(Object object : list){ |
| | | if(object instanceof Map){ |
| | | ars.add(convertToHumpMap((Map)object)); |
| | | }else if(object instanceof List){ |
| | | ars.add(convertToHumpList((List)object)); |
| | | }else { |
| | | ars.add(object); |
| | | } |
| | | } |
| | | return ars; |
| | | } |
| | | /** |
| | | * 驼峰命名 |
| | | * @param name |
| | | * @return |
| | | */ |
| | | public static String humpName(String name){ |
| | | String[] strings = name.split("_"); |
| | | StringBuilder sb = new StringBuilder(); |
| | | sb.append(strings[0]); |
| | | for(int i = 1 ; i < strings.length ; i ++){ |
| | | sb.append(toUperFirst(strings[i])); |
| | | } |
| | | if(sb.toString().equals("new")){//关键字 转成大写 |
| | | return "NEW"; |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 首字母大写 |
| | | * @param name |
| | | * @return |
| | | */ |
| | | public static String toUperFirst(String name){ |
| | | return name.substring(0,1).toUpperCase()+name.substring(1); |
| | | } |
| | | |
| | | /** |
| | | * json字符串缩进 |
| | | * @param json |
| | | * @return |
| | | */ |
| | | public static String retractJson(String json){ |
| | | int level = 0 ; |
| | | StringBuffer jsonForMatStr = new StringBuffer(); |
| | | for(int index=0;index<json.length();index++)//将字符串中的字符逐个按行输出 |
| | | { |
| | | //获取s中的每个字符 |
| | | char c = json.charAt(index); |
| | | // System.out.println(s.charAt(index)); |
| | | |
| | | //level大于0并且jsonForMatStr中的最后一个字符为\n,jsonForMatStr加入\t |
| | | if (level > 0 && '\n' == jsonForMatStr.charAt(jsonForMatStr.length() - 1)) { |
| | | jsonForMatStr.append(getLevelStr(level)); |
| | | // System.out.println("123"+jsonForMatStr); |
| | | } |
| | | //遇到"{"和"["要增加空格和换行,遇到"}"和"]"要减少空格,以对应,遇到","要换行 |
| | | switch (c) { |
| | | case '{': |
| | | case '[': |
| | | jsonForMatStr.append(c + "\n"); |
| | | level++; |
| | | break; |
| | | case ',': |
| | | if(index>0 && index < json.length()-2 && (json.charAt(index-1) != '\n') && json.charAt(index+1) == '"'){ |
| | | jsonForMatStr.append(c + "\n"); |
| | | }else{ |
| | | jsonForMatStr.append(c); |
| | | } |
| | | break; |
| | | case '}': |
| | | case ']': |
| | | jsonForMatStr.append("\n"); |
| | | level--; |
| | | jsonForMatStr.append(getLevelStr(level)); |
| | | jsonForMatStr.append(c); |
| | | break; |
| | | default: |
| | | jsonForMatStr.append(c); |
| | | break; |
| | | } |
| | | } |
| | | return jsonForMatStr.toString(); |
| | | } |
| | | private static String getLevelStr(int level) { |
| | | StringBuffer levelStr = new StringBuffer(); |
| | | for (int levelI = 0; levelI < level; levelI++) { |
| | | levelStr.append("\t");//\t或空格 |
| | | } |
| | | return levelStr.toString(); |
| | | } |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.util; |
| | | |
| | | import android.util.Log; |
| | | |
| | | import com.runt.open.mvvm.BuildConfig; |
| | | |
| | | |
| | | /** |
| | | * My father is Object, ites purpose of |
| | | * |
| | | * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-4-5. |
| | | */ |
| | | public class MyLog { |
| | | |
| | | |
| | | public static void i(String tag,String msg){ |
| | | if(BuildConfig.DEBUG) { |
| | | Log.i(tag, BuildConfig.BUILD_TYPE +" "+ msg); |
| | | } |
| | | } |
| | | |
| | | public static void e(String tag,String msg){ |
| | | if(BuildConfig.DEBUG) { |
| | | Log.e(tag, msg); |
| | | } |
| | | } |
| | | |
| | | |
| | | public static void d(String tag,String msg){ |
| | | if(BuildConfig.DEBUG) { |
| | | Log.d(tag, msg); |
| | | } |
| | | } |
| | | |
| | | |
| | | public static void v(String tag,String msg){ |
| | | if(BuildConfig.DEBUG) { |
| | | Log.v(tag, msg); |
| | | } |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.util; |
| | | |
| | | import android.annotation.SuppressLint; |
| | | import android.content.Context; |
| | | import android.net.ConnectivityManager; |
| | | import android.net.NetworkInfo; |
| | | import android.net.wifi.WifiInfo; |
| | | import android.net.wifi.WifiManager; |
| | | import android.util.Log; |
| | | |
| | | import org.json.JSONException; |
| | | import org.json.JSONObject; |
| | | |
| | | import java.io.BufferedReader; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.io.InputStreamReader; |
| | | import java.net.HttpURLConnection; |
| | | import java.net.Inet6Address; |
| | | import java.net.InetAddress; |
| | | import java.net.MalformedURLException; |
| | | import java.net.NetworkInterface; |
| | | import java.net.SocketException; |
| | | import java.net.URL; |
| | | import java.net.URLConnection; |
| | | import java.util.Enumeration; |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * My father is Object, ites purpose of |
| | | * |
| | | * @purpose Created by Runt (qingingrunt2010@qq.com) on 2019-3-19. |
| | | */ |
| | | |
| | | public class NetWorkUtils { |
| | | |
| | | |
| | | /** |
| | | * 获取外网IP地址 |
| | | * @return |
| | | */ |
| | | public static String getNetIp() { |
| | | final Map<String,String> param = new HashMap<>(); |
| | | Thread thread = new Thread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | String line = ""; |
| | | URL infoUrl = null; |
| | | InputStream inStream = null; |
| | | try { |
| | | infoUrl = new URL("http://pv.sohu.com/cityjson?ie=utf-8"); |
| | | URLConnection connection = infoUrl.openConnection(); |
| | | HttpURLConnection httpConnection = (HttpURLConnection) connection; |
| | | int responseCode = httpConnection.getResponseCode(); |
| | | if (responseCode == HttpURLConnection.HTTP_OK) { |
| | | inStream = httpConnection.getInputStream(); |
| | | BufferedReader reader = new BufferedReader(new InputStreamReader(inStream, "utf-8")); |
| | | StringBuilder strber = new StringBuilder(); |
| | | while ((line = reader.readLine()) != null) |
| | | strber.append(line + "\n"); |
| | | inStream.close(); |
| | | // 从反馈的结果中提取出IP地址 |
| | | int start = strber.indexOf("{"); |
| | | int end = strber.indexOf("}"); |
| | | String json = strber.substring(start, end + 1); |
| | | if (json != null) { |
| | | try { |
| | | JSONObject jsonObject = new JSONObject(json); |
| | | line = jsonObject.optString("cip"); |
| | | } catch (JSONException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | param.put("ip",line); |
| | | } |
| | | } catch (MalformedURLException e) { |
| | | e.printStackTrace(); |
| | | } catch (IOException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | }); |
| | | try { |
| | | thread.start(); |
| | | thread.join(); |
| | | } catch (InterruptedException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | return param.get("ip"); |
| | | } |
| | | |
| | | /*** |
| | | * 获取局域网ip |
| | | * @param context |
| | | * @return |
| | | */ |
| | | @SuppressLint("MissingPermission") |
| | | public static String getLocalIpAddress(Context context) { |
| | | |
| | | ConnectivityManager connect = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); |
| | | //检查网络连接 |
| | | NetworkInfo info = connect.getActiveNetworkInfo(); |
| | | |
| | | if (info == null) { |
| | | return ""; |
| | | } |
| | | int netType = info.getType(); |
| | | int netSubtype = info.getSubtype(); |
| | | |
| | | if (netType == ConnectivityManager.TYPE_WIFI) { //WIFI |
| | | return getWifiIpAddress(context); |
| | | } else /*if (netType == ConnectivityManager.TYPE_MOBILE) { //MOBILE |
| | | return getGPRSIpAddress(); |
| | | } else */if (netType == ConnectivityManager.TYPE_ETHERNET) { //MOBILE |
| | | return getEthernetIp(); |
| | | } else { |
| | | return ""; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 使用wifi |
| | | * @param context |
| | | * @return |
| | | */ |
| | | @SuppressLint("MissingPermission") |
| | | public static String getWifiIpAddress(Context context) { |
| | | |
| | | //获取wifi服务 |
| | | WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); |
| | | //判断wifi是否开启 |
| | | // if (!wifiManager.isWifiEnabled()) { |
| | | // <span style="white-space:pre"> </span> wifiManager.setWifiEnabled(true); |
| | | // } |
| | | WifiInfo wifiInfo = wifiManager.getConnectionInfo(); |
| | | int ipAddress = wifiInfo.getIpAddress(); |
| | | String ip = intToIp(ipAddress); |
| | | return ip; |
| | | } |
| | | |
| | | private static String intToIp(int i) { |
| | | return (i & 0xFF) + "." + |
| | | ((i >> 8) & 0xFF) + "." + |
| | | ((i >> 16) & 0xFF) + "." + |
| | | (i >> 24 & 0xFF); |
| | | } |
| | | /** |
| | | * 使用GPRS |
| | | * @return |
| | | */ |
| | | public static String getGPRSIpAddress() { |
| | | try { |
| | | for (Enumeration<NetworkInterface> en = NetworkInterface |
| | | .getNetworkInterfaces(); en.hasMoreElements(); ) { |
| | | NetworkInterface intf = en.nextElement(); |
| | | for (Enumeration<InetAddress> enumIpAddr = intf |
| | | .getInetAddresses(); enumIpAddr.hasMoreElements(); ) { |
| | | InetAddress inetAddress = enumIpAddr.nextElement(); |
| | | if (!inetAddress.isLoopbackAddress()) { |
| | | return inetAddress.getHostAddress().toString(); |
| | | } |
| | | } |
| | | } |
| | | } catch (SocketException ex) { |
| | | MyLog.e("Exception", ex.toString()); |
| | | } |
| | | return "127.0.0.1"; |
| | | } |
| | | |
| | | public static String getEthernetIp() { |
| | | String hostIp = null; |
| | | try { |
| | | Enumeration nis = NetworkInterface.getNetworkInterfaces(); |
| | | InetAddress ia = null; |
| | | while (nis.hasMoreElements()) { |
| | | NetworkInterface ni = (NetworkInterface) nis.nextElement(); |
| | | Enumeration<InetAddress> ias = ni.getInetAddresses(); |
| | | while (ias.hasMoreElements()) { |
| | | ia = ias.nextElement(); |
| | | if (ia instanceof Inet6Address) { |
| | | continue;// skip ipv6 |
| | | } |
| | | String ip = ia.getHostAddress(); |
| | | if (!"127.0.0.1".equals(ip)) { |
| | | hostIp = ia.getHostAddress(); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | } catch (SocketException e) { |
| | | Log.i("yao", "SocketException"); |
| | | e.printStackTrace(); |
| | | } |
| | | return hostIp; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 将得到的int类型的IP转换为String类型 |
| | | * |
| | | * @param ip |
| | | * @return |
| | | */ |
| | | public static String intIP2StringIP(int ip) { |
| | | return (ip & 0xFF) + "." + |
| | | ((ip >> 8) & 0xFF) + "." + |
| | | ((ip >> 16) & 0xFF) + "." + |
| | | (ip >> 24 & 0xFF); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.util; |
| | | |
| | | import android.content.Context; |
| | | import android.content.SharedPreferences; |
| | | |
| | | import androidx.collection.ArraySet; |
| | | |
| | | import java.util.Set; |
| | | |
| | | /** |
| | | * Created by Administrator on 2021/10/28 0028. |
| | | */ |
| | | public class SpUtils { |
| | | |
| | | static SpUtils instance; |
| | | |
| | | /** |
| | | * 获取SP实例 |
| | | * |
| | | * @return {@link SpUtils} |
| | | */ |
| | | public static SpUtils getInstance() { |
| | | if(instance == null){ |
| | | instance = new SpUtils(); |
| | | } |
| | | return instance; |
| | | } |
| | | |
| | | |
| | | public final String PROJECT = "project"; |
| | | public final String USER = "user"; |
| | | |
| | | |
| | | public boolean clearData(Context context, String keyShared){ |
| | | SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); |
| | | return settings.edit().clear().commit(); |
| | | } |
| | | |
| | | |
| | | public boolean clearData(Context context, String key, String keyShared){ |
| | | putString(context,key,null,keyShared); |
| | | return true; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * remove key preferences |
| | | * |
| | | * @param context |
| | | * @param key The name of the preference to modify |
| | | * @return True if the new values were successfully written to persistent storage. |
| | | */ |
| | | public void removeKey(Context context, String key, String keyShared){ |
| | | |
| | | SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); |
| | | SharedPreferences.Editor editor = settings.edit(); |
| | | editor.remove(key); |
| | | } |
| | | |
| | | /** |
| | | * remove value preferences |
| | | * |
| | | * @param context |
| | | * @param value The name of the preference to modify |
| | | * @return True if the new values were successfully written to persistent storage. |
| | | */ |
| | | public void removeValue(Context context, String value, String keyShared){ |
| | | SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); |
| | | SharedPreferences.Editor editor = settings.edit(); |
| | | editor.remove(value); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * put string preferences |
| | | * |
| | | * @param context |
| | | * @param key The name of the preference to modify |
| | | * @param value The new value for the preference |
| | | * @return True if the new values were successfully written to persistent storage. |
| | | */ |
| | | public boolean putString(Context context, String key, String value, String keyShared) { |
| | | SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); |
| | | SharedPreferences.Editor editor = settings.edit(); |
| | | editor.putString(key, value); |
| | | return editor.commit(); |
| | | } |
| | | |
| | | /** |
| | | * get string preferences |
| | | * |
| | | * @param context |
| | | * @param key The name of the preference to retrieve |
| | | * @return The preference value if it exists, or null. Throws ClassCastException if there is a preference with this |
| | | * name that is not a string |
| | | * @see #getString(Context, String, String) |
| | | */ |
| | | public String getString(Context context, String key, String keyShared) { |
| | | return getString(context, key, null,keyShared); |
| | | } |
| | | |
| | | /** |
| | | * get string preferences |
| | | * |
| | | * @param context |
| | | * @param key The name of the preference to retrieve |
| | | * @param defaultValue Value to return if this preference does not exist |
| | | * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with |
| | | * this name that is not a string |
| | | */ |
| | | public String getString(Context context, String key, String defaultValue, String keyShared) { |
| | | SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); |
| | | return settings.getString(key, defaultValue); |
| | | } |
| | | |
| | | /** |
| | | * put int preferences |
| | | * |
| | | * @param context |
| | | * @param key The name of the preference to modify |
| | | * @param value The new value for the preference |
| | | * @return True if the new values were successfully written to persistent storage. |
| | | */ |
| | | public boolean putInt(Context context, String key, int value, String keyShared) { |
| | | SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); |
| | | SharedPreferences.Editor editor = settings.edit(); |
| | | editor.putInt(key, value); |
| | | return editor.commit(); |
| | | } |
| | | |
| | | /** |
| | | * get int preferences |
| | | * |
| | | * @param context |
| | | * @param key The name of the preference to retrieve |
| | | * @return The preference value if it exists, or -1. Throws ClassCastException if there is a preference with this |
| | | * name that is not a int |
| | | */ |
| | | public int getInt(Context context, String key, String keyShared) { |
| | | return getInt(context, key, -1,keyShared); |
| | | } |
| | | |
| | | /** |
| | | * get int preferences |
| | | * |
| | | * @param context |
| | | * @param key The name of the preference to retrieve |
| | | * @param defaultValue Value to return if this preference does not exist |
| | | * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with |
| | | * this name that is not a int |
| | | */ |
| | | public int getInt(Context context, String key, int defaultValue, String keyShared) { |
| | | SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); |
| | | try { |
| | | return settings.getInt(key, defaultValue); |
| | | }catch (ClassCastException e){ |
| | | try { |
| | | return Integer.parseInt(settings.getString(key,defaultValue+"")); |
| | | }catch (NumberFormatException en){ |
| | | return defaultValue; |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * put long preferences |
| | | * |
| | | * @param context |
| | | * @param key The name of the preference to modify |
| | | * @param value The new value for the preference |
| | | * @return True if the new values were successfully written to persistent storage. |
| | | */ |
| | | public boolean putLong(Context context, String key, long value, String keyShared) { |
| | | SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); |
| | | SharedPreferences.Editor editor = settings.edit(); |
| | | editor.putLong(key, value); |
| | | return editor.commit(); |
| | | } |
| | | |
| | | /** |
| | | * get long preferences |
| | | * |
| | | * @param context |
| | | * @param key The name of the preference to retrieve |
| | | * @return The preference value if it exists, or -1. Throws ClassCastException if there is a preference with this |
| | | * name that is not a long |
| | | */ |
| | | public long getLong(Context context, String key, String keyShared) { |
| | | return getLong(context, key, -1,keyShared); |
| | | } |
| | | |
| | | /** |
| | | * get long preferences |
| | | * |
| | | * @param context |
| | | * @param key The name of the preference to retrieve |
| | | * @param defaultValue Value to return if this preference does not exist |
| | | * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with |
| | | * this name that is not a long |
| | | */ |
| | | public long getLong(Context context, String key, long defaultValue, String keyShared) { |
| | | SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); |
| | | try { |
| | | return settings.getLong(key, defaultValue); |
| | | }catch (ClassCastException e){ |
| | | try { |
| | | return Long.parseLong(settings.getString(key,defaultValue+"")); |
| | | }catch (NumberFormatException en){ |
| | | return defaultValue; |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * put float preferences |
| | | * |
| | | * @param context |
| | | * @param key The name of the preference to modify |
| | | * @param value The new value for the preference |
| | | * @return True if the new values were successfully written to persistent storage. |
| | | */ |
| | | public boolean putFloat(Context context, String key, float value, String keyShared) { |
| | | SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); |
| | | SharedPreferences.Editor editor = settings.edit(); |
| | | editor.putFloat(key, value); |
| | | return editor.commit(); |
| | | } |
| | | |
| | | /** |
| | | * get float preferences |
| | | * |
| | | * @param context |
| | | * @param key The name of the preference to retrieve |
| | | * @return The preference value if it exists, or -1. Throws ClassCastException if there is a preference with this |
| | | * name that is not a float |
| | | */ |
| | | public float getFloat(Context context, String key, String keyShared) { |
| | | return getFloat(context, key, -1,keyShared); |
| | | } |
| | | |
| | | /** |
| | | * get float preferences |
| | | * |
| | | * @param context |
| | | * @param key The name of the preference to retrieve |
| | | * @param defaultValue Value to return if this preference does not exist |
| | | * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with |
| | | * this name that is not a float |
| | | */ |
| | | public float getFloat(Context context, String key, float defaultValue, String keyShared) { |
| | | SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); |
| | | try { |
| | | return settings.getFloat(key, defaultValue); |
| | | }catch (ClassCastException e){ |
| | | try { |
| | | return Float.parseFloat(settings.getString(key,defaultValue+"")); |
| | | }catch (NumberFormatException en){ |
| | | return defaultValue; |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * put boolean preferences |
| | | * |
| | | * @param context |
| | | * @param key The name of the preference to modify |
| | | * @param value The new value for the preference |
| | | * @return True if the new values were successfully written to persistent storage. |
| | | */ |
| | | public boolean putBoolean(Context context, String key, boolean value, String keyShared) { |
| | | SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); |
| | | SharedPreferences.Editor editor = settings.edit(); |
| | | editor.putBoolean(key, value); |
| | | return editor.commit(); |
| | | } |
| | | |
| | | /** |
| | | * get boolean preferences, default is false |
| | | * |
| | | * @param context |
| | | * @param key The name of the preference to retrieve |
| | | * @return The preference value if it exists, or false. Throws ClassCastException if there is a preference with this |
| | | * name that is not a boolean |
| | | */ |
| | | public boolean getBoolean(Context context, String key, String keyShared) { |
| | | return getBoolean(context, key, false,keyShared); |
| | | } |
| | | |
| | | /** |
| | | * get boolean preferences |
| | | * |
| | | * @param context |
| | | * @param key The name of the preference to retrieve |
| | | * @param defaultValue Value to return if this preference does not exist |
| | | * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with |
| | | * this name that is not a boolean |
| | | */ |
| | | public boolean getBoolean(Context context, String key, boolean defaultValue, String keyShared) { |
| | | SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); |
| | | try { |
| | | return settings.getBoolean(key, defaultValue); |
| | | }catch (ClassCastException e){ |
| | | try { |
| | | return Boolean.parseBoolean(settings.getString(key,defaultValue+"")); |
| | | }catch (NumberFormatException en){ |
| | | return defaultValue; |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * put boolean preferences |
| | | * |
| | | * @param context |
| | | * @param key The name of the preference to modify |
| | | * @param value The new value for the preference , the value of set ,canot be the other class out of java collection |
| | | * @return True if the new values were successfully written to persistent storage. |
| | | */ |
| | | public boolean putStringSet(Context context, String key, Set value, String keyShared) { |
| | | SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); |
| | | SharedPreferences.Editor editor = settings.edit(); |
| | | editor.putStringSet(key, value); |
| | | return editor.commit(); |
| | | } |
| | | |
| | | /** |
| | | * get boolean preferences, default is false |
| | | * |
| | | * @param context |
| | | * @param key The name of the preference to retrieve |
| | | * @return The preference value if it exists, or false. Throws ClassCastException if there is a preference with this |
| | | * name that is not a boolean 获取出来的值最终被转换为hashset类型 |
| | | */ |
| | | public Set getStringSet(Context context, String key, String keyShared) { |
| | | return getStringSet(context, key,new ArraySet(),keyShared); |
| | | } |
| | | |
| | | /** |
| | | * get boolean preferences |
| | | * |
| | | * @param context |
| | | * @param key The name of the preference to retrieve |
| | | * @param defaultValue Value to return if this preference does not exist |
| | | * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with |
| | | * this name that is not a boolean 获取出来的值最终被转换为hashset类型 |
| | | */ |
| | | public Set getStringSet(Context context, String key, Set defaultValue, String keyShared) { |
| | | SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE); |
| | | return settings.getStringSet(key, defaultValue); |
| | | } |
| | | |
| | | public boolean getBooleanValOfUser(Context context, String key){ |
| | | return getBoolean(context,key,false,USER); |
| | | } |
| | | |
| | | public boolean getBooleanValOfProject(Context context, String key){ |
| | | return getBoolean(context,key,false,PROJECT); |
| | | } |
| | | |
| | | public String getStringValOfUser(Context context, String key){ |
| | | return getString(context,key,"",USER); |
| | | } |
| | | |
| | | public String getStringValOfProject(Context context, String key){ |
| | | return getString(context,key,"",PROJECT); |
| | | } |
| | | |
| | | public int getIntValOfProject(Context context, String key){ |
| | | return getInt(context,key,0,PROJECT); |
| | | } |
| | | |
| | | public Long getLongValOfProject(Context context, String key){ |
| | | return getLong(context,key,0,PROJECT); |
| | | } |
| | | |
| | | public float getFloatValOfProject(Context context, String key){ |
| | | return getFloat(context,key,0,PROJECT); |
| | | } |
| | | |
| | | public Set getStringSetValOfProject(Context context, String key){ |
| | | return getStringSet(context,key,PROJECT); |
| | | } |
| | | |
| | | public int getIntValOfUser(Context context, String key){ |
| | | return getInt(context,key,0,USER); |
| | | } |
| | | |
| | | public Long getLongValOfUser(Context context, String key){ |
| | | return getLong(context,key,0,USER); |
| | | } |
| | | |
| | | public float getFloatValOfUser(Context context, String key){ |
| | | return getFloat(context,key,0,USER); |
| | | } |
| | | |
| | | public Set getStringSetValOfUser(Context context, String key){ |
| | | return getStringSet(context,key,USER); |
| | | } |
| | | |
| | | |
| | | public void putBooleanValOfUser(Context context, String key ,Boolean value){ |
| | | putBoolean(context,key,value,USER); |
| | | } |
| | | |
| | | public void putBooleanValOfProject(Context context, String key,Boolean value){ |
| | | putBoolean(context,key,value,PROJECT); |
| | | } |
| | | |
| | | public void putStringValOfUser(Context context,String key,String value){ |
| | | putString(context,key,value,USER); |
| | | } |
| | | |
| | | public void putStringValOfProject(Context context,String key,String value){ |
| | | putString(context,key,value,PROJECT); |
| | | } |
| | | |
| | | public void putIntValOfProject(Context context,String key,int value){ |
| | | putInt(context,key,value,PROJECT); |
| | | } |
| | | |
| | | public void putLongValOfProject(Context context,String key,long value){ |
| | | putLong(context,key,value,PROJECT); |
| | | } |
| | | |
| | | public void putFloatValOfProject(Context context,String key,float value){ |
| | | putFloat(context,key,value,PROJECT); |
| | | } |
| | | |
| | | public void putStringSetValOfProject(Context context,String key,Set value){ |
| | | putStringSet(context,key,value,PROJECT); |
| | | } |
| | | |
| | | public void putIntValOfUser(Context context,String key,int value){ |
| | | putInt(context,key,value,USER); |
| | | } |
| | | |
| | | public void putLongValOfUser(Context context,String key,long value){ |
| | | putLong(context,key,value,USER); |
| | | } |
| | | |
| | | public void putFloatValOfUser(Context context,String key,float value){ |
| | | putFloat(context,key,value,USER); |
| | | } |
| | | |
| | | public void putStringSetValOfUser(Context context,String key, Set value){ |
| | | putStringSet(context,key,value,USER); |
| | | } |
| | | |
| | | |
| | | public void removeUserKey(Context context, String key){ |
| | | removeKey(context,key,USER); |
| | | } |
| | | |
| | | public void removeProjectKey(Context context, String key){ |
| | | removeKey(context,key,PROJECT); |
| | | } |
| | | |
| | | public void removeUserValue(Context context, String Value){ |
| | | removeValue(context,Value,USER); |
| | | } |
| | | |
| | | public void removeProjectValue(Context context, String Value){ |
| | | removeValue(context,Value,PROJECT); |
| | | } |
| | | |
| | | |
| | | public void clearProjectData(Context context){ |
| | | clearData(context,PROJECT); |
| | | } |
| | | public void clearUserData(Context context){ |
| | | clearData(context,USER); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.widgets; |
| | | |
| | | import android.annotation.SuppressLint; |
| | | import android.content.Context; |
| | | import android.graphics.drawable.Drawable; |
| | | import android.text.Editable; |
| | | import android.text.TextWatcher; |
| | | import android.util.AttributeSet; |
| | | import android.view.MotionEvent; |
| | | import android.view.View; |
| | | import android.view.animation.Animation; |
| | | import android.view.animation.CycleInterpolator; |
| | | import android.view.animation.TranslateAnimation; |
| | | import android.widget.EditText; |
| | | |
| | | import com.runt.open.mvvm.R; |
| | | |
| | | |
| | | /** |
| | | * My father is Object, ites purpose of |
| | | * |
| | | * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-2-25. |
| | | */ |
| | | @SuppressLint("AppCompatCustomView") |
| | | public class ClearEditText extends EditText implements View.OnFocusChangeListener, |
| | | TextWatcher { |
| | | /** |
| | | * 删除按钮的引用 |
| | | */ |
| | | private Drawable mClearDrawable; |
| | | /** |
| | | * 控件是否有焦点 |
| | | */ |
| | | private boolean hasFoucs; |
| | | |
| | | /** |
| | | * 是否可清除内容 |
| | | */ |
| | | private boolean isClearable = true; |
| | | |
| | | /** |
| | | * @param context |
| | | * @Description TODO |
| | | */ |
| | | public ClearEditText(Context context) { |
| | | this(context, null); |
| | | } |
| | | |
| | | /** |
| | | * @param context |
| | | * @param attrs |
| | | * @Description TODO |
| | | */ |
| | | public ClearEditText(Context context, AttributeSet attrs) { |
| | | // 这里构造方法也很重要,不加这个很多属性不能再XML里面定义 |
| | | this(context, attrs, android.R.attr.editTextStyle); |
| | | } |
| | | |
| | | /** |
| | | * @param context |
| | | * @param attrs |
| | | * @param defStyle |
| | | * @Description TODO |
| | | */ |
| | | public ClearEditText(Context context, AttributeSet attrs, int defStyle) { |
| | | super(context, attrs, defStyle); |
| | | init(); |
| | | } |
| | | |
| | | private void init() { |
| | | // 获取EditText的DrawableRight,假如没有设置我们就使用默认的图片 |
| | | mClearDrawable = getCompoundDrawables()[2]; |
| | | if (mClearDrawable == null) { |
| | | // throw new |
| | | // NullPointerException("You can add drawableRight attribute in XML"); |
| | | mClearDrawable = getResources().getDrawable(R.mipmap.icon_delete); |
| | | } |
| | | |
| | | mClearDrawable.setBounds(0, 0, mClearDrawable.getIntrinsicWidth(), |
| | | mClearDrawable.getIntrinsicHeight()); |
| | | |
| | | // 默认设置隐藏图标 |
| | | setClearIconVisible(false); |
| | | // 设置焦点改变的监听 |
| | | setOnFocusChangeListener(this); |
| | | // 设置输入框里面内容发生改变的监听 |
| | | addTextChangedListener(this); |
| | | } |
| | | |
| | | /** |
| | | * 因为我们不能直接给EditText设置点击事件,所以我们用记住我们按下的位置来模拟点击事件 当我们按下的位置 在 EditText的宽度 - |
| | | * 图标到控件右边的间距 - 图标的宽度 和 EditText的宽度 - 图标到控件右边的间距之间我们就算点击了图标,竖直方向就没有考虑 |
| | | */ |
| | | @Override |
| | | public boolean onTouchEvent(MotionEvent event) { |
| | | if (event.getAction() == MotionEvent.ACTION_UP) { |
| | | if (getCompoundDrawables()[2] != null) { |
| | | |
| | | boolean touchable = event.getX() > (getWidth() - getTotalPaddingRight()) |
| | | && (event.getX() < ((getWidth() - getPaddingRight()))); |
| | | |
| | | if (touchable && isClearable) { |
| | | this.setText(""); |
| | | } |
| | | } |
| | | } |
| | | |
| | | return super.onTouchEvent(event); |
| | | } |
| | | |
| | | /** |
| | | * 当ClearEditText焦点发生变化的时候,判断里面字符串长度设置清除图标的显示与隐藏 |
| | | */ |
| | | @Override |
| | | public void onFocusChange(View v, boolean hasFocus) { |
| | | this.hasFoucs = hasFocus; |
| | | if (hasFocus) { |
| | | setClearIconVisible(getText().toString().length() > 0); |
| | | } else { |
| | | setClearIconVisible(false); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 设置清除图标的显示与隐藏,调用setCompoundDrawables为EditText绘制上去 |
| | | * |
| | | * @param visible |
| | | */ |
| | | public void setClearIconVisible(boolean visible) { |
| | | Drawable right = visible ? mClearDrawable : null; |
| | | setCompoundDrawables(getCompoundDrawables()[0], |
| | | getCompoundDrawables()[1], right, getCompoundDrawables()[3]); |
| | | } |
| | | |
| | | /** |
| | | * 当输入框里面内容发生变化的时候回调的方法 |
| | | */ |
| | | @Override |
| | | public void onTextChanged(CharSequence s, int start, int count, int after) { |
| | | if (hasFoucs) { |
| | | setClearIconVisible(s.toString().length() > 0); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void beforeTextChanged(CharSequence s, int start, int count, |
| | | int after) { |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public void afterTextChanged(Editable s) { |
| | | |
| | | } |
| | | |
| | | /** |
| | | * 设置晃动动画 |
| | | */ |
| | | public void startShakeAnimation() { |
| | | this.startAnimation(shakeAnimation(5)); |
| | | } |
| | | |
| | | /** |
| | | * 晃动动画 |
| | | * |
| | | * @param counts 1秒钟晃动多少下 |
| | | * @return |
| | | */ |
| | | public static Animation shakeAnimation(int counts) { |
| | | Animation translateAnimation = new TranslateAnimation(0, 10, 0, 0); |
| | | translateAnimation.setInterpolator(new CycleInterpolator(counts)); |
| | | translateAnimation.setDuration(1000); |
| | | return translateAnimation; |
| | | } |
| | | |
| | | /** |
| | | * 设置是否可清除 |
| | | * |
| | | * @param clearable |
| | | */ |
| | | public void setClearable(boolean clearable) { |
| | | isClearable = clearable; |
| | | } |
| | | |
| | | /** |
| | | * 获取是否可清除 |
| | | * |
| | | * @return |
| | | */ |
| | | public boolean getClearable() { |
| | | return isClearable; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.widgets; |
| | | |
| | | import android.content.Context; |
| | | import android.util.AttributeSet; |
| | | import android.widget.LinearLayout; |
| | | |
| | | /** |
| | | * My father is Object, ites purpose of 正方形控件 |
| | | * |
| | | * @purpose Created by Runt (qingingrunt2010@qq.com) on 2019-3-24. |
| | | */ |
| | | |
| | | public class QuadrateLinearLayout extends LinearLayout { |
| | | |
| | | public QuadrateLinearLayout(Context context, AttributeSet attrs, |
| | | int defStyle) { |
| | | super(context, attrs, defStyle); |
| | | } |
| | | |
| | | public QuadrateLinearLayout(Context context, AttributeSet attrs) { |
| | | super(context, attrs); |
| | | } |
| | | |
| | | public QuadrateLinearLayout(Context context) { |
| | | super(context); |
| | | } |
| | | |
| | | @Override |
| | | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| | | setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), |
| | | getDefaultSize(0, heightMeasureSpec)); |
| | | |
| | | int childWidthSize = getMeasuredWidth(); |
| | | // 高度和宽度一样 |
| | | heightMeasureSpec = widthMeasureSpec = MeasureSpec.makeMeasureSpec( |
| | | childWidthSize, MeasureSpec.EXACTLY); |
| | | super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.runt.open.mvvm.widgets; |
| | | |
| | | import android.content.Context; |
| | | import android.content.res.TypedArray; |
| | | import android.graphics.Bitmap; |
| | | import android.graphics.Canvas; |
| | | import android.graphics.Color; |
| | | import android.graphics.Paint; |
| | | import android.graphics.Rect; |
| | | import android.graphics.RectF; |
| | | import android.graphics.drawable.BitmapDrawable; |
| | | import android.graphics.drawable.Drawable; |
| | | import android.util.AttributeSet; |
| | | import android.util.Log; |
| | | import android.view.MotionEvent; |
| | | import android.view.View; |
| | | |
| | | import androidx.annotation.ColorInt; |
| | | import androidx.annotation.Nullable; |
| | | |
| | | import com.runt.open.mvvm.R; |
| | | import com.runt.open.mvvm.util.DimensionUtils; |
| | | |
| | | |
| | | /** |
| | | * Created by Administrator on 2021/11/1 0001. |
| | | */ |
| | | public class TitleBarView extends View { |
| | | Drawable leftDra,rightDra; |
| | | private Rect mRect; // 绘制区域 |
| | | private RectF leftClickRect,rightClickRect;//点击事件响应区域 |
| | | String titleText,rightText; |
| | | @ColorInt int titleColor,rightTextColor; |
| | | float titleSize,rightTextSize,rightPadding; |
| | | Paint textPaint,rightTextPaint,drawPaint; |
| | | int viewWidth,viewHeight; |
| | | OnClickListener leftClick,rightClick; |
| | | int touchStartX,touchStartY; |
| | | |
| | | |
| | | public TitleBarView(Context context) { |
| | | this(context,null); |
| | | } |
| | | |
| | | public TitleBarView(Context context, @Nullable AttributeSet attrs) { |
| | | this(context, attrs,0); |
| | | } |
| | | |
| | | public TitleBarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { |
| | | super(context, attrs, defStyleAttr); |
| | | initWidget(context.obtainStyledAttributes(attrs, R.styleable.TitleBarView)); |
| | | } |
| | | |
| | | public TitleBarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { |
| | | super(context, attrs, defStyleAttr, defStyleRes); |
| | | initWidget(context.obtainStyledAttributes(attrs,R.styleable.TitleBarView)); |
| | | } |
| | | |
| | | public void initWidget(TypedArray array){ |
| | | leftDra = array.getDrawable(R.styleable.TitleBarView_leftDrawable); |
| | | rightDra = array.getDrawable(R.styleable.TitleBarView_rightDrawable); |
| | | titleText = array.getString(R.styleable.TitleBarView_titleText); |
| | | titleColor = array.getColor(R.styleable.TitleBarView_titleTextColor, Color.BLACK); |
| | | titleSize = array.getDimension(R.styleable.TitleBarView_titleTextSize, DimensionUtils.convertSpToPixel(getContext(),16)); |
| | | rightText = array.getString(R.styleable.TitleBarView_rightText); |
| | | rightTextColor = array.getColor(R.styleable.TitleBarView_rightTextColor,Color.BLACK); |
| | | rightTextSize = array.getDimension(R.styleable.TitleBarView_rightTextSize, DimensionUtils.convertSpToPixel(getContext(),14)); |
| | | rightPadding = array.getDimension(R.styleable.TitleBarView_rightDrawablePadding,10); |
| | | |
| | | textPaint = new Paint(); |
| | | textPaint.setAntiAlias(true); // 是否抗锯齿 |
| | | //mTextPaint.setAlpha(50); // 设置alpha不透明度,范围为0~255 |
| | | textPaint.setColor(titleColor); |
| | | textPaint.setTextSize(titleSize); |
| | | // 设置画笔属性 |
| | | textPaint.setStyle(Paint.Style.FILL);//画笔属性是实心圆 |
| | | // paint.setStyle(Paint.Style.STROKE);//画笔属性是空心圆 |
| | | textPaint.setStrokeWidth(4);//设置画笔粗细 |
| | | |
| | | |
| | | rightTextPaint = new Paint(); |
| | | rightTextPaint.setAntiAlias(true); // 是否抗锯齿 |
| | | //mTextPaint.setAlpha(50); // 设置alpha不透明度,范围为0~255 |
| | | rightTextPaint.setColor(rightTextColor); |
| | | rightTextPaint.setTextSize(rightTextSize); |
| | | // 设置画笔属性 |
| | | rightTextPaint.setStyle(Paint.Style.FILL);//画笔属性是实心圆 |
| | | // paint.setStyle(Paint.Style.STROKE);//画笔属性是空心圆 |
| | | rightTextPaint.setStrokeWidth(4);//设置画笔粗细 |
| | | |
| | | } |
| | | |
| | | @Override |
| | | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| | | super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
| | | viewWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); |
| | | viewHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); |
| | | mRect = new Rect(getPaddingLeft(),getPaddingTop(),getMeasuredWidth() - getPaddingRight(),getMeasuredHeight() - getPaddingBottom()); |
| | | Log.e("TitleBarView","onMeasure mRect:"+mRect); |
| | | |
| | | } |
| | | |
| | | @Override |
| | | protected void onDraw(Canvas canvas) { |
| | | super.onDraw(canvas); |
| | | Log.e("TitleBarView","onDraw mRect:"+mRect); |
| | | if(leftDra != null){ |
| | | final Bitmap bitmap = ((BitmapDrawable) leftDra).getBitmap(); |
| | | float top = mRect.top+(viewHeight-bitmap.getHeight()*2f)/2; |
| | | float left = mRect.left-bitmap.getWidth()*0.5f; |
| | | leftClickRect = new RectF(left,top,left + (bitmap.getWidth()*2f),top + bitmap.getHeight()*2f); |
| | | canvas.drawBitmap(bitmap,mRect.left,mRect.top+(viewHeight-bitmap.getHeight())/2,null); |
| | | |
| | | } |
| | | if(rightDra != null){ |
| | | final Bitmap bitmap = ((BitmapDrawable) rightDra).getBitmap(); |
| | | float chaTop = (viewHeight-bitmap.getHeight())/2; |
| | | float top = mRect.top+chaTop; |
| | | float left = mRect.right-bitmap.getWidth(); |
| | | rightClickRect = new RectF(left-bitmap.getWidth()/2,top-bitmap.getHeight()/2,left+bitmap.getWidth()*2f,top+bitmap.getHeight()*1.5f); |
| | | canvas.drawBitmap(bitmap,left,top,null); |
| | | } |
| | | if(titleText != null){ |
| | | final int textWidth = getTextWidth(textPaint, titleText); |
| | | final int textHeight = getTextHeight(textPaint); |
| | | final Paint.FontMetrics fontMetrics = textPaint.getFontMetrics(); |
| | | canvas.drawText(titleText,mRect.left+((viewWidth-textWidth)/2),mRect.top+((viewHeight-textHeight)/2)+(0-fontMetrics.ascent),textPaint); |
| | | } |
| | | if(rightText != null){ |
| | | final int textWidth = getTextWidth(rightTextPaint, rightText); |
| | | final int textHeight = getTextHeight(rightTextPaint); |
| | | final Paint.FontMetrics fontMetrics = rightTextPaint.getFontMetrics(); |
| | | float left = mRect.right-textWidth; |
| | | float top = mRect.top+((viewHeight-textHeight)/2)+(0-fontMetrics.ascent); |
| | | rightClickRect = new RectF(left-textWidth/2,top-textHeight,left+textWidth*2f,top+textHeight/2); |
| | | canvas.drawText(rightText,left,top,rightTextPaint); |
| | | } |
| | | } |
| | | @Override |
| | | public boolean onTouchEvent(MotionEvent event) { |
| | | int x = (int) event.getX(); |
| | | int y = (int) event.getY(); |
| | | int action = event.getAction(); |
| | | switch (action) { |
| | | case MotionEvent.ACTION_DOWN: |
| | | touchStartX = x; |
| | | touchStartY = y; |
| | | break; |
| | | case MotionEvent.ACTION_MOVE: |
| | | break; |
| | | case MotionEvent.ACTION_UP: |
| | | if(isTouched(x,y,leftClickRect) && leftClick != null){ |
| | | leftClick.onClick(this); |
| | | }else if(isTouched(x,y,rightClickRect) && rightClick != null){ |
| | | rightClick.onClick(this); |
| | | } |
| | | |
| | | break; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * 是否为点击事件 |
| | | * @param x |
| | | * @param y |
| | | * @param rectF |
| | | * @return |
| | | */ |
| | | private boolean isTouched(int x, int y, RectF rectF){ |
| | | if(rectF == null){ |
| | | return false; |
| | | } |
| | | if(x < rectF.right && x > rectF.left && |
| | | touchStartX < rectF.right && touchStartX > rectF.left && |
| | | y < rectF.bottom && y > rectF.top && |
| | | touchStartY < rectF.bottom && touchStartY > rectF.top ){ |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | public void setLeftDra(Drawable leftDra) { |
| | | this.leftDra = leftDra; |
| | | invalidate(); |
| | | } |
| | | |
| | | public void setRightDra(Drawable rightDra) { |
| | | this.rightDra = rightDra; |
| | | invalidate(); |
| | | } |
| | | |
| | | public void setTitleText(String titleText) { |
| | | this.titleText = titleText; |
| | | invalidate(); |
| | | } |
| | | |
| | | public void setRightText(String rightText) { |
| | | this.rightText = rightText; |
| | | invalidate(); |
| | | } |
| | | |
| | | public void setTitleColor(@ColorInt int titleColor) { |
| | | textPaint.setColor(titleColor); |
| | | invalidate(); |
| | | } |
| | | |
| | | public void setRightTextColor(@ColorInt int rightTextColor) { |
| | | rightTextPaint.setColor(rightTextColor); |
| | | invalidate(); |
| | | } |
| | | |
| | | public void setTitleSize(int titleSize) { |
| | | textPaint.setTextSize(DimensionUtils.convertSpToPixel(getContext(),titleSize)); |
| | | invalidate(); |
| | | } |
| | | |
| | | public void setRightTextSize(float rightTextSize) { |
| | | rightTextPaint.setTextSize(DimensionUtils.convertSpToPixel(getContext(),rightTextSize)); |
| | | invalidate(); |
| | | } |
| | | |
| | | public void setRightPadding(float rightPadding) { |
| | | this.rightPadding = rightPadding; |
| | | invalidate(); |
| | | } |
| | | |
| | | public void setLeftClick(OnClickListener leftClick) { |
| | | this.leftClick = leftClick; |
| | | } |
| | | |
| | | public void setRightClick(OnClickListener rightClick) { |
| | | this.rightClick = rightClick; |
| | | } |
| | | |
| | | //第二个参数是一个数组.传进去个长度跟字符串长度相同的float数组,方法调用后,里边塞的是每个字符的长度. |
| | | public int getTextWidth(Paint paint, String str) { |
| | | int iRet = 0; |
| | | if (str != null && str.length() > 0) { |
| | | int len = str.length(); |
| | | float[] widths = new float[len]; |
| | | paint.getTextWidths(str, widths); |
| | | for (int j = 0; j < len; j++) { |
| | | iRet += (int) Math.ceil(widths[j]); |
| | | } |
| | | } |
| | | return iRet; |
| | | } |
| | | |
| | | //第二个参数是一个数组.传进去个长度跟字符串长度相同的float数组,方法调用后,里边塞的是每个字符的长度. |
| | | public int getTextHeight(Paint paint) { |
| | | Paint.FontMetrics fm = paint.getFontMetrics(); |
| | | float height1 = fm.descent - fm.ascent;//文字的高度 |
| | | float height2 = fm.bottom - fm.top + fm.leading;//行高 |
| | | return (int) height2; |
| | | } |
| | | } |
New file |
| | |
| | | <vector xmlns:android="http://schemas.android.com/apk/res/android" |
| | | xmlns:aapt="http://schemas.android.com/aapt" |
| | | android:width="108dp" |
| | | android:height="108dp" |
| | | android:viewportWidth="108" |
| | | android:viewportHeight="108"> |
| | | <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z"> |
| | | <aapt:attr name="android:fillColor"> |
| | | <gradient |
| | | android:endX="85.84757" |
| | | android:endY="92.4963" |
| | | android:startX="42.9492" |
| | | android:startY="49.59793" |
| | | android:type="linear"> |
| | | <item |
| | | android:color="#44000000" |
| | | android:offset="0.0" /> |
| | | <item |
| | | android:color="#00000000" |
| | | android:offset="1.0" /> |
| | | </gradient> |
| | | </aapt:attr> |
| | | </path> |
| | | <path |
| | | android:fillColor="#FFFFFF" |
| | | android:fillType="nonZero" |
| | | android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" |
| | | android:strokeWidth="1" |
| | | android:strokeColor="#00000000" /> |
| | | </vector> |
New file |
| | |
| | | <vector xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:width="24dp" |
| | | android:height="24dp" |
| | | android:viewportWidth="24.0" |
| | | android:viewportHeight="24.0"> |
| | | <path |
| | | android:fillColor="#FF000000" |
| | | android:pathData="M3,13h8L11,3L3,3v10zM3,21h8v-6L3,15v6zM13,21h8L21,11h-8v10zM13,3v6h8L21,3h-8z"/> |
| | | </vector> |
New file |
| | |
| | | <vector xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:width="24dp" |
| | | android:height="24dp" |
| | | android:viewportWidth="24.0" |
| | | android:viewportHeight="24.0"> |
| | | <path |
| | | android:fillColor="#FF000000" |
| | | android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/> |
| | | </vector> |
New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <vector xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:width="108dp" |
| | | android:height="108dp" |
| | | android:viewportWidth="108" |
| | | android:viewportHeight="108"> |
| | | <path |
| | | android:fillColor="#3DDC84" |
| | | android:pathData="M0,0h108v108h-108z" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M9,0L9,108" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M19,0L19,108" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M29,0L29,108" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M39,0L39,108" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M49,0L49,108" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M59,0L59,108" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M69,0L69,108" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M79,0L79,108" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M89,0L89,108" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M99,0L99,108" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M0,9L108,9" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M0,19L108,19" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M0,29L108,29" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M0,39L108,39" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M0,49L108,49" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M0,59L108,59" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M0,69L108,69" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M0,79L108,79" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M0,89L108,89" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M0,99L108,99" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M19,29L89,29" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M19,39L89,39" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M19,49L89,49" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M19,59L89,59" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M19,69L89,69" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M19,79L89,79" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M29,19L29,89" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M39,19L39,89" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M49,19L49,89" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M59,19L59,89" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M69,19L69,89" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M79,19L79,89" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | </vector> |
New file |
| | |
| | | <vector xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:width="24dp" |
| | | android:height="24dp" |
| | | android:viewportWidth="24.0" |
| | | android:viewportHeight="24.0"> |
| | | <path |
| | | android:fillColor="#FF000000" |
| | | android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z"/> |
| | | </vector> |
New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <androidx.constraintlayout.widget.ConstraintLayout |
| | | xmlns:android="http://schemas.android.com/apk/res/android" |
| | | xmlns:app="http://schemas.android.com/apk/res-auto" |
| | | android:id="@+id/container" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | android:paddingTop="?attr/actionBarSize" > |
| | | |
| | | <com.google.android.material.bottomnavigation.BottomNavigationView |
| | | android:id="@+id/nav_view" |
| | | android:layout_width="0dp" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginStart="0dp" |
| | | android:layout_marginEnd="0dp" |
| | | android:background="?android:attr/windowBackground" |
| | | app:layout_constraintBottom_toBottomOf="parent" |
| | | app:layout_constraintLeft_toLeftOf="parent" |
| | | app:layout_constraintRight_toRightOf="parent" |
| | | app:menu="@menu/bottom_nav_menu"/> |
| | | |
| | | <fragment |
| | | android:id="@+id/nav_host_fragment_activity_main" |
| | | android:name="androidx.navigation.fragment.NavHostFragment" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | app:defaultNavHost="true" |
| | | app:layout_constraintBottom_toTopOf="@id/nav_view" |
| | | app:layout_constraintLeft_toLeftOf="parent" |
| | | app:layout_constraintRight_toRightOf="parent" |
| | | app:layout_constraintTop_toTopOf="parent" |
| | | app:navGraph="@navigation/mobile_navigation" |
| | | /> |
| | | |
| | | </androidx.constraintlayout.widget.ConstraintLayout> |
New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <androidx.constraintlayout.widget.ConstraintLayout |
| | | 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=".ui.dashboard.DashboardFragment" > |
| | | |
| | | <TextView |
| | | android:id="@+id/text_dashboard" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginStart="8dp" |
| | | android:layout_marginTop="8dp" |
| | | android:layout_marginEnd="8dp" |
| | | android:textAlignment="center" |
| | | android:textSize="20sp" |
| | | app:layout_constraintEnd_toEndOf="parent" |
| | | app:layout_constraintStart_toStartOf="parent" |
| | | app:layout_constraintTop_toTopOf="parent" |
| | | app:layout_constraintBottom_toBottomOf="parent" /> |
| | | </androidx.constraintlayout.widget.ConstraintLayout> |
New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <androidx.constraintlayout.widget.ConstraintLayout |
| | | 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=".ui.home.HomeFragment" > |
| | | |
| | | <TextView |
| | | android:id="@+id/text_home" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginStart="8dp" |
| | | android:layout_marginTop="8dp" |
| | | android:layout_marginEnd="8dp" |
| | | android:textAlignment="center" |
| | | android:textSize="20sp" |
| | | app:layout_constraintEnd_toEndOf="parent" |
| | | app:layout_constraintStart_toStartOf="parent" |
| | | app:layout_constraintTop_toTopOf="parent" |
| | | app:layout_constraintBottom_toBottomOf="parent" /> |
| | | </androidx.constraintlayout.widget.ConstraintLayout> |
New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <androidx.constraintlayout.widget.ConstraintLayout |
| | | 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=".ui.notifications.NotificationsFragment" > |
| | | |
| | | <TextView |
| | | android:id="@+id/text_notifications" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginStart="8dp" |
| | | android:layout_marginTop="8dp" |
| | | android:layout_marginEnd="8dp" |
| | | android:textAlignment="center" |
| | | android:textSize="20sp" |
| | | app:layout_constraintEnd_toEndOf="parent" |
| | | app:layout_constraintStart_toStartOf="parent" |
| | | app:layout_constraintTop_toTopOf="parent" |
| | | app:layout_constraintBottom_toBottomOf="parent" /> |
| | | </androidx.constraintlayout.widget.ConstraintLayout> |
New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <RelativeLayout |
| | | 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:minHeight="400dp" |
| | | android:layout_height="wrap_content"> |
| | | |
| | | <TextView |
| | | android:id="@+id/textView" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="暂无数据" |
| | | android:layout_centerHorizontal="true" |
| | | android:layout_alignParentBottom="true" |
| | | android:layout_gravity="center_horizontal" /> |
| | | </RelativeLayout> |
New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <com.scwang.smart.refresh.layout.SmartRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| | | xmlns:tools="http://schemas.android.com/tools" |
| | | android:id="@+id/smart_refresh" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent"> |
| | | |
| | | <androidx.recyclerview.widget.RecyclerView |
| | | android:id="@+id/recycler" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | tools:itemCount="1" |
| | | tools:listitem="@layout/layout_null"/> |
| | | </com.scwang.smart.refresh.layout.SmartRefreshLayout> |
New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <menu xmlns:android="http://schemas.android.com/apk/res/android"> |
| | | |
| | | <item |
| | | android:id="@+id/navigation_home" |
| | | android:icon="@drawable/ic_home_black_24dp" |
| | | android:title="@string/title_home"/> |
| | | |
| | | <item |
| | | android:id="@+id/navigation_dashboard" |
| | | android:icon="@drawable/ic_dashboard_black_24dp" |
| | | android:title="@string/title_dashboard"/> |
| | | |
| | | <item |
| | | android:id="@+id/navigation_notifications" |
| | | android:icon="@drawable/ic_notifications_black_24dp" |
| | | android:title="@string/title_notifications"/> |
| | | |
| | | </menu> |
New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> |
| | | <background android:drawable="@drawable/ic_launcher_background" /> |
| | | <foreground android:drawable="@drawable/ic_launcher_foreground" /> |
| | | </adaptive-icon> |
New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> |
| | | <background android:drawable="@drawable/ic_launcher_background" /> |
| | | <foreground android:drawable="@drawable/ic_launcher_foreground" /> |
| | | </adaptive-icon> |
New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <navigation 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:id="@+id/mobile_navigation" |
| | | app:startDestination="@+id/navigation_home"> |
| | | |
| | | <fragment |
| | | android:id="@+id/navigation_home" |
| | | android:name="com.runt.open.mvvm.ui.home.HomeFragment" |
| | | android:label="@string/title_home" |
| | | tools:layout="@layout/fragment_home" /> |
| | | |
| | | <fragment |
| | | android:id="@+id/navigation_dashboard" |
| | | android:name="com.runt.open.mvvm.ui.dashboard.DashboardFragment" |
| | | android:label="@string/title_dashboard" |
| | | tools:layout="@layout/fragment_dashboard" /> |
| | | |
| | | <fragment |
| | | android:id="@+id/navigation_notifications" |
| | | android:name="com.runt.open.mvvm.ui.notifications.NotificationsFragment" |
| | | android:label="@string/title_notifications" |
| | | tools:layout="@layout/fragment_notifications" /> |
| | | </navigation> |
New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <resources> |
| | | <color name="teal_700">#FF018786</color> |
| | | <color name="black">#FF000000</color> |
| | | <color name="black_4">#373737</color> |
| | | <color name="white">#FFFFFFFF</color> |
| | | <color name="white_2">#F3F3F3</color> |
| | | <color name="red">#FF1414</color> |
| | | <color name="trans_red">#4DFF1414</color> |
| | | <color name="light_gray">#E3E3E3</color> |
| | | <color name="gray">#CDCDCD</color> |
| | | <color name="deep_gray">#808080</color> |
| | | <color name="trans_gray">#4DFFFFFF</color> |
| | | <color name="enable">#ECECEC</color> |
| | | <color name="enable_sky">#889DB6</color> |
| | | <color name="deep_sky">#4184D6</color> |
| | | <color name="sky">#509CFA</color> |
| | | <color name="gold">#FAD550</color> |
| | | </resources> |
New file |
| | |
| | | <resources> |
| | | <!-- Default screen margins, per the Android Design guidelines. --> |
| | | <dimen name="activity_horizontal_margin">16dp</dimen> |
| | | <dimen name="activity_vertical_margin">16dp</dimen> |
| | | <dimen name="small_radios">3dp</dimen> |
| | | <dimen name="circle">1000dp</dimen> |
| | | <dimen name="radios">10dp</dimen> |
| | | <dimen name="frame_margin_lr">10dp</dimen> |
| | | </resources> |
New file |
| | |
| | | <resources> |
| | | <string name="title_home">Home</string> |
| | | <string name="title_dashboard">Dashboard</string> |
| | | <string name="title_notifications">Notifications</string> |
| | | </resources> |
New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <resources> |
| | | |
| | | <declare-styleable name="TitleBarView"> |
| | | <attr name="leftDrawable" format="reference" /> |
| | | <attr name="rightDrawable" format="reference" /> |
| | | <attr name="rightDrawablePadding" format="dimension" /> |
| | | <attr name="titleText" format="string" /> |
| | | <attr name="titleTextSize" format="dimension" /> |
| | | <attr name="titleTextColor" format="color" /> |
| | | <attr name="rightText" format="string" /> |
| | | <attr name="rightTextSize" format="dimension" /> |
| | | <attr name="rightTextColor" format="color" /> |
| | | </declare-styleable> |
| | | </resources> |
New file |
| | |
| | | <resources xmlns:tools="http://schemas.android.com/tools"> |
| | | <!-- Base application theme. --> |
| | | <style name="Theme.OpemMvvm" parent="Theme.MaterialComponents.Light.NoActionBar"> |
| | | <!-- Primary brand color. --> |
| | | <item name="colorPrimary">@color/sky</item> |
| | | <item name="colorPrimaryVariant">@color/red</item> |
| | | <item name="colorOnPrimary">@color/black</item> |
| | | <!-- Secondary brand color. --> |
| | | <item name="colorSecondary">@color/black_4</item> |
| | | <item name="colorSecondaryVariant">@color/black_4</item> |
| | | <item name="colorOnSecondary">@color/black</item> |
| | | <!-- Status bar color. --> |
| | | <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item> |
| | | <!-- Customize your theme here. --> |
| | | </style> |
| | | </resources> |
New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <network-security-config> |
| | | <base-config cleartextTrafficPermitted="true" /> |
| | | </network-security-config> |
New file |
| | |
| | | // Top-level build file where you can add configuration options common to all sub-projects/modules. |
| | | buildscript { |
| | | repositories { |
| | | google() |
| | | mavenCentral() |
| | | } |
| | | dependencies { |
| | | classpath "com.android.tools.build:gradle:7.0.3" |
| | | |
| | | // NOTE: Do not place your application dependencies here; they belong |
| | | // in the individual module build.gradle files |
| | | } |
| | | } |
| | | |
| | | task clean(type: Delete) { |
| | | delete rootProject.buildDir |
| | | } |
New file |
| | |
| | | # Project-wide Gradle settings. |
| | | # IDE (e.g. Android Studio) users: |
| | | # Gradle settings configured through the IDE *will override* |
| | | # any settings specified in this file. |
| | | # For more details on how to configure your build environment visit |
| | | # http://www.gradle.org/docs/current/userguide/build_environment.html |
| | | # Specifies the JVM arguments used for the daemon process. |
| | | # The setting is particularly useful for tweaking memory settings. |
| | | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 |
| | | # When configured, Gradle will run in incubating parallel mode. |
| | | # This option should only be used with decoupled projects. More details, visit |
| | | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects |
| | | # org.gradle.parallel=true |
| | | # AndroidX package structure to make it clearer which packages are bundled with the |
| | | # Android operating system, and which are packaged with your app"s APK |
| | | # https://developer.android.com/topic/libraries/support-library/androidx-rn |
| | | android.useAndroidX=true |
| | | # Automatically convert third-party libraries to use AndroidX |
| | | android.enableJetifier=true |
New file |
| | |
| | | #Mon Nov 15 09:23:49 CST 2021 |
| | | distributionBase=GRADLE_USER_HOME |
| | | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip |
| | | distributionPath=wrapper/dists |
| | | zipStorePath=wrapper/dists |
| | | zipStoreBase=GRADLE_USER_HOME |
New file |
| | |
| | | #!/usr/bin/env sh |
| | | |
| | | # |
| | | # Copyright 2015 the original author or authors. |
| | | # |
| | | # Licensed under the Apache License, Version 2.0 (the "License"); |
| | | # you may not use this file except in compliance with the License. |
| | | # You may obtain a copy of the License at |
| | | # |
| | | # https://www.apache.org/licenses/LICENSE-2.0 |
| | | # |
| | | # Unless required by applicable law or agreed to in writing, software |
| | | # distributed under the License is distributed on an "AS IS" BASIS, |
| | | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | # See the License for the specific language governing permissions and |
| | | # limitations under the License. |
| | | # |
| | | |
| | | ############################################################################## |
| | | ## |
| | | ## Gradle start up script for UN*X |
| | | ## |
| | | ############################################################################## |
| | | |
| | | # Attempt to set APP_HOME |
| | | # Resolve links: $0 may be a link |
| | | PRG="$0" |
| | | # Need this for relative symlinks. |
| | | while [ -h "$PRG" ] ; do |
| | | ls=`ls -ld "$PRG"` |
| | | link=`expr "$ls" : '.*-> \(.*\)$'` |
| | | if expr "$link" : '/.*' > /dev/null; then |
| | | PRG="$link" |
| | | else |
| | | PRG=`dirname "$PRG"`"/$link" |
| | | fi |
| | | done |
| | | SAVED="`pwd`" |
| | | cd "`dirname \"$PRG\"`/" >/dev/null |
| | | APP_HOME="`pwd -P`" |
| | | cd "$SAVED" >/dev/null |
| | | |
| | | APP_NAME="Gradle" |
| | | APP_BASE_NAME=`basename "$0"` |
| | | |
| | | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. |
| | | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' |
| | | |
| | | # Use the maximum available, or set MAX_FD != -1 to use that value. |
| | | MAX_FD="maximum" |
| | | |
| | | warn () { |
| | | echo "$*" |
| | | } |
| | | |
| | | die () { |
| | | echo |
| | | echo "$*" |
| | | echo |
| | | exit 1 |
| | | } |
| | | |
| | | # OS specific support (must be 'true' or 'false'). |
| | | cygwin=false |
| | | msys=false |
| | | darwin=false |
| | | nonstop=false |
| | | case "`uname`" in |
| | | CYGWIN* ) |
| | | cygwin=true |
| | | ;; |
| | | Darwin* ) |
| | | darwin=true |
| | | ;; |
| | | MINGW* ) |
| | | msys=true |
| | | ;; |
| | | NONSTOP* ) |
| | | nonstop=true |
| | | ;; |
| | | esac |
| | | |
| | | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar |
| | | |
| | | |
| | | # Determine the Java command to use to start the JVM. |
| | | if [ -n "$JAVA_HOME" ] ; then |
| | | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then |
| | | # IBM's JDK on AIX uses strange locations for the executables |
| | | JAVACMD="$JAVA_HOME/jre/sh/java" |
| | | else |
| | | JAVACMD="$JAVA_HOME/bin/java" |
| | | fi |
| | | if [ ! -x "$JAVACMD" ] ; then |
| | | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME |
| | | |
| | | Please set the JAVA_HOME variable in your environment to match the |
| | | location of your Java installation." |
| | | fi |
| | | else |
| | | JAVACMD="java" |
| | | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. |
| | | |
| | | Please set the JAVA_HOME variable in your environment to match the |
| | | location of your Java installation." |
| | | fi |
| | | |
| | | # Increase the maximum file descriptors if we can. |
| | | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then |
| | | MAX_FD_LIMIT=`ulimit -H -n` |
| | | if [ $? -eq 0 ] ; then |
| | | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then |
| | | MAX_FD="$MAX_FD_LIMIT" |
| | | fi |
| | | ulimit -n $MAX_FD |
| | | if [ $? -ne 0 ] ; then |
| | | warn "Could not set maximum file descriptor limit: $MAX_FD" |
| | | fi |
| | | else |
| | | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" |
| | | fi |
| | | fi |
| | | |
| | | # For Darwin, add options to specify how the application appears in the dock |
| | | if $darwin; then |
| | | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" |
| | | fi |
| | | |
| | | # For Cygwin or MSYS, switch paths to Windows format before running java |
| | | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then |
| | | APP_HOME=`cygpath --path --mixed "$APP_HOME"` |
| | | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` |
| | | |
| | | JAVACMD=`cygpath --unix "$JAVACMD"` |
| | | |
| | | # We build the pattern for arguments to be converted via cygpath |
| | | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` |
| | | SEP="" |
| | | for dir in $ROOTDIRSRAW ; do |
| | | ROOTDIRS="$ROOTDIRS$SEP$dir" |
| | | SEP="|" |
| | | done |
| | | OURCYGPATTERN="(^($ROOTDIRS))" |
| | | # Add a user-defined pattern to the cygpath arguments |
| | | if [ "$GRADLE_CYGPATTERN" != "" ] ; then |
| | | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" |
| | | fi |
| | | # Now convert the arguments - kludge to limit ourselves to /bin/sh |
| | | i=0 |
| | | for arg in "$@" ; do |
| | | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` |
| | | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option |
| | | |
| | | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition |
| | | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` |
| | | else |
| | | eval `echo args$i`="\"$arg\"" |
| | | fi |
| | | i=`expr $i + 1` |
| | | done |
| | | case $i in |
| | | 0) set -- ;; |
| | | 1) set -- "$args0" ;; |
| | | 2) set -- "$args0" "$args1" ;; |
| | | 3) set -- "$args0" "$args1" "$args2" ;; |
| | | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; |
| | | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; |
| | | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; |
| | | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; |
| | | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; |
| | | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; |
| | | esac |
| | | fi |
| | | |
| | | # Escape application args |
| | | save () { |
| | | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done |
| | | echo " " |
| | | } |
| | | APP_ARGS=`save "$@"` |
| | | |
| | | # Collect all arguments for the java command, following the shell quoting and substitution rules |
| | | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" |
| | | |
| | | exec "$JAVACMD" "$@" |
New file |
| | |
| | | @rem |
| | | @rem Copyright 2015 the original author or authors. |
| | | @rem |
| | | @rem Licensed under the Apache License, Version 2.0 (the "License"); |
| | | @rem you may not use this file except in compliance with the License. |
| | | @rem You may obtain a copy of the License at |
| | | @rem |
| | | @rem https://www.apache.org/licenses/LICENSE-2.0 |
| | | @rem |
| | | @rem Unless required by applicable law or agreed to in writing, software |
| | | @rem distributed under the License is distributed on an "AS IS" BASIS, |
| | | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | @rem See the License for the specific language governing permissions and |
| | | @rem limitations under the License. |
| | | @rem |
| | | |
| | | @if "%DEBUG%" == "" @echo off |
| | | @rem ########################################################################## |
| | | @rem |
| | | @rem Gradle startup script for Windows |
| | | @rem |
| | | @rem ########################################################################## |
| | | |
| | | @rem Set local scope for the variables with windows NT shell |
| | | if "%OS%"=="Windows_NT" setlocal |
| | | |
| | | set DIRNAME=%~dp0 |
| | | if "%DIRNAME%" == "" set DIRNAME=. |
| | | set APP_BASE_NAME=%~n0 |
| | | set APP_HOME=%DIRNAME% |
| | | |
| | | @rem Resolve any "." and ".." in APP_HOME to make it shorter. |
| | | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi |
| | | |
| | | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. |
| | | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" |
| | | |
| | | @rem Find java.exe |
| | | if defined JAVA_HOME goto findJavaFromJavaHome |
| | | |
| | | set JAVA_EXE=java.exe |
| | | %JAVA_EXE% -version >NUL 2>&1 |
| | | if "%ERRORLEVEL%" == "0" goto execute |
| | | |
| | | echo. |
| | | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. |
| | | echo. |
| | | echo Please set the JAVA_HOME variable in your environment to match the |
| | | echo location of your Java installation. |
| | | |
| | | goto fail |
| | | |
| | | :findJavaFromJavaHome |
| | | set JAVA_HOME=%JAVA_HOME:"=% |
| | | set JAVA_EXE=%JAVA_HOME%/bin/java.exe |
| | | |
| | | if exist "%JAVA_EXE%" goto execute |
| | | |
| | | echo. |
| | | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% |
| | | echo. |
| | | echo Please set the JAVA_HOME variable in your environment to match the |
| | | echo location of your Java installation. |
| | | |
| | | goto fail |
| | | |
| | | :execute |
| | | @rem Setup the command line |
| | | |
| | | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar |
| | | |
| | | |
| | | @rem Execute Gradle |
| | | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* |
| | | |
| | | :end |
| | | @rem End local scope for the variables with windows NT shell |
| | | if "%ERRORLEVEL%"=="0" goto mainEnd |
| | | |
| | | :fail |
| | | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of |
| | | rem the _cmd.exe /c_ return code! |
| | | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 |
| | | exit /b 1 |
| | | |
| | | :mainEnd |
| | | if "%OS%"=="Windows_NT" endlocal |
| | | |
| | | :omega |
New file |
| | |
| | | dependencyResolutionManagement { |
| | | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) |
| | | repositories { |
| | | google() |
| | | mavenCentral() |
| | | jcenter() // Warning: this repository is going to shut down soon |
| | | } |
| | | } |
| | | rootProject.name = "OpemMvvm" |
| | | include ':app' |