diff --git a/app/build.gradle b/app/build.gradle index d18e5b5..034e12c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -47,6 +47,7 @@ dependencies { implementation 'androidx.lifecycle:lifecycle-reactivestreams:2.4.1' implementation project(path: ':library-ijkplayer') implementation project(path: ':library-serialPort') + implementation project(path: ':library-common') //添加excel implementation rootProject.ext.dependencies.jxl // 时间日期选择控件 diff --git a/library-common/.gitignore b/library-common/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/library-common/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/library-common/build.gradle b/library-common/build.gradle new file mode 100644 index 0000000..be127c5 --- /dev/null +++ b/library-common/build.gradle @@ -0,0 +1,72 @@ +apply from: "${rootProject.rootDir}/buildCommon/commonLibConfig.gradle" +project.ext.setLibDefaultConfig project + +android { + defaultConfig { + ndk { + abiFilters 'armeabi', 'armeabi-v7a', 'x86' + } + } + sourceSets { + main { + jni.srcDirs = [] + jniLibs.srcDirs = ['libs'] + } + } + buildTypes { + debug { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + buildConfigField 'String', 'BASE_URL_CONFIG_PATH', '"baseUrl.properties"' + } + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + buildConfigField 'String', 'BASE_URL_CONFIG_PATH', '"baseUrl.properties"' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'com.google.android.material:material:1.4.0' + + implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' + implementation 'androidx.navigation:navigation-ui-ktx:2.3.5' + + implementation 'com.airbnb.android:lottie:5.0.2' + + implementation(rootProject.ext.dependencies.mmkv) + + // 添加kotlin依赖 + implementation(rootProject.ext.dependencies.kotlin) + implementation(rootProject.ext.dependencies.kotlin_android) + // 添加okhttp依赖 + implementation(rootProject.ext.dependencies.okhttp) + // 添加okhttp日志 + implementation(rootProject.ext.dependencies.okhttp_logger) + // 添加retrofit依赖 + implementation rootProject.ext.dependencies.retrofit + // 添加retrofit转化gson能力依赖 + implementation rootProject.ext.dependencies.converter_gson + // 添加rxjava依赖 + implementation rootProject.ext.dependencies.rxjava + // 添加rxAndroid依赖 + implementation rootProject.ext.dependencies.rxandroid + // 添加retrofit和rxjava适配器 + implementation rootProject.ext.dependencies.retrofit_rxjava + + //luban图片压缩 + implementation rootProject.ext.dependencies.luban + + // glide + implementation rootProject.ext.dependencies.glide + // glide替换为OkHttp3代理请求 + implementation rootProject.ext.dependencies.gilde_integration + // lifecycle_process + implementation rootProject.ext.dependencies.lifecycle_process + // glide注解 + kapt rootProject.ext.dependencies.annotationProcessor +} \ No newline at end of file diff --git a/library-common/consumer-rules.pro b/library-common/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/library-common/libs/commons-net-3.3.jar b/library-common/libs/commons-net-3.3.jar new file mode 100644 index 0000000..f4f19a9 Binary files /dev/null and b/library-common/libs/commons-net-3.3.jar differ diff --git a/library-common/priguardMapping.txt b/library-common/priguardMapping.txt new file mode 100644 index 0000000..e8d76c9 --- /dev/null +++ b/library-common/priguardMapping.txt @@ -0,0 +1,940 @@ +# compiler: R8 +# compiler_version: 3.0.73 +# pg_map_id: 11aaacb +# common_typos_disable +com.common.commonlib.CommonApplication -> com.common.commonlib.CommonApplication: +# {"id":"sourceFile","fileName":"CommonApplication.kt"} + 1:1:void ():16:16 -> + 1:1:void onCreate():18:18 -> onCreate + 2:2:void intLibs(android.content.Context):26:26 -> onCreate + 2:2:void onCreate():19 -> onCreate + 3:3:void initNet():32:32 -> onCreate + 3:3:void intLibs(android.content.Context):28 -> onCreate + 3:3:void onCreate():19 -> onCreate +com.common.commonlib.image.module.OkHttpLibraryGlideModule -> com.common.commonlib.image.module.OkHttpLibraryGlideModule: +# {"id":"sourceFile","fileName":"OkHttpLibraryGlideModule.kt"} + 1:1:void ():19:19 -> + 1:1:void registerComponents(android.content.Context,com.bumptech.glide.Glide,com.bumptech.glide.Registry):23:23 -> registerComponents +com.common.commonlib.net.manager.RequestManager -> a.a: +# {"id":"sourceFile","fileName":"RequestManager.kt"} + com.common.commonlib.net.manager.RequestManager INSTANCE -> a + 1:17:void ():22:38 -> + 1:1:void ():20:20 -> + 1:1:void loggingInterceptor$lambda-0(java.lang.String):39:39 -> a + 2:59:void com.common.commonlib.log.Logger.d(java.lang.String,java.lang.Object):0:57 -> a + 2:59:void loggingInterceptor$lambda-0(java.lang.String):39 -> a + 60:138:void com.common.commonlib.log.Logger.d(java.lang.String,java.lang.Object,boolean):0:78 -> a + 60:138:void com.common.commonlib.log.Logger.d(java.lang.String,java.lang.Object):57 -> a + 60:138:void loggingInterceptor$lambda-0(java.lang.String):39 -> a +com.common.commonlib.net.bean.BaseResponse -> com.common.commonlib.net.bean.BaseResponse: +# {"id":"sourceFile","fileName":"BaseResponse.kt"} + 1:1:void ():12:12 -> +com.common.commonlib.net.bean.BaseResponse$Creator -> com.common.commonlib.net.bean.BaseResponse$a: +# {"id":"sourceFile","fileName":"BaseResponse.kt"} + 1:1:com.common.commonlib.net.bean.BaseResponse createFromParcel(android.os.Parcel):0:0 -> createFromParcel + 1:1:java.lang.Object createFromParcel(android.os.Parcel):0 -> createFromParcel + 1:1:com.common.commonlib.net.bean.BaseResponse[] newArray(int):0:0 -> newArray + 1:1:java.lang.Object[] newArray(int):0 -> newArray +com.common.commonlib.utils.BaseUtils -> com.common.commonlib.utils.BaseUtils: +# {"id":"sourceFile","fileName":"BaseUtils.kt"} + 1:1:void ():10:10 -> + 1:1:void enableBasicLog():39:39 -> enableBasicLog + 2:2:void com.common.commonlib.net.manager.RequestManager.openBasicLog():102:102 -> enableBasicLog + 2:2:void enableBasicLog():39 -> enableBasicLog + 1:1:void enableFullLog():32:32 -> enableFullLog + 2:2:void com.common.commonlib.net.manager.RequestManager.openFullLog():95:95 -> enableFullLog + 2:2:void enableFullLog():32 -> enableFullLog + 1:1:void enableHeadersLog():46:46 -> enableHeadersLog + 2:2:void com.common.commonlib.net.manager.RequestManager.openHeadLog():109:109 -> enableHeadersLog + 2:2:void enableHeadersLog():46 -> enableHeadersLog + 1:1:java.lang.String getExternalStorageDirectory(android.content.Context):25:25 -> getExternalStorageDirectory + 1:1:java.lang.String getExternalStoragePath(android.content.Context):21:21 -> getExternalStoragePath + 1:1:boolean isListEmpty(java.util.List):14:14 -> isListEmpty +com.common.commonlib.utils.DateUtils -> com.common.commonlib.utils.DateUtils: +# {"id":"sourceFile","fileName":"DateUtils.kt"} + 1:1:void ():10:10 -> + 1:18:java.lang.String getDurationTimeByInt(int):61:78 -> getDurationTimeByInt + 1:1:java.util.Date getNow():42:42 -> getNow + 1:2:java.lang.String getNowTimeFormat(java.lang.String):47:48 -> getNowTimeFormat +com.common.commonlib.utils.DisplayUtils -> com.common.commonlib.utils.DisplayUtils: +# {"id":"sourceFile","fileName":"DisplayUtils.kt"} + 1:1:void ():8:8 -> + 1:1:int dp2px(android.content.Context,float):14:14 -> dp2px + 1:1:int px2dp(android.content.Context,float):22:22 -> px2dp +com.common.commonlib.utils.FTPUtils -> com.common.commonlib.utils.FTPUtils: +# {"id":"sourceFile","fileName":"FTPUtils.kt"} + 1:1:void ():17:17 -> + 1:5:com.common.commonlib.utils.FTPUtils$RESULT ftpDown(java.lang.String,int,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):103:107 -> ftpDown + 6:6:int org.apache.commons.net.ftp.FTP.getReplyCode():670:670 -> ftpDown + 6:6:com.common.commonlib.utils.FTPUtils$RESULT ftpDown(java.lang.String,int,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):108 -> ftpDown + 7:7:com.common.commonlib.utils.FTPUtils$RESULT ftpDown(java.lang.String,int,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):109:109 -> ftpDown + 8:8:void org.apache.commons.net.ftp.FTPClient.setBufferSize(int):3451:3451 -> ftpDown + 8:8:com.common.commonlib.utils.FTPUtils$RESULT ftpDown(java.lang.String,int,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):110 -> ftpDown + 9:9:com.common.commonlib.utils.FTPUtils$RESULT ftpDown(java.lang.String,int,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):111:111 -> ftpDown + 10:10:void org.apache.commons.net.ftp.FTP.setControlEncoding(java.lang.String):418:418 -> ftpDown + 10:10:com.common.commonlib.utils.FTPUtils$RESULT ftpDown(java.lang.String,int,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):111 -> ftpDown + 11:15:void org.apache.commons.net.ftp.FTPClient.enterLocalPassiveMode():1233:1237 -> ftpDown + 11:15:com.common.commonlib.utils.FTPUtils$RESULT ftpDown(java.lang.String,int,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):112 -> ftpDown + 16:19:com.common.commonlib.utils.FTPUtils$RESULT ftpDown(java.lang.String,int,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):113:116 -> ftpDown + 20:20:int org.apache.commons.net.ftp.FTP.quit():864:864 -> ftpDown + 20:20:boolean org.apache.commons.net.ftp.FTPClient.logout():1109 -> ftpDown + 20:20:com.common.commonlib.utils.FTPUtils$RESULT ftpDown(java.lang.String,int,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):117 -> ftpDown + 21:34:com.common.commonlib.utils.FTPUtils$RESULT ftpDown(java.lang.String,int,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):118:131 -> ftpDown + 35:42:com.common.commonlib.utils.FTPUtils$RESULT ftpDown(java.lang.String,int,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):124:131 -> ftpDown + 43:44:com.common.commonlib.utils.FTPUtils$RESULT ftpDown(java.lang.String,int,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):130:131 -> ftpDown + 1:6:com.common.commonlib.utils.FTPUtils$RESULT ftpUpload(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):51:56 -> ftpUpload + 7:7:int org.apache.commons.net.ftp.FTP.getReplyCode():670:670 -> ftpUpload + 7:7:com.common.commonlib.utils.FTPUtils$RESULT ftpUpload(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):57 -> ftpUpload + 8:8:com.common.commonlib.utils.FTPUtils$RESULT ftpUpload(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):58:58 -> ftpUpload + 9:9:void org.apache.commons.net.ftp.FTPClient.setBufferSize(int):3451:3451 -> ftpUpload + 9:9:com.common.commonlib.utils.FTPUtils$RESULT ftpUpload(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):59 -> ftpUpload + 10:10:com.common.commonlib.utils.FTPUtils$RESULT ftpUpload(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):60:60 -> ftpUpload + 11:11:void org.apache.commons.net.ftp.FTP.setControlEncoding(java.lang.String):418:418 -> ftpUpload + 11:11:com.common.commonlib.utils.FTPUtils$RESULT ftpUpload(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):60 -> ftpUpload + 12:12:int org.apache.commons.net.ftp.FTP.type(int):1081:1081 -> ftpUpload + 12:12:boolean org.apache.commons.net.ftp.FTPClient.setFileType(int):1519 -> ftpUpload + 12:12:com.common.commonlib.utils.FTPUtils$RESULT ftpUpload(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):61 -> ftpUpload + 13:13:int org.apache.commons.net.ftp.FTP.sendCommand(org.apache.commons.net.ftp.FTPCmd,java.lang.String):608:608 -> ftpUpload + 13:13:int org.apache.commons.net.ftp.FTP.type(int):1081 -> ftpUpload + 13:13:boolean org.apache.commons.net.ftp.FTPClient.setFileType(int):1519 -> ftpUpload + 13:13:com.common.commonlib.utils.FTPUtils$RESULT ftpUpload(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):61 -> ftpUpload + 14:16:boolean org.apache.commons.net.ftp.FTPClient.setFileType(int):1519:1521 -> ftpUpload + 14:16:com.common.commonlib.utils.FTPUtils$RESULT ftpUpload(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):61 -> ftpUpload + 17:21:void org.apache.commons.net.ftp.FTPClient.enterLocalPassiveMode():1233:1237 -> ftpUpload + 17:21:com.common.commonlib.utils.FTPUtils$RESULT ftpUpload(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):62 -> ftpUpload + 22:22:com.common.commonlib.utils.FTPUtils$RESULT ftpUpload(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):63:63 -> ftpUpload + 23:23:boolean org.apache.commons.net.ftp.FTPClient.storeFile(java.lang.String,java.io.InputStream):1976:1976 -> ftpUpload + 23:23:com.common.commonlib.utils.FTPUtils$RESULT ftpUpload(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):64 -> ftpUpload + 24:36:com.common.commonlib.utils.FTPUtils$RESULT ftpUpload(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):65:77 -> ftpUpload + 37:44:com.common.commonlib.utils.FTPUtils$RESULT ftpUpload(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):70:77 -> ftpUpload + 45:48:com.common.commonlib.utils.FTPUtils$RESULT ftpUpload(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):74:77 -> ftpUpload + 49:50:com.common.commonlib.utils.FTPUtils$RESULT ftpUpload(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String):76:77 -> ftpUpload +com.common.commonlib.utils.FTPUtils$RESULT -> com.common.commonlib.utils.FTPUtils$RESULT: +# {"id":"sourceFile","fileName":"FTPUtils.kt"} + 1:1:void ():31:31 -> + 1:1:void (java.lang.String,int):31:31 -> +com.common.commonlib.utils.MMKVUtils -> com.common.commonlib.utils.MMKVUtils: +# {"id":"sourceFile","fileName":"MMKVUtils.kt"} + 1:1:void ():12:12 -> + 1:1:boolean getBoolean(java.lang.String,java.lang.Object[]):43:43 -> getBoolean + 1:1:byte[] getByteArray(java.lang.String,java.lang.Object[]):39:39 -> getByteArray + 1:1:float getFloat(java.lang.String,java.lang.Object[]):47:47 -> getFloat + 1:1:int getInt(java.lang.String,java.lang.Object[]):51:51 -> getInt + 1:4:com.tencent.mmkv.MMKV getKV(java.lang.Object[]):67:70 -> getKV + 5:5:com.tencent.mmkv.MMKV getKV(java.lang.Object[]):69:69 -> getKV + 6:6:com.tencent.mmkv.MMKV getKV(java.lang.Object[]):68:68 -> getKV + 1:1:long getLong(java.lang.String,java.lang.Object[]):55:55 -> getLong + 1:1:java.lang.String getString(java.lang.String,java.lang.Object[]):59:59 -> getString + 1:1:java.util.Set getStringSet(java.lang.String,java.lang.Object[]):63:63 -> getStringSet + 1:11:void put(java.lang.String,java.lang.Object,java.lang.Object[]):23:33 -> put + 12:12:void com.common.commonlib.log.Logger.e(java.lang.String,java.lang.Object):0:0 -> put + 12:12:void put(java.lang.String,java.lang.Object,java.lang.Object[]):33 -> put + 13:109:void com.common.commonlib.log.Logger.e(java.lang.String,java.lang.Object,boolean):0:96 -> put + 13:109:void com.common.commonlib.log.Logger.e(java.lang.String,java.lang.Object):65 -> put + 13:109:void put(java.lang.String,java.lang.Object,java.lang.Object[]):33 -> put +com.common.commonlib.utils.PhotoUtils -> com.common.commonlib.utils.PhotoUtils: +# {"id":"sourceFile","fileName":"PhotoUtils.kt"} + 1:1:void ():18:18 -> + 1:5:void compressPicture(android.content.Context,java.io.File,java.lang.String,top.zibin.luban.OnCompressListener):73:77 -> compressPicture + 1:10:java.io.File generatePhotoFileByTime(android.content.Context,java.lang.String):25:34 -> generatePhotoFileByTime + 1:6:java.lang.String getImageParentPath(android.content.Context,java.lang.String):54:59 -> getImageParentPath + 1:9:java.lang.String initPicPath(android.content.Context,java.lang.String):39:47 -> initPicPath + 1:1:void tans2PNGSave(java.io.File,java.lang.String,com.common.commonlib.utils.PhotoUtils$TransformCallBack):86:86 -> tans2PNGSave +com.common.commonlib.utils.PhotoUtils$TransformCallBack -> com.common.commonlib.utils.PhotoUtils$TransformCallBack: +# {"id":"sourceFile","fileName":"PhotoUtils.kt"} +com.common.commonlib.utils.PhotoUtils$tans2PNGSave$1 -> com.common.commonlib.utils.PhotoUtils$tans2PNGSave$1: +# {"id":"sourceFile","fileName":"PhotoUtils.kt"} + 1:12:java.lang.Object invokeSuspend(java.lang.Object):86:97 -> invokeSuspend + 13:22:java.lang.Object invokeSuspend(java.lang.Object):95:104 -> invokeSuspend + 23:30:java.lang.Object invokeSuspend(java.lang.Object):97:104 -> invokeSuspend +com.common.commonlib.utils.PhotoUtils$tans2PNGSave$1$1 -> com.common.commonlib.utils.PhotoUtils$tans2PNGSave$1$1: +# {"id":"sourceFile","fileName":"PhotoUtils.kt"} + 1:4:java.lang.Object invokeSuspend(java.lang.Object):100:103 -> invokeSuspend +com.common.commonlib.utils.SpManager -> com.common.commonlib.utils.SpManager: +# {"id":"sourceFile","fileName":"SpManager.kt"} + 1:1:void ():13:13 -> + 1:1:boolean getBoolean(java.lang.String):30:30 -> getBoolean + 1:1:int getInt(java.lang.String):39:39 -> getInt + 1:1:long getLong(java.lang.String):57:57 -> getLong + 1:1:java.lang.String getString(java.lang.String):48:48 -> getString + 1:2:void init(android.content.Context,java.lang.String,int):20:21 -> init + 1:2:void putBoolean(java.lang.String,boolean):25:26 -> putBoolean + 1:2:void putInt(java.lang.String,int):34:35 -> putInt + 1:2:void putLong(java.lang.String,long):52:53 -> putLong + 1:2:void putString(java.lang.String,java.lang.String):43:44 -> putString +com.common.commonlib.utils.StringUtils -> com.common.commonlib.utils.StringUtils: +# {"id":"sourceFile","fileName":"StringUtils.kt"} + 1:1:void ():8:8 -> + 1:4:java.util.List getListByString(java.lang.String):29:32 -> getListByString + 1:6:java.lang.String getStringSeparateByLine(java.util.List):14:19 -> getStringSeparateByLine +com.common.commonlib.view.CommonTitleView -> com.common.commonlib.view.CommonTitleView: + android.content.Context mContext -> a + java.lang.String title -> g + int bgColor -> o + int fixedHeight -> i + int showStyle -> f + android.widget.ImageView ivRight -> d + android.widget.ImageView ivLeft -> b + com.common.commonlib.view.CommonTitleView$RightIconListener rightIconListener -> q + android.view.View rlRightHotZone -> k + android.view.View rlLeftHotZone -> j + android.widget.TextView tv_title -> c + android.view.View rlEditZone -> l + android.widget.TextView tv_edit -> e + android.graphics.drawable.Drawable leftDrawable -> m + com.common.commonlib.view.CommonTitleView$EditListener editListener -> r + boolean isFixedHeight -> h + android.graphics.drawable.Drawable rightDrawable -> n + com.common.commonlib.view.CommonTitleView$LeftIconListener leftIconListener -> p + 1:1:void (android.content.Context):52:52 -> + 2:2:void (android.content.Context,android.util.AttributeSet):56:56 -> + 3:5:void (android.content.Context,android.util.AttributeSet,int):60:62 -> + 1:10:void initAttrs(android.util.AttributeSet):89:98 -> a + 1:10:void init(android.util.AttributeSet):66 -> a + 11:25:void init(android.util.AttributeSet):67:81 -> a + 26:86:void initStyle():114:174 -> a + 26:86:void init(android.util.AttributeSet):83 -> a + 87:96:void initStyle():156:165 -> a + 87:96:void init(android.util.AttributeSet):83 -> a + 97:102:void initStyle():140:145 -> a + 97:102:void init(android.util.AttributeSet):83 -> a + 103:124:void initStyle():132:153 -> a + 103:124:void init(android.util.AttributeSet):83 -> a + 125:130:void initStyle():124:129 -> a + 125:130:void init(android.util.AttributeSet):83 -> a + 131:136:void initStyle():116:121 -> a + 131:136:void init(android.util.AttributeSet):83 -> a + 137:137:void init(android.util.AttributeSet):84:84 -> a + 1:3:int getEditState():234:236 -> getEditState + 1:12:void onClick(android.view.View):187:198 -> onClick + 1:5:void onMeasure(int,int):105:109 -> onMeasure + 1:1:void setEditListener(com.common.commonlib.view.CommonTitleView$EditListener):230:230 -> setEditListener + 1:5:void setEditText(int):245:249 -> setEditText + 1:1:void setLeftIconListener(com.common.commonlib.view.CommonTitleView$LeftIconListener):210:210 -> setLeftIconListener + 1:1:void setRightIconListener(com.common.commonlib.view.CommonTitleView$RightIconListener):220:220 -> setRightIconListener + 1:2:void setTitle(java.lang.String):180:181 -> setTitle +com.common.commonlib.view.MikeView -> com.common.commonlib.view.MikeView: + int mImageW -> n + int mImageH -> o + int mCurrentPosition -> l + int mBackgroundRound -> m + int SCREEN_WIDTH -> j + int mVoiceRectStart -> k + int SCREEN_HEIGHT -> i + int mVoiceRectMarginTop -> d + int mVoiceRectH -> b + int mInterval -> c + android.graphics.Bitmap mVoiceRecording -> h + int mVoiceRectW -> a + android.graphics.Paint mBackgroundPaint -> g + android.graphics.Paint mVoiceRectPaint -> f + android.graphics.Paint mImagePaint -> e + int mMarginTop -> p + int mTextMarginBottom -> q + 1:1:void (android.content.Context):55:55 -> + 2:2:void (android.content.Context,android.util.AttributeSet):59:59 -> + 3:3:void (android.content.Context,android.util.AttributeSet,int):63:63 -> + 4:39:void (android.content.Context,android.util.AttributeSet,int):29:64 -> + 1:22:void init():68:89 -> a + 23:31:android.graphics.Bitmap scaleBitmap(android.graphics.Bitmap,int,int):149:157 -> a + 23:31:void init():89 -> a + 32:34:void init():89:91 -> a + 1:21:void onDraw(android.graphics.Canvas):96:116 -> onDraw + 1:2:void setIndex(int):128:129 -> setIndex + 3:3:void setIndex(double):134:134 -> setIndex +com.common.commonlib.view.SlideRecyclerView -> com.common.commonlib.view.SlideRecyclerView: + android.view.VelocityTracker mVelocityTracker -> a + int mPosition -> j + float mFirstY -> g + int mMenuViewWidth -> k + android.widget.Scroller mScroller -> d + float mFirstX -> f + float mLastX -> e + android.view.ViewGroup mFlingView -> i + boolean mIsSlide -> h + int mTouchSlop -> b + android.graphics.Rect mTouchFrame -> c + 1:1:void (android.content.Context):41:41 -> + 2:2:void (android.content.Context,android.util.AttributeSet):45:45 -> + 3:5:void (android.content.Context,android.util.AttributeSet,int):49:51 -> + 1:4:void releaseVelocity():164:167 -> a + 1:2:void closeMenu():213:214 -> closeMenu + 1:3:void computeScroll():202:204 -> computeScroll + 1:2:boolean onInterceptTouchEvent(android.view.MotionEvent):56:57 -> onInterceptTouchEvent + 3:6:void obtainVelocity(android.view.MotionEvent):172:175 -> onInterceptTouchEvent + 3:6:boolean onInterceptTouchEvent(android.view.MotionEvent):58 -> onInterceptTouchEvent + 7:48:boolean onInterceptTouchEvent(android.view.MotionEvent):59:100 -> onInterceptTouchEvent + 49:91:boolean onInterceptTouchEvent(android.view.MotionEvent):61:103 -> onInterceptTouchEvent + 1:2:boolean onTouchEvent(android.view.MotionEvent):108:109 -> onTouchEvent + 3:6:void obtainVelocity(android.view.MotionEvent):172:175 -> onTouchEvent + 3:6:boolean onTouchEvent(android.view.MotionEvent):110 -> onTouchEvent + 7:56:boolean onTouchEvent(android.view.MotionEvent):111:160 -> onTouchEvent + 1:14:int pointToPosition(int,int):179:192 -> pointToPosition +com.common.commonlib.view.SpaceItemDecoration -> com.common.commonlib.view.SpaceItemDecoration: + 1:1:void ():16:16 -> + 1:7:void getItemOffsets(android.graphics.Rect,android.view.View,androidx.recyclerview.widget.RecyclerView,androidx.recyclerview.widget.RecyclerView$State):29:35 -> getItemOffsets +com.common.commonlib.view.activity.BaseActivity -> com.common.commonlib.view.activity.BaseActivity: + 1:1:void ():18:18 -> + 1:1:void onCreate(android.os.Bundle):22:22 -> onCreate + 2:7:void initStatusBar():27:32 -> onCreate + 2:7:void onCreate(android.os.Bundle):23 -> onCreate +com.common.commonlib.view.activity.PermissionCompatActivity -> com.common.commonlib.view.activity.PermissionCompatActivity: + android.app.Dialog mPermissionDialog -> a + 1:1:void ():35:35 -> + 1:1:void onCreate(android.os.Bundle):44:44 -> onCreate + 1:6:void onRequestPermissionsResult(int,java.lang.String[],int[]):81:86 -> onRequestPermissionsResult + 7:28:void showPermissionDialog():102:123 -> onRequestPermissionsResult + 7:28:void onRequestPermissionsResult(int,java.lang.String[],int[]):93 -> onRequestPermissionsResult + 29:29:void onRequestPermissionsResult(int,java.lang.String[],int[]):96:96 -> onRequestPermissionsResult + 1:1:void onRestart():49:49 -> onRestart +com.common.commonlib.view.activity.PermissionCompatActivity$1 -> b.a: + com.common.commonlib.view.activity.PermissionCompatActivity this$0 -> a + 1:1:void (com.common.commonlib.view.activity.PermissionCompatActivity):114:114 -> + 1:1:void onClick(android.content.DialogInterface,int):118:118 -> onClick + 2:3:void com.common.commonlib.view.activity.PermissionCompatActivity.cancelPermissionDialog():128:129 -> onClick + 2:3:void com.common.commonlib.view.activity.PermissionCompatActivity.access$000(com.common.commonlib.view.activity.PermissionCompatActivity):35 -> onClick + 2:3:void onClick(android.content.DialogInterface,int):118 -> onClick + 4:4:void onClick(android.content.DialogInterface,int):119:119 -> onClick +com.common.commonlib.view.activity.PermissionCompatActivity$2 -> b.b: + com.common.commonlib.view.activity.PermissionCompatActivity this$0 -> a + 1:1:void (com.common.commonlib.view.activity.PermissionCompatActivity):105:105 -> + 1:1:void onClick(android.content.DialogInterface,int):108:108 -> onClick + 2:3:void com.common.commonlib.view.activity.PermissionCompatActivity.cancelPermissionDialog():128:129 -> onClick + 2:3:void com.common.commonlib.view.activity.PermissionCompatActivity.access$000(com.common.commonlib.view.activity.PermissionCompatActivity):35 -> onClick + 2:3:void onClick(android.content.DialogInterface,int):108 -> onClick + 4:7:void onClick(android.content.DialogInterface,int):109:112 -> onClick +com.common.commonlib.view.fragment.BaseFragment -> com.common.commonlib.view.fragment.BaseFragment: + 1:1:void ():10:10 -> + 1:2:void onAttach(android.content.Context):15:16 -> onAttach +com.common.commonlib.view.fragment.BaseNavigationFragment -> com.common.commonlib.view.fragment.BaseNavigationFragment: +# {"id":"sourceFile","fileName":"BaseNavigationFragment.kt"} + android.view.View lastView -> b + boolean isNavigationViewInit -> a + 1:1:void ():8:8 -> + 1:1:boolean getIsNavigationViewInit():53:53 -> getIsNavigationViewInit + 1:10:android.view.View onCreateView(android.view.LayoutInflater,android.view.ViewGroup,android.os.Bundle):16:25 -> onCreateView + 1:6:void onViewCreated(android.view.View,android.os.Bundle):29:34 -> onViewCreated + 1:1:void setIsNavigationViewInit(boolean):57:57 -> setIsNavigationViewInit +com.common.commonlib.view.viewpagerlayoutmanager.AutoPlayRecyclerView -> com.common.commonlib.view.viewpagerlayoutmanager.AutoPlayRecyclerView: + com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper autoPlaySnapHelper -> a + 1:1:void (android.content.Context):17:17 -> + 2:2:void (android.content.Context,android.util.AttributeSet):21:21 -> + 3:8:void (android.content.Context,android.util.AttributeSet,int):25:30 -> + 1:9:boolean dispatchTouchEvent(android.view.MotionEvent):35:43 -> dispatchTouchEvent + 10:12:void com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper.start():91:93 -> dispatchTouchEvent + 10:12:boolean dispatchTouchEvent(android.view.MotionEvent):44 -> dispatchTouchEvent + 13:13:boolean dispatchTouchEvent(android.view.MotionEvent):38:38 -> dispatchTouchEvent + 14:16:void com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper.pause():84:86 -> dispatchTouchEvent + 14:16:boolean dispatchTouchEvent(android.view.MotionEvent):39 -> dispatchTouchEvent + 1:2:void setLayoutManager(androidx.recyclerview.widget.RecyclerView$LayoutManager):60:61 -> setLayoutManager + 3:3:void com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper.attachToRecyclerView(androidx.recyclerview.widget.RecyclerView):38:38 -> setLayoutManager + 3:3:void setLayoutManager(androidx.recyclerview.widget.RecyclerView$LayoutManager):61 -> setLayoutManager + 4:5:void com.common.commonlib.view.viewpagerlayoutmanager.CenterSnapHelper.destroyCallbacks():155:156 -> setLayoutManager + 4:5:void com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper.destroyCallbacks():76 -> setLayoutManager + 4:5:void com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper.attachToRecyclerView(androidx.recyclerview.widget.RecyclerView):42 -> setLayoutManager + 4:5:void setLayoutManager(androidx.recyclerview.widget.RecyclerView$LayoutManager):61 -> setLayoutManager + 6:8:void com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper.destroyCallbacks():77:79 -> setLayoutManager + 6:8:void com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper.attachToRecyclerView(androidx.recyclerview.widget.RecyclerView):42 -> setLayoutManager + 6:8:void setLayoutManager(androidx.recyclerview.widget.RecyclerView$LayoutManager):61 -> setLayoutManager + 9:12:void com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper.attachToRecyclerView(androidx.recyclerview.widget.RecyclerView):44:47 -> setLayoutManager + 9:12:void setLayoutManager(androidx.recyclerview.widget.RecyclerView$LayoutManager):61 -> setLayoutManager + 13:17:void com.common.commonlib.view.viewpagerlayoutmanager.CenterSnapHelper.setupCallbacks():144:148 -> setLayoutManager + 13:17:void com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper.attachToRecyclerView(androidx.recyclerview.widget.RecyclerView):49 -> setLayoutManager + 13:17:void setLayoutManager(androidx.recyclerview.widget.RecyclerView$LayoutManager):61 -> setLayoutManager + 18:21:void com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper.attachToRecyclerView(androidx.recyclerview.widget.RecyclerView):50:53 -> setLayoutManager + 18:21:void setLayoutManager(androidx.recyclerview.widget.RecyclerView$LayoutManager):61 -> setLayoutManager + 22:27:void com.common.commonlib.view.viewpagerlayoutmanager.ViewPagerLayoutManager.setInfinite(boolean):822:827 -> setLayoutManager + 22:27:void com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper.attachToRecyclerView(androidx.recyclerview.widget.RecyclerView):56 -> setLayoutManager + 22:27:void setLayoutManager(androidx.recyclerview.widget.RecyclerView$LayoutManager):61 -> setLayoutManager + 28:40:void com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper.attachToRecyclerView(androidx.recyclerview.widget.RecyclerView):58:70 -> setLayoutManager + 28:40:void setLayoutManager(androidx.recyclerview.widget.RecyclerView$LayoutManager):61 -> setLayoutManager + 41:41:void com.common.commonlib.view.viewpagerlayoutmanager.CenterSnapHelper.setupCallbacks():145:145 -> setLayoutManager + 41:41:void com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper.attachToRecyclerView(androidx.recyclerview.widget.RecyclerView):49 -> setLayoutManager + 41:41:void setLayoutManager(androidx.recyclerview.widget.RecyclerView$LayoutManager):61 -> setLayoutManager +com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper -> com.common.commonlib.view.viewpagerlayoutmanager.b: + android.os.Handler handler -> e + java.lang.Runnable autoPlayRunnable -> g + int direction -> i + int timeInterval -> f + boolean runnableAdded -> h + 1:6:void (int,int):28:33 -> + 1:1:void checkDirection(int):109:109 -> a + 1:1:void checkTimeInterval(int):114:114 -> b +com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper$1 -> com.common.commonlib.view.viewpagerlayoutmanager.a: + com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper this$0 -> b + androidx.recyclerview.widget.RecyclerView$LayoutManager val$layoutManager -> a + 1:1:void (com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper,androidx.recyclerview.widget.RecyclerView$LayoutManager):58:58 -> + 1:4:void run():61:64 -> run + 5:5:int com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper.access$000(com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper):16:16 -> run + 5:5:void run():65 -> run + 6:8:void run():64:66 -> run + 9:9:android.os.Handler com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper.access$300(com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper):16:16 -> run + 9:9:void run():66 -> run + 10:10:java.lang.Runnable com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper.access$100(com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper):16:16 -> run + 10:10:void run():66 -> run + 11:11:int com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper.access$200(com.common.commonlib.view.viewpagerlayoutmanager.AutoPlaySnapHelper):16:16 -> run + 11:11:void run():66 -> run + 12:12:void run():66:66 -> run +com.common.commonlib.view.viewpagerlayoutmanager.CarouselLayoutManager -> com.common.commonlib.view.viewpagerlayoutmanager.CarouselLayoutManager: + float getDistanceRatio() -> f + 1:1:float setInterval():83:83 -> k +com.common.commonlib.view.viewpagerlayoutmanager.CenterSnapHelper -> com.common.commonlib.view.viewpagerlayoutmanager.CenterSnapHelper: + androidx.recyclerview.widget.RecyclerView$OnScrollListener mScrollListener -> d + android.widget.Scroller mGravityScroller -> b + androidx.recyclerview.widget.RecyclerView mRecyclerView -> a + boolean snapToCenter -> c + 1:14:void ():9:22 -> + 1:1:boolean access$002(com.common.commonlib.view.viewpagerlayoutmanager.CenterSnapHelper,boolean):9:9 -> a + 2:5:int com.common.commonlib.view.viewpagerlayoutmanager.ViewPagerLayoutManager.getOffsetToCenter():798:801 -> a + 2:5:void snapToCenterView(com.common.commonlib.view.viewpagerlayoutmanager.ViewPagerLayoutManager,com.common.commonlib.view.viewpagerlayoutmanager.ViewPagerLayoutManager$OnPageChangeListener):124 -> a + 6:13:void snapToCenterView(com.common.commonlib.view.viewpagerlayoutmanager.ViewPagerLayoutManager,com.common.commonlib.view.viewpagerlayoutmanager.ViewPagerLayoutManager$OnPageChangeListener):126:133 -> a + 1:5:boolean onFling(int,int):60:64 -> onFling + 6:6:boolean com.common.commonlib.view.viewpagerlayoutmanager.ViewPagerLayoutManager.getInfinite():818:818 -> onFling + 6:6:boolean onFling(int,int):69 -> onFling + 7:30:boolean onFling(int,int):69:92 -> onFling +com.common.commonlib.view.viewpagerlayoutmanager.CenterSnapHelper$1 -> com.common.commonlib.view.viewpagerlayoutmanager.CenterSnapHelper$a: + com.common.commonlib.view.viewpagerlayoutmanager.CenterSnapHelper this$0 -> b + boolean mScrolled -> a + 1:3:void (com.common.commonlib.view.viewpagerlayoutmanager.CenterSnapHelper):23:25 -> + 1:13:void onScrollStateChanged(androidx.recyclerview.widget.RecyclerView,int):29:41 -> onScrollStateChanged + 14:14:boolean com.common.commonlib.view.viewpagerlayoutmanager.CenterSnapHelper.access$000(com.common.commonlib.view.viewpagerlayoutmanager.CenterSnapHelper):9:9 -> onScrollStateChanged + 14:14:void onScrollStateChanged(androidx.recyclerview.widget.RecyclerView,int):41 -> onScrollStateChanged + 15:18:void onScrollStateChanged(androidx.recyclerview.widget.RecyclerView,int):42:45 -> onScrollStateChanged + 1:1:void onScrolled(androidx.recyclerview.widget.RecyclerView,int,int):53:53 -> onScrolled +com.common.commonlib.view.viewpagerlayoutmanager.CircleLayoutManager -> com.common.commonlib.view.viewpagerlayoutmanager.CircleLayoutManager: + int radius -> q + float getDistanceRatio() -> f + float maxRemoveOffset() -> i + float minRemoveOffset() -> j + float setInterval() -> k + 1:1:void setUp():181:181 -> l +com.common.commonlib.view.viewpagerlayoutmanager.CircleScaleLayoutManager -> com.common.commonlib.view.viewpagerlayoutmanager.CircleScaleLayoutManager: + int radius -> q + float getDistanceRatio() -> f + float maxRemoveOffset() -> i + float minRemoveOffset() -> j + float setInterval() -> k + 1:1:void setUp():194:194 -> l +com.common.commonlib.view.viewpagerlayoutmanager.GalleryLayoutManager -> com.common.commonlib.view.viewpagerlayoutmanager.GalleryLayoutManager: + float getDistanceRatio() -> f + 1:1:float setInterval():138:138 -> k +com.common.commonlib.view.viewpagerlayoutmanager.OrientationHelper -> com.common.commonlib.view.viewpagerlayoutmanager.e: + androidx.recyclerview.widget.RecyclerView$LayoutManager mLayoutManager -> a + 1:1:void (androidx.recyclerview.widget.RecyclerView$LayoutManager,com.common.commonlib.view.viewpagerlayoutmanager.OrientationHelper$1):26:26 -> + 2:2:void (androidx.recyclerview.widget.RecyclerView$LayoutManager):40:40 -> + 3:6:void (androidx.recyclerview.widget.RecyclerView$LayoutManager):38:41 -> + int getDecoratedMeasurement(android.view.View) -> a + int getStartAfterPadding() -> a + int getDecoratedMeasurementInOther(android.view.View) -> b + int getTotalSpace() -> b + int getTotalSpaceInOther() -> c +com.common.commonlib.view.viewpagerlayoutmanager.OrientationHelper$1 -> com.common.commonlib.view.viewpagerlayoutmanager.c: + 1:1:void (androidx.recyclerview.widget.RecyclerView$LayoutManager):233:233 -> + 1:1:int getStartAfterPadding():246:246 -> a + 2:3:int getDecoratedMeasurement(android.view.View):252:253 -> a + 1:2:int getDecoratedMeasurementInOther(android.view.View):260:261 -> b + 3:4:int getTotalSpace():293:294 -> b + 1:2:int getTotalSpaceInOther():299:300 -> c +com.common.commonlib.view.viewpagerlayoutmanager.OrientationHelper$2 -> com.common.commonlib.view.viewpagerlayoutmanager.d: + 1:1:void (androidx.recyclerview.widget.RecyclerView$LayoutManager):327:327 -> + 1:1:int getStartAfterPadding():340:340 -> a + 2:3:int getDecoratedMeasurement(android.view.View):346:347 -> a + 1:2:int getDecoratedMeasurementInOther(android.view.View):354:355 -> b + 3:4:int getTotalSpace():387:388 -> b + 1:2:int getTotalSpaceInOther():393:394 -> c +com.common.commonlib.view.viewpagerlayoutmanager.PageSnapHelper -> com.common.commonlib.view.viewpagerlayoutmanager.PageSnapHelper: + 1:1:void ():5:5 -> + 1:5:boolean onFling(int,int):9:13 -> onFling + 6:6:boolean com.common.commonlib.view.viewpagerlayoutmanager.ViewPagerLayoutManager.getInfinite():818:818 -> onFling + 6:6:boolean onFling(int,int):18 -> onFling + 7:28:boolean onFling(int,int):18:39 -> onFling +com.common.commonlib.view.viewpagerlayoutmanager.RotateLayoutManager -> com.common.commonlib.view.viewpagerlayoutmanager.RotateLayoutManager: + float getDistanceRatio() -> f + 1:1:float setInterval():94:94 -> k +com.common.commonlib.view.viewpagerlayoutmanager.ScaleLayoutManager -> com.common.commonlib.view.viewpagerlayoutmanager.ScaleLayoutManager: + float getDistanceRatio() -> f + 1:1:float setInterval():109:109 -> k +com.common.commonlib.view.viewpagerlayoutmanager.ScrollHelper -> com.common.commonlib.view.viewpagerlayoutmanager.f: + 1:5:void smoothScrollToPosition(androidx.recyclerview.widget.RecyclerView,com.common.commonlib.view.viewpagerlayoutmanager.ViewPagerLayoutManager,int):10:14 -> a +com.common.commonlib.view.viewpagerlayoutmanager.ViewPagerLayoutManager -> com.common.commonlib.view.viewpagerlayoutmanager.ViewPagerLayoutManager: + boolean mRecycleChildrenOnDetach -> n + int mPendingScrollPosition -> l + boolean mInfinite -> o + boolean mReverseLayout -> i + float mInterval -> g + boolean mShouldReverseLayout -> j + com.common.commonlib.view.viewpagerlayoutmanager.OrientationHelper mOrientationHelper -> f + int mOrientation -> h + boolean mSmoothScrollbarEnabled -> k + float mOffset -> e + int mSpaceInOther -> d + com.common.commonlib.view.viewpagerlayoutmanager.ViewPagerLayoutManager$SavedState mPendingSavedState -> m + int mDecoratedMeasurementInOther -> b + int mSpaceMain -> c + int mDecoratedMeasurement -> a + int mDistanceToBottom -> p + 1:5:android.view.View getMeasureView(androidx.recyclerview.widget.RecyclerView$Recycler,androidx.recyclerview.widget.RecyclerView$State,int):386:390 -> a + 6:14:int computeScrollExtent():520:528 -> a + 15:36:int scrollBy(int,androidx.recyclerview.widget.RecyclerView$Recycler,androidx.recyclerview.widget.RecyclerView$State):560:581 -> a + 37:38:void layoutItems(androidx.recyclerview.widget.RecyclerView$Recycler):590:591 -> a + 37:38:int scrollBy(int,androidx.recyclerview.widget.RecyclerView$Recycler,androidx.recyclerview.widget.RecyclerView$State):584 -> a + 39:44:int getOffsetToPosition(int):805:810 -> a + 1:7:int computeScrollOffset():506:512 -> b + 8:19:float getOffsetOfRightAdapterPosition():778:789 -> b + 8:19:int computeScrollOffset():515 -> b + 20:20:int computeScrollOffset():516:516 -> b + 1:9:int computeScrollRange():532:540 -> c + 1:1:boolean canScrollHorizontally():200:200 -> canScrollHorizontally + 1:1:boolean canScrollVertically():208:208 -> canScrollVertically + 1:1:int computeHorizontalScrollExtent(androidx.recyclerview.widget.RecyclerView$State):487:487 -> computeHorizontalScrollExtent + 1:1:int computeHorizontalScrollOffset(androidx.recyclerview.widget.RecyclerView$State):477:477 -> computeHorizontalScrollOffset + 1:1:int computeHorizontalScrollRange(androidx.recyclerview.widget.RecyclerView$State):497:497 -> computeHorizontalScrollRange + 1:1:int computeVerticalScrollExtent(androidx.recyclerview.widget.RecyclerView$State):492:492 -> computeVerticalScrollExtent + 1:1:int computeVerticalScrollOffset(androidx.recyclerview.widget.RecyclerView$State):482:482 -> computeVerticalScrollOffset + 1:1:int computeVerticalScrollRange(androidx.recyclerview.widget.RecyclerView$State):502:502 -> computeVerticalScrollRange + 1:14:int getCurrentPosition():726:739 -> d + 1:2:int getCurrentPositionOffset():769:770 -> e + 1:2:void ensureLayoutState():445:446 -> ensureLayoutState + 3:3:com.common.commonlib.view.viewpagerlayoutmanager.OrientationHelper com.common.commonlib.view.viewpagerlayoutmanager.OrientationHelper.createVerticalHelper(androidx.recyclerview.widget.RecyclerView$LayoutManager):327:327 -> ensureLayoutState + 3:3:com.common.commonlib.view.viewpagerlayoutmanager.OrientationHelper com.common.commonlib.view.viewpagerlayoutmanager.OrientationHelper.createOrientationHelper(androidx.recyclerview.widget.RecyclerView$LayoutManager,int):220 -> ensureLayoutState + 3:3:void ensureLayoutState():446 -> ensureLayoutState + 4:4:com.common.commonlib.view.viewpagerlayoutmanager.OrientationHelper com.common.commonlib.view.viewpagerlayoutmanager.OrientationHelper.createOrientationHelper(androidx.recyclerview.widget.RecyclerView$LayoutManager,int):222:222 -> ensureLayoutState + 4:4:void ensureLayoutState():446 -> ensureLayoutState + 5:5:com.common.commonlib.view.viewpagerlayoutmanager.OrientationHelper com.common.commonlib.view.viewpagerlayoutmanager.OrientationHelper.createHorizontalHelper(androidx.recyclerview.widget.RecyclerView$LayoutManager):233:233 -> ensureLayoutState + 5:5:com.common.commonlib.view.viewpagerlayoutmanager.OrientationHelper com.common.commonlib.view.viewpagerlayoutmanager.OrientationHelper.createOrientationHelper(androidx.recyclerview.widget.RecyclerView$LayoutManager,int):218 -> ensureLayoutState + 5:5:void ensureLayoutState():446 -> ensureLayoutState + 6:6:void ensureLayoutState():446:446 -> ensureLayoutState + float getDistanceRatio() -> f + 1:3:android.view.View findViewByPosition(int):744:746 -> findViewByPosition + 1:1:float getMaxOffset():677:677 -> g + 1:1:androidx.recyclerview.widget.RecyclerView$LayoutParams generateDefaultLayoutParams():134:134 -> generateDefaultLayoutParams + 1:1:int getOrientation():218:218 -> getOrientation + 1:1:boolean getRecycleChildrenOnDetach():146:146 -> getRecycleChildrenOnDetach + 1:1:boolean getReverseLayout():285:285 -> getReverseLayout + 1:1:float getMinOffset():681:681 -> h + 1:1:float maxRemoveOffset():710:710 -> i + 1:1:float minRemoveOffset():718:718 -> j + float setInterval() -> k + void setUp() -> l + 1:2:void onAdapterChanged(androidx.recyclerview.widget.RecyclerView$Adapter,androidx.recyclerview.widget.RecyclerView$Adapter):463:464 -> onAdapterChanged + 1:4:boolean onAddFocusables(androidx.recyclerview.widget.RecyclerView,java.util.ArrayList,int,int):403:406 -> onAddFocusables + 5:17:int getMovement(int):425:437 -> onAddFocusables + 5:17:boolean onAddFocusables(androidx.recyclerview.widget.RecyclerView,java.util.ArrayList,int,int):407 -> onAddFocusables + 18:21:boolean onAddFocusables(androidx.recyclerview.widget.RecyclerView,java.util.ArrayList,int,int):411:414 -> onAddFocusables + 1:4:void onDetachedFromWindow(androidx.recyclerview.widget.RecyclerView,androidx.recyclerview.widget.RecyclerView$Recycler):168:171 -> onDetachedFromWindow + 1:7:void onLayoutChildren(androidx.recyclerview.widget.RecyclerView$Recycler,androidx.recyclerview.widget.RecyclerView$State):334:340 -> onLayoutChildren + 8:11:void resolveShouldLayoutReverse():271:274 -> onLayoutChildren + 8:11:void onLayoutChildren(androidx.recyclerview.widget.RecyclerView$Recycler,androidx.recyclerview.widget.RecyclerView$State):341 -> onLayoutChildren + 12:12:void resolveShouldLayoutReverse():272:272 -> onLayoutChildren + 12:12:void onLayoutChildren(androidx.recyclerview.widget.RecyclerView$Recycler,androidx.recyclerview.widget.RecyclerView$State):341 -> onLayoutChildren + 13:48:void onLayoutChildren(androidx.recyclerview.widget.RecyclerView$Recycler,androidx.recyclerview.widget.RecyclerView$State):344:379 -> onLayoutChildren + 49:50:void layoutItems(androidx.recyclerview.widget.RecyclerView$Recycler):590:591 -> onLayoutChildren + 49:50:void onLayoutChildren(androidx.recyclerview.widget.RecyclerView$Recycler,androidx.recyclerview.widget.RecyclerView$State):382 -> onLayoutChildren + 1:3:void onLayoutCompleted(androidx.recyclerview.widget.RecyclerView$State):396:398 -> onLayoutCompleted + 1:3:void onRestoreInstanceState(android.os.Parcelable):189:191 -> onRestoreInstanceState + 1:7:android.os.Parcelable onSaveInstanceState():177:183 -> onSaveInstanceState + 1:4:int scrollHorizontallyBy(int,androidx.recyclerview.widget.RecyclerView$Recycler,androidx.recyclerview.widget.RecyclerView$State):545:548 -> scrollHorizontallyBy + 1:4:void scrollToPosition(int):469:472 -> scrollToPosition + 1:4:int scrollVerticallyBy(int,androidx.recyclerview.widget.RecyclerView$Recycler,androidx.recyclerview.widget.RecyclerView$State):553:556 -> scrollVerticallyBy + 1:10:void setOrientation(int):229:238 -> setOrientation + 1:1:void setRecycleChildrenOnDetach(boolean):163:163 -> setRecycleChildrenOnDetach + 1:6:void setReverseLayout(boolean):289:294 -> setReverseLayout + 1:1:void setSmoothScrollbarEnabled(boolean):882:882 -> setSmoothScrollbarEnabled + 1:23:void smoothScrollToPosition(androidx.recyclerview.widget.RecyclerView,androidx.recyclerview.widget.RecyclerView$State,int):306:328 -> smoothScrollToPosition +com.common.commonlib.view.viewpagerlayoutmanager.ViewPagerLayoutManager$OnPageChangeListener -> com.common.commonlib.view.viewpagerlayoutmanager.ViewPagerLayoutManager$a: +com.common.commonlib.view.viewpagerlayoutmanager.ViewPagerLayoutManager$SavedState -> com.common.commonlib.view.viewpagerlayoutmanager.ViewPagerLayoutManager$SavedState: + float offset -> b + boolean isReverseLayout -> c + int position -> a + 1:1:void ():892:892 -> + 1:1:void ():908:908 -> + 2:5:void (android.os.Parcel):912:915 -> + 6:9:void (com.common.commonlib.view.viewpagerlayoutmanager.ViewPagerLayoutManager$SavedState):918:921 -> + 1:3:void writeToParcel(android.os.Parcel,int):931:933 -> writeToParcel +com.common.commonlib.view.viewpagerlayoutmanager.ViewPagerLayoutManager$SavedState$1 -> com.common.commonlib.view.viewpagerlayoutmanager.ViewPagerLayoutManager$SavedState$a: + 1:1:void ():893:893 -> + 1:1:com.common.commonlib.view.viewpagerlayoutmanager.ViewPagerLayoutManager$SavedState createFromParcel(android.os.Parcel):896:896 -> createFromParcel + 1:1:java.lang.Object createFromParcel(android.os.Parcel):893 -> createFromParcel + 1:1:com.common.commonlib.view.viewpagerlayoutmanager.ViewPagerLayoutManager$SavedState[] newArray(int):901:901 -> newArray + 1:1:java.lang.Object[] newArray(int):893 -> newArray +org.apache.commons.net.MalformedServerReplyException -> c.a: + 1:1:void (java.lang.String):53:53 -> +org.apache.commons.net.ProtocolCommandEvent -> c.b: + 1:5:void (java.lang.Object,java.lang.String,java.lang.String):57:61 -> + 6:10:void (java.lang.Object,int,java.lang.String):81:85 -> +org.apache.commons.net.ProtocolCommandListener -> c.c: + void protocolCommandSent(org.apache.commons.net.ProtocolCommandEvent) -> a + void protocolReplyReceived(org.apache.commons.net.ProtocolCommandEvent) -> b +org.apache.commons.net.ProtocolCommandSupport -> c.d: + 1:3:void (java.lang.Object):50:52 -> + 1:5:void fireCommandSent(java.lang.String,java.lang.String):71:75 -> a + 6:10:void fireReplyReceived(int,java.lang.String):95:99 -> a + 11:11:int getListenerCount():131:131 -> a +org.apache.commons.net.SocketClient -> c.e: + javax.net.SocketFactory _socketFactory_ -> d + javax.net.ServerSocketFactory _serverSocketFactory_ -> e + javax.net.SocketFactory __DEFAULT_SOCKET_FACTORY -> f + java.io.OutputStream _output_ -> c + javax.net.ServerSocketFactory __DEFAULT_SERVER_SOCKET_FACTORY -> g + java.net.Socket _socket_ -> a + java.io.InputStream _input_ -> b + 1:5:void ():64:68 -> + 1:1:void ():124:124 -> + 2:19:void ():114:131 -> + 1:1:void connect(java.lang.String,int):203:203 -> a + 2:10:void org.apache.commons.net.SocketClient.connect(java.net.InetAddress,int):175:183 -> a + 2:10:void connect(java.lang.String,int):203 -> a + 11:13:void org.apache.commons.net.SocketClient._connectAction_():153:155 -> a + 11:13:void org.apache.commons.net.ftp.FTP._connectAction_():378 -> a + 11:13:void org.apache.commons.net.ftp.FTPClient._connectAction_():924 -> a + 11:13:void org.apache.commons.net.SocketClient.connect(java.net.InetAddress,int):183 -> a + 11:13:void connect(java.lang.String,int):203 -> a + 14:14:void org.apache.commons.net.ftp.FTP._connectAction_():379:379 -> a + 14:14:void org.apache.commons.net.ftp.FTPClient._connectAction_():924 -> a + 14:14:void org.apache.commons.net.SocketClient.connect(java.net.InetAddress,int):183 -> a + 14:14:void connect(java.lang.String,int):203 -> a + 15:15:java.lang.String org.apache.commons.net.ftp.FTP.getControlEncoding():427:427 -> a + 15:15:void org.apache.commons.net.ftp.FTP._connectAction_():379 -> a + 15:15:void org.apache.commons.net.ftp.FTPClient._connectAction_():924 -> a + 15:15:void org.apache.commons.net.SocketClient.connect(java.net.InetAddress,int):183 -> a + 15:15:void connect(java.lang.String,int):203 -> a + 16:18:void org.apache.commons.net.ftp.FTP._connectAction_():379:381 -> a + 16:18:void org.apache.commons.net.ftp.FTPClient._connectAction_():924 -> a + 16:18:void org.apache.commons.net.SocketClient.connect(java.net.InetAddress,int):183 -> a + 16:18:void connect(java.lang.String,int):203 -> a + 19:19:java.lang.String org.apache.commons.net.ftp.FTP.getControlEncoding():427:427 -> a + 19:19:void org.apache.commons.net.ftp.FTP._connectAction_():381 -> a + 19:19:void org.apache.commons.net.ftp.FTPClient._connectAction_():924 -> a + 19:19:void org.apache.commons.net.SocketClient.connect(java.net.InetAddress,int):183 -> a + 19:19:void connect(java.lang.String,int):203 -> a + 20:20:void org.apache.commons.net.ftp.FTP._connectAction_():381:381 -> a + 20:20:void org.apache.commons.net.ftp.FTPClient._connectAction_():924 -> a + 20:20:void org.apache.commons.net.SocketClient.connect(java.net.InetAddress,int):183 -> a + 20:20:void connect(java.lang.String,int):203 -> a + 21:21:void org.apache.commons.net.ftp.FTP.__getReply():294:294 -> a + 21:21:void org.apache.commons.net.ftp.FTP._connectAction_():400 -> a + 21:21:void org.apache.commons.net.ftp.FTPClient._connectAction_():924 -> a + 21:21:void org.apache.commons.net.SocketClient.connect(java.net.InetAddress,int):183 -> a + 21:21:void connect(java.lang.String,int):203 -> a + 22:22:void org.apache.commons.net.ftp.FTP._connectAction_():402:402 -> a + 22:22:void org.apache.commons.net.ftp.FTPClient._connectAction_():924 -> a + 22:22:void org.apache.commons.net.SocketClient.connect(java.net.InetAddress,int):183 -> a + 22:22:void connect(java.lang.String,int):203 -> a + 23:23:void org.apache.commons.net.ftp.FTP.__getReply():294:294 -> a + 23:23:void org.apache.commons.net.ftp.FTP._connectAction_():403 -> a + 23:23:void org.apache.commons.net.ftp.FTPClient._connectAction_():924 -> a + 23:23:void org.apache.commons.net.SocketClient.connect(java.net.InetAddress,int):183 -> a + 23:23:void connect(java.lang.String,int):203 -> a + 24:24:void org.apache.commons.net.ftp.FTPClient._connectAction_():925:925 -> a + 24:24:void org.apache.commons.net.SocketClient.connect(java.net.InetAddress,int):183 -> a + 24:24:void connect(java.lang.String,int):203 -> a +org.apache.commons.net.ftp.FTP -> d.a: + java.lang.String _replyString -> k + boolean _newReplyString -> j + java.io.BufferedReader _controlInput_ -> n + int _replyCode -> h + java.util.ArrayList _replyLines -> i + java.lang.String _controlEncoding -> l + java.io.BufferedWriter _controlOutput_ -> o + org.apache.commons.net.ProtocolCommandSupport _commandSupport_ -> m + 1:7:void ():265:271 -> + 1:1:java.lang.String org.apache.commons.net.ftp.FTPCmd.getCommand():113:113 -> a + 1:1:int org.apache.commons.net.ftp.FTP.sendCommand(org.apache.commons.net.ftp.FTPCmd,java.lang.String):608 -> a + 1:1:int sendCommand(org.apache.commons.net.ftp.FTPCmd):582 -> a + 2:2:int org.apache.commons.net.ftp.FTP.sendCommand(org.apache.commons.net.ftp.FTPCmd,java.lang.String):608:608 -> a + 2:2:int sendCommand(org.apache.commons.net.ftp.FTPCmd):582 -> a + 3:47:void __getReply(boolean):311:355 -> a + 48:48:boolean org.apache.commons.net.ftp.FTP.__lenientCheck(java.lang.String):285:285 -> a + 48:48:void __getReply(boolean):361 -> a + 49:62:void __getReply(boolean):351:364 -> a + 63:76:java.lang.String org.apache.commons.net.ftp.FTP.getReplyString():720:733 -> a + 63:76:void __getReply(boolean):364 -> a + 77:77:org.apache.commons.net.ProtocolCommandSupport org.apache.commons.net.ftp.FTP.getCommandSupport():1789:1789 -> a + 77:77:void org.apache.commons.net.SocketClient.fireReplyReceived(int,java.lang.String):778 -> a + 77:77:void __getReply(boolean):364 -> a + 78:78:void org.apache.commons.net.SocketClient.fireReplyReceived(int,java.lang.String):778:778 -> a + 78:78:void __getReply(boolean):364 -> a + 79:79:org.apache.commons.net.ProtocolCommandSupport org.apache.commons.net.ftp.FTP.getCommandSupport():1789:1789 -> a + 79:79:void org.apache.commons.net.SocketClient.fireReplyReceived(int,java.lang.String):779 -> a + 79:79:void __getReply(boolean):364 -> a + 80:80:void org.apache.commons.net.SocketClient.fireReplyReceived(int,java.lang.String):779:779 -> a + 80:80:void __getReply(boolean):364 -> a + 81:82:void __getReply(boolean):366:367 -> a + 83:83:void __getReply(boolean):337:337 -> a + 84:84:void __getReply(boolean):325:325 -> a + 85:85:void __getReply(boolean):317:317 -> a + 86:96:java.lang.String __buildMessage(java.lang.String,java.lang.String):488:498 -> a + 97:98:void __send(java.lang.String):504:505 -> a + 99:103:boolean org.apache.commons.net.SocketClient.isConnected():346:350 -> a + 99:103:void __send(java.lang.String):509 -> a + 104:108:void __send(java.lang.String):511:515 -> a + 109:132:int eprt(java.net.InetAddress,int):964:987 -> a + 133:133:int org.apache.commons.net.ftp.FTP.sendCommand(org.apache.commons.net.ftp.FTPCmd,java.lang.String):608:608 -> a + 133:133:int eprt(java.net.InetAddress,int):987 -> a + 1:7:int sendCommand(java.lang.String,java.lang.String):473:479 -> b + 8:8:org.apache.commons.net.ProtocolCommandSupport org.apache.commons.net.ftp.FTP.getCommandSupport():1789:1789 -> b + 8:8:void org.apache.commons.net.SocketClient.fireCommandSent(java.lang.String,java.lang.String):791 -> b + 8:8:int sendCommand(java.lang.String,java.lang.String):481 -> b + 9:9:void org.apache.commons.net.SocketClient.fireCommandSent(java.lang.String,java.lang.String):791:791 -> b + 9:9:int sendCommand(java.lang.String,java.lang.String):481 -> b + 10:10:org.apache.commons.net.ProtocolCommandSupport org.apache.commons.net.ftp.FTP.getCommandSupport():1789:1789 -> b + 10:10:void org.apache.commons.net.SocketClient.fireCommandSent(java.lang.String,java.lang.String):792 -> b + 10:10:int sendCommand(java.lang.String,java.lang.String):481 -> b + 11:11:void org.apache.commons.net.SocketClient.fireCommandSent(java.lang.String,java.lang.String):792:792 -> b + 11:11:int sendCommand(java.lang.String,java.lang.String):481 -> b + 12:12:void org.apache.commons.net.ftp.FTP.__getReply():294:294 -> b + 12:12:int sendCommand(java.lang.String,java.lang.String):483 -> b + 13:13:int sendCommand(java.lang.String,java.lang.String):484:484 -> b + 14:14:int sendCommand(java.lang.String,java.lang.String):474:474 -> b +org.apache.commons.net.ftp.FTPClient -> d.b: + java.util.HashMap __featuresMap -> x + java.util.regex.Pattern __PARMS_PAT -> y + boolean __remoteVerificationEnabled -> u + int __bufferSize -> v + int __fileType -> t + boolean __passiveNatWorkaround -> w + int __passivePort -> r + java.lang.String __passiveHost -> s + int __dataConnectionMode -> p + int __dataTimeout -> q + 1:1:void ():401:401 -> + 1:1:void ():458:458 -> + 2:72:void ():396:466 -> + 1:1:java.lang.String org.apache.commons.net.ftp.FTPCmd.getCommand():113:113 -> a + 1:1:boolean __storeFile(org.apache.commons.net.ftp.FTPCmd,java.lang.String,java.io.InputStream):624 -> a + 2:8:boolean org.apache.commons.net.ftp.FTPClient._storeFile(java.lang.String,java.lang.String,java.io.InputStream):633:639 -> a + 2:8:boolean __storeFile(org.apache.commons.net.ftp.FTPCmd,java.lang.String,java.io.InputStream):624 -> a + 9:12:java.io.OutputStream org.apache.commons.net.ftp.FTPClient.getBufferedOutputStream(java.io.OutputStream):3644:3647 -> a + 9:12:boolean org.apache.commons.net.ftp.FTPClient._storeFile(java.lang.String,java.lang.String,java.io.InputStream):639 -> a + 9:12:boolean __storeFile(org.apache.commons.net.ftp.FTPCmd,java.lang.String,java.io.InputStream):624 -> a + 13:14:boolean org.apache.commons.net.ftp.FTPClient._storeFile(java.lang.String,java.lang.String,java.io.InputStream):641:642 -> a + 13:14:boolean __storeFile(org.apache.commons.net.ftp.FTPCmd,java.lang.String,java.io.InputStream):624 -> a + 15:15:int org.apache.commons.net.ftp.FTPClient.getBufferSize():3459:3459 -> a + 15:15:boolean org.apache.commons.net.ftp.FTPClient._storeFile(java.lang.String,java.lang.String,java.io.InputStream):653 -> a + 15:15:boolean __storeFile(org.apache.commons.net.ftp.FTPCmd,java.lang.String,java.io.InputStream):624 -> a + 16:30:boolean org.apache.commons.net.ftp.FTPClient._storeFile(java.lang.String,java.lang.String,java.io.InputStream):653:667 -> a + 16:30:boolean __storeFile(org.apache.commons.net.ftp.FTPCmd,java.lang.String,java.io.InputStream):624 -> a + 31:31:void org.apache.commons.net.ftp.FTP.__getReply():294:294 -> a + 31:31:int org.apache.commons.net.ftp.FTP.getReply():692 -> a + 31:31:boolean org.apache.commons.net.ftp.FTPClient.completePendingCommand():1813 -> a + 31:31:boolean org.apache.commons.net.ftp.FTPClient._storeFile(java.lang.String,java.lang.String,java.io.InputStream):672 -> a + 31:31:boolean __storeFile(org.apache.commons.net.ftp.FTPCmd,java.lang.String,java.io.InputStream):624 -> a + 32:32:int org.apache.commons.net.ftp.FTP.getReply():693:693 -> a + 32:32:boolean org.apache.commons.net.ftp.FTPClient.completePendingCommand():1813 -> a + 32:32:boolean org.apache.commons.net.ftp.FTPClient._storeFile(java.lang.String,java.lang.String,java.io.InputStream):672 -> a + 32:32:boolean __storeFile(org.apache.commons.net.ftp.FTPCmd,java.lang.String,java.io.InputStream):624 -> a + 33:33:boolean org.apache.commons.net.ftp.FTPClient.completePendingCommand():1813:1813 -> a + 33:33:boolean org.apache.commons.net.ftp.FTPClient._storeFile(java.lang.String,java.lang.String,java.io.InputStream):672 -> a + 33:33:boolean __storeFile(org.apache.commons.net.ftp.FTPCmd,java.lang.String,java.io.InputStream):624 -> a + 34:34:void org.apache.commons.net.io.Util.closeQuietly(java.net.Socket):370:370 -> a + 34:34:boolean org.apache.commons.net.ftp.FTPClient._storeFile(java.lang.String,java.lang.String,java.io.InputStream):659 -> a + 34:34:boolean __storeFile(org.apache.commons.net.ftp.FTPCmd,java.lang.String,java.io.InputStream):624 -> a + 35:35:boolean org.apache.commons.net.ftp.FTPClient._storeFile(java.lang.String,java.lang.String,java.io.InputStream):663:663 -> a + 35:35:boolean __storeFile(org.apache.commons.net.ftp.FTPCmd,java.lang.String,java.io.InputStream):624 -> a + 36:42:boolean org.apache.commons.net.ftp.FTPClient._retrieveFile(java.lang.String,java.lang.String,java.io.OutputStream):1854:1860 -> a + 36:42:boolean retrieveFile(java.lang.String,java.io.OutputStream):1845 -> a + 43:46:java.io.InputStream org.apache.commons.net.ftp.FTPClient.getBufferedInputStream(java.io.InputStream):3651:3654 -> a + 43:46:boolean org.apache.commons.net.ftp.FTPClient._retrieveFile(java.lang.String,java.lang.String,java.io.OutputStream):1860 -> a + 43:46:boolean retrieveFile(java.lang.String,java.io.OutputStream):1845 -> a + 47:48:boolean org.apache.commons.net.ftp.FTPClient._retrieveFile(java.lang.String,java.lang.String,java.io.OutputStream):1861:1862 -> a + 47:48:boolean retrieveFile(java.lang.String,java.io.OutputStream):1845 -> a + 49:49:int org.apache.commons.net.ftp.FTPClient.getBufferSize():3459:3459 -> a + 49:49:boolean org.apache.commons.net.ftp.FTPClient._retrieveFile(java.lang.String,java.lang.String,java.io.OutputStream):1873 -> a + 49:49:boolean retrieveFile(java.lang.String,java.io.OutputStream):1845 -> a + 50:50:boolean org.apache.commons.net.ftp.FTPClient._retrieveFile(java.lang.String,java.lang.String,java.io.OutputStream):1873:1873 -> a + 50:50:boolean retrieveFile(java.lang.String,java.io.OutputStream):1845 -> a + 51:51:void org.apache.commons.net.io.Util.closeQuietly(java.io.Closeable):354:354 -> a + 51:51:boolean org.apache.commons.net.ftp.FTPClient._retrieveFile(java.lang.String,java.lang.String,java.io.OutputStream):1877 -> a + 51:51:boolean retrieveFile(java.lang.String,java.io.OutputStream):1845 -> a + 52:52:void org.apache.commons.net.io.Util.closeQuietly(java.net.Socket):370:370 -> a + 52:52:boolean org.apache.commons.net.ftp.FTPClient._retrieveFile(java.lang.String,java.lang.String,java.io.OutputStream):1878 -> a + 52:52:boolean retrieveFile(java.lang.String,java.io.OutputStream):1845 -> a + 53:53:void org.apache.commons.net.ftp.FTP.__getReply():294:294 -> a + 53:53:int org.apache.commons.net.ftp.FTP.getReply():692 -> a + 53:53:boolean org.apache.commons.net.ftp.FTPClient.completePendingCommand():1813 -> a + 53:53:boolean org.apache.commons.net.ftp.FTPClient._retrieveFile(java.lang.String,java.lang.String,java.io.OutputStream):1885 -> a + 53:53:boolean retrieveFile(java.lang.String,java.io.OutputStream):1845 -> a + 54:54:int org.apache.commons.net.ftp.FTP.getReply():693:693 -> a + 54:54:boolean org.apache.commons.net.ftp.FTPClient.completePendingCommand():1813 -> a + 54:54:boolean org.apache.commons.net.ftp.FTPClient._retrieveFile(java.lang.String,java.lang.String,java.io.OutputStream):1885 -> a + 54:54:boolean retrieveFile(java.lang.String,java.io.OutputStream):1845 -> a + 55:55:boolean org.apache.commons.net.ftp.FTPClient.completePendingCommand():1813:1813 -> a + 55:55:boolean org.apache.commons.net.ftp.FTPClient._retrieveFile(java.lang.String,java.lang.String,java.io.OutputStream):1885 -> a + 55:55:boolean retrieveFile(java.lang.String,java.io.OutputStream):1845 -> a + 56:56:void org.apache.commons.net.io.Util.closeQuietly(java.io.Closeable):354:354 -> a + 56:56:boolean org.apache.commons.net.ftp.FTPClient._retrieveFile(java.lang.String,java.lang.String,java.io.OutputStream):1877 -> a + 56:56:boolean retrieveFile(java.lang.String,java.io.OutputStream):1845 -> a + 57:57:void org.apache.commons.net.io.Util.closeQuietly(java.net.Socket):370:370 -> a + 57:57:boolean org.apache.commons.net.ftp.FTPClient._retrieveFile(java.lang.String,java.lang.String,java.io.OutputStream):1878 -> a + 57:57:boolean retrieveFile(java.lang.String,java.io.OutputStream):1845 -> a + 58:58:boolean org.apache.commons.net.ftp.FTPClient._retrieveFile(java.lang.String,java.lang.String,java.io.OutputStream):1880:1880 -> a + 58:58:boolean retrieveFile(java.lang.String,java.io.OutputStream):1845 -> a + 1:16:void __initDefaults():473:488 -> b + 1:1:void org.apache.commons.net.SocketClient.disconnect():312:312 -> c + 1:1:void org.apache.commons.net.ftp.FTP.disconnect():443 -> c + 1:1:void disconnect():986 -> c + 2:2:void org.apache.commons.net.SocketClient.closeQuietly(java.net.Socket):323:323 -> c + 2:2:void org.apache.commons.net.SocketClient.disconnect():312 -> c + 2:2:void org.apache.commons.net.ftp.FTP.disconnect():443 -> c + 2:2:void disconnect():986 -> c + 3:3:void org.apache.commons.net.SocketClient.disconnect():313:313 -> c + 3:3:void org.apache.commons.net.ftp.FTP.disconnect():443 -> c + 3:3:void disconnect():986 -> c + 4:4:void org.apache.commons.net.SocketClient.closeQuietly(java.io.Closeable):332:332 -> c + 4:4:void org.apache.commons.net.SocketClient.disconnect():313 -> c + 4:4:void org.apache.commons.net.ftp.FTP.disconnect():443 -> c + 4:4:void disconnect():986 -> c + 5:5:void org.apache.commons.net.SocketClient.disconnect():314:314 -> c + 5:5:void org.apache.commons.net.ftp.FTP.disconnect():443 -> c + 5:5:void disconnect():986 -> c + 6:6:void org.apache.commons.net.SocketClient.closeQuietly(java.io.Closeable):332:332 -> c + 6:6:void org.apache.commons.net.SocketClient.disconnect():314 -> c + 6:6:void org.apache.commons.net.ftp.FTP.disconnect():443 -> c + 6:6:void disconnect():986 -> c + 7:9:void org.apache.commons.net.SocketClient.disconnect():315:317 -> c + 7:9:void org.apache.commons.net.ftp.FTP.disconnect():443 -> c + 7:9:void disconnect():986 -> c + 10:13:void org.apache.commons.net.ftp.FTP.disconnect():444:447 -> c + 10:13:void disconnect():986 -> c + 14:14:void disconnect():987:987 -> c + 15:15:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):784:784 -> c + 16:16:java.net.InetAddress org.apache.commons.net.SocketClient.getRemoteAddress():658:658 -> c + 16:16:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):789 -> c + 17:25:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):789:797 -> c + 26:26:java.net.InetAddress org.apache.commons.net.SocketClient.getLocalAddress():633:633 -> c + 26:26:java.net.InetAddress org.apache.commons.net.ftp.FTPClient.getHostAddress():1396 -> c + 26:26:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):797 -> c + 27:27:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):797:797 -> c + 28:28:java.net.InetAddress org.apache.commons.net.SocketClient.getLocalAddress():633:633 -> c + 28:28:java.net.InetAddress org.apache.commons.net.ftp.FTPClient.getHostAddress():1396 -> c + 28:28:java.net.InetAddress org.apache.commons.net.ftp.FTPClient.getReportHostAddress():1412 -> c + 28:28:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):808 -> c + 29:66:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):808:845 -> c + 67:67:java.net.InetAddress org.apache.commons.net.SocketClient.getLocalAddress():633:633 -> c + 67:67:java.net.InetAddress org.apache.commons.net.ftp.FTPClient.getHostAddress():1396 -> c + 67:67:java.net.InetAddress org.apache.commons.net.ftp.FTPClient.getReportHostAddress():1412 -> c + 67:67:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):812 -> c + 68:68:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):812:812 -> c + 69:79:int org.apache.commons.net.ftp.FTP.port(java.net.InetAddress,int):922:932 -> c + 69:79:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):812 -> c + 80:80:int org.apache.commons.net.ftp.FTP.sendCommand(org.apache.commons.net.ftp.FTPCmd,java.lang.String):608:608 -> c + 80:80:int org.apache.commons.net.ftp.FTP.port(java.net.InetAddress,int):932 -> c + 80:80:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):812 -> c + 81:114:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):812:845 -> c + 115:139:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):821:845 -> c + 140:156:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):829:845 -> c + 157:157:int org.apache.commons.net.ftp.FTP.epsv():1028:1028 -> c + 157:157:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):859 -> c + 158:158:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):861:861 -> c + 159:177:void org.apache.commons.net.ftp.FTPClient._parseExtendedPassiveModeReply(java.lang.String):589:607 -> c + 159:177:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):861 -> c + 178:178:java.net.InetAddress org.apache.commons.net.SocketClient.getRemoteAddress():658:658 -> c + 178:178:void org.apache.commons.net.ftp.FTPClient._parseExtendedPassiveModeReply(java.lang.String):617 -> c + 178:178:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):861 -> c + 179:180:void org.apache.commons.net.ftp.FTPClient._parseExtendedPassiveModeReply(java.lang.String):617:618 -> c + 179:180:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):861 -> c + 181:181:void org.apache.commons.net.ftp.FTPClient._parseExtendedPassiveModeReply(java.lang.String):611:611 -> c + 181:181:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):861 -> c + 182:182:void org.apache.commons.net.ftp.FTPClient._parseExtendedPassiveModeReply(java.lang.String):600:600 -> c + 182:182:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):861 -> c + 183:183:int org.apache.commons.net.ftp.FTP.pasv():1007:1007 -> c + 183:183:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):869 -> c + 184:184:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):872:872 -> c + 185:209:void org.apache.commons.net.ftp.FTPClient._parsePassiveModeReply(java.lang.String):546:570 -> c + 185:209:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):872 -> c + 210:210:java.net.InetAddress org.apache.commons.net.SocketClient.getRemoteAddress():658:658 -> c + 210:210:void org.apache.commons.net.ftp.FTPClient._parsePassiveModeReply(java.lang.String):571 -> c + 210:210:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):872 -> c + 211:213:void org.apache.commons.net.ftp.FTPClient._parsePassiveModeReply(java.lang.String):572:574 -> c + 211:213:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):872 -> c + 214:214:org.apache.commons.net.ProtocolCommandSupport org.apache.commons.net.ftp.FTP.getCommandSupport():1789:1789 -> c + 214:214:void org.apache.commons.net.SocketClient.fireReplyReceived(int,java.lang.String):778 -> c + 214:214:void org.apache.commons.net.ftp.FTPClient._parsePassiveModeReply(java.lang.String):574 -> c + 214:214:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):872 -> c + 215:215:void org.apache.commons.net.SocketClient.fireReplyReceived(int,java.lang.String):778:778 -> c + 215:215:void org.apache.commons.net.ftp.FTPClient._parsePassiveModeReply(java.lang.String):574 -> c + 215:215:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):872 -> c + 216:216:org.apache.commons.net.ProtocolCommandSupport org.apache.commons.net.ftp.FTP.getCommandSupport():1789:1789 -> c + 216:216:void org.apache.commons.net.SocketClient.fireReplyReceived(int,java.lang.String):779 -> c + 216:216:void org.apache.commons.net.ftp.FTPClient._parsePassiveModeReply(java.lang.String):574 -> c + 216:216:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):872 -> c + 217:217:void org.apache.commons.net.SocketClient.fireReplyReceived(int,java.lang.String):779:779 -> c + 217:217:void org.apache.commons.net.ftp.FTPClient._parsePassiveModeReply(java.lang.String):574 -> c + 217:217:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):872 -> c + 218:222:void org.apache.commons.net.ftp.FTPClient._parsePassiveModeReply(java.lang.String):576:580 -> c + 218:222:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):872 -> c + 223:256:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):875:908 -> c + 257:257:boolean org.apache.commons.net.SocketClient.verifyRemote(java.net.Socket):675:675 -> c + 257:257:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):908 -> c + 258:258:java.net.InetAddress org.apache.commons.net.SocketClient.getRemoteAddress():658:658 -> c + 258:258:boolean org.apache.commons.net.SocketClient.verifyRemote(java.net.Socket):676 -> c + 258:258:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):908 -> c + 259:259:boolean org.apache.commons.net.SocketClient.verifyRemote(java.net.Socket):678:678 -> c + 259:259:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):908 -> c + 260:262:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):910:912 -> c + 263:263:java.net.InetAddress org.apache.commons.net.SocketClient.getRemoteAddress():658:658 -> c + 263:263:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):912 -> c + 264:264:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):912:912 -> c + 265:265:void org.apache.commons.net.ftp.FTPClient._parsePassiveModeReply(java.lang.String):562:562 -> c + 265:265:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):872 -> c + 266:266:void org.apache.commons.net.ftp.FTPClient._parsePassiveModeReply(java.lang.String):548:548 -> c + 266:266:java.net.Socket _openDataConnection_(java.lang.String,java.lang.String):872 -> c + 1:1:int org.apache.commons.net.ftp.FTP.sendCommand(org.apache.commons.net.ftp.FTPCmd,java.lang.String):608:608 -> d + 1:1:int org.apache.commons.net.ftp.FTP.user(java.lang.String):753 -> d + 1:1:boolean login(java.lang.String,java.lang.String):1034 -> d + 2:8:boolean login(java.lang.String,java.lang.String):1036:1042 -> d + 9:9:int org.apache.commons.net.ftp.FTP.sendCommand(org.apache.commons.net.ftp.FTPCmd,java.lang.String):608:608 -> d + 9:9:int org.apache.commons.net.ftp.FTP.pass(java.lang.String):771 -> d + 9:9:boolean login(java.lang.String,java.lang.String):1046 -> d + 10:10:boolean login(java.lang.String,java.lang.String):1046:1046 -> d +org.apache.commons.net.ftp.FTPCmd -> d.c: + org.apache.commons.net.ftp.FTPCmd STOR -> e + org.apache.commons.net.ftp.FTPCmd QUIT -> d + org.apache.commons.net.ftp.FTPCmd PASV -> c + org.apache.commons.net.ftp.FTPCmd FEAT -> b + org.apache.commons.net.ftp.FTPCmd[] $VALUES -> f + org.apache.commons.net.ftp.FTPCmd EPSV -> a + 1:40:void ():25:64 -> + 41:41:void ():24:24 -> + 1:1:void (java.lang.String,int):24:24 -> + 1:1:org.apache.commons.net.ftp.FTPCmd valueOf(java.lang.String):24:24 -> valueOf + 1:1:org.apache.commons.net.ftp.FTPCmd[] values():24:24 -> values +org.apache.commons.net.ftp.FTPConnectionClosedException -> d.d: + 1:1:void (java.lang.String):53:53 -> +org.apache.commons.net.ftp.FTPReply -> d.e: + boolean isPositiveCompletion(int) -> a + boolean isPositivePreliminary(int) -> b +org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory -> e.a: + 1:1:void ():46:46 -> +org.apache.commons.net.io.CRLFLineReader -> f.a: + 1:1:void (java.io.Reader):43:43 -> + 1:19:java.lang.String readLine():54:72 -> readLine + 20:20:java.lang.String readLine():70:70 -> readLine +org.apache.commons.net.io.CopyStreamException -> f.b: + 1:3:void (java.lang.String,long,java.io.IOException):47:49 -> +org.apache.commons.net.io.CopyStreamListener -> f.c: +org.apache.commons.net.io.FromNetASCIIInputStream -> f.d: + boolean _noConversionRequired -> b + byte[] _lineSeparatorBytes -> c + int __length -> a + 1:6:void ():42:47 -> + 1:1:void (java.io.InputStream):73:73 -> + 2:2:void (java.io.InputStream):51:51 -> + 1:16:int __read():81:96 -> a + 1:4:int available():214:217 -> available + 5:5:int available():215:215 -> available + 1:5:int read():122:126 -> read + 6:6:int read(byte[]):144:144 -> read + 7:37:int read(byte[],int,int):165:195 -> read +org.apache.commons.net.io.ToNetASCIIOutputStream -> f.e: + boolean __lastWasCR -> a + 1:2:void (java.io.OutputStream):47:48 -> + 1:11:void write(int):70:80 -> write + 12:12:void write(byte[]):97:97 -> write + 13:13:void write(byte[],int,int):116:116 -> write +org.apache.commons.net.io.Util -> f.f: + 1:39:long copyStream(java.io.InputStream,java.io.OutputStream,int,long,org.apache.commons.net.io.CopyStreamListener,boolean):96:134 -> a +org.apache.commons.net.util.ListenerList -> g.a: + 1:2:void ():35:36 -> + 1:1:int getListenerCount():51:51 -> a + 1:1:java.util.Iterator iterator():63:63 -> iterator diff --git a/library-common/proguard-rules.pro b/library-common/proguard-rules.pro new file mode 100644 index 0000000..565676a --- /dev/null +++ b/library-common/proguard-rules.pro @@ -0,0 +1,135 @@ +# 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 +# 有了verbose这句话,混淆后就会生成映射文件 +# 包含有类名->混淆后类名的映射关系 +# 然后使用printmapping指定映射文件的名称 +-verbose +-printmapping priguardMapping.txt + +# 保留所有的本地native方法不被混淆 +-keepclasseswithmembernames class * { + native ; +} + +# 保护代码中的Annotation不被混淆 +# 这在JSON实体映射时非常重要,比如fastJson +-keepattributes *Annotation* + +# 避免混淆泛型 +# 这在JSON实体映射时非常重要,比如fastJson +-keepattributes Signature + +# 抛出异常时保留代码行号 +-keepattributes SourceFile,LineNumberTable + +# 对R文件下的所有类及其方法,都不能被混淆 +-keepclassmembers class **.R$* { + *; +} + +# 保留了继承自Activity、Application这些类的子类 +# 因为这些子类有可能被外部调用 +# 比如第一行就保证了所有Activity的子类不要被混淆 +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class * extends android.app.backup.BackupAgentHelper +-keep public class * extends android.preference.Preference +-keep public class * extends android.view.View + +# 枚举类不能被混淆 +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +# 保留自定义控件(继承自View)不能被混淆 +-keep public class * extends android.view.View { + public (android.content.Context); + public (android.content.Context, android.util.AttributeSet); + public (android.content.Context, android.util.AttributeSet, int); + public void set*(***); + *** get* (); +} + +# 保留Parcelable序列化的类不能被混淆 +-keep class * implements android.os.Parcelable{ + public static final android.os.Parcelable$Creator *; +} + +# 保留Serializable 序列化的类不被混淆 +-keepclassmembers class * implements java.io.Serializable { + static final long serialVersionUID; + private static final java.io.ObjectStreamField[] serialPersistentFields; + !static !transient ; + private void writeObject(java.io.ObjectOutputStream); + private void readObject(java.io.ObjectInputStream); + java.lang.Object writeReplace(); + java.lang.Object readResolve(); +} + + +# AndroidX混淆 +-keep class com.google.android.material.** {*;} +-keep class androidx.** {*;} +-keep public class * extends androidx.** +-keep interface androidx.** {*;} +-dontwarn com.google.android.material.** +-dontnote com.google.android.material.** +-dontwarn androidx.** + +# 保留BaseFragment公共成员和方法不被混淆 +-keep class com.common.commonlib.utils.*{*;} + +# 保留BaseFragment公共成员和方法不被混淆 +-keep class com.common.commonlib.view.fragment.BaseFragment{ + public; + public; +} +# 保留BaseNavigationFragment公共成员和方法不被混淆 +-keep class com.common.commonlib.view.fragment.BaseNavigationFragment{ + public; + public; +} +# 保留BaseActivity公共成员和方法不被混淆 +-keep class com.common.commonlib.view.activity.BaseActivity{ + public; + public; +} + +# 保留CommonTitleView的内部类不被混淆 +-keep class com.common.commonlib.view.CommonTitleView$*{ + *; +} + +# 保留BaseNavigationFragment公共成员和方法不被混淆 +-keep class com.common.commonlib.view.SlideRecyclerView{ + public; + public; +} + +# 保留glide的配置类 +-keep public class * extends com.bumptech.glide.module.AppGlideModule +-keep class com.common.commonlib.image.module.OkHttpLibraryGlideModule diff --git a/library-common/src/main/AndroidManifest.xml b/library-common/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8c78a06 --- /dev/null +++ b/library-common/src/main/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/library-common/src/main/assets/baseUrl.properties b/library-common/src/main/assets/baseUrl.properties new file mode 100644 index 0000000..c8eead0 --- /dev/null +++ b/library-common/src/main/assets/baseUrl.properties @@ -0,0 +1,2 @@ +debug1 = https://www.wanandroid.com +debug2 = https://www.baidu diff --git a/library-common/src/main/java/com/common/commonlib/CommonApplication.kt b/library-common/src/main/java/com/common/commonlib/CommonApplication.kt new file mode 100644 index 0000000..f21f3c4 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/CommonApplication.kt @@ -0,0 +1,57 @@ +package com.common.commonlib + +import android.annotation.SuppressLint +import android.app.Application +import android.content.Context +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.OnLifecycleEvent +import androidx.lifecycle.ProcessLifecycleOwner +import com.common.commonlib.utils.BaseUtils +import com.tencent.mmkv.MMKV + +/** + * 基础Application + * + * @author wangym + * @since 2021/7/28 + */ +@SuppressLint("StaticFieldLeak") +open class CommonApplication : Application(), LifecycleObserver { + override fun onCreate() { + super.onCreate() + ProcessLifecycleOwner.get().lifecycle.addObserver(this) + intLibs(this) + } + + private fun intLibs(context: Context) { + commonContext = context + + // 初始化MMKV + MMKV.initialize(this) + + initNet() + } + + private fun initNet() { + BaseUtils.enableFullLog() + } + + companion object { + private var commonContext: Context? = null + + fun getContext(): Context? { + return commonContext + } + } + + @OnLifecycleEvent(Lifecycle.Event.ON_START) + open fun onAppForeground(){ + + } + + @OnLifecycleEvent(Lifecycle.Event.ON_STOP) + open fun onAppBackground(){ + + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/bean/GridPicBean.kt b/library-common/src/main/java/com/common/commonlib/bean/GridPicBean.kt new file mode 100644 index 0000000..dad0409 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/bean/GridPicBean.kt @@ -0,0 +1,3 @@ +package com.common.commonlib.bean + +data class GridPicBean(val imagePath:String, val isConverting: Boolean) diff --git a/library-common/src/main/java/com/common/commonlib/image/config/GlideBaseConfig.kt b/library-common/src/main/java/com/common/commonlib/image/config/GlideBaseConfig.kt new file mode 100644 index 0000000..1baf07e --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/image/config/GlideBaseConfig.kt @@ -0,0 +1,62 @@ +package com.common.commonlib.image.config + +import com.bumptech.glide.MemoryCategory + +/** + * 图片加载基础配置 + * + * @author wangym + * @since 2021/7/28 + */ +object GlideBaseConfig { + + enum class MemoryType(var memoryCategory: MemoryCategory) { + /** + * Tells Glide's memory cache and bitmap pool to use at most half of their initial maximum size. + */ + LOW(MemoryCategory.LOW), + + /** + * Tells Glide's memory cache and bitmap pool to use at most their initial maximum size. + */ + NORMAL(MemoryCategory.NORMAL), + + /** + * Tells Glide's memory cache and bitmap pool to use at most one and a half times their initial + * maximum size. + */ + HIGH(MemoryCategory.HIGH) + } + + enum class LoadMode { + /** + * default loading type + */ + DEFAULT, + + /** + * cache all resource + */ + ALL, + + /** + * skip loading from disk + */ + SKIP_DISK, + + /** + * skip loading from memory cache + */ + SKIP_MEM, + + /** + * skip loading form all cache + */ + SKIP_CACHE, + + /** + * don't load form internet + */ + SKIP_NET + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/image/config/GlideHolderConfig.kt b/library-common/src/main/java/com/common/commonlib/image/config/GlideHolderConfig.kt new file mode 100644 index 0000000..e6360b2 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/image/config/GlideHolderConfig.kt @@ -0,0 +1,30 @@ +package com.common.commonlib.image.config + +import android.graphics.drawable.Drawable + +/** + * 占位符配置 + * + * @author wangym + * @since 2021/7/28 + */ +class GlideHolderConfig { + /** + * holder占位图 + */ + var holderDrawable: Drawable? = null + var holderId: Int = -1 + + /** + * 加载失败时,且没有配置fallback占位图时展示 + */ + var errorDrawable: Drawable? = null + var errorId: Int = -1 + var errorBuilder: Any? = null + + /** + * 加载失败时是否允许失败作为正常流程展示 + */ + var fallBackDrawable: Drawable? = null + var fallBackId: Int = -1 +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/image/config/GlideTransitionConfig.kt b/library-common/src/main/java/com/common/commonlib/image/config/GlideTransitionConfig.kt new file mode 100644 index 0000000..5ece0a3 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/image/config/GlideTransitionConfig.kt @@ -0,0 +1,18 @@ +package com.common.commonlib.image.config + +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions + +/** + * 转换配置 + * @param time 动画时长 + * + * @author wangym + * @since 2021/7/28 + */ +class GlideTransitionConfig(time: Int?) { + val transition: DrawableTransitionOptions = if (time != null) { + DrawableTransitionOptions.withCrossFade(time) + } else { + DrawableTransitionOptions.withCrossFade() + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/image/loader/ConfigHelper.kt b/library-common/src/main/java/com/common/commonlib/image/loader/ConfigHelper.kt new file mode 100644 index 0000000..e54ad1c --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/image/loader/ConfigHelper.kt @@ -0,0 +1,64 @@ +package com.common.commonlib.image.loader + +import android.graphics.drawable.Drawable +import com.bumptech.glide.RequestBuilder +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation +import com.common.commonlib.image.config.GlideBaseConfig + +/** + * 加载配置辅助类 + * + * @author wangym + * @since 2021/7/28 + */ +object ConfigHelper { + + /** + * 带有过渡效果的加载 + * @param requestBuilder 请求构建器 + * @param transformations 过渡效果 + * + * @return 请求构建器 + */ + fun loadWithTransformation( + requestBuilder: RequestBuilder, + transformations: List + ): RequestBuilder { + var temp = requestBuilder + for (item in transformations) { + temp = temp.transform(item) + } + return temp + } + + /** + * 带有缓存模式的加载 + * @param requestBuilder 请求构建器 + * @param loadingMode 加载缓存模式 + * + * @return 请求构建器 + */ + fun loadWithMod( + requestBuilder: RequestBuilder, + loadingMode: GlideBaseConfig.LoadMode + ): RequestBuilder { + var request = requestBuilder + request = when (loadingMode) { + GlideBaseConfig.LoadMode.DEFAULT -> + request + GlideBaseConfig.LoadMode.ALL -> + request.diskCacheStrategy(DiskCacheStrategy.ALL) + GlideBaseConfig.LoadMode.SKIP_CACHE -> + request.skipMemoryCache(true) + .diskCacheStrategy(DiskCacheStrategy.NONE) + GlideBaseConfig.LoadMode.SKIP_DISK -> + request.diskCacheStrategy(DiskCacheStrategy.NONE) + GlideBaseConfig.LoadMode.SKIP_MEM -> + request.skipMemoryCache(true) + GlideBaseConfig.LoadMode.SKIP_NET -> + request.onlyRetrieveFromCache(true) + } + return request + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/image/loader/GlobalConfigHelper.kt b/library-common/src/main/java/com/common/commonlib/image/loader/GlobalConfigHelper.kt new file mode 100644 index 0000000..bf254ec --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/image/loader/GlobalConfigHelper.kt @@ -0,0 +1,71 @@ +package com.common.commonlib.image.loader + +import android.graphics.drawable.Drawable +import com.bumptech.glide.RequestBuilder +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions +import com.common.commonlib.image.config.GlideHolderConfig + +/** + * 全局配置适配器 + * + * @author wangym + * @since 2021/7/28 + */ +object GlobalConfigHelper { + /** + * 全局占位符配置 + */ + var globalHolderConfig: GlideHolderConfig? = null + + /** + * 全局过渡效果 + */ + var globalTransition: DrawableTransitionOptions? = null + + fun loadWithGlobalConfig(requestBuilder: RequestBuilder): RequestBuilder { + return loadWithGlobalConfig(requestBuilder, globalHolderConfig, globalTransition) + } + + fun loadWithGlobalConfig( + requestBuilder: RequestBuilder, + holderConfig: GlideHolderConfig? + ): RequestBuilder { + return loadWithGlobalConfig(requestBuilder, holderConfig, globalTransition) + } + + private fun loadWithGlobalConfig( + requestBuilder: RequestBuilder, + holderConfig: GlideHolderConfig?, + transition: DrawableTransitionOptions? + ): RequestBuilder { + var request = requestBuilder + if (holderConfig != null) { + if (holderConfig.holderDrawable != null) { + request = request.placeholder(holderConfig.holderDrawable) + } + if (holderConfig.holderId != -1) { + request = request.placeholder(holderConfig.holderId) + } + if (holderConfig.errorDrawable != null) { + request = request.error(holderConfig.errorDrawable) + } + if (holderConfig.errorId != -1) { + request = request.error(holderConfig.errorId) + } + if (holderConfig.errorBuilder != null) { + request = request.error(holderConfig.errorBuilder) + } + if (holderConfig.fallBackDrawable != null) { + request = request.fallback(holderConfig.fallBackDrawable) + } + if (holderConfig.fallBackId != -1) { + request = request.fallback(holderConfig.fallBackId) + } + } + + if (transition != null) { + request = request.transition(transition) + } + return request + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/image/loader/ILoader.kt b/library-common/src/main/java/com/common/commonlib/image/loader/ILoader.kt new file mode 100644 index 0000000..36326a5 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/image/loader/ILoader.kt @@ -0,0 +1,85 @@ +package com.common.commonlib.image.loader + +import android.content.Context +import android.widget.ImageView +import com.common.commonlib.image.config.GlideBaseConfig +import com.common.commonlib.image.config.GlideHolderConfig + +/** + * 图片加载器接口 + * + * @author wangym + * @since 2021/7/28 + */ +interface ILoader { + + /** + * 初始化 + * @param context 上下文 + * @param memoryCategory 缓存类型 + */ + fun init(context: Context, memoryCategory: GlideBaseConfig.MemoryType) + + /** + * 加载图片 + * @param context 上下文 + * @param url 图片链接 + * @param view 图片控件 + */ + fun load(context: Context, url: String?, view: ImageView) + + /** + * 加载圆角图片 + * @param context 上下文 + * @param url 图片链接 + * @param view 图片控件 + * @param radius 圆角半径 + */ + fun loadRound(context: Context, url: String?, view: ImageView, radius: Float) + + /** + * 加载图片 + * @param context 上下文 + * @param url 图片链接 + * @param view 图片控件 + * @param loadingMode 加载模式 + */ + fun loadWithMode( + context: Context, + url: String?, + view: ImageView, + loadingMode: GlideBaseConfig.LoadMode + ) + + /** + * 加载图片 + * @param context 上下文 + * @param url 图片链接 + * @param view 图片控件 + * @param placeGlideHolder 占位图 + */ + fun loadWithPlaceHolder( + context: Context, + url: String, + view: ImageView, + placeGlideHolder: GlideHolderConfig + ) + + /** + * 清理内存缓存 + * @param context 上下文 + */ + fun clearMemoryCache(context: Context) + + /** + * 清理磁盘缓存 + * @param context 上下文 + */ + fun clearDiskCache(context: Context) + + /** + * 清理缓存 + * @param context 上下文 + */ + fun clearAllCache(context: Context) +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/image/loader/ImageLoader.kt b/library-common/src/main/java/com/common/commonlib/image/loader/ImageLoader.kt new file mode 100644 index 0000000..9fc4230 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/image/loader/ImageLoader.kt @@ -0,0 +1,158 @@ +package com.common.commonlib.image.loader + +import android.content.Context +import android.graphics.drawable.Drawable +import android.widget.ImageView +import com.bumptech.glide.Glide +import com.bumptech.glide.RequestBuilder +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation +import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners +import com.common.commonlib.image.config.GlideBaseConfig +import com.common.commonlib.image.config.GlideHolderConfig +import com.common.commonlib.image.config.GlideTransitionConfig +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking + +/** + * 图片加载类 + * + * @author wangym + * @since 2021/7/28 + */ +object ImageLoader : ILoader { + + /** + * 初始化Glide基础配置 + */ + override fun init( + context: Context, memoryCategory: GlideBaseConfig.MemoryType + ) { + Glide.get(context).setMemoryCategory(memoryCategory.memoryCategory) + } + + /** + * 设置全局占位符配置 + * @param globalHolderConfig 占位符配置 + */ + fun setGlobalConfig(globalHolderConfig: GlideHolderConfig) { + GlobalConfigHelper.globalHolderConfig = globalHolderConfig + } + + /** + * 设置全局过渡效果 + * @param transitionConfig 全局过渡配置 + */ + fun setGlobalTransition(transitionConfig: GlideTransitionConfig) { + GlobalConfigHelper.globalTransition = transitionConfig.transition + } + + /** + * 加载图片 + * @param context 上下文 + * @param url 图片链接 + * @param view 图片控件 + */ + override fun load(context: Context, url: String?, view: ImageView) { + loadWithMode(context, url, view, GlideBaseConfig.LoadMode.DEFAULT) + } + + /** + * 加载圆角图片 + * @param context 上下文 + * @param url 图片链接 + * @param view 图片控件 + * @param radius 圆角弧度 + */ + override fun loadRound(context: Context, url: String?, view: ImageView, radius: Float) { + var request = GlobalConfigHelper.loadWithGlobalConfig(Glide.with(context).load(url)) + request = ConfigHelper.loadWithMod(request, GlideBaseConfig.LoadMode.DEFAULT) + + val transformations = ArrayList() + transformations.add(GranularRoundedCorners(radius, radius, radius, radius)) + request = ConfigHelper.loadWithTransformation(request, transformations) + loadIntoView(request, view) + } + + /** + * 以模式,加载图片 + * @param context 上下文 + * @param url 图片链接 + * @param view 图片控件 + * @param loadingMode 加载模式 + */ + override fun loadWithMode( + context: Context, + url: String?, + view: ImageView, + loadingMode: GlideBaseConfig.LoadMode + ) { + var request = GlobalConfigHelper.loadWithGlobalConfig(Glide.with(context).load(url)) + request = ConfigHelper.loadWithMod(request, loadingMode) + loadIntoView(request, view) + } + + /** + * 加载图片,带有占位图 + * @param context 上下文 + * @param url 图片链接 + * @param view 图片控件 + * @param placeGlideHolder 占位图 + */ + override fun loadWithPlaceHolder( + context: Context, + url: String, + view: ImageView, + placeGlideHolder: GlideHolderConfig + ) { + loadIntoView( + GlobalConfigHelper.loadWithGlobalConfig( + Glide.with(context).load(url), + placeGlideHolder + ), view + ) + } + + /** + * 加载进控件 + * @param requestBuilder 请求构建器 + * @param view 图片控件 + */ + private fun loadIntoView(requestBuilder: RequestBuilder, view: ImageView) { + requestBuilder.into(view) + } + + /** + * 清理内存缓存 + * @param context 上下文 + */ + override fun clearMemoryCache(context: Context) { + GlobalScope.launch(Dispatchers.Main) { + Glide.get(context).clearMemory() + } + } + + /** + * 清理磁盘缓存 + * @param context 上下文 + */ + override fun clearDiskCache(context: Context) = runBlocking { + launch(Dispatchers.Default) { + Glide.get(context).clearDiskCache() + } + } + + /** + * 清理缓存 + * @param context 上下文 + */ + override fun clearAllCache(context: Context) = runBlocking { + GlobalScope.launch(Dispatchers.Main) { + Glide.get(context).clearMemory() + } + launch(Dispatchers.Default) { + Glide.get(context).clearDiskCache() + } + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/image/module/OkHttpLibraryGlideModule.kt b/library-common/src/main/java/com/common/commonlib/image/module/OkHttpLibraryGlideModule.kt new file mode 100644 index 0000000..4b8ade9 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/image/module/OkHttpLibraryGlideModule.kt @@ -0,0 +1,25 @@ +package com.common.commonlib.image.module + +import android.content.Context +import com.bumptech.glide.Glide +import com.bumptech.glide.Registry +import com.bumptech.glide.annotation.GlideModule +import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader +import com.bumptech.glide.load.model.GlideUrl +import com.bumptech.glide.module.LibraryGlideModule +import java.io.InputStream + +/** + * okhttp配置 + * + * @author wangym + * @since 2021/7/28 + */ +@GlideModule +class OkHttpLibraryGlideModule : LibraryGlideModule() { + + override fun registerComponents(context: Context, glide: Glide, registry: Registry) { + // 替换网路请求使用okhttp + registry.replace(GlideUrl::class.java, InputStream::class.java, OkHttpUrlLoader.Factory()) + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/log/LogSecureHelper.kt b/library-common/src/main/java/com/common/commonlib/log/LogSecureHelper.kt new file mode 100644 index 0000000..f186bf1 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/log/LogSecureHelper.kt @@ -0,0 +1,23 @@ +package com.common.commonlib.log + +/** + * 日志敏感信息处理 + * + * @author wangym + * @since 2021/7/28 + */ +object LogSecureHelper { + + /** + * 一个间隔一个将数据替换成* + */ + fun cast2Star(source: String): String { + val sourceTemp = source.toCharArray() + for (item in sourceTemp.withIndex()) { + if (item.index / 2 == 0) { + sourceTemp[item.index] = '*' + } + } + return sourceTemp.toString() + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/log/Logger.kt b/library-common/src/main/java/com/common/commonlib/log/Logger.kt new file mode 100644 index 0000000..ad87293 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/log/Logger.kt @@ -0,0 +1,103 @@ +package com.common.commonlib.log + +import android.util.Log + +/** + * Log工具类 + * + * @author wangym + * @since 2021/7/28 + */ +object Logger { + var commonTag = "" + var needSecurity = false + + fun init(tag: String, security: Boolean) { + commonTag = tag + needSecurity = security + } + + fun w(msg: Any?) { + w(commonTag, msg.toString(), needSecurity) + } + + fun d(msg: Any?) { + d(commonTag, msg.toString(), needSecurity) + } + + fun i(msg: Any?) { + i(commonTag, msg.toString(), needSecurity) + } + + fun e(msg: Any?) { + e(commonTag, msg.toString(), needSecurity) + } + + fun w(msg: Any?, needSecurity: Boolean) { + w(commonTag, msg.toString(), needSecurity) + } + + fun d(msg: Any?, needSecurity: Boolean) { + d(commonTag, msg.toString(), needSecurity) + } + + fun i(msg: Any?, needSecurity: Boolean) { + i(commonTag, msg.toString(), needSecurity) + } + + fun e(msg: Any?, needSecurity: Boolean) { + e(commonTag, msg.toString(), needSecurity) + } + + fun w(tag: String, msg: Any?) { + w(tag, msg.toString(), needSecurity) + } + + fun d(tag: String, msg: Any?) { + d(tag, msg.toString(), needSecurity) + } + + fun i(tag: String, msg: Any?) { + i(tag, msg.toString(), needSecurity) + } + + fun e(tag: String, msg: Any?) { + e(tag, msg.toString(), needSecurity) + } + + fun w(tag: String, msg: Any?, needSecurity: Boolean) { + Log.w( + tag, if (needSecurity) + LogSecureHelper.cast2Star(msg.toString()) + else + msg.toString() + ) + } + + fun d(tag: String, msg: Any?, needSecurity: Boolean) { + Log.d( + tag, if (needSecurity) + LogSecureHelper.cast2Star(msg.toString()) + else + msg.toString() + ) + } + + fun i(tag: String, msg: Any?, needSecurity: Boolean) { + Log.i( + tag, if (needSecurity) + LogSecureHelper.cast2Star(msg.toString()) + else + msg.toString() + ) + } + + fun e(tag: String, msg: Any?, needSecurity: Boolean) { + Log.e( + tag, if (needSecurity) + LogSecureHelper.cast2Star(msg.toString()) + else + msg.toString() + ) + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/net/BaseObserve.kt b/library-common/src/main/java/com/common/commonlib/net/BaseObserve.kt new file mode 100644 index 0000000..ea87db8 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/net/BaseObserve.kt @@ -0,0 +1,124 @@ +package com.common.commonlib.net + +import android.net.ParseException +import android.util.Log +import com.common.commonlib.log.Logger +import com.common.commonlib.net.callback.RequestCallBack +import com.common.commonlib.net.interfaces.ITypedCommon +import com.common.commonlib.net.manager.RequestManager +import com.google.gson.JsonParseException +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Observer +import io.reactivex.rxjava3.disposables.Disposable +import io.reactivex.rxjava3.schedulers.Schedulers +import okhttp3.Interceptor +import org.json.JSONException +import retrofit2.HttpException +import java.net.ConnectException +import java.net.SocketTimeoutException +import java.net.UnknownHostException +import javax.net.ssl.SSLHandshakeException + +/** + * 请求基类 + * + * @author wangym + * @since 2021/7/28 + */ +open class BaseObserve : ITypedCommon { + var api: T + + constructor(clazz: Class) { + api = RequestManager.create(clazz, null, getType()) + } + + constructor(clazz: Class, interceptors: List) { + api = RequestManager.create(clazz, interceptors, getType()) + } + + /** + * 提供方法给外部获取API + */ + fun getAPI(): T { + return api + } + + /** + * 调用接口 + * @param observable 可订阅 + * @param callBack 请求回调 + */ + fun observe(observable: Observable?, callBack: RequestCallBack?) { + if (observable == null) { + Logger.e("BaseObserve", "observe observable is null") + return + } + observable + .subscribeOn(Schedulers.io()) + .unsubscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : Observer { + override fun onSubscribe(d: Disposable) { + } + + override fun onNext(t: R) { + callBack?.onResult(t) + } + + override fun onError(e: Throwable) { + e.message?.let { Log.e(TAG, it) } + callBack?.onError(dealError(e)) + } + + override fun onComplete() { + callBack?.onComplete() + } + }) + } + + /** + * 处理错误 + * @param e 异常 + */ + private fun dealError(e: Throwable): String? { + if (e is HttpException) { + return when (e.code()) { + UNAUTHORIZED -> "登录验证过期" + INTERNAL_SERVER_ERROR -> "服务器错误" + FORBIDDEN, NOT_FOUND -> "无效的请求" + REQUEST_TIMEOUT, GATEWAY_TIMEOUT, BAD_GATEWAY, SERVICE_UNAVAILABLE -> e.message() + else -> e.message() + } + } else if (e is ConnectException) { + return "网络连接异常,请检查您的网络状态" + } else if (e is SocketTimeoutException) { + return "网络连接超时,请检查您的网络状态,稍后重试" + } else if (e is UnknownHostException) { + return "网络异常,请检查您的网络状态" + } else if (e is JsonParseException || e is JSONException || e is ParseException) { + return "数据解析错误" + } else if (e is SSLHandshakeException) { + return "网络异常,请检查您的网络状态" + } else if (e is RuntimeException) { + return "运行时异常" + } + return e.message + } + + companion object { + const val TAG: String = "BaseLoader" + const val UNAUTHORIZED = 401 + const val FORBIDDEN = 403 + const val NOT_FOUND = 404 + const val REQUEST_TIMEOUT = 408 + const val INTERNAL_SERVER_ERROR = 500 + const val BAD_GATEWAY = 502 + const val SERVICE_UNAVAILABLE = 503 + const val GATEWAY_TIMEOUT = 504 + } + + override fun getType(): String { + return BaseObserve::javaClass.name + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/net/bean/BaseResponse.kt b/library-common/src/main/java/com/common/commonlib/net/bean/BaseResponse.kt new file mode 100644 index 0000000..69fb856 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/net/bean/BaseResponse.kt @@ -0,0 +1,24 @@ +package com.common.commonlib.net.bean + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +/** + * 基础响应类 + * + * @author wangym + * @since 2021/7/28 + */ +@Parcelize +open class BaseResponse : Parcelable { + var errorCode: Int = 0 + var errorMessage: String = "" + + fun getMErrorCode(): Int { + return errorCode + } + + fun getMErrorMessage(): String { + return errorMessage + } +} diff --git a/library-common/src/main/java/com/common/commonlib/net/bean/RequestParam.kt b/library-common/src/main/java/com/common/commonlib/net/bean/RequestParam.kt new file mode 100644 index 0000000..e0a2884 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/net/bean/RequestParam.kt @@ -0,0 +1,12 @@ +package com.common.commonlib.net.bean + +/** + * 请求参数 + * + * @author wangym + * @since 2021/7/28 + */ +data class RequestParam(var baseUrl: String) { + var params: Map? = null +} + diff --git a/library-common/src/main/java/com/common/commonlib/net/callback/RequestCallBack.kt b/library-common/src/main/java/com/common/commonlib/net/callback/RequestCallBack.kt new file mode 100644 index 0000000..a6f9b3f --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/net/callback/RequestCallBack.kt @@ -0,0 +1,13 @@ +package com.common.commonlib.net.callback + +/** + * 请求回调 + * + * @author wangym + * @since 2021/7/28 + */ +interface RequestCallBack { + fun onResult(result: T) + fun onError(error: String?) + fun onComplete() +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/net/callback/RequestNoResultCallBack.kt b/library-common/src/main/java/com/common/commonlib/net/callback/RequestNoResultCallBack.kt new file mode 100644 index 0000000..d1771f9 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/net/callback/RequestNoResultCallBack.kt @@ -0,0 +1,19 @@ +package com.common.commonlib.net.callback + +import com.common.commonlib.log.Logger + +/** + * 不关注结果的的回调 + * + * @author wangym + * @since 2021/8/2 + */ +abstract class RequestNoResultCallBack : RequestCallBack { + override fun onResult(result: T) { + Logger.d("result ignore") + } + + abstract override fun onError(error: String?) + + abstract override fun onComplete() +} diff --git a/library-common/src/main/java/com/common/commonlib/net/callback/RequestResultCallBack.kt b/library-common/src/main/java/com/common/commonlib/net/callback/RequestResultCallBack.kt new file mode 100644 index 0000000..7aac15c --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/net/callback/RequestResultCallBack.kt @@ -0,0 +1,19 @@ +package com.common.commonlib.net.callback + +import com.common.commonlib.log.Logger + +/** + * 不需要关注onComplete的请求回调 + * + * @author wangym + * @since 2021/8/2 + */ +abstract class RequestResultCallBack : RequestCallBack { + abstract override fun onResult(result: T) + + abstract override fun onError(error: String?) + + override fun onComplete() { + Logger.d("RequestResultCallBack: onComplete") + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/net/interceptor/CommonInterceptor.kt b/library-common/src/main/java/com/common/commonlib/net/interceptor/CommonInterceptor.kt new file mode 100644 index 0000000..ef9afea --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/net/interceptor/CommonInterceptor.kt @@ -0,0 +1,26 @@ +package com.common.commonlib.net.interceptor + +import com.common.commonlib.net.interfaces.ITypedCommon +import okhttp3.Interceptor + +/** + * 通用拦截器基类 + * + * @author Alex Wang + * @since 2022-2-24 + */ +abstract class CommonInterceptor : Interceptor, ITypedCommon { + /** + * 获取拦截器类型 + */ + override fun getType(): String { + return CommonInterceptor::class.java.simpleName + } + + /** + * 获取拦截器key + */ + open fun getKey(): String { + return CommonInterceptor::class.java.simpleName + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/net/interceptor/HostInterceptor.kt b/library-common/src/main/java/com/common/commonlib/net/interceptor/HostInterceptor.kt new file mode 100644 index 0000000..04f903a --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/net/interceptor/HostInterceptor.kt @@ -0,0 +1,86 @@ +package com.common.commonlib.net.interceptor + +import android.text.TextUtils +import android.util.Log +import com.common.commonlib.BuildConfig +import com.common.commonlib.CommonApplication +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.Interceptor +import okhttp3.Request +import okhttp3.Response +import java.io.BufferedInputStream +import java.util.* + +/** + * HOST拦截器:动态替换HOST + * + * @author wangym + * @since 2021/7/28 + */ +class HostInterceptor : Interceptor { + /** + * 拦截替换BaseUrl + */ + override fun intercept(chain: Interceptor.Chain): Response { + val oldRequest: Request = chain.request() + val requestBuilder: Request.Builder = oldRequest.newBuilder() + val headKey = BASE_URL_KEY + val headValue: String? = oldRequest.header(headKey) + // 判断是否需要替换BaseUrl,不需要则直接跳转 + if (TextUtils.isEmpty(headValue)) { + return chain.proceed(chain.request()) + } + // 该HEAD只用于替换BaseUrl,将对应的自定义HEAD去除 + requestBuilder.removeHeader(headKey) + // 尝试获取新BaseUrl + val newUrlValue = getValueFromProperties(headValue) + val newBaseUrl: HttpUrl? = newUrlValue.toHttpUrlOrNull() + // 获取到新BaseUrl进行替换 + return if (newBaseUrl != null) { + val oldHttpUrl: HttpUrl = oldRequest.url + val newUrl = oldHttpUrl.newBuilder() + .scheme(newBaseUrl.scheme) + .host(newBaseUrl.host) + .port(newBaseUrl.port) + .build() + chain.proceed(requestBuilder.url(newUrl).build()) + } else { + chain.proceed(chain.request()) + } + } + + /** + * 通过KEY从配置文件中获取值 + */ + private fun getValueFromProperties(key: String?): String { + val prop = Properties() + var value = "" + try { + val context = CommonApplication.getContext() + if (context == null) { + Log.e(TAG, "got context null, please init context!") + return value + } + val mAssets = context.assets + if (mAssets == null) { + Log.e(TAG, "got assets null, please check the assets folder") + } + // 通过输入缓冲流进行读取配置文件 + val inputStream = + BufferedInputStream(mAssets.open((BuildConfig.BASE_URL_CONFIG_PATH))) + // 加载输入流 + prop.load(inputStream) + // 根据关键字获取value值 + value = prop.getProperty(key); + } catch (e: Exception) { + Log.e(TAG, e.toString()) + } + return value + } + + companion object { + const val TAG = "BaseUrlInterceptor" + const val BASE_URL_KEY = "baseurl" + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/net/interceptor/RequestHeadInterceptor.kt b/library-common/src/main/java/com/common/commonlib/net/interceptor/RequestHeadInterceptor.kt new file mode 100644 index 0000000..39b487b --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/net/interceptor/RequestHeadInterceptor.kt @@ -0,0 +1,73 @@ +package com.common.commonlib.net.interceptor + +import okhttp3.Interceptor +import okhttp3.Request +import okhttp3.Response + +/** + * 公共请求拦截器 + * + * @author wangym + * @since 2021/7/28 + */ +open class RequestHeadInterceptor : CommonInterceptor() { + /** + * 请求头部参数 + */ + var mHeaderHashMap: HashMap = HashMap() + + /** + * 拦截请求并添加参数 + */ + override fun intercept(chain: Interceptor.Chain): Response { + val oldRequest: Request = chain.request() + val requestBuilder: Request.Builder = oldRequest.newBuilder() + requestBuilder.method(oldRequest.method, oldRequest.body) + if (mHeaderHashMap.size > 0) { + for (item: Map.Entry in mHeaderHashMap.entries) { + requestBuilder.addHeader(item.key, item.value) + } + } + return chain.proceed(requestBuilder.build()) + } + + /** + * builder构造函数 + */ + class Builder { + private var mHttpCommonInterceptor: RequestHeadInterceptor? = null + + init { + mHttpCommonInterceptor = RequestHeadInterceptor() + } + + fun addHeadParams(key: String, value: String): Builder { + mHttpCommonInterceptor?.mHeaderHashMap?.set(key, value) + return this + } + + fun addHeadParams(key: String, value: Int): Builder { + mHttpCommonInterceptor?.mHeaderHashMap?.set(key, value.toString()) + return this + } + + fun addHeadParams(key: String, value: Long): Builder { + mHttpCommonInterceptor?.mHeaderHashMap?.set(key, value.toString()) + return this + } + + fun addHeadParams(key: String, value: Float): Builder { + mHttpCommonInterceptor?.mHeaderHashMap?.set(key, value.toString()) + return this + } + + fun addHeadParams(key: String, value: Double): Builder { + mHttpCommonInterceptor?.mHeaderHashMap?.set(key, value.toString()) + return this + } + + fun build(): RequestHeadInterceptor { + return mHttpCommonInterceptor!! + } + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/net/interceptor/ResponseHeadInterceptor.kt b/library-common/src/main/java/com/common/commonlib/net/interceptor/ResponseHeadInterceptor.kt new file mode 100644 index 0000000..2cd2aca --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/net/interceptor/ResponseHeadInterceptor.kt @@ -0,0 +1,23 @@ +package com.common.commonlib.net.interceptor + +import okhttp3.Interceptor +import okhttp3.Response + +/** + * 响应头部拦截器 + * + * @author wangym + * @since 2021/7/28 + */ +open class ResponseHeadInterceptor(private var callBack: ResponseCallBack?) : CommonInterceptor() { + override fun intercept(chain: Interceptor.Chain): Response { + // 获取响应 + val response = chain.proceed(chain.request()) + callBack?.onResult(response) + return response + } + + interface ResponseCallBack { + fun onResult(response: Response) + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/net/interfaces/ITypedCommon.kt b/library-common/src/main/java/com/common/commonlib/net/interfaces/ITypedCommon.kt new file mode 100644 index 0000000..6251595 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/net/interfaces/ITypedCommon.kt @@ -0,0 +1,14 @@ +package com.common.commonlib.net.interfaces + +/** + * 获取请求类别基础接口 + * + * @author Alex Wang + * @since 2022-2-25 + */ +interface ITypedCommon { + /** + * 获取请求类别 + */ + fun getType(): String +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/net/manager/CommonInterceptorManager.kt b/library-common/src/main/java/com/common/commonlib/net/manager/CommonInterceptorManager.kt new file mode 100644 index 0000000..45d295c --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/net/manager/CommonInterceptorManager.kt @@ -0,0 +1,104 @@ +package com.common.commonlib.net.manager + +import com.common.commonlib.net.interceptor.CommonInterceptor + +/** + * 通用拦截器管理类 + * 用于请求的通用拦截器管理 + * + * @author Alex Wang + * @since 2022-2-24 + */ +object CommonInterceptorManager { + const val TAG = "CommonInterceptorManager" + + /** + * 拦截器 + */ + private val mInterceptors: HashMap> = HashMap() + + /** + * 通过类型获取该类型拦截器列表 + * + * @param type 拦截器类型 + * + * @return 拦截器列表 + */ + fun getInterceptorsByType(type: String): Map? { + return mInterceptors[type] + } + + /** + * 通过类型获取该类型拦截器列表 + * + * @param type 拦截器类型 + * + * @return 拦截器列表 + */ + fun getInterceptorsByType(type: String, key: String): CommonInterceptor? { + return mInterceptors[type]?.get(key) + } + + /** + * 通过类型列表获取通用拦截器 + * @param typeList 拦截器类型 + * + * @return 拦截器map + */ + fun getInterceptorsByTypeList(typeList: ArrayList): HashMap { + val result = HashMap() + for (item in typeList) { + if (!getInterceptorsByType(item).isNullOrEmpty()) { + result.putAll(getInterceptorsByType(item)!!) + } + } + return result + } + + /** + * 添加新的通用拦截器 + * + * @param interceptor 拦截器 + */ + fun addInterceptorByType(interceptor: CommonInterceptor) { + val type = interceptor.getType() + val key = interceptor.getKey() + var map = mInterceptors[type] + if (map.isNullOrEmpty()) { + map = HashMap() + mInterceptors[type] = map + } + map[key] = interceptor + } + + /** + * 删除通用拦截器 + * + * @param type 请求类型 + * @param key 拦截器key + */ + fun removeInterceptorByType(type: String, key: String) { + val map = mInterceptors[type] + if (!map.isNullOrEmpty()) { + map.remove(key) + } + } + + /** + * 删除通用拦截器 + * + * @param type 请求类型 + */ + fun removeInterceptorByType(type: String) { + mInterceptors.remove(type) + } + + /** + * 删除通用拦截器 + * + * @param interceptor 拦截器 + */ + fun removeInterceptorByType(interceptor: CommonInterceptor) { + removeInterceptorByType(interceptor.getType(), interceptor.getKey()) + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/net/manager/RequestManager.kt b/library-common/src/main/java/com/common/commonlib/net/manager/RequestManager.kt new file mode 100644 index 0000000..c75f071 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/net/manager/RequestManager.kt @@ -0,0 +1,105 @@ +package com.common.commonlib.net.manager + +import com.common.commonlib.log.Logger.d +import com.common.commonlib.net.interceptor.HostInterceptor +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory +import retrofit2.converter.gson.GsonConverterFactory +import java.util.concurrent.TimeUnit + +/** + * 网络请求管理器 + * + * @author wangym + * @since 2021/7/28 + */ +object RequestManager { + const val TAG = "RequestManager" + private var logLevel = HttpLoggingInterceptor.Level.NONE + + /** + * 默认超时时间(s) + */ + private const val defaultTimeOut: Long = 5 + + /** + * 默认读取超时时间(s) + */ + private const val defaultReadTimeOut: Long = 10 + + private var mRetrofit: Retrofit? = null + + private val loggingInterceptor = HttpLoggingInterceptor { + d(TAG, it) + } + + /** + * 创建retrofit + * + * @param service 类型 + * @param interceptors 拦截器 + * @param loaderType 请求类别 + */ + fun create(service: Class, interceptors: List?, loaderType: String): T { + init(interceptors, loaderType) + return mRetrofit!!.create(service) + } + + private fun init(interceptors: List?, loaderType: String) { + val builder: OkHttpClient.Builder = OkHttpClient.Builder() + builder.connectTimeout(defaultTimeOut, TimeUnit.SECONDS) + builder.readTimeout(defaultReadTimeOut, TimeUnit.SECONDS) + + // 动态HOST替换拦截器 + builder.addInterceptor(HostInterceptor()) + // 添加通用拦截器 + val interceptorList = CommonInterceptorManager.getInterceptorsByType(loaderType) + if (!interceptorList.isNullOrEmpty()) { + d(TAG, "got common interceptor list") + for (item in interceptorList.values) { + builder.addInterceptor(item) + } + } + // 添加接口自定义的拦截器 + if (!interceptors.isNullOrEmpty()) { + for (intercept in interceptors) { + builder.addInterceptor(intercept) + } + } + + // 添加日志拦截器 日志拦截器,需要添加在所有拦截器之后 + loggingInterceptor.level = logLevel + builder.addInterceptor(loggingInterceptor) + + mRetrofit = Retrofit.Builder() + .client(builder.build()) + .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) + .addConverterFactory(GsonConverterFactory.create()) + .baseUrl("https://www") + .build() + } + + /** + * 打开完整日志 + */ + fun openFullLog() { + logLevel = HttpLoggingInterceptor.Level.BODY + } + + /** + * 打开基础日志 + */ + fun openBasicLog() { + logLevel = HttpLoggingInterceptor.Level.BASIC + } + + /** + * 打开头部日志 + */ + fun openHeadLog() { + logLevel = HttpLoggingInterceptor.Level.HEADERS + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/utils/BaseUtils.kt b/library-common/src/main/java/com/common/commonlib/utils/BaseUtils.kt new file mode 100644 index 0000000..b5bf316 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/utils/BaseUtils.kt @@ -0,0 +1,48 @@ +package com.common.commonlib.utils + +import android.content.Context +import android.os.Environment +import com.common.commonlib.net.manager.RequestManager + +/** + * 基础工具类 + */ +object BaseUtils { + private const val TAG: String = "BaseUtils" + + fun isListEmpty(list: List?): Boolean { + return list == null || list.isEmpty() + } + + /** + * 获取应用中文件存储 + */ + fun getExternalStoragePath(context: Context): String? { + return context.getExternalFilesDir(null)?.path + } + + fun getExternalStorageDirectory(context: Context): String? { + return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).path + } + + /** + * 打开网络请求日志 + */ + fun enableFullLog() { + RequestManager.openFullLog() + } + + /** + * 打开网络请求基础日志 + */ + fun enableBasicLog() { + RequestManager.openBasicLog() + } + + /** + * 打开网络请求日志 + */ + fun enableHeadersLog() { + RequestManager.openHeadLog() + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/utils/DateUtils.kt b/library-common/src/main/java/com/common/commonlib/utils/DateUtils.kt new file mode 100644 index 0000000..2ad1d63 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/utils/DateUtils.kt @@ -0,0 +1,83 @@ +package com.common.commonlib.utils + +import android.annotation.SuppressLint +import java.text.SimpleDateFormat +import java.util.* + +/** + * 时间工具类 + */ +object DateUtils { + /** + * 如果使用大写HH标识使用24小时显示格式,如果使用小写hh就表示使用12小时制格式 + */ + const val DATE_TO_STRING_DETAIL_PATTERN = "yyyy-MM-dd HH:mm:ss" + + /** + * 年-月-日 显示格式 + * */ + const val DATE_TO_STRING_SHORT_PATTERN = "yyyy.MM.dd" + + /** + * 年-月-日 显示格式 + * */ + const val DATE_TO_STRING_LONG_PATTERN = "yyyy_MM_dd HH:mm:ss" + + /** + * 60秒 + */ + const val SECONDS = 60 + + /** + * 60分钟 + */ + const val MINUS = 60 + + /** + * 得到现在时间 + * + * @return + */ + private fun getNow(): Date { + return Date() + } + + @SuppressLint("SimpleDateFormat") + fun getNowTimeFormat(format: String): String { + val formatter = SimpleDateFormat(format) + return formatter.format(getNow()) + } + + /** + * 将int型的数据转化成 XX:XX:XX" 形式的字符串 + */ + fun getDurationTimeByInt(source: Int): String { + var temp = source + var h = 0 + var m = 0 + var s = 0 + var result = "" + if (temp >= SECONDS * MINUS) { + h = temp / (SECONDS * MINUS) + temp -= h * (SECONDS * MINUS) + } + if (temp >= SECONDS) { + m = temp / SECONDS + temp -= m * SECONDS + } + s = temp + + result = when { + h > 0 -> { + "$h:$m:$s\"" + } + m > 0 -> { + "$m:$s\"" + } + else -> { + "$s\"" + } + } + return result + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/utils/DisplayUtils.kt b/library-common/src/main/java/com/common/commonlib/utils/DisplayUtils.kt new file mode 100644 index 0000000..3dedb34 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/utils/DisplayUtils.kt @@ -0,0 +1,25 @@ +package com.common.commonlib.utils + +import android.content.Context + +/** + * 界面工具类 + */ +object DisplayUtils { + + /** + * dp 转 px + */ + fun dp2px(context: Context, dpVale: Float): Int { + val density = context.resources.displayMetrics.density + return ((dpVale * density + 0.5f).toInt()) + } + + /** + * px 转 dp + */ + fun px2dp(context: Context, pxVale: Float): Int { + val density = context.resources.displayMetrics.density + return ((pxVale / density + 0.5f).toInt()) + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/utils/FTPUtils.kt b/library-common/src/main/java/com/common/commonlib/utils/FTPUtils.kt new file mode 100644 index 0000000..5f84163 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/utils/FTPUtils.kt @@ -0,0 +1,136 @@ +package com.common.commonlib.utils + +import org.apache.commons.net.ftp.FTP +import org.apache.commons.net.ftp.FTPClient +import org.apache.commons.net.ftp.FTPReply +import java.io.BufferedOutputStream +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.IOException + +/** + * FTP工具类 + * + * @author wangym + * @since 2021/7/28 + */ +object FTPUtils { + /** + * 默认缓冲池大小 + */ + private const val DEFAULT_BUFFER_SIZE = 1024 * 10 + + /** + * 默认编码模式 + */ + private const val DEFAULT_ENCODING = "UTF-8" + + /** + * 结果 + */ + enum class RESULT { SUCCESS, FAILED } + + /** + * ftp上传 + * @param url ftp地址 + * @param port ftp连接端口号 + * @param username 登录用户名 + * @param password 登录密码 + * @param fileNamePath 本地文件保存路径 + * @param fileName 本地文件名 + * @return + */ + fun ftpUpload( + url: String?, + port: String, + username: String?, + password: String?, + fileNamePath: String, + fileName: String + ): RESULT { + val ftpClient = FTPClient() + val fis: FileInputStream? + var returnMessage = RESULT.FAILED + try { + ftpClient.connect(url, port.toInt()) + val loginResult = ftpClient.login(username, password) + val returnCode = ftpClient.replyCode + if (loginResult && FTPReply.isPositiveCompletion(returnCode)) { // 如果登录成功 + ftpClient.bufferSize = DEFAULT_BUFFER_SIZE + ftpClient.controlEncoding = DEFAULT_ENCODING + ftpClient.setFileType(FTP.BINARY_FILE_TYPE) + ftpClient.enterLocalPassiveMode() + fis = FileInputStream(fileNamePath + fileName) + ftpClient.storeFile(fileName, fis) + returnMessage = RESULT.SUCCESS //上传成功 + } else { // 如果登录失败 + returnMessage = RESULT.FAILED + } + } catch (e: IOException) { + e.printStackTrace() + returnMessage = RESULT.FAILED + } finally { + try { + ftpClient.disconnect() + } catch (e: IOException) { + e.printStackTrace() + throw RuntimeException("关闭FTP连接发生异常!", e) + } + } + return returnMessage + } + + /** + * ftp下载 + * @param url + * @param port + * @param username + * @param password + * @param filePath 存放文件的路径 + * @param FTP_file 要下载的文件名 + * @param SD_file 本地文件名 + */ + fun ftpDown( + url: String?, + port: Int, + username: String?, + password: String?, + filePath: String, + FTP_file: String?, + SD_file: String + ): RESULT { + val buffOut: BufferedOutputStream? + val ftpClient = FTPClient() + var returnMessage = RESULT.FAILED + try { + ftpClient.connect(url, port) + val loginResult = ftpClient.login(username, password) + val returnCode = ftpClient.replyCode + if (loginResult && FTPReply.isPositiveCompletion(returnCode)) { // 如果登录成功 + ftpClient.bufferSize = DEFAULT_BUFFER_SIZE + ftpClient.controlEncoding = DEFAULT_ENCODING + ftpClient.enterLocalPassiveMode() + buffOut = BufferedOutputStream(FileOutputStream(filePath + SD_file), 8 * 1024) + ftpClient.retrieveFile(FTP_file, buffOut) + buffOut.flush() + buffOut.close() + ftpClient.logout() + ftpClient.disconnect() + returnMessage = RESULT.SUCCESS //上传成功 + } else { // 如果登录失败 + returnMessage = RESULT.FAILED + } + } catch (e: IOException) { + e.printStackTrace() + throw java.lang.RuntimeException("FTP客户端出错!", e) + } finally { + try { + ftpClient.disconnect() + } catch (e: IOException) { + e.printStackTrace() + throw java.lang.RuntimeException("关闭FTP连接发生异常!", e) + } + } + return returnMessage + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/utils/KtExtensions.kt b/library-common/src/main/java/com/common/commonlib/utils/KtExtensions.kt new file mode 100644 index 0000000..e23b66d --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/utils/KtExtensions.kt @@ -0,0 +1,37 @@ +/** + * Kotlin扩展属性和扩展函数 + */ +package com.common.commonlib.utils + +import android.app.Activity +import android.content.res.Resources +import android.util.TypedValue +import android.view.View + +/** + * Kt扩展属性,判断Activity是否存活 + */ +val Activity?.isAlive: Boolean + get() = !(this?.isDestroyed ?: true || this?.isFinishing ?: true) + +/** + * Boolean转Visibility + */ +fun Boolean.toVisibility() = if (this) View.VISIBLE else View.GONE + +/** + * Float dp2px + */ +val Float.dp: Float + get() = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + this, + Resources.getSystem().displayMetrics + ) + +/** + * Int dp2px + */ +val Int.dp: Float + get() = this.toFloat().dp + diff --git a/library-common/src/main/java/com/common/commonlib/utils/MMKVUtils.kt b/library-common/src/main/java/com/common/commonlib/utils/MMKVUtils.kt new file mode 100644 index 0000000..6697572 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/utils/MMKVUtils.kt @@ -0,0 +1,73 @@ +package com.common.commonlib.utils + +import com.common.commonlib.log.Logger.e +import com.tencent.mmkv.MMKV + +/** + * MMKV的工具类 + * + * @author wangym + * @since 2021-12-14 + */ +object MMKVUtils { + const val TAG = "MMKVUtils" + + /** + * 保存数据 + * @param key 键 + * @param value 值 + * @param args 第一位:自定义保存ID + * 第二位:是否支持跨进程 + */ + fun put(key: String, value: T, vararg args: Any) { + val kv: MMKV = getKV(*args) + when (value) { + is String -> kv.putString(key, value) + is Boolean -> kv.putBoolean(key, value) + is Int -> kv.putInt(key, value) + is Float -> kv.putFloat(key, value) + is Long -> kv.putLong(key, value) + is ByteArray -> kv.putBytes(key, value) + is Set<*> -> kv.putStringSet(key, value as Set) + else -> { + e(TAG, "got error value type,please check") + } + } + } + + fun getByteArray(key: String, vararg args: Any): ByteArray? { + return getKV(*args).getBytes(key, null) + } + + fun getBoolean(key: String, vararg args: Any): Boolean { + return getKV(*args).getBoolean(key, false) + } + + fun getFloat(key: String, vararg args: Any): Float { + return getKV(*args).getFloat(key, 0f) + } + + fun getInt(key: String, vararg args: Any): Int { + return getKV(*args).getInt(key, 0) + } + + fun getLong(key: String, vararg args: Any): Long { + return getKV(*args).getLong(key, 0L) + } + + fun getString(key: String, vararg args: Any): String? { + return getKV(*args).getString(key, "") + } + + fun getStringSet(key: String, vararg args: Any): Set? { + return getKV(*args).getStringSet(key, null) + } + + private fun getKV(vararg args: Any): MMKV { + return when (args.size) { + 1 -> MMKV.mmkvWithID(args[0] as String) + 2 -> MMKV.mmkvWithID(args[0] as String, MMKV.MULTI_PROCESS_MODE) + else -> MMKV.defaultMMKV() + } + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/utils/NetworkHelper.kt b/library-common/src/main/java/com/common/commonlib/utils/NetworkHelper.kt new file mode 100644 index 0000000..704a4ea --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/utils/NetworkHelper.kt @@ -0,0 +1,57 @@ +package com.common.commonlib.utils + +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkRequest +import android.provider.Settings +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.OnLifecycleEvent + +object NetworkHelper { + + /** + * 网络状态监听 + */ + inline fun observerNetworkState(activity: FragmentActivity, crossinline callback: (Boolean) -> Unit) { + val manager = activity.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val networkCallback = object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + super.onAvailable(network) + if (activity.isAlive) { + activity.runOnUiThread { callback(true) } + } + } + + override fun onLost(network: Network) { + super.onLost(network) + if (activity.isAlive) { + activity.runOnUiThread { callback(false) } + } + } + } + manager.requestNetwork(NetworkRequest.Builder().build(), networkCallback) + + activity.lifecycle.addObserver(object : LifecycleObserver { + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + fun onDestroy() { + manager.unregisterNetworkCallback(networkCallback) + } + }) + } + + @SuppressLint("MissingPermission") + fun isNetworkConnect(context: Context): Boolean { + val cm = context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + return cm.activeNetworkInfo?.isAvailable ?: false + } + + fun toNetworkSetting(context: Context) { + val intent = Intent(Settings.ACTION_WIFI_SETTINGS) + context.startActivity(intent) + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/utils/PhotoUtils.kt b/library-common/src/main/java/com/common/commonlib/utils/PhotoUtils.kt new file mode 100644 index 0000000..c6deb78 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/utils/PhotoUtils.kt @@ -0,0 +1,111 @@ +package com.common.commonlib.utils + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.util.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import top.zibin.luban.Luban +import top.zibin.luban.OnCompressListener +import java.io.File +import java.io.FileOutputStream +import java.text.SimpleDateFormat +import java.util.* + + +object PhotoUtils { + private const val TAG = "PhotoUtils"; + + /** + * 根据时间生成照片文件 + */ + fun generatePhotoFileByTime(context: Context, photoPath: String): File { + val rootPath: String = context.applicationContext.getExternalFilesDir(null)?.path ?: "" + val savePhotoFolderPath: String = rootPath + photoPath + val savePhotoFolder = File(savePhotoFolderPath) + if (!savePhotoFolder.exists()) { + //创建路径,需要权限 + savePhotoFolder.mkdirs() + } + val photoName = SimpleDateFormat("yyyy_MM_dd_hh_mm_ss_SSS").format(Date()) + ".png" + + return File(savePhotoFolder, photoName) + + } + + fun initPicPath(context: Context?, photoPath: String): String { + val rootPath: String = context?.getExternalFilesDir(null)?.path ?: "" + val savePhotoFilePath: String = rootPath + photoPath + val file = File(savePhotoFilePath) + if (!file.exists()) { + //创建路径,需要权限 + file.mkdirs() + } + val photoName = SimpleDateFormat("yyyy_MM_dd_hh_mm_ss_SSS").format(Date()) + ".png" + return "$savePhotoFilePath/$photoName" + } + + /** + * 得到保存图片的文件夹地址 + */ + fun getImageParentPath(context: Context, photoPath: String): String { + val rootPath: String = context.getExternalFilesDir(null)?.path ?: "" + val savePhotoFilePath: String = rootPath + photoPath + val file = File(savePhotoFilePath) + if (!file.exists()) { + //创建路径,需要权限 + file.mkdirs() + } + return savePhotoFilePath + } + + /** + * 压缩图片,使用Luban框架压缩处理 + */ + fun compressPicture( + context: Context, + file: File, + targetDir: String, + listener: OnCompressListener + ) { + Luban.with(context.applicationContext) + .load(file) + .ignoreBy(100) + .setTargetDir(targetDir) + .setCompressListener(listener).launch() + } + + /** + *把其他格式的图片转换成png格式的图片,耗时操作 + * originImageFile:原图片文件 + * imgSavePath:图片要保存的地址,如:/sdcard/0/emulator/data/xxx.xxx.xx/file/test.png + */ + fun tans2PNGSave(originImageFile: File?, imgSavePath: String?, callBack: TransformCallBack) { + GlobalScope.launch(Dispatchers.IO) { + val decodeFile = BitmapFactory.decodeFile(originImageFile?.absolutePath) + var out: FileOutputStream? = null + try { + out = FileOutputStream(File(imgSavePath)) + decodeFile.compress(Bitmap.CompressFormat.PNG, 90, out) + out.flush() + out.close() + } catch (e: Exception) { + Log.e(TAG, "tans2PNGSave error==" + e.message) + } finally { + out?.close() + } + + GlobalScope.launch(Dispatchers.Main) { + //通知主线程图片保存成功 + callBack.onSuccess() + } + } + } + + interface TransformCallBack { + fun onSuccess() + fun onError(); + } +} diff --git a/library-common/src/main/java/com/common/commonlib/utils/SpManager.kt b/library-common/src/main/java/com/common/commonlib/utils/SpManager.kt new file mode 100644 index 0000000..6e0075f --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/utils/SpManager.kt @@ -0,0 +1,59 @@ +package com.common.commonlib.utils + +import android.annotation.SuppressLint +import android.content.Context +import android.content.SharedPreferences + +/** + * sharedPreference管理类 + * + * @author wangym + * @since 2021/7/28 + */ +object SpManager { + const val HAS_INIT = "has_init" + private var sp: SharedPreferences? = null + private var editor: SharedPreferences.Editor? = null + + @SuppressLint("CommitPrefEdits") + fun init(context: Context, name: String, mod: Int) { + sp = context.getSharedPreferences(name, mod) + editor = sp!!.edit() + } + + fun putBoolean(key: String, boolean: Boolean) { + editor!!.putBoolean(key, boolean) + editor!!.apply() + } + + fun getBoolean(key: String): Boolean { + return sp!!.getBoolean(key, false) + } + + fun putInt(key: String, value: Int) { + editor!!.putInt(key, value) + editor!!.apply() + } + + fun getInt(key: String): Int { + return sp!!.getInt(key, -1) + } + + fun putString(key: String, value: String) { + editor!!.putString(key, value) + editor!!.apply() + } + + fun getString(key: String): String? { + return sp!!.getString(key, "") + } + + fun putLong(key: String, value: Long) { + editor!!.putLong(key, value) + editor!!.apply() + } + + fun getLong(key: String): Long { + return sp!!.getLong(key, -1L) + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/utils/StringUtils.kt b/library-common/src/main/java/com/common/commonlib/utils/StringUtils.kt new file mode 100644 index 0000000..a9fd8be --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/utils/StringUtils.kt @@ -0,0 +1,34 @@ +package com.common.commonlib.utils + +import android.text.TextUtils + +/** + * 字符串工具类 + */ +class StringUtils { + + /** + * 将字符串List转化为用 | 分割的字符串 + */ + fun getStringSeparateByLine(list: List): String { + var result = "" + for (item in list.withIndex()) { + result = if (item.index == (list.size - 1)) { + "$result${item.value}" + } else { + "$result${item.value}|" + } + } + return result + } + + /** + * 将 | 分割符转化为list + */ + fun getListByString(source: String): List { + if (TextUtils.isEmpty(source)) { + return ArrayList() + } + return source.split("|") + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/view/CommonTitleView.java b/library-common/src/main/java/com/common/commonlib/view/CommonTitleView.java new file mode 100644 index 0000000..05f5441 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/CommonTitleView.java @@ -0,0 +1,252 @@ +package com.common.commonlib.view; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import com.common.commonlib.R; +import com.common.commonlib.utils.DisplayUtils; + +/** + * 通用标题栏 + * + * @author miracle + * @since 2021/8/2 + */ +public class CommonTitleView extends FrameLayout implements View.OnClickListener { + private Context mContext; + private ImageView ivLeft; + private TextView tv_title; + private ImageView ivRight; + private TextView tv_edit; + private int showStyle; + + private static final int STYLE_LEFT = 1; + private static final int STYLE_LEFT_MIDDLE = 2; + private static final int STYLE_LEFT_MIDDLE_RIGHT = 3; + private static final int STYLE_MIDDLE = 4; + private static final int STYLE_MIDDLE_RIGHT = 5; + private static final int STYLE_RIGHT = 6; + private static final int STYLE_EDIT = 7; + private String title; + //是否要固定高度的模式,不受match parent\warp content等影响 + private boolean isFixedHeight; + //设置固定的高度值,默认是48dp + private int fixedHeight; + + private static final int DEFAULT_HEIGHT = 48; + private View rlLeftHotZone; + private View rlRightHotZone; + private View rlEditZone; + private Drawable leftDrawable; + private Drawable rightDrawable; + private int bgColor; + + public CommonTitleView(Context context) { + this(context, null); + } + + public CommonTitleView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CommonTitleView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mContext = context; + init(attrs); + } + + private void init(AttributeSet attrs) { + initAttrs(attrs); + View inflate = LayoutInflater.from(mContext).inflate(R.layout.common_title_view, this, true); + View root_view = findViewById(R.id.root_view); + ivLeft = findViewById(R.id.iv_left); + ivRight = findViewById(R.id.iv_right); + tv_title = findViewById(R.id.tv_title); + rlLeftHotZone = findViewById(R.id.rl_left_hot_zone); + rlRightHotZone = findViewById(R.id.rl_add_hot_zone); + tv_edit = findViewById(R.id.tv_edit); + rlEditZone = findViewById(R.id.rl_edit_hot_zone); + root_view.setBackgroundColor(bgColor); + if (leftDrawable != null) { + ivLeft.setImageDrawable(leftDrawable); + } + if (rightDrawable != null) { + ivRight.setImageDrawable(rightDrawable); + } + initStyle(); + setTitle(title); + } + + + private void initAttrs(AttributeSet attrs) { + TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.CommonTitleView); + showStyle = typedArray.getInt(R.styleable.CommonTitleView_show_style, STYLE_MIDDLE); + title = typedArray.getString(R.styleable.CommonTitleView_title); + isFixedHeight = typedArray.getBoolean(R.styleable.CommonTitleView_is_fixed_height, true); + fixedHeight = typedArray.getInteger(R.styleable.CommonTitleView_fixed_height, DisplayUtils.INSTANCE.dp2px(mContext, DEFAULT_HEIGHT)); + leftDrawable = typedArray.getDrawable(R.styleable.CommonTitleView_drawableLeft); + rightDrawable = typedArray.getDrawable(R.styleable.CommonTitleView_drawableRight); + bgColor = typedArray.getColor(R.styleable.CommonTitleView_bgColor, getResources().getColor(R.color.title_bar_bg_color)); + + typedArray.recycle(); + + + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (isFixedHeight) { + int makeMeasureSpec = MeasureSpec.makeMeasureSpec(fixedHeight, MeasureSpec.EXACTLY); + super.onMeasure(widthMeasureSpec, makeMeasureSpec); + } else { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } + + private void initStyle() { + switch (showStyle) { + case STYLE_LEFT: + ivLeft.setVisibility(VISIBLE); + rlLeftHotZone.setOnClickListener(this); + tv_title.setVisibility(INVISIBLE); + tv_title.setText(""); + ivRight.setVisibility(INVISIBLE); + rlRightHotZone.setOnClickListener(null); + break; + case STYLE_LEFT_MIDDLE: + ivLeft.setVisibility(VISIBLE); + rlLeftHotZone.setOnClickListener(this); + tv_title.setVisibility(VISIBLE); + tv_title.setText(""); + ivRight.setVisibility(INVISIBLE); + rlRightHotZone.setOnClickListener(null); + break; + case STYLE_MIDDLE_RIGHT: + ivLeft.setVisibility(INVISIBLE); + rlLeftHotZone.setOnClickListener(null); + tv_title.setVisibility(VISIBLE); + tv_title.setText(""); + ivRight.setVisibility(VISIBLE); + rlRightHotZone.setOnClickListener(this); + break; + case STYLE_RIGHT: + ivLeft.setVisibility(INVISIBLE); + rlLeftHotZone.setOnClickListener(null); + tv_title.setVisibility(INVISIBLE); + tv_title.setText(""); + ivRight.setVisibility(VISIBLE); + rlRightHotZone.setOnClickListener(this); + break; + case STYLE_LEFT_MIDDLE_RIGHT: + ivLeft.setVisibility(VISIBLE); + rlLeftHotZone.setOnClickListener(this); + tv_title.setVisibility(VISIBLE); + tv_title.setText(""); + ivRight.setVisibility(VISIBLE); + rlRightHotZone.setOnClickListener(this); + break; + case STYLE_EDIT: + ivLeft.setVisibility(VISIBLE); + rlLeftHotZone.setOnClickListener(this); + tv_title.setVisibility(VISIBLE); + tv_title.setText(""); + ivRight.setVisibility(GONE); + rlRightHotZone.setVisibility(GONE); + rlRightHotZone.setOnClickListener(null); + tv_edit.setVisibility(VISIBLE); + rlEditZone.setVisibility(VISIBLE); + rlEditZone.setOnClickListener(this); + break; + case STYLE_MIDDLE: + default: + ivLeft.setVisibility(INVISIBLE); + rlLeftHotZone.setOnClickListener(null); + tv_title.setVisibility(VISIBLE); + tv_title.setText(""); + ivRight.setVisibility(INVISIBLE); + rlRightHotZone.setOnClickListener(null); + break; + } + } + + public void setTitle(String title) { + this.title = title; + tv_title.setText(title); + } + + + @Override + public void onClick(View v) { + int viewId = v.getId(); + if (viewId == R.id.rl_left_hot_zone) { + if (leftIconListener != null) { + leftIconListener.onClick(); + } + } else if (viewId == R.id.rl_add_hot_zone) { + if (rightIconListener != null) { + rightIconListener.onClick(); + } + } else if (viewId == R.id.rl_edit_hot_zone) { + if (editListener != null) { + editListener.onClick(); + } + } + } + + public interface LeftIconListener { + void onClick(); + } + + private LeftIconListener leftIconListener; + + public void setLeftIconListener(LeftIconListener leftIconListener) { + this.leftIconListener = leftIconListener; + } + + public interface RightIconListener { + void onClick(); + } + + private RightIconListener rightIconListener; + + public void setRightIconListener(RightIconListener rightIconListener) { + this.rightIconListener = rightIconListener; + } + + public interface EditListener { + void onClick(); + } + + private EditListener editListener; + + public void setEditListener(EditListener editListener) { + this.editListener = editListener; + } + + public int getEditState() { + if (tv_edit.getText().equals(mContext.getResources().getString(R.string.edit))) { + return 0; + } else if (tv_edit.getText().equals(mContext.getResources().getString(R.string.save))) { + return 1; + } else { + return -1; + } + } + + public void setEditText(int state) { + if (state == 0) { + tv_edit.setText(mContext.getResources().getString(R.string.edit)); + } else if (state == 1) { + tv_edit.setText(mContext.getResources().getString(R.string.save)); + } else { + tv_edit.setText(""); + } + } +} diff --git a/library-common/src/main/java/com/common/commonlib/view/MikeView.java b/library-common/src/main/java/com/common/commonlib/view/MikeView.java new file mode 100644 index 0000000..4324164 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/MikeView.java @@ -0,0 +1,161 @@ +package com.common.commonlib.view; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.View; + +import androidx.annotation.Nullable; + +import com.common.commonlib.R; + +/** + * mic自定义View + * + * @author miracle + * @since 2021/8/2 + */ +public class MikeView extends View { + //进度条格数 + private final int COUNT = 16; + //宽、高、间隔、进度条居上距离 + private int mVoiceRectW = 1; + private int mVoiceRectH = 10; + private int mInterval = 3; + private int mVoiceRectMarginTop = 83; + //mic 文字 进度条 背景 + private Paint mImagePaint; + private Paint mVoiceRectPaint; + private Paint mBackgroundPaint; + + private Bitmap mVoiceRecording;//mic图片 + + //屏幕宽高 + private int SCREEN_HEIGHT = 0; + private int SCREEN_WIDTH = 0; + + private int mVoiceRectStart = 0, mDescriptionW = 0;//进度条开始左边位置、文字长度 + + private int mCurrentPosition = 0;//当前位置 + private int mBackgroundRound = 3;//背景圆角 + + private int mImageW = 42;//新mic图片宽 + private int mImageH = 42;//新mic图片高 + + private int mMarginTop = 25, mTextMarginBottom = 11;//mic图片居上距离 文字居底部距离 + + public MikeView(Context context) { + this(context, null); + } + + public MikeView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public MikeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + DisplayMetrics dm = getResources().getDisplayMetrics(); + + mImageW = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mImageW, dm); + mImageH = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mImageH, dm); + mMarginTop = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mMarginTop, dm); + mTextMarginBottom = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mTextMarginBottom, dm); + mVoiceRectW = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mVoiceRectW, dm); + mVoiceRectH = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mVoiceRectH, dm); + mInterval = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mInterval, dm); + mVoiceRectMarginTop = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mVoiceRectMarginTop, dm); + mBackgroundRound = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mBackgroundRound, dm); + + mImagePaint = new Paint(); + + mVoiceRectPaint = new Paint(); + mVoiceRectPaint.setColor(Color.WHITE); + + mBackgroundPaint = new Paint(); + mBackgroundPaint.setColor(Color.TRANSPARENT);//背景颜色 + + mVoiceRecording = BitmapFactory.decodeResource(getResources(), R.drawable.mike); + mVoiceRecording = scaleBitmap(mVoiceRecording, mImageW, mImageH); + + setWillNotDraw(false); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (SCREEN_HEIGHT == 0 || SCREEN_WIDTH == 0) { + SCREEN_HEIGHT = getHeight(); + SCREEN_WIDTH = getWidth(); + } + if (mVoiceRectStart <= 0) { + mVoiceRectStart = SCREEN_WIDTH / 2 - (COUNT * mVoiceRectW + (COUNT - 1) * mInterval) / 2; + } + + canvas.drawRoundRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, mBackgroundRound, mBackgroundRound, mBackgroundPaint);//画灰色背景和圆角 + + canvas.drawBitmap(mVoiceRecording, SCREEN_WIDTH / 2 - mVoiceRecording.getWidth() / 2, mMarginTop, mImagePaint);//画mic图片 + + //画前几个是白色 后几个是灰色 + for (int i = 0; i < COUNT; i++) { + if (i < mCurrentPosition) { + mVoiceRectPaint.setColor(Color.WHITE); + } else { + mVoiceRectPaint.setColor(Color.parseColor("#73FFFFFF")); + } + canvas.drawRect(i * mVoiceRectW + (i * mInterval) + mVoiceRectStart, mVoiceRectMarginTop, i * mVoiceRectW + (i * mInterval) + mVoiceRectW + mVoiceRectStart, mVoiceRectMarginTop + mVoiceRectH, mVoiceRectPaint); + } + } + + public void setIndex(int p) { + if (p < 0) { + p = 0; + } + if (p >= COUNT) { + p = COUNT - 1; + } + + mCurrentPosition = p; + postInvalidate(); + } + + public void setIndex(double percent) { + int p = (int) (COUNT * percent); + setIndex(p); + } + + /** + * bitmap 缩放 + * + * @param origin 源 + * @param newWidth 宽 + * @param newHeight 高 + * @return bitmap + */ + private Bitmap scaleBitmap(Bitmap origin, int newWidth, int newHeight) { + if (origin == null) { + return null; + } + int height = origin.getHeight(); + int width = origin.getWidth(); + float scaleWidth = ((float) newWidth) / width; + float scaleHeight = ((float) newHeight) / height; + Matrix matrix = new Matrix(); + matrix.postScale(scaleWidth, scaleHeight);// 使用后乘 + Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false); + if (!origin.isRecycled()) { + origin.recycle(); + } + return newBM; + } +} diff --git a/library-common/src/main/java/com/common/commonlib/view/SlideRecyclerView.java b/library-common/src/main/java/com/common/commonlib/view/SlideRecyclerView.java new file mode 100644 index 0000000..d4562e6 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/SlideRecyclerView.java @@ -0,0 +1,217 @@ +package com.common.commonlib.view; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.widget.Scroller; + +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +/** + * 支持侧滑删除的RecyclerView + * + * @author wangym + * @since 2021/8/2 + */ +public class SlideRecyclerView extends RecyclerView { + private static final String TAG = "SlideRecyclerView"; + private static final int INVALID_POSITION = -1; // 触摸到的点不在子View范围内 + private static final int INVALID_CHILD_WIDTH = -1; // 子ItemView不含两个子View + private static final int SNAP_VELOCITY = 600; // 最小滑动速度 + + private VelocityTracker mVelocityTracker; // 速度追踪器 + private int mTouchSlop; // 认为是滑动的最小距离(一般由系统提供) + private Rect mTouchFrame; // 子View所在的矩形范围 + private Scroller mScroller; + private float mLastX; // 滑动过程中记录上次触碰点X + private float mFirstX, mFirstY; // 首次触碰范围 + private boolean mIsSlide; // 是否滑动子View + private ViewGroup mFlingView; // 触碰的子View + private int mPosition; // 触碰的view的位置 + private int mMenuViewWidth; // 菜单按钮宽度 + + public SlideRecyclerView(Context context) { + this(context, null); + } + + public SlideRecyclerView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public SlideRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + mScroller = new Scroller(context); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent e) { + int x = (int) e.getX(); + int y = (int) e.getY(); + obtainVelocity(e); + switch (e.getAction()) { + case MotionEvent.ACTION_DOWN: + if (!mScroller.isFinished()) { // 如果动画还没停止,则立即终止动画 + mScroller.abortAnimation(); + } + mFirstX = mLastX = x; + mFirstY = y; + mPosition = pointToPosition(x, y); // 获取触碰点所在的position + if (mPosition != INVALID_POSITION) { + View view = mFlingView; + // 获取触碰点所在的view + mFlingView = (ViewGroup) getChildAt(mPosition - ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition()); + // 这里判断一下如果之前触碰的view已经打开,而当前碰到的view不是那个view则立即关闭之前的view,此处并不需要担动画没完成冲突,因为之前已经abortAnimation + if (view != null && mFlingView != view && view.getScrollX() != 0) { + view.scrollTo(0, 0); + } + // 这里进行了强制的要求,RecyclerView的子ViewGroup必须要有2个子view,这样菜单按钮才会有值, + // 需要注意的是:如果不定制RecyclerView的子View,则要求子View必须要有固定的width。 + // 比如使用LinearLayout作为根布局,而content部分width已经是match_parent,此时如果菜单view用的是wrap_content,menu的宽度就会为0。 + if (mFlingView.getChildCount() == 2) { + mMenuViewWidth = mFlingView.getChildAt(1).getWidth(); + } else { + mMenuViewWidth = INVALID_CHILD_WIDTH; + } + } + break; + case MotionEvent.ACTION_MOVE: + mVelocityTracker.computeCurrentVelocity(1000); + // 此处有俩判断,满足其一则认为是侧滑: + // 1.如果x方向速度大于y方向速度,且大于最小速度限制; + // 2.如果x方向的侧滑距离大于y方向滑动距离,且x方向达到最小滑动距离; + float xVelocity = mVelocityTracker.getXVelocity(); + float yVelocity = mVelocityTracker.getYVelocity(); + if (Math.abs(xVelocity) > SNAP_VELOCITY && Math.abs(xVelocity) > Math.abs(yVelocity) + || Math.abs(x - mFirstX) >= mTouchSlop + && Math.abs(x - mFirstX) > Math.abs(y - mFirstY)) { + mIsSlide = true; + return true; + } + break; + case MotionEvent.ACTION_UP: + releaseVelocity(); + break; + } + return super.onInterceptTouchEvent(e); + } + + @Override + public boolean onTouchEvent(MotionEvent e) { + if (mIsSlide && mPosition != INVALID_POSITION) { + float x = e.getX(); + obtainVelocity(e); + switch (e.getAction()) { + case MotionEvent.ACTION_DOWN: // 因为没有拦截,所以不会被调用到 + break; + case MotionEvent.ACTION_MOVE: + // 随手指滑动 + if (mMenuViewWidth != INVALID_CHILD_WIDTH) { + float dx = mLastX - x; + if (mFlingView.getScrollX() + dx <= mMenuViewWidth + && mFlingView.getScrollX() + dx > 0) { + mFlingView.scrollBy((int) dx, 0); + } + mLastX = x; + } + break; + case MotionEvent.ACTION_UP: + if (mMenuViewWidth != INVALID_CHILD_WIDTH) { + int scrollX = mFlingView.getScrollX(); + mVelocityTracker.computeCurrentVelocity(1000); + // 此处有两个原因决定是否打开菜单: + // 1.菜单被拉出宽度大于菜单宽度一半; + // 2.横向滑动速度大于最小滑动速度; + // 注意:之所以要小于负值,是因为向左滑则速度为负值 + if (mVelocityTracker.getXVelocity() < -SNAP_VELOCITY) { // 向左侧滑达到侧滑最低速度,则打开 + mScroller.startScroll(scrollX, 0, mMenuViewWidth - scrollX, 0, Math.abs(mMenuViewWidth - scrollX)); + } else if (mVelocityTracker.getXVelocity() >= SNAP_VELOCITY) { // 向右侧滑达到侧滑最低速度,则关闭 + mScroller.startScroll(scrollX, 0, -scrollX, 0, Math.abs(scrollX)); + } else if (scrollX >= mMenuViewWidth / 2) { // 如果超过删除按钮一半,则打开 + mScroller.startScroll(scrollX, 0, mMenuViewWidth - scrollX, 0, Math.abs(mMenuViewWidth - scrollX)); + } else { // 其他情况则关闭 + mScroller.startScroll(scrollX, 0, -scrollX, 0, Math.abs(scrollX)); + } + invalidate(); + } + mMenuViewWidth = INVALID_CHILD_WIDTH; + mIsSlide = false; + mPosition = INVALID_POSITION; + releaseVelocity(); // 这里之所以会调用,是因为如果前面拦截了,就不会执行ACTION_UP,需要在这里释放追踪 + break; + } + return true; + } else { + // 此处防止RecyclerView正常滑动时,还有菜单未关闭 + closeMenu(); + // Velocity,这里的释放是防止RecyclerView正常拦截了,但是在onTouchEvent中却没有被释放; + // 有三种情况:1.onInterceptTouchEvent并未拦截,在onInterceptTouchEvent方法中,DOWN和UP一对获取和释放; + // 2.onInterceptTouchEvent拦截,DOWN获取,但事件不是被侧滑处理,需要在这里进行释放; + // 3.onInterceptTouchEvent拦截,DOWN获取,事件被侧滑处理,则在onTouchEvent的UP中释放。 + releaseVelocity(); + } + return super.onTouchEvent(e); + } + + private void releaseVelocity() { + if (mVelocityTracker != null) { + mVelocityTracker.clear(); + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } + + private void obtainVelocity(MotionEvent event) { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(event); + } + + public int pointToPosition(int x, int y) { + if (null == getLayoutManager()) return INVALID_POSITION; + int firstPosition = ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition(); + Rect frame = mTouchFrame; + if (frame == null) { + mTouchFrame = new Rect(); + frame = mTouchFrame; + } + + final int count = getChildCount(); + for (int i = count - 1; i >= 0; i--) { + final View child = getChildAt(i); + if (child.getVisibility() == View.VISIBLE) { + child.getHitRect(frame); + if (frame.contains(x, y)) { + return firstPosition + i; + } + } + } + return INVALID_POSITION; + } + + @Override + public void computeScroll() { + if (mScroller.computeScrollOffset()) { + mFlingView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); + invalidate(); + } + } + + /** + * 将显示子菜单的子view关闭 + * 这里本身是要自己来实现的,但是由于不定制item,因此不好监听器点击事件,因此需要调用者手动的关闭 + */ + public void closeMenu() { + if (mFlingView != null && mFlingView.getScrollX() != 0) { + mFlingView.scrollTo(0, 0); + } + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/view/SpaceItemDecoration.java b/library-common/src/main/java/com/common/commonlib/view/SpaceItemDecoration.java new file mode 100644 index 0000000..3fef5de --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/SpaceItemDecoration.java @@ -0,0 +1,38 @@ +package com.common.commonlib.view; + +import android.graphics.Rect; +import android.view.View; + +import androidx.recyclerview.widget.RecyclerView; + +import org.jetbrains.annotations.NotNull; + +/** + * rv辅助类 + * + * @author wangym + * @since 2021/7/28 + */ +public class SpaceItemDecoration extends RecyclerView.ItemDecoration { + /** + * 默认图片展示单行数量 + */ + private static final int DEFAULT_MEDIA_SIZE = 3; + + /** + * 默认图片展示间距 + */ + private static final int DEFAULT_MEDIA_OFFSET = 30; + + @Override + public void getItemOffsets(@NotNull Rect outRect, @NotNull View view, RecyclerView parent, @NotNull RecyclerView.State state) { + int temp = parent.getChildAdapterPosition(view) % DEFAULT_MEDIA_SIZE; + outRect.left = DEFAULT_MEDIA_OFFSET * (temp); + + if (parent.getChildAdapterPosition(view) >= DEFAULT_MEDIA_SIZE) { + outRect.top = DEFAULT_MEDIA_OFFSET; + } else { + outRect.top = 0; + } + } +} diff --git a/library-common/src/main/java/com/common/commonlib/view/activity/BaseActivity.java b/library-common/src/main/java/com/common/commonlib/view/activity/BaseActivity.java new file mode 100644 index 0000000..88659cb --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/activity/BaseActivity.java @@ -0,0 +1,34 @@ +package com.common.commonlib.view.activity; + +import android.os.Bundle; +import android.view.Window; +import android.view.WindowManager; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.common.commonlib.R; + +/** + * 基础activity + * + * @author miracle + * @since 2021/8/2 + */ +public class BaseActivity extends AppCompatActivity { + + @Override + protected void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + initStatusBar(); + } + + private void initStatusBar() { + Window window = getWindow(); + //After LOLLIPOP not translucent status bar + window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + //Then call setStatusBarColor. + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.setStatusBarColor(getResources().getColor(R.color.title_bar_bg_color)); + } +} diff --git a/library-common/src/main/java/com/common/commonlib/view/activity/PermissionCompatActivity.java b/library-common/src/main/java/com/common/commonlib/view/activity/PermissionCompatActivity.java new file mode 100644 index 0000000..1f21c2c --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/activity/PermissionCompatActivity.java @@ -0,0 +1,133 @@ +package com.common.commonlib.view.activity; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import com.common.commonlib.R; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * 权限申请Activity + * 继承与此Activity,调用requestPermission()方法,即可快速实现权限动态申请功能 + * 默认基于系统的的弹窗样式申请权限 + * + * @author miracle + * @since 2021/8/2 + */ +public class PermissionCompatActivity extends AppCompatActivity { + private static final String TAG = "PermissionCompatActivity"; + protected String[] permissionArray; + private List unAuthorizedPermissionList; + private static final int REQUEST_PERMISSIONS_CODE = 1608; + private Dialog mPermissionDialog; + + @Override + protected void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + protected void onRestart() { + super.onRestart(); + checkAndRequestPermission(); + } + + protected void requestPermission(String[] args) { + permissionArray = args; + checkAndRequestPermission(); + } + + @SuppressLint("LongLogTag") + private void checkAndRequestPermission() { + if (permissionArray != null && permissionArray.length > 0) { + unAuthorizedPermissionList = new ArrayList<>(); + for (int i = 0; i < permissionArray.length; i++) { + if (ContextCompat.checkSelfPermission(this, permissionArray[i]) != PackageManager.PERMISSION_GRANTED) { + //添加还未授予的权限 + unAuthorizedPermissionList.add(permissionArray[i]); + } + } + //申请权限 + if (unAuthorizedPermissionList.size() > 0) {//有权限没有通过,需要申请 + ActivityCompat.requestPermissions(this, permissionArray, REQUEST_PERMISSIONS_CODE); + } else { + //说明权限都已经通过,可以做你想做的事情去 + Log.i(TAG, "所有权限都申请到了"); + } + } + } + + @SuppressLint("LongLogTag") + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull @NotNull String[] permissions, @NonNull @NotNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == REQUEST_PERMISSIONS_CODE) { + //有权限没有通过 + boolean hasPermissionDismiss = false; + for (int i = 0; i < grantResults.length; i++) { + if (grantResults[i] == -1) { + hasPermissionDismiss = true; + } + } + //如果有权限没有被允许 + if (hasPermissionDismiss) { + //跳转到系统设置权限页面,或者直接关闭页面,不让他继续访问 + showPermissionDialog(); + } else { + //全部权限通过 + Log.i(TAG, "所有权限申请完成!"); + } + } + } + + private void showPermissionDialog() { + if (mPermissionDialog == null) { + mPermissionDialog = new AlertDialog.Builder(this) + .setMessage(getResources().getString(R.string.default_un_authorized_prompt)) + .setPositiveButton(getResources().getString(R.string.default_dialog_setting), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + cancelPermissionDialog(); + String packageName = getApplication().getPackageName(); + Uri packageURI = Uri.parse("package:" + packageName); + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI); + startActivity(intent); + } + }).setNegativeButton(getResources().getString(R.string.default_dialog_cancel), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + //关闭页面或者做其他操作 + cancelPermissionDialog(); + finish(); + } + }).create(); + } + mPermissionDialog.show(); + } + + //关闭对话框 + private void cancelPermissionDialog() { + if (mPermissionDialog != null && mPermissionDialog.isShowing()) { + mPermissionDialog.cancel(); + } + } + +} diff --git a/library-common/src/main/java/com/common/commonlib/view/adapter/PicGridAdapter.java b/library-common/src/main/java/com/common/commonlib/view/adapter/PicGridAdapter.java new file mode 100644 index 0000000..3393ee9 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/adapter/PicGridAdapter.java @@ -0,0 +1,164 @@ +package com.common.commonlib.view.adapter; + +import android.content.Context; +import android.graphics.drawable.AnimatedVectorDrawable; +import android.os.Build; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.MultiTransformation; +import com.bumptech.glide.load.resource.bitmap.CenterCrop; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; +import com.common.commonlib.R; +import com.common.commonlib.bean.GridPicBean; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public class PicGridAdapter extends RecyclerView.Adapter { + private static final int TYPE_ONE = 0; + private static final int TYPE_TWO = 1; + private final List gridPicBeanList = new ArrayList<>(); + private Context mContext; + private onAddClickListener onAddClickListener; + + public PicGridAdapter(Context context) { + mContext = context; + } + + @Override + public int getItemViewType(int position) { + if (position == gridPicBeanList.size()) { + return TYPE_ONE; + } else { + return TYPE_TWO; + } + } + + @NotNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + RecyclerView.ViewHolder viewHolder; + if (viewType == TYPE_ONE) { + View inflate = LayoutInflater.from(mContext).inflate(R.layout.item_grid_pic_add, parent, false); + viewHolder = new AddIconViewHolder(inflate); + } else { + View inflate = LayoutInflater.from(mContext).inflate(R.layout.item_grid_pic_show, parent, false); + viewHolder = new PicViewHolder(inflate); + } + return viewHolder; + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + if (position < gridPicBeanList.size()) { + if (holder instanceof PicViewHolder) { + PicViewHolder viewHolder = (PicViewHolder) holder; + GridPicBean gridPicBean = gridPicBeanList.get(position); + Glide.with(mContext).load(gridPicBean.getImagePath()) + .transform(new MultiTransformation(new CenterCrop(), new RoundedCorners(5))) + .into(viewHolder.imageView); + viewHolder.imageDeleteView.setOnClickListener(l -> { + gridPicBeanList.remove(holder.getAdapterPosition()); + notifyItemRemoved(holder.getAdapterPosition()); + }); + + AnimatedVectorDrawable drawable = (AnimatedVectorDrawable) viewHolder.ivLoading.getDrawable(); + if (gridPicBean.isConverting()) { + viewHolder.ivLoading.setVisibility(View.VISIBLE); + viewHolder.imageDeleteView.setVisibility(View.GONE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + drawable.reset(); + } + drawable.start(); + + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + drawable.reset(); + } + viewHolder.ivLoading.setVisibility(View.GONE); + viewHolder.imageDeleteView.setVisibility(View.VISIBLE); + } + } + } else { + if (holder instanceof AddIconViewHolder) { + AddIconViewHolder viewHolder = (AddIconViewHolder) holder; + viewHolder.imageView.setOnClickListener(v -> { + if (gridPicBeanList.size() < 4) { + if (onAddClickListener != null) { + onAddClickListener.onClick(viewHolder.getAdapterPosition()); + } + } else { + Toast.makeText(mContext, "最多添加四张!", Toast.LENGTH_SHORT).show(); + } + }); + } + } + } + + @Override + public int getItemCount() { + return gridPicBeanList.size() + 1; + } + + public void setOnAddClickListener(PicGridAdapter.onAddClickListener onAddClickListener) { + this.onAddClickListener = onAddClickListener; + } + + public void addPicData(GridPicBean gridPicBean) { + gridPicBeanList.add(gridPicBean); + notifyItemRangeChanged(gridPicBeanList.size() - 1, 2); + } + + public void clearPicData() { + gridPicBeanList.clear(); + } + + public void replaceData(int position, GridPicBean gridPicBean) { + gridPicBeanList.set(position, gridPicBean); + notifyDataSetChanged(); + } + + public List getImagePathList() { + List images = new ArrayList<>(); + for (GridPicBean gridPicBean : gridPicBeanList) { + images.add(gridPicBean.getImagePath()); + } + return images; + } + + interface onAddClickListener { + void onClick(int position); + } + + private static class AddIconViewHolder extends RecyclerView.ViewHolder { + private ImageView imageView; + + public AddIconViewHolder(@NonNull @NotNull View itemView) { + super(itemView); + imageView = (ImageView) itemView.findViewById(R.id.bg_pic_add); + } + } + + private static class PicViewHolder extends RecyclerView.ViewHolder { + private ImageView imageView; + private ImageView imageDeleteView; + private ImageView ivLoading; + + public PicViewHolder(@NonNull @NotNull View itemView) { + super(itemView); + imageView = itemView.findViewById(R.id.pic_iv); + imageDeleteView = itemView.findViewById(R.id.pic_delete_iv); + ivLoading = itemView.findViewById(R.id.iv_loading); + } + } +} diff --git a/library-common/src/main/java/com/common/commonlib/view/fragment/BaseFragment.java b/library-common/src/main/java/com/common/commonlib/view/fragment/BaseFragment.java new file mode 100644 index 0000000..8b169b5 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/fragment/BaseFragment.java @@ -0,0 +1,18 @@ +package com.common.commonlib.view.fragment; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; + +import org.jetbrains.annotations.NotNull; + +public class BaseFragment extends Fragment { + public Context mContext; + + @Override + public void onAttach(@NonNull @NotNull Context context) { + super.onAttach(context); + mContext = context; + } +} diff --git a/library-common/src/main/java/com/common/commonlib/view/fragment/BaseNavigationFragment.kt b/library-common/src/main/java/com/common/commonlib/view/fragment/BaseNavigationFragment.kt new file mode 100644 index 0000000..a1e26d2 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/fragment/BaseNavigationFragment.kt @@ -0,0 +1,59 @@ +package com.common.commonlib.view.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup + +abstract class BaseNavigationFragment : BaseFragment() { + private var isNavigationViewInit = false//记录是否已经初始化过一次视图 + private var lastView: View? = null//记录上次创建的view + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + doPrepare() + //如果fragment的view已经创建则不再重新创建 + if (lastView == null) { + lastView = getRootView( + inflater, + container, + savedInstanceState + ) + } + return lastView + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + if (!isNavigationViewInit) {//初始化过视图则不再进行view和data初始化 + super.onViewCreated(view, savedInstanceState) + initView() + initData() + initOthers() + isNavigationViewInit = true + } + } + + abstract fun getRootView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? + + abstract fun initData() + + abstract fun initView() + + abstract fun initOthers() + + abstract fun doPrepare() + + fun getIsNavigationViewInit():Boolean{ + return isNavigationViewInit + } + + fun setIsNavigationViewInit(isNavigationViewInit:Boolean){ + this.isNavigationViewInit=isNavigationViewInit + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/AutoPlayRecyclerView.java b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/AutoPlayRecyclerView.java new file mode 100644 index 0000000..856f291 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/AutoPlayRecyclerView.java @@ -0,0 +1,63 @@ +package com.common.commonlib.view.viewpagerlayoutmanager; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; + +import com.common.commonlib.R; + +public class AutoPlayRecyclerView extends RecyclerView { + private AutoPlaySnapHelper autoPlaySnapHelper; + + public AutoPlayRecyclerView(Context context) { + this(context, null); + } + + public AutoPlayRecyclerView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public AutoPlayRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AutoPlayRecyclerView); + final int timeInterval = typedArray.getInt(R.styleable.AutoPlayRecyclerView_timeInterval, AutoPlaySnapHelper.TIME_INTERVAL); + final int direction = typedArray.getInt(R.styleable.AutoPlayRecyclerView_direction, AutoPlaySnapHelper.RIGHT); + typedArray.recycle(); + autoPlaySnapHelper = new AutoPlaySnapHelper(timeInterval, direction); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + boolean result = super.dispatchTouchEvent(ev); + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + if (autoPlaySnapHelper != null) { + autoPlaySnapHelper.pause(); + } + break; + case MotionEvent.ACTION_UP: + if (autoPlaySnapHelper != null) { + autoPlaySnapHelper.start(); + } + } + return result; + } + + public void start() { + autoPlaySnapHelper.start(); + } + + public void pause() { + autoPlaySnapHelper.pause(); + } + + @Override + public void setLayoutManager(LayoutManager layout) { + super.setLayoutManager(layout); + autoPlaySnapHelper.attachToRecyclerView(this); + } +} diff --git a/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/AutoPlaySnapHelper.java b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/AutoPlaySnapHelper.java new file mode 100644 index 0000000..fd55042 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/AutoPlaySnapHelper.java @@ -0,0 +1,116 @@ +package com.common.commonlib.view.viewpagerlayoutmanager; + +import android.os.Handler; +import android.os.Looper; +import android.view.animation.DecelerateInterpolator; +import android.widget.Scroller; + +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; + + +/** + * Used by {@link AutoPlayRecyclerView} to implement auto play effect + */ + +class AutoPlaySnapHelper extends CenterSnapHelper { + final static int TIME_INTERVAL = 2000; + + final static int LEFT = 1; + final static int RIGHT = 2; + + private Handler handler; + private int timeInterval; + private Runnable autoPlayRunnable; + private boolean runnableAdded; + private int direction; + + AutoPlaySnapHelper(int timeInterval, int direction) { + checkTimeInterval(timeInterval); + checkDirection(direction); + handler = new Handler(Looper.getMainLooper()); + this.timeInterval = timeInterval; + this.direction = direction; + } + + @Override + public void attachToRecyclerView(@Nullable RecyclerView recyclerView) throws IllegalStateException { + if (mRecyclerView == recyclerView) { + return; // nothing to do + } + if (mRecyclerView != null) { + destroyCallbacks(); + } + mRecyclerView = recyclerView; + if (mRecyclerView != null) { + final RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager(); + if (!(layoutManager instanceof ViewPagerLayoutManager)) return; + + setupCallbacks(); + mGravityScroller = new Scroller(mRecyclerView.getContext(), + new DecelerateInterpolator()); + + snapToCenterView((ViewPagerLayoutManager) layoutManager, + ((ViewPagerLayoutManager) layoutManager).onPageChangeListener); + + ((ViewPagerLayoutManager) layoutManager).setInfinite(true); + + autoPlayRunnable = new Runnable() { + @Override + public void run() { + final int currentPosition = + ((ViewPagerLayoutManager) layoutManager).getCurrentPositionOffset() * + (((ViewPagerLayoutManager) layoutManager).getReverseLayout() ? -1 : 1); + ScrollHelper.smoothScrollToPosition(mRecyclerView, + (ViewPagerLayoutManager) layoutManager, direction == RIGHT ? currentPosition + 1 : currentPosition - 1); + handler.postDelayed(autoPlayRunnable, timeInterval); + } + }; + handler.postDelayed(autoPlayRunnable, timeInterval); + runnableAdded = true; + } + } + + @Override + void destroyCallbacks() { + super.destroyCallbacks(); + if (runnableAdded) { + handler.removeCallbacks(autoPlayRunnable); + runnableAdded = false; + } + } + + void pause() { + if (runnableAdded) { + handler.removeCallbacks(autoPlayRunnable); + runnableAdded = false; + } + } + + void start() { + if (!runnableAdded) { + handler.postDelayed(autoPlayRunnable, timeInterval); + runnableAdded = true; + } + } + + void setTimeInterval(int timeInterval) { + checkTimeInterval(timeInterval); + this.timeInterval = timeInterval; + } + + void setDirection(int direction) { + checkDirection(direction); + this.direction = direction; + } + + private void checkDirection(int direction) { + if (direction != LEFT && direction != RIGHT) + throw new IllegalArgumentException("direction should be one of left or right"); + } + + private void checkTimeInterval(int timeInterval) { + if (timeInterval <= 0) + throw new IllegalArgumentException("time interval should greater than 0"); + } +} diff --git a/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/CarouselLayoutManager.java b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/CarouselLayoutManager.java new file mode 100644 index 0000000..6f2ff8c --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/CarouselLayoutManager.java @@ -0,0 +1,167 @@ +package com.common.commonlib.view.viewpagerlayoutmanager; + +import android.content.Context; +import android.view.View; + +/** + * An implementation of {@link ViewPagerLayoutManager} + * which layouts items like carousel + */ + +@SuppressWarnings({"WeakerAccess", "unused"}) +public class CarouselLayoutManager extends ViewPagerLayoutManager { + + private int itemSpace; + private float minScale; + private float moveSpeed; + + public CarouselLayoutManager(Context context, int itemSpace) { + this(new Builder(context, itemSpace)); + } + + public CarouselLayoutManager(Context context, int itemSpace, int orientation) { + this(new Builder(context, itemSpace).setOrientation(orientation)); + } + + public CarouselLayoutManager(Context context, int itemSpace, int orientation, boolean reverseLayout) { + this(new Builder(context, itemSpace).setOrientation(orientation).setReverseLayout(reverseLayout)); + } + + public CarouselLayoutManager(Builder builder) { + this(builder.context, builder.itemSpace, builder.minScale, builder.orientation, + builder.maxVisibleItemCount, builder.moveSpeed, builder.distanceToBottom, + builder.reverseLayout); + } + + private CarouselLayoutManager(Context context, int itemSpace, float minScale, int orientation, + int maxVisibleItemCount, float moveSpeed, int distanceToBottom, + boolean reverseLayout) { + super(context, orientation, reverseLayout); + setEnableBringCenterToFront(true); + setDistanceToBottom(distanceToBottom); + setMaxVisibleItemCount(maxVisibleItemCount); + this.itemSpace = itemSpace; + this.minScale = minScale; + this.moveSpeed = moveSpeed; + } + + public int getItemSpace() { + return itemSpace; + } + + public float getMinScale() { + return minScale; + } + + public float getMoveSpeed() { + return moveSpeed; + } + + public void setItemSpace(int itemSpace) { + assertNotInLayoutOrScroll(null); + if (this.itemSpace == itemSpace) return; + this.itemSpace = itemSpace; + removeAllViews(); + } + + public void setMinScale(float minScale) { + assertNotInLayoutOrScroll(null); + if (minScale > 1f) minScale = 1f; + if (this.minScale == minScale) return; + this.minScale = minScale; + requestLayout(); + } + + public void setMoveSpeed(float moveSpeed) { + assertNotInLayoutOrScroll(null); + if (this.moveSpeed == moveSpeed) return; + this.moveSpeed = moveSpeed; + } + + @Override + protected float setInterval() { + return (mDecoratedMeasurement - itemSpace); + } + + @Override + protected void setItemViewProperty(View itemView, float targetOffset) { + float scale = calculateScale(targetOffset + mSpaceMain); + itemView.setScaleX(scale); + itemView.setScaleY(scale); + } + + @Override + protected float getDistanceRatio() { + if (moveSpeed == 0) return Float.MAX_VALUE; + return 1 / moveSpeed; + } + + @Override + protected float setViewElevation(View itemView, float targetOffset) { + return itemView.getScaleX() * 5; + } + + private float calculateScale(float x) { + float deltaX = Math.abs(x - (mOrientationHelper.getTotalSpace() - mDecoratedMeasurement) / 2f); + return (minScale - 1) * deltaX / (mOrientationHelper.getTotalSpace() / 2f) + 1f; + } + + public static class Builder { + private static final float DEFAULT_SPEED = 1f; + private static final float MIN_SCALE = 0.5f; + + private Context context; + private int itemSpace; + private int orientation; + private float minScale; + private float moveSpeed; + private int maxVisibleItemCount; + private boolean reverseLayout; + private int distanceToBottom; + + public Builder(Context context, int itemSpace) { + this.itemSpace = itemSpace; + this.context = context; + orientation = HORIZONTAL; + minScale = MIN_SCALE; + this.moveSpeed = DEFAULT_SPEED; + reverseLayout = false; + maxVisibleItemCount = ViewPagerLayoutManager.DETERMINE_BY_MAX_AND_MIN; + distanceToBottom = ViewPagerLayoutManager.INVALID_SIZE; + } + + public Builder setOrientation(int orientation) { + this.orientation = orientation; + return this; + } + + public Builder setMinScale(float minScale) { + this.minScale = minScale; + return this; + } + + public Builder setReverseLayout(boolean reverseLayout) { + this.reverseLayout = reverseLayout; + return this; + } + + public Builder setMoveSpeed(float moveSpeed) { + this.moveSpeed = moveSpeed; + return this; + } + + public Builder setMaxVisibleItemCount(int maxVisibleItemCount) { + this.maxVisibleItemCount = maxVisibleItemCount; + return this; + } + + public Builder setDistanceToBottom(int distanceToBottom) { + this.distanceToBottom = distanceToBottom; + return this; + } + + public CarouselLayoutManager build() { + return new CarouselLayoutManager(this); + } + } +} diff --git a/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/CenterSnapHelper.java b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/CenterSnapHelper.java new file mode 100644 index 0000000..bd5a67a --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/CenterSnapHelper.java @@ -0,0 +1,158 @@ +package com.common.commonlib.view.viewpagerlayoutmanager; + +import android.view.animation.DecelerateInterpolator; +import android.widget.Scroller; + +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; + +public class CenterSnapHelper extends RecyclerView.OnFlingListener { + + RecyclerView mRecyclerView; + Scroller mGravityScroller; + + /** + * when the dataSet is extremely large + * {@link #snapToCenterView(ViewPagerLayoutManager, ViewPagerLayoutManager.OnPageChangeListener)} + * may keep calling itself because the accuracy of float + */ + private boolean snapToCenter = false; + + // Handles the snap on scroll case. + private final RecyclerView.OnScrollListener mScrollListener = + new RecyclerView.OnScrollListener() { + + boolean mScrolled = false; + + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + + final ViewPagerLayoutManager layoutManager = + (ViewPagerLayoutManager) recyclerView.getLayoutManager(); + final ViewPagerLayoutManager.OnPageChangeListener onPageChangeListener = + layoutManager.onPageChangeListener; + if (onPageChangeListener != null) { + onPageChangeListener.onPageScrollStateChanged(newState); + } + + if (newState == RecyclerView.SCROLL_STATE_IDLE && mScrolled) { + mScrolled = false; + if (!snapToCenter) { + snapToCenter = true; + snapToCenterView(layoutManager, onPageChangeListener); + } else { + snapToCenter = false; + } + } + } + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + if (dx != 0 || dy != 0) { + mScrolled = true; + } + } + }; + + @Override + public boolean onFling(int velocityX, int velocityY) { + ViewPagerLayoutManager layoutManager = (ViewPagerLayoutManager) mRecyclerView.getLayoutManager(); + if (layoutManager == null) { + return false; + } + RecyclerView.Adapter adapter = mRecyclerView.getAdapter(); + if (adapter == null) { + return false; + } + + if (!layoutManager.getInfinite() && + (layoutManager.mOffset == layoutManager.getMaxOffset() + || layoutManager.mOffset == layoutManager.getMinOffset())) { + return false; + } + + final int minFlingVelocity = mRecyclerView.getMinFlingVelocity(); + mGravityScroller.fling(0, 0, velocityX, velocityY, + Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); + + if (layoutManager.mOrientation == ViewPagerLayoutManager.VERTICAL + && Math.abs(velocityY) > minFlingVelocity) { + final int currentPosition = layoutManager.getCurrentPositionOffset(); + final int offsetPosition = (int) (mGravityScroller.getFinalY() / + layoutManager.mInterval / layoutManager.getDistanceRatio()); + ScrollHelper.smoothScrollToPosition(mRecyclerView, layoutManager, layoutManager.getReverseLayout() ? + -currentPosition - offsetPosition : currentPosition + offsetPosition); + return true; + } else if (layoutManager.mOrientation == ViewPagerLayoutManager.HORIZONTAL + && Math.abs(velocityX) > minFlingVelocity) { + final int currentPosition = layoutManager.getCurrentPositionOffset(); + final int offsetPosition = (int) (mGravityScroller.getFinalX() / + layoutManager.mInterval / layoutManager.getDistanceRatio()); + ScrollHelper.smoothScrollToPosition(mRecyclerView, layoutManager, layoutManager.getReverseLayout() ? + -currentPosition - offsetPosition : currentPosition + offsetPosition); + return true; + } + + return true; + } + + public void attachToRecyclerView(@Nullable RecyclerView recyclerView) + throws IllegalStateException { + if (mRecyclerView == recyclerView) { + return; // nothing to do + } + if (mRecyclerView != null) { + destroyCallbacks(); + } + mRecyclerView = recyclerView; + if (mRecyclerView != null) { + final RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager(); + if (!(layoutManager instanceof ViewPagerLayoutManager)) return; + + setupCallbacks(); + mGravityScroller = new Scroller(mRecyclerView.getContext(), + new DecelerateInterpolator()); + + snapToCenterView((ViewPagerLayoutManager) layoutManager, + ((ViewPagerLayoutManager) layoutManager).onPageChangeListener); + } + } + + void snapToCenterView(ViewPagerLayoutManager layoutManager, + ViewPagerLayoutManager.OnPageChangeListener listener) { + final int delta = layoutManager.getOffsetToCenter(); + if (delta != 0) { + if (layoutManager.getOrientation() + == RecyclerView.VERTICAL) + mRecyclerView.smoothScrollBy(0, delta); + else + mRecyclerView.smoothScrollBy(delta, 0); + } else { + // set it false to make smoothScrollToPosition keep trigger the listener + snapToCenter = false; + } + + if (listener != null) + listener.onPageSelected(layoutManager.getCurrentPosition()); + } + + /** + * Called when an instance of a {@link RecyclerView} is attached. + */ + void setupCallbacks() throws IllegalStateException { + if (mRecyclerView.getOnFlingListener() != null) { + throw new IllegalStateException("An instance of OnFlingListener already set."); + } + mRecyclerView.addOnScrollListener(mScrollListener); + mRecyclerView.setOnFlingListener(this); + } + + /** + * Called when the instance of a {@link RecyclerView} is detached. + */ + void destroyCallbacks() { + mRecyclerView.removeOnScrollListener(mScrollListener); + mRecyclerView.setOnFlingListener(null); + } +} diff --git a/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/CircleLayoutManager.java b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/CircleLayoutManager.java new file mode 100644 index 0000000..581954e --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/CircleLayoutManager.java @@ -0,0 +1,357 @@ +package com.common.commonlib.view.viewpagerlayoutmanager; + +import android.content.Context; +import android.view.View; + +import androidx.recyclerview.widget.RecyclerView; + +/** + * An implementation of {@link ViewPagerLayoutManager} + * which layouts item in a circle + */ + +@SuppressWarnings({"WeakerAccess", "unused"}) +public class CircleLayoutManager extends ViewPagerLayoutManager { + public static final int LEFT = 10; + public static final int RIGHT = 11; + public static final int TOP = 12; + public static final int BOTTOM = 13; + + public static final int LEFT_ON_TOP = 4; + public static final int RIGHT_ON_TOP = 5; + public static final int CENTER_ON_TOP = 6; + + private int radius; + private int angleInterval; + private float moveSpeed; + private float maxRemoveAngle; + private float minRemoveAngle; + private int gravity; + private boolean flipRotate; + private int zAlignment; + + public CircleLayoutManager(Context context) { + this(new Builder(context)); + } + + public CircleLayoutManager(Context context, boolean reverseLayout) { + this(new Builder(context).setReverseLayout(reverseLayout)); + } + + public CircleLayoutManager(Context context, int gravity, boolean reverseLayout) { + this(new Builder(context).setGravity(gravity).setReverseLayout(reverseLayout)); + } + + public CircleLayoutManager(Builder builder) { + this(builder.context, builder.radius, builder.angleInterval, builder.moveSpeed, builder.maxRemoveAngle, + builder.minRemoveAngle, builder.gravity, builder.zAlignment, builder.flipRotate, + builder.maxVisibleItemCount, builder.distanceToBottom, builder.reverseLayout); + } + + private CircleLayoutManager(Context context, int radius, int angleInterval, float moveSpeed, + float max, float min, int gravity, int zAlignment, boolean flipRotate, + int maxVisibleItemCount, int distanceToBottom, boolean reverseLayout) { + super(context, (gravity == LEFT || gravity == RIGHT) ? VERTICAL : HORIZONTAL, reverseLayout); + setEnableBringCenterToFront(true); + setMaxVisibleItemCount(maxVisibleItemCount); + setDistanceToBottom(distanceToBottom); + this.radius = radius; + this.angleInterval = angleInterval; + this.moveSpeed = moveSpeed; + this.maxRemoveAngle = max; + this.minRemoveAngle = min; + this.gravity = gravity; + this.flipRotate = flipRotate; + this.zAlignment = zAlignment; + } + + private static void assertGravity(int gravity) { + if (gravity != LEFT && gravity != RIGHT && gravity != TOP && gravity != BOTTOM) { + throw new IllegalArgumentException("gravity must be one of LEFT RIGHT TOP and BOTTOM"); + } + } + + private static void assertZAlignmentState(int zAlignment) { + if (zAlignment != LEFT_ON_TOP && zAlignment != RIGHT_ON_TOP && zAlignment != CENTER_ON_TOP) { + throw new IllegalArgumentException("zAlignment must be one of LEFT_ON_TOP RIGHT_ON_TOP and CENTER_ON_TOP"); + } + } + + public int getRadius() { + return radius; + } + + public void setRadius(int radius) { + assertNotInLayoutOrScroll(null); + if (this.radius == radius) return; + this.radius = radius; + removeAllViews(); + } + + public int getAngleInterval() { + return angleInterval; + } + + public void setAngleInterval(int angleInterval) { + assertNotInLayoutOrScroll(null); + if (this.angleInterval == angleInterval) return; + this.angleInterval = angleInterval; + removeAllViews(); + } + + public float getMoveSpeed() { + return moveSpeed; + } + + public void setMoveSpeed(float moveSpeed) { + assertNotInLayoutOrScroll(null); + if (this.moveSpeed == moveSpeed) return; + this.moveSpeed = moveSpeed; + } + + public float getMaxRemoveAngle() { + return maxRemoveAngle; + } + + public void setMaxRemoveAngle(float maxRemoveAngle) { + assertNotInLayoutOrScroll(null); + if (this.maxRemoveAngle == maxRemoveAngle) return; + this.maxRemoveAngle = maxRemoveAngle; + requestLayout(); + } + + public float getMinRemoveAngle() { + return minRemoveAngle; + } + + public void setMinRemoveAngle(float minRemoveAngle) { + assertNotInLayoutOrScroll(null); + if (this.minRemoveAngle == minRemoveAngle) return; + this.minRemoveAngle = minRemoveAngle; + requestLayout(); + } + + public int getGravity() { + return gravity; + } + + public void setGravity(int gravity) { + assertNotInLayoutOrScroll(null); + assertGravity(gravity); + if (this.gravity == gravity) return; + this.gravity = gravity; + if (gravity == LEFT || gravity == RIGHT) { + setOrientation(RecyclerView.VERTICAL); + } else { + setOrientation(RecyclerView.HORIZONTAL); + } + requestLayout(); + } + + public boolean getFlipRotate() { + return flipRotate; + } + + public void setFlipRotate(boolean flipRotate) { + assertNotInLayoutOrScroll(null); + if (this.flipRotate == flipRotate) return; + this.flipRotate = flipRotate; + requestLayout(); + } + + public int getZAlignment() { + return zAlignment; + } + + public void setZAlignment(int zAlignment) { + assertNotInLayoutOrScroll(null); + assertZAlignmentState(zAlignment); + if (this.zAlignment == zAlignment) return; + this.zAlignment = zAlignment; + requestLayout(); + } + + @Override + protected float setInterval() { + return angleInterval; + } + + @Override + protected void setUp() { + radius = radius == Builder.INVALID_VALUE ? mDecoratedMeasurementInOther : radius; + } + + @Override + protected float maxRemoveOffset() { + return maxRemoveAngle; + } + + @Override + protected float minRemoveOffset() { + return minRemoveAngle; + } + + @Override + protected int calItemLeft(View itemView, float targetOffset) { + switch (gravity) { + case LEFT: + return (int) (radius * Math.sin(Math.toRadians(90 - targetOffset)) - radius); + case RIGHT: + return (int) (radius - radius * Math.sin(Math.toRadians(90 - targetOffset))); + case TOP: + case BOTTOM: + default: + return (int) (radius * Math.cos(Math.toRadians(90 - targetOffset))); + } + } + + @Override + protected int calItemTop(View itemView, float targetOffset) { + switch (gravity) { + case LEFT: + case RIGHT: + return (int) (radius * Math.cos(Math.toRadians(90 - targetOffset))); + case TOP: + return (int) (radius * Math.sin(Math.toRadians(90 - targetOffset)) - radius); + case BOTTOM: + default: + return (int) (radius - radius * Math.sin(Math.toRadians(90 - targetOffset))); + } + } + + @Override + protected void setItemViewProperty(View itemView, float targetOffset) { + switch (gravity) { + case RIGHT: + case TOP: + if (flipRotate) { + itemView.setRotation(targetOffset); + } else { + itemView.setRotation(360 - targetOffset); + } + break; + case LEFT: + case BOTTOM: + default: + if (flipRotate) { + itemView.setRotation(360 - targetOffset); + } else { + itemView.setRotation(targetOffset); + } + break; + } + } + + @Override + protected float setViewElevation(View itemView, float targetOffset) { + if (zAlignment == LEFT_ON_TOP) + return (540 - targetOffset) / 72; + else if (zAlignment == RIGHT_ON_TOP) + return (targetOffset - 540) / 72; + else + return (360 - Math.abs(targetOffset)) / 72; + } + + @Override + protected float getDistanceRatio() { + if (moveSpeed == 0) return Float.MAX_VALUE; + return 1 / moveSpeed; + } + + public static class Builder { + private static int INTERVAL_ANGLE = 30;// The default mInterval angle between each items + private static float DISTANCE_RATIO = 10f; // Finger swipe distance divide item rotate angle + private static int INVALID_VALUE = Integer.MIN_VALUE; + private static int MAX_REMOVE_ANGLE = 90; + private static int MIN_REMOVE_ANGLE = -90; + + private int radius; + private int angleInterval; + private float moveSpeed; + private float maxRemoveAngle; + private float minRemoveAngle; + private boolean reverseLayout; + private Context context; + private int gravity; + private boolean flipRotate; + private int zAlignment; + private int maxVisibleItemCount; + private int distanceToBottom; + + public Builder(Context context) { + this.context = context; + radius = INVALID_VALUE; + angleInterval = INTERVAL_ANGLE; + moveSpeed = 1 / DISTANCE_RATIO; + maxRemoveAngle = MAX_REMOVE_ANGLE; + minRemoveAngle = MIN_REMOVE_ANGLE; + reverseLayout = false; + flipRotate = false; + gravity = BOTTOM; + zAlignment = LEFT_ON_TOP; + maxVisibleItemCount = ViewPagerLayoutManager.DETERMINE_BY_MAX_AND_MIN; + distanceToBottom = ViewPagerLayoutManager.INVALID_SIZE; + } + + public Builder setRadius(int radius) { + this.radius = radius; + return this; + } + + public Builder setAngleInterval(int angleInterval) { + this.angleInterval = angleInterval; + return this; + } + + public Builder setMoveSpeed(int moveSpeed) { + this.moveSpeed = moveSpeed; + return this; + } + + public Builder setMaxRemoveAngle(float maxRemoveAngle) { + this.maxRemoveAngle = maxRemoveAngle; + return this; + } + + public Builder setMinRemoveAngle(float minRemoveAngle) { + this.minRemoveAngle = minRemoveAngle; + return this; + } + + public Builder setReverseLayout(boolean reverseLayout) { + this.reverseLayout = reverseLayout; + return this; + } + + public Builder setGravity(int gravity) { + assertGravity(gravity); + this.gravity = gravity; + return this; + } + + public Builder setFlipRotate(boolean flipRotate) { + this.flipRotate = flipRotate; + return this; + } + + public Builder setZAlignment(int zAlignment) { + assertZAlignmentState(zAlignment); + this.zAlignment = zAlignment; + return this; + } + + public Builder setMaxVisibleItemCount(int maxVisibleItemCount) { + this.maxVisibleItemCount = maxVisibleItemCount; + return this; + } + + public Builder setDistanceToBottom(int distanceToBottom) { + this.distanceToBottom = distanceToBottom; + return this; + } + + public CircleLayoutManager build() { + return new CircleLayoutManager(this); + } + } +} diff --git a/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/CircleScaleLayoutManager.java b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/CircleScaleLayoutManager.java new file mode 100644 index 0000000..e56a09c --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/CircleScaleLayoutManager.java @@ -0,0 +1,395 @@ +package com.common.commonlib.view.viewpagerlayoutmanager; + +import android.content.Context; +import android.view.View; + +import androidx.recyclerview.widget.RecyclerView; + +/** + * An implementation of {@link ViewPagerLayoutManager} + * which layouts item in a circle and will change the child's centerScale while scrolling + */ + +@SuppressWarnings({"WeakerAccess", "unused"}) +public class CircleScaleLayoutManager extends ViewPagerLayoutManager { + public static final int LEFT = 10; + public static final int RIGHT = 11; + public static final int TOP = 12; + public static final int BOTTOM = 13; + + public static final int LEFT_ON_TOP = 4; + public static final int RIGHT_ON_TOP = 5; + public static final int CENTER_ON_TOP = 6; + + private int radius; + private int angleInterval; + private float moveSpeed; + private float centerScale; + private float maxRemoveAngle; + private float minRemoveAngle; + private int gravity; + private boolean flipRotate; + private int zAlignment; + + public CircleScaleLayoutManager(Context context) { + this(new Builder(context)); + } + + public CircleScaleLayoutManager(Context context, int gravity, boolean reverseLayout) { + this(new Builder(context).setGravity(gravity).setReverseLayout(reverseLayout)); + } + + public CircleScaleLayoutManager(Context context, boolean reverseLayout) { + this(new Builder(context).setReverseLayout(reverseLayout)); + } + + public CircleScaleLayoutManager(Builder builder) { + this(builder.context, builder.radius, builder.angleInterval, builder.centerScale, builder.moveSpeed, + builder.maxRemoveAngle, builder.minRemoveAngle, builder.gravity, builder.zAlignment, + builder.flipRotate, builder.maxVisibleItemCount, builder.distanceToBottom, builder.reverseLayout); + } + + private CircleScaleLayoutManager(Context context, int radius, int angleInterval, float centerScale, + float moveSpeed, float max, float min, int gravity, int zAlignment, + boolean flipRotate, int maxVisibleItemCount, int distanceToBottom, boolean reverseLayout) { + super(context, HORIZONTAL, reverseLayout); + setEnableBringCenterToFront(true); + setMaxVisibleItemCount(maxVisibleItemCount); + setDistanceToBottom(distanceToBottom); + this.radius = radius; + this.angleInterval = angleInterval; + this.centerScale = centerScale; + this.moveSpeed = moveSpeed; + this.maxRemoveAngle = max; + this.minRemoveAngle = min; + this.gravity = gravity; + this.flipRotate = flipRotate; + this.zAlignment = zAlignment; + } + + private static void assertGravity(int gravity) { + if (gravity != LEFT && gravity != RIGHT && gravity != TOP && gravity != BOTTOM) { + throw new IllegalArgumentException("gravity must be one of LEFT RIGHT TOP and BOTTOM"); + } + } + + private static void assertZAlignmentState(int zAlignment) { + if (zAlignment != LEFT_ON_TOP && zAlignment != RIGHT_ON_TOP && zAlignment != CENTER_ON_TOP) { + throw new IllegalArgumentException("zAlignment must be one of LEFT_ON_TOP RIGHT_ON_TOP and CENTER_ON_TOP"); + } + } + + public int getRadius() { + return radius; + } + + public void setRadius(int radius) { + assertNotInLayoutOrScroll(null); + if (this.radius == radius) return; + this.radius = radius; + removeAllViews(); + } + + public int getAngleInterval() { + return angleInterval; + } + + public void setAngleInterval(int angleInterval) { + assertNotInLayoutOrScroll(null); + if (this.angleInterval == angleInterval) return; + this.angleInterval = angleInterval; + removeAllViews(); + } + + public float getCenterScale() { + return centerScale; + } + + public void setCenterScale(float centerScale) { + assertNotInLayoutOrScroll(null); + if (this.centerScale == centerScale) return; + this.centerScale = centerScale; + requestLayout(); + } + + public float getMoveSpeed() { + return moveSpeed; + } + + public void setMoveSpeed(float moveSpeed) { + assertNotInLayoutOrScroll(null); + if (this.moveSpeed == moveSpeed) return; + this.moveSpeed = moveSpeed; + } + + public float getMaxRemoveAngle() { + return maxRemoveAngle; + } + + public void setMaxRemoveAngle(float maxRemoveAngle) { + assertNotInLayoutOrScroll(null); + if (this.maxRemoveAngle == maxRemoveAngle) return; + this.maxRemoveAngle = maxRemoveAngle; + requestLayout(); + } + + public float getMinRemoveAngle() { + return minRemoveAngle; + } + + public void setMinRemoveAngle(float minRemoveAngle) { + assertNotInLayoutOrScroll(null); + if (this.minRemoveAngle == minRemoveAngle) return; + this.minRemoveAngle = minRemoveAngle; + requestLayout(); + } + + public int getGravity() { + return gravity; + } + + public void setGravity(int gravity) { + assertNotInLayoutOrScroll(null); + assertGravity(gravity); + if (this.gravity == gravity) return; + this.gravity = gravity; + if (gravity == LEFT || gravity == RIGHT) { + setOrientation(RecyclerView.VERTICAL); + } else { + setOrientation(RecyclerView.HORIZONTAL); + } + requestLayout(); + } + + public boolean getFlipRotate() { + return flipRotate; + } + + public void setFlipRotate(boolean flipRotate) { + assertNotInLayoutOrScroll(null); + if (this.flipRotate == flipRotate) return; + this.flipRotate = flipRotate; + requestLayout(); + } + + public int getZAlignment() { + return zAlignment; + } + + public void setZAlignment(int zAlignment) { + assertNotInLayoutOrScroll(null); + assertZAlignmentState(zAlignment); + if (this.zAlignment == zAlignment) return; + this.zAlignment = zAlignment; + requestLayout(); + } + + @Override + protected float setInterval() { + return angleInterval; + } + + @Override + protected void setUp() { + radius = radius == Builder.INVALID_VALUE ? mDecoratedMeasurementInOther : radius; + } + + @Override + protected float maxRemoveOffset() { + return maxRemoveAngle; + } + + @Override + protected float minRemoveOffset() { + return minRemoveAngle; + } + + @Override + protected int calItemLeft(View itemView, float targetOffset) { + switch (gravity) { + case LEFT: + return (int) (radius * Math.sin(Math.toRadians(90 - targetOffset)) - radius); + case RIGHT: + return (int) (radius - radius * Math.sin(Math.toRadians(90 - targetOffset))); + case TOP: + case BOTTOM: + default: + return (int) (radius * Math.cos(Math.toRadians(90 - targetOffset))); + } + } + + @Override + protected int calItemTop(View itemView, float targetOffset) { + switch (gravity) { + case LEFT: + case RIGHT: + return (int) (radius * Math.cos(Math.toRadians(90 - targetOffset))); + case TOP: + return (int) (radius * Math.sin(Math.toRadians(90 - targetOffset)) - radius); + case BOTTOM: + default: + return (int) (radius - radius * Math.sin(Math.toRadians(90 - targetOffset))); + } + } + + @Override + protected void setItemViewProperty(View itemView, float targetOffset) { + float scale = 1f; + switch (gravity) { + case RIGHT: + case TOP: + if (flipRotate) { + itemView.setRotation(targetOffset); + if (targetOffset < angleInterval && targetOffset > -angleInterval) { + float diff = Math.abs(Math.abs(itemView.getRotation() - angleInterval) - angleInterval); + scale = (centerScale - 1f) / -angleInterval * diff + centerScale; + } + } else { + itemView.setRotation(360 - targetOffset); + if (targetOffset < angleInterval && targetOffset > -angleInterval) { + float diff = Math.abs(Math.abs(360 - itemView.getRotation() - angleInterval) - angleInterval); + scale = (centerScale - 1f) / -angleInterval * diff + centerScale; + } + } + break; + case LEFT: + case BOTTOM: + default: + if (flipRotate) { + itemView.setRotation(360 - targetOffset); + if (targetOffset < angleInterval && targetOffset > -angleInterval) { + float diff = Math.abs(Math.abs(360 - itemView.getRotation() - angleInterval) - angleInterval); + scale = (centerScale - 1f) / -angleInterval * diff + centerScale; + } + } else { + itemView.setRotation(targetOffset); + if (targetOffset < angleInterval && targetOffset > -angleInterval) { + float diff = Math.abs(Math.abs(itemView.getRotation() - angleInterval) - angleInterval); + scale = (centerScale - 1f) / -angleInterval * diff + centerScale; + } + } + break; + } + itemView.setScaleX(scale); + itemView.setScaleY(scale); + } + + @Override + protected float setViewElevation(View itemView, float targetOffset) { + if (zAlignment == LEFT_ON_TOP) + return (540 - targetOffset) / 72; + else if (zAlignment == RIGHT_ON_TOP) + return (targetOffset - 540) / 72; + else + return (360 - Math.abs(targetOffset)) / 72; + } + + @Override + protected float getDistanceRatio() { + if (moveSpeed == 0) return Float.MAX_VALUE; + return 1 / moveSpeed; + } + + public static class Builder { + private static final float SCALE_RATE = 1.2f; + private static int INTERVAL_ANGLE = 30;// The default mInterval angle between each items + private static float DISTANCE_RATIO = 10f; // Finger swipe distance divide item rotate angle + private static int INVALID_VALUE = Integer.MIN_VALUE; + + private int radius; + private int angleInterval; + private float centerScale; + private float moveSpeed; + private float maxRemoveAngle; + private float minRemoveAngle; + private boolean reverseLayout; + private Context context; + private int gravity; + private boolean flipRotate; + private int zAlignment; + private int maxVisibleItemCount; + private int distanceToBottom; + + public Builder(Context context) { + this.context = context; + radius = INVALID_VALUE; + angleInterval = INTERVAL_ANGLE; + centerScale = SCALE_RATE; + moveSpeed = 1 / DISTANCE_RATIO; + maxRemoveAngle = 90; + minRemoveAngle = -90; + reverseLayout = false; + flipRotate = false; + gravity = BOTTOM; + zAlignment = CENTER_ON_TOP; + distanceToBottom = ViewPagerLayoutManager.INVALID_SIZE; + maxVisibleItemCount = ViewPagerLayoutManager.DETERMINE_BY_MAX_AND_MIN; + } + + public Builder setRadius(int radius) { + this.radius = radius; + return this; + } + + public Builder setAngleInterval(int angleInterval) { + this.angleInterval = angleInterval; + return this; + } + + public Builder setCenterScale(float centerScale) { + this.centerScale = centerScale; + return this; + } + + public Builder setMoveSpeed(int moveSpeed) { + this.moveSpeed = moveSpeed; + return this; + } + + public Builder setMaxRemoveAngle(float maxRemoveAngle) { + this.maxRemoveAngle = maxRemoveAngle; + return this; + } + + public Builder setMinRemoveAngle(float minRemoveAngle) { + this.minRemoveAngle = minRemoveAngle; + return this; + } + + public Builder setReverseLayout(boolean reverseLayout) { + this.reverseLayout = reverseLayout; + return this; + } + + public Builder setGravity(int gravity) { + assertGravity(gravity); + this.gravity = gravity; + return this; + } + + public Builder setFlipRotate(boolean flipRotate) { + this.flipRotate = flipRotate; + return this; + } + + public Builder setZAlignment(int zAlignment) { + assertZAlignmentState(zAlignment); + this.zAlignment = zAlignment; + return this; + } + + public Builder setMaxVisibleItemCount(int maxVisibleItemCount) { + this.maxVisibleItemCount = maxVisibleItemCount; + return this; + } + + public Builder setDistanceToBottom(int distanceToBottom) { + this.distanceToBottom = distanceToBottom; + return this; + } + + public CircleScaleLayoutManager build() { + return new CircleScaleLayoutManager(this); + } + } +} diff --git a/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/GalleryLayoutManager.java b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/GalleryLayoutManager.java new file mode 100644 index 0000000..916d453 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/GalleryLayoutManager.java @@ -0,0 +1,288 @@ +package com.common.commonlib.view.viewpagerlayoutmanager; + +import android.content.Context; +import android.view.View; + +import androidx.recyclerview.widget.RecyclerView; + +/** + * An implementation of {@link ViewPagerLayoutManager} + * which will change rotate x or rotate y + */ + +@SuppressWarnings({"WeakerAccess", "unused"}) +public class GalleryLayoutManager extends ViewPagerLayoutManager { + private final float MAX_ELEVATION = 5F; + + private int itemSpace; + private float moveSpeed; + private float maxAlpha; + private float minAlpha; + private float angle; + private boolean flipRotate; + private boolean rotateFromEdge; + + public GalleryLayoutManager(Context context, int itemSpace) { + this(new Builder(context, itemSpace)); + } + + public GalleryLayoutManager(Context context, int itemSpace, int orientation) { + this(new Builder(context, itemSpace).setOrientation(orientation)); + } + + public GalleryLayoutManager(Context context, int itemSpace, int orientation, boolean reverseLayout) { + this(new Builder(context, itemSpace).setOrientation(orientation).setReverseLayout(reverseLayout)); + } + + public GalleryLayoutManager(Builder builder) { + this(builder.context, builder.itemSpace, builder.angle, builder.maxAlpha, builder.minAlpha, + builder.orientation, builder.moveSpeed, builder.flipRotate, builder.rotateFromEdge, + builder.maxVisibleItemCount, builder.distanceToBottom, builder.reverseLayout); + } + + private GalleryLayoutManager(Context context, int itemSpace, float angle, float maxAlpha, float minAlpha, + int orientation, float moveSpeed, boolean flipRotate, boolean rotateFromEdge, + int maxVisibleItemCount, int distanceToBottom, boolean reverseLayout) { + super(context, orientation, reverseLayout); + setDistanceToBottom(distanceToBottom); + setMaxVisibleItemCount(maxVisibleItemCount); + this.itemSpace = itemSpace; + this.moveSpeed = moveSpeed; + this.angle = angle; + this.maxAlpha = maxAlpha; + this.minAlpha = minAlpha; + this.flipRotate = flipRotate; + this.rotateFromEdge = rotateFromEdge; + } + + public int getItemSpace() { + return itemSpace; + } + + public void setItemSpace(int itemSpace) { + assertNotInLayoutOrScroll(null); + if (this.itemSpace == itemSpace) return; + this.itemSpace = itemSpace; + removeAllViews(); + } + + public float getMaxAlpha() { + return maxAlpha; + } + + public void setMaxAlpha(float maxAlpha) { + assertNotInLayoutOrScroll(null); + if (maxAlpha > 1) maxAlpha = 1; + if (this.maxAlpha == maxAlpha) return; + this.maxAlpha = maxAlpha; + requestLayout(); + } + + public float getMinAlpha() { + return minAlpha; + } + + public void setMinAlpha(float minAlpha) { + assertNotInLayoutOrScroll(null); + if (minAlpha < 0) minAlpha = 0; + if (this.minAlpha == minAlpha) return; + this.minAlpha = minAlpha; + requestLayout(); + } + + public float getAngle() { + return angle; + } + + public void setAngle(float angle) { + assertNotInLayoutOrScroll(null); + if (this.angle == angle) return; + this.angle = angle; + requestLayout(); + } + + public float getMoveSpeed() { + return moveSpeed; + } + + public void setMoveSpeed(float moveSpeed) { + assertNotInLayoutOrScroll(null); + if (this.moveSpeed == moveSpeed) return; + this.moveSpeed = moveSpeed; + } + + public boolean getFlipRotate() { + return flipRotate; + } + + public void setFlipRotate(boolean flipRotate) { + assertNotInLayoutOrScroll(null); + if (this.flipRotate == flipRotate) return; + this.flipRotate = flipRotate; + requestLayout(); + } + + public boolean getRotateFromEdge() { + return rotateFromEdge; + } + + public void setRotateFromEdge(boolean rotateFromEdge) { + assertNotInLayoutOrScroll(null); + if (this.rotateFromEdge == rotateFromEdge) return; + this.rotateFromEdge = rotateFromEdge; + removeAllViews(); + } + + @Override + protected float setInterval() { + return mDecoratedMeasurement + itemSpace; + } + + @Override + protected void setItemViewProperty(View itemView, float targetOffset) { + final float rotation = calRotation(targetOffset); + if (getOrientation() == RecyclerView.HORIZONTAL) { + if (rotateFromEdge) { + itemView.setPivotX(rotation > 0 ? 0 : mDecoratedMeasurement); + itemView.setPivotY(mDecoratedMeasurementInOther * 0.5f); + } + if (flipRotate) { + itemView.setRotationX(rotation); + } else { + itemView.setRotationY(rotation); + } + } else { + if (rotateFromEdge) { + itemView.setPivotY(rotation > 0 ? 0 : mDecoratedMeasurement); + itemView.setPivotX(mDecoratedMeasurementInOther * 0.5f); + + } + if (flipRotate) { + itemView.setRotationY(-rotation); + } else { + itemView.setRotationX(-rotation); + } + } + final float alpha = calAlpha(targetOffset); + itemView.setAlpha(alpha); + } + + @Override + protected float setViewElevation(View itemView, float targetOffset) { + final float ele = Math.max(Math.abs(itemView.getRotationX()), Math.abs(itemView.getRotationY())) * MAX_ELEVATION / 360; + return MAX_ELEVATION - ele; + } + + @Override + protected float getDistanceRatio() { + if (moveSpeed == 0) return Float.MAX_VALUE; + return 1 / moveSpeed; + } + + private float calRotation(float targetOffset) { + return -angle / mInterval * targetOffset; + } + + private float calAlpha(float targetOffset) { + final float offset = Math.abs(targetOffset); + float alpha = (minAlpha - maxAlpha) / mInterval * offset + maxAlpha; + if (offset >= mInterval) alpha = minAlpha; + return alpha; + } + + public static class Builder { + private static final float DEFAULT_SPEED = 1f; + private static float INTERVAL_ANGLE = 30f; + private static float MIN_ALPHA = 0.5f; + private static float MAX_ALPHA = 1f; + + private int itemSpace; + private float moveSpeed; + private int orientation; + private float maxAlpha; + private float minAlpha; + private float angle; + private boolean flipRotate; + private boolean reverseLayout; + private Context context; + private int maxVisibleItemCount; + private int distanceToBottom; + private boolean rotateFromEdge; + + public Builder(Context context, int itemSpace) { + this.itemSpace = itemSpace; + this.context = context; + orientation = HORIZONTAL; + angle = INTERVAL_ANGLE; + maxAlpha = MAX_ALPHA; + minAlpha = MIN_ALPHA; + this.moveSpeed = DEFAULT_SPEED; + reverseLayout = false; + flipRotate = false; + rotateFromEdge = false; + distanceToBottom = ViewPagerLayoutManager.INVALID_SIZE; + maxVisibleItemCount = ViewPagerLayoutManager.DETERMINE_BY_MAX_AND_MIN; + } + + public Builder setItemSpace(int itemSpace) { + this.itemSpace = itemSpace; + return this; + } + + public Builder setMoveSpeed(float moveSpeed) { + this.moveSpeed = moveSpeed; + return this; + } + + public Builder setOrientation(int orientation) { + this.orientation = orientation; + return this; + } + + public Builder setMaxAlpha(float maxAlpha) { + if (maxAlpha > 1) maxAlpha = 1; + this.maxAlpha = maxAlpha; + return this; + } + + public Builder setMinAlpha(float minAlpha) { + if (minAlpha < 0) minAlpha = 0; + this.minAlpha = minAlpha; + return this; + } + + public Builder setAngle(float angle) { + this.angle = angle; + return this; + } + + public Builder setFlipRotate(boolean flipRotate) { + this.flipRotate = flipRotate; + return this; + } + + public Builder setReverseLayout(boolean reverseLayout) { + this.reverseLayout = reverseLayout; + return this; + } + + public Builder setMaxVisibleItemCount(int maxVisibleItemCount) { + this.maxVisibleItemCount = maxVisibleItemCount; + return this; + } + + public Builder setDistanceToBottom(int distanceToBottom) { + this.distanceToBottom = distanceToBottom; + return this; + } + + public Builder setRotateFromEdge(boolean rotateFromEdge) { + this.rotateFromEdge = rotateFromEdge; + return this; + } + + public GalleryLayoutManager build() { + return new GalleryLayoutManager(this); + } + } +} diff --git a/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/OrientationHelper.java b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/OrientationHelper.java new file mode 100644 index 0000000..25b3671 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/OrientationHelper.java @@ -0,0 +1,413 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * 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 + * + * http://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. + */ + +package com.common.commonlib.view.viewpagerlayoutmanager; + +import android.graphics.Rect; +import android.view.View; +import android.widget.LinearLayout; + +import androidx.recyclerview.widget.RecyclerView; + +@SuppressWarnings({"WeakerAccess","unused"}) +public abstract class OrientationHelper { + + private static final int INVALID_SIZE = Integer.MIN_VALUE; + + protected final RecyclerView.LayoutManager mLayoutManager; + + public static final int HORIZONTAL = LinearLayout.HORIZONTAL; + + public static final int VERTICAL = LinearLayout.VERTICAL; + + private int mLastTotalSpace = INVALID_SIZE; + + final Rect mTmpRect = new Rect(); + + private OrientationHelper(RecyclerView.LayoutManager layoutManager) { + mLayoutManager = layoutManager; + } + + /** + * Call this method after onLayout method is complete if state is NOT pre-layout. + * This method records information like layout bounds that might be useful in the next layout + * calculations. + */ + public void onLayoutComplete() { + mLastTotalSpace = getTotalSpace(); + } + + /** + * Returns the layout space change between the previous layout pass and current layout pass. + *

+ * Make sure you call {@link #onLayoutComplete()} at the end of your LayoutManager's + * {@link RecyclerView.LayoutManager#onLayoutChildren(RecyclerView.Recycler, + * RecyclerView.State)} method. + * + * @return The difference between the current total space and previous layout's total space. + * @see #onLayoutComplete() + */ + public int getTotalSpaceChange() { + return INVALID_SIZE == mLastTotalSpace ? 0 : getTotalSpace() - mLastTotalSpace; + } + + /** + * Returns the start of the view including its decoration and margin. + *

+ * For example, for the horizontal helper, if a View's left is at pixel 20, has 2px left + * decoration and 3px left margin, returned value will be 15px. + * + * @param view The view element to check + * @return The first pixel of the element + * @see #getDecoratedEnd(View) + */ + public abstract int getDecoratedStart(View view); + + /** + * Returns the end of the view including its decoration and margin. + *

+ * For example, for the horizontal helper, if a View's right is at pixel 200, has 2px right + * decoration and 3px right margin, returned value will be 205. + * + * @param view The view element to check + * @return The last pixel of the element + * @see #getDecoratedStart(View) + */ + public abstract int getDecoratedEnd(View view); + + /** + * Returns the end of the View after its matrix transformations are applied to its layout + * position. + *

+ * This method is useful when trying to detect the visible edge of a View. + *

+ * It includes the decorations but does not include the margins. + * + * @param view The view whose transformed end will be returned + * @return The end of the View after its decor insets and transformation matrix is applied to + * its position + * @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect) + */ + public abstract int getTransformedEndWithDecoration(View view); + + /** + * Returns the start of the View after its matrix transformations are applied to its layout + * position. + *

+ * This method is useful when trying to detect the visible edge of a View. + *

+ * It includes the decorations but does not include the margins. + * + * @param view The view whose transformed start will be returned + * @return The start of the View after its decor insets and transformation matrix is applied to + * its position + * @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect) + */ + public abstract int getTransformedStartWithDecoration(View view); + + /** + * Returns the space occupied by this View in the current orientation including decorations and + * margins. + * + * @param view The view element to check + * @return Total space occupied by this view + * @see #getDecoratedMeasurementInOther(View) + */ + public abstract int getDecoratedMeasurement(View view); + + /** + * Returns the space occupied by this View in the perpendicular orientation including + * decorations and margins. + * + * @param view The view element to check + * @return Total space occupied by this view in the perpendicular orientation to current one + * @see #getDecoratedMeasurement(View) + */ + public abstract int getDecoratedMeasurementInOther(View view); + + /** + * Returns the start position of the layout after the start padding is added. + * + * @return The very first pixel we can draw. + */ + public abstract int getStartAfterPadding(); + + /** + * Returns the end position of the layout after the end padding is removed. + * + * @return The end boundary for this layout. + */ + public abstract int getEndAfterPadding(); + + /** + * Returns the end position of the layout without taking padding into account. + * + * @return The end boundary for this layout without considering padding. + */ + public abstract int getEnd(); + + /** + * Returns the total space to layout. This number is the difference between + * {@link #getEndAfterPadding()} and {@link #getStartAfterPadding()}. + * + * @return Total space to layout children + */ + public abstract int getTotalSpace(); + + /** + * Returns the total space in other direction to layout. This number is the difference between + * {@link #getEndAfterPadding()} and {@link #getStartAfterPadding()}. + * + * @return Total space to layout children + */ + public abstract int getTotalSpaceInOther(); + + /** + * Returns the padding at the end of the layout. For horizontal helper, this is the right + * padding and for vertical helper, this is the bottom padding. This method does not check + * whether the layout is RTL or not. + * + * @return The padding at the end of the layout. + */ + public abstract int getEndPadding(); + + /** + * Returns the MeasureSpec mode for the current orientation from the LayoutManager. + * + * @return The current measure spec mode. + * @see View.MeasureSpec + * @see RecyclerView.LayoutManager#getWidthMode() + * @see RecyclerView.LayoutManager#getHeightMode() + */ + public abstract int getMode(); + + /** + * Returns the MeasureSpec mode for the perpendicular orientation from the LayoutManager. + * + * @return The current measure spec mode. + * @see View.MeasureSpec + * @see RecyclerView.LayoutManager#getWidthMode() + * @see RecyclerView.LayoutManager#getHeightMode() + */ + public abstract int getModeInOther(); + + /** + * Creates an OrientationHelper for the given LayoutManager and orientation. + * + * @param layoutManager LayoutManager to attach to + * @param orientation Desired orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL} + * @return A new OrientationHelper + */ + public static OrientationHelper createOrientationHelper( + RecyclerView.LayoutManager layoutManager, int orientation) { + switch (orientation) { + case HORIZONTAL: + return createHorizontalHelper(layoutManager); + case VERTICAL: + return createVerticalHelper(layoutManager); + } + throw new IllegalArgumentException("invalid orientation"); + } + + /** + * Creates a horizontal OrientationHelper for the given LayoutManager. + * + * @param layoutManager The LayoutManager to attach to. + * @return A new OrientationHelper + */ + public static OrientationHelper createHorizontalHelper( + RecyclerView.LayoutManager layoutManager) { + return new OrientationHelper(layoutManager) { + @Override + public int getEndAfterPadding() { + return mLayoutManager.getWidth() - mLayoutManager.getPaddingRight(); + } + + @Override + public int getEnd() { + return mLayoutManager.getWidth(); + } + + @Override + public int getStartAfterPadding() { + return mLayoutManager.getPaddingLeft(); + } + + @Override + public int getDecoratedMeasurement(View view) { + final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) + view.getLayoutParams(); + return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin + + params.rightMargin; + } + + @Override + public int getDecoratedMeasurementInOther(View view) { + final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) + view.getLayoutParams(); + return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin + + params.bottomMargin; + } + + @Override + public int getDecoratedEnd(View view) { + final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) + view.getLayoutParams(); + return mLayoutManager.getDecoratedRight(view) + params.rightMargin; + } + + @Override + public int getDecoratedStart(View view) { + final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) + view.getLayoutParams(); + return mLayoutManager.getDecoratedLeft(view) - params.leftMargin; + } + + @Override + public int getTransformedEndWithDecoration(View view) { + mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect); + return mTmpRect.right; + } + + @Override + public int getTransformedStartWithDecoration(View view) { + mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect); + return mTmpRect.left; + } + + @Override + public int getTotalSpace() { + return mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft() + - mLayoutManager.getPaddingRight(); + } + + @Override + public int getTotalSpaceInOther() { + return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop() + - mLayoutManager.getPaddingBottom(); + } + + @Override + public int getEndPadding() { + return mLayoutManager.getPaddingRight(); + } + + @Override + public int getMode() { + return mLayoutManager.getWidthMode(); + } + + @Override + public int getModeInOther() { + return mLayoutManager.getHeightMode(); + } + }; + } + + /** + * Creates a vertical OrientationHelper for the given LayoutManager. + * + * @param layoutManager The LayoutManager to attach to. + * @return A new OrientationHelper + */ + public static OrientationHelper createVerticalHelper(RecyclerView.LayoutManager layoutManager) { + return new OrientationHelper(layoutManager) { + @Override + public int getEndAfterPadding() { + return mLayoutManager.getHeight() - mLayoutManager.getPaddingBottom(); + } + + @Override + public int getEnd() { + return mLayoutManager.getHeight(); + } + + @Override + public int getStartAfterPadding() { + return mLayoutManager.getPaddingTop(); + } + + @Override + public int getDecoratedMeasurement(View view) { + final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) + view.getLayoutParams(); + return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin + + params.bottomMargin; + } + + @Override + public int getDecoratedMeasurementInOther(View view) { + final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) + view.getLayoutParams(); + return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin + + params.rightMargin; + } + + @Override + public int getDecoratedEnd(View view) { + final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) + view.getLayoutParams(); + return mLayoutManager.getDecoratedBottom(view) + params.bottomMargin; + } + + @Override + public int getDecoratedStart(View view) { + final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) + view.getLayoutParams(); + return mLayoutManager.getDecoratedTop(view) - params.topMargin; + } + + @Override + public int getTransformedEndWithDecoration(View view) { + mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect); + return mTmpRect.bottom; + } + + @Override + public int getTransformedStartWithDecoration(View view) { + mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect); + return mTmpRect.top; + } + + @Override + public int getTotalSpace() { + return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop() + - mLayoutManager.getPaddingBottom(); + } + + @Override + public int getTotalSpaceInOther() { + return mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft() + - mLayoutManager.getPaddingRight(); + } + + @Override + public int getEndPadding() { + return mLayoutManager.getPaddingBottom(); + } + + @Override + public int getMode() { + return mLayoutManager.getHeightMode(); + } + + @Override + public int getModeInOther() { + return mLayoutManager.getWidthMode(); + } + }; + } +} \ No newline at end of file diff --git a/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/PageSnapHelper.java b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/PageSnapHelper.java new file mode 100644 index 0000000..6d9bdb2 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/PageSnapHelper.java @@ -0,0 +1,46 @@ +package com.common.commonlib.view.viewpagerlayoutmanager; + +import androidx.recyclerview.widget.RecyclerView; + +public class PageSnapHelper extends CenterSnapHelper { + + @Override + public boolean onFling(int velocityX, int velocityY) { + ViewPagerLayoutManager layoutManager = (ViewPagerLayoutManager) mRecyclerView.getLayoutManager(); + if (layoutManager == null) { + return false; + } + RecyclerView.Adapter adapter = mRecyclerView.getAdapter(); + if (adapter == null) { + return false; + } + + if (!layoutManager.getInfinite() && + (layoutManager.mOffset == layoutManager.getMaxOffset() + || layoutManager.mOffset == layoutManager.getMinOffset())) { + return false; + } + + final int minFlingVelocity = mRecyclerView.getMinFlingVelocity(); + mGravityScroller.fling(0, 0, velocityX, velocityY, + Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); + + if (layoutManager.mOrientation == ViewPagerLayoutManager.VERTICAL + && Math.abs(velocityY) > minFlingVelocity) { + final int currentPosition = layoutManager.getCurrentPositionOffset(); + final int offsetPosition = mGravityScroller.getFinalY() * layoutManager.getDistanceRatio() > layoutManager.mInterval ? 1 : 0; + ScrollHelper.smoothScrollToPosition(mRecyclerView, layoutManager, layoutManager.getReverseLayout() ? + -currentPosition - offsetPosition : currentPosition + offsetPosition); + return true; + } else if (layoutManager.mOrientation == ViewPagerLayoutManager.HORIZONTAL + && Math.abs(velocityX) > minFlingVelocity) { + final int currentPosition = layoutManager.getCurrentPositionOffset(); + final int offsetPosition = mGravityScroller.getFinalX() * layoutManager.getDistanceRatio() > layoutManager.mInterval ? 1 : 0; + ScrollHelper.smoothScrollToPosition(mRecyclerView, layoutManager, layoutManager.getReverseLayout() ? + -currentPosition - offsetPosition : currentPosition + offsetPosition); + return true; + } + + return true; + } +} diff --git a/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/RotateLayoutManager.java b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/RotateLayoutManager.java new file mode 100644 index 0000000..b2860ee --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/RotateLayoutManager.java @@ -0,0 +1,178 @@ +package com.common.commonlib.view.viewpagerlayoutmanager; + +import android.content.Context; +import android.view.View; + +/** + * An implementation of {@link ViewPagerLayoutManager} + * which rotates items + */ + +@SuppressWarnings({"WeakerAccess", "unused"}) +public class RotateLayoutManager extends ViewPagerLayoutManager { + + private int itemSpace; + private float angle; + private float moveSpeed; + private boolean reverseRotate; + + public RotateLayoutManager(Context context, int itemSpace) { + this(new Builder(context, itemSpace)); + } + + public RotateLayoutManager(Context context, int itemSpace, int orientation) { + this(new Builder(context, itemSpace).setOrientation(orientation)); + } + + public RotateLayoutManager(Context context, int itemSpace, int orientation, boolean reverseLayout) { + this(new Builder(context, itemSpace).setOrientation(orientation).setReverseLayout(reverseLayout)); + } + + public RotateLayoutManager(Builder builder) { + this(builder.context, builder.itemSpace, builder.angle, builder.orientation, builder.moveSpeed, + builder.reverseRotate, builder.maxVisibleItemCount, builder.distanceToBottom, + builder.reverseLayout); + } + + private RotateLayoutManager(Context context, int itemSpace, float angle, int orientation, float moveSpeed, + boolean reverseRotate, int maxVisibleItemCount, int distanceToBottom, + boolean reverseLayout) { + super(context, orientation, reverseLayout); + setDistanceToBottom(distanceToBottom); + setMaxVisibleItemCount(maxVisibleItemCount); + this.itemSpace = itemSpace; + this.angle = angle; + this.moveSpeed = moveSpeed; + this.reverseRotate = reverseRotate; + } + + public int getItemSpace() { + return itemSpace; + } + + public float getAngle() { + return angle; + } + + public float getMoveSpeed() { + return moveSpeed; + } + + public boolean getReverseRotate() { + return reverseRotate; + } + + public void setItemSpace(int itemSpace) { + assertNotInLayoutOrScroll(null); + if (this.itemSpace == itemSpace) return; + this.itemSpace = itemSpace; + removeAllViews(); + } + + public void setAngle(float centerScale) { + assertNotInLayoutOrScroll(null); + if (this.angle == centerScale) return; + this.angle = centerScale; + requestLayout(); + } + + public void setMoveSpeed(float moveSpeed) { + assertNotInLayoutOrScroll(null); + if (this.moveSpeed == moveSpeed) return; + this.moveSpeed = moveSpeed; + } + + public void setReverseRotate(boolean reverseRotate) { + assertNotInLayoutOrScroll(null); + if (this.reverseRotate == reverseRotate) return; + this.reverseRotate = reverseRotate; + requestLayout(); + } + + @Override + protected float setInterval() { + return mDecoratedMeasurement + itemSpace; + } + + @Override + protected void setItemViewProperty(View itemView, float targetOffset) { + itemView.setRotation(calRotation(targetOffset)); + } + + @Override + protected float getDistanceRatio() { + if (moveSpeed == 0) return Float.MAX_VALUE; + return 1 / moveSpeed; + } + + private float calRotation(float targetOffset) { + final float realAngle = reverseRotate ? angle : -angle; + return realAngle / mInterval * targetOffset; + } + + public static class Builder { + private static float INTERVAL_ANGLE = 360f; + private static final float DEFAULT_SPEED = 1f; + + private int itemSpace; + private int orientation; + private float angle; + private float moveSpeed; + private boolean reverseRotate; + private boolean reverseLayout; + private Context context; + private int maxVisibleItemCount; + private int distanceToBottom; + + public Builder(Context context, int itemSpace) { + this.context = context; + this.itemSpace = itemSpace; + orientation = HORIZONTAL; + angle = INTERVAL_ANGLE; + this.moveSpeed = DEFAULT_SPEED; + reverseRotate = false; + reverseLayout = false; + distanceToBottom = ViewPagerLayoutManager.INVALID_SIZE; + maxVisibleItemCount = ViewPagerLayoutManager.DETERMINE_BY_MAX_AND_MIN; + } + + public Builder setOrientation(int orientation) { + this.orientation = orientation; + return this; + } + + public Builder setAngle(float angle) { + this.angle = angle; + return this; + } + + public Builder setReverseLayout(boolean reverseLayout) { + this.reverseLayout = reverseLayout; + return this; + } + + public Builder setMoveSpeed(float moveSpeed) { + this.moveSpeed = moveSpeed; + return this; + } + + public Builder setReverseRotate(boolean reverseRotate) { + this.reverseRotate = reverseRotate; + return this; + } + + public Builder setMaxVisibleItemCount(int maxVisibleItemCount) { + this.maxVisibleItemCount = maxVisibleItemCount; + return this; + } + + public Builder setDistanceToBottom(int distanceToBottom) { + this.distanceToBottom = distanceToBottom; + return this; + } + + public RotateLayoutManager build() { + return new RotateLayoutManager(this); + } + } +} diff --git a/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/ScaleLayoutManager.java b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/ScaleLayoutManager.java new file mode 100644 index 0000000..9b9d418 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/ScaleLayoutManager.java @@ -0,0 +1,221 @@ +package com.common.commonlib.view.viewpagerlayoutmanager; + +import android.content.Context; +import android.view.View; + +/** + * An implementation of {@link ViewPagerLayoutManager} + * which zooms the center item + */ + +@SuppressWarnings({"WeakerAccess", "unused"}) +public class ScaleLayoutManager extends ViewPagerLayoutManager { + + private int itemSpace; + private float minScale; + private float moveSpeed; + private float maxAlpha; + private float minAlpha; + + public ScaleLayoutManager(Context context, int itemSpace) { + this(new Builder(context, itemSpace)); + } + + public ScaleLayoutManager(Context context, int itemSpace, int orientation) { + this(new Builder(context, itemSpace).setOrientation(orientation)); + } + + public ScaleLayoutManager(Context context, int itemSpace, int orientation, boolean reverseLayout) { + this(new Builder(context, itemSpace).setOrientation(orientation).setReverseLayout(reverseLayout)); + } + + public ScaleLayoutManager(Builder builder) { + this(builder.context, builder.itemSpace, builder.minScale, builder.maxAlpha, builder.minAlpha, + builder.orientation, builder.moveSpeed, builder.maxVisibleItemCount, builder.distanceToBottom, + builder.reverseLayout); + } + + private ScaleLayoutManager(Context context, int itemSpace, float minScale, float maxAlpha, float minAlpha, + int orientation, float moveSpeed, int maxVisibleItemCount, int distanceToBottom, + boolean reverseLayout) { + super(context, orientation, reverseLayout); + setDistanceToBottom(distanceToBottom); + setMaxVisibleItemCount(maxVisibleItemCount); + this.itemSpace = itemSpace; + this.minScale = minScale; + this.moveSpeed = moveSpeed; + this.maxAlpha = maxAlpha; + this.minAlpha = minAlpha; + } + + public int getItemSpace() { + return itemSpace; + } + + public float getMinScale() { + return minScale; + } + + public float getMoveSpeed() { + return moveSpeed; + } + + public float getMaxAlpha() { + return maxAlpha; + } + + public float getMinAlpha() { + return minAlpha; + } + + public void setItemSpace(int itemSpace) { + assertNotInLayoutOrScroll(null); + if (this.itemSpace == itemSpace) return; + this.itemSpace = itemSpace; + removeAllViews(); + } + + public void setMinScale(float minScale) { + assertNotInLayoutOrScroll(null); + if (this.minScale == minScale) return; + this.minScale = minScale; + removeAllViews(); + } + + public void setMaxAlpha(float maxAlpha) { + assertNotInLayoutOrScroll(null); + if (maxAlpha > 1) maxAlpha = 1; + if (this.maxAlpha == maxAlpha) return; + this.maxAlpha = maxAlpha; + requestLayout(); + } + + public void setMinAlpha(float minAlpha) { + assertNotInLayoutOrScroll(null); + if (minAlpha < 0) minAlpha = 0; + if (this.minAlpha == minAlpha) return; + this.minAlpha = minAlpha; + requestLayout(); + } + + public void setMoveSpeed(float moveSpeed) { + assertNotInLayoutOrScroll(null); + if (this.moveSpeed == moveSpeed) return; + this.moveSpeed = moveSpeed; + } + + @Override + protected float setInterval() { + return itemSpace + mDecoratedMeasurement; + } + + @Override + protected void setItemViewProperty(View itemView, float targetOffset) { + float scale = calculateScale(targetOffset + mSpaceMain); + itemView.setScaleX(scale); + itemView.setScaleY(scale); + final float alpha = calAlpha(targetOffset); + itemView.setAlpha(alpha); + } + + private float calAlpha(float targetOffset) { + final float offset = Math.abs(targetOffset); + float alpha = (minAlpha - maxAlpha) / mInterval * offset + maxAlpha; + if (offset >= mInterval) alpha = minAlpha; + return alpha; + } + + @Override + protected float getDistanceRatio() { + if (moveSpeed == 0) return Float.MAX_VALUE; + return 1 / moveSpeed; + } + + /** + * @param x start positon of the view you want scale + * @return the scale rate of current scroll mOffset + */ + private float calculateScale(float x) { + float deltaX = Math.abs(x - mSpaceMain); + if (deltaX - mDecoratedMeasurement > 0) deltaX = mDecoratedMeasurement; + return 1f - deltaX / mDecoratedMeasurement * (1f - minScale); + } + + public static class Builder { + private static final float SCALE_RATE = 0.8f; + private static final float DEFAULT_SPEED = 1f; + private static float MIN_ALPHA = 1f; + private static float MAX_ALPHA = 1f; + + private int itemSpace; + private int orientation; + private float minScale; + private float moveSpeed; + private float maxAlpha; + private float minAlpha; + private boolean reverseLayout; + private Context context; + private int maxVisibleItemCount; + private int distanceToBottom; + + public Builder(Context context, int itemSpace) { + this.itemSpace = itemSpace; + this.context = context; + orientation = HORIZONTAL; + minScale = SCALE_RATE; + this.moveSpeed = DEFAULT_SPEED; + maxAlpha = MAX_ALPHA; + minAlpha = MIN_ALPHA; + reverseLayout = false; + distanceToBottom = ViewPagerLayoutManager.INVALID_SIZE; + maxVisibleItemCount = ViewPagerLayoutManager.DETERMINE_BY_MAX_AND_MIN; + } + + public Builder setOrientation(int orientation) { + this.orientation = orientation; + return this; + } + + public Builder setMinScale(float minScale) { + this.minScale = minScale; + return this; + } + + public Builder setReverseLayout(boolean reverseLayout) { + this.reverseLayout = reverseLayout; + return this; + } + + public Builder setMaxAlpha(float maxAlpha) { + if (maxAlpha > 1) maxAlpha = 1; + this.maxAlpha = maxAlpha; + return this; + } + + public Builder setMinAlpha(float minAlpha) { + if (minAlpha < 0) minAlpha = 0; + this.minAlpha = minAlpha; + return this; + } + + public Builder setMoveSpeed(float moveSpeed) { + this.moveSpeed = moveSpeed; + return this; + } + + public Builder setMaxVisibleItemCount(int maxVisibleItemCount) { + this.maxVisibleItemCount = maxVisibleItemCount; + return this; + } + + public Builder setDistanceToBottom(int distanceToBottom) { + this.distanceToBottom = distanceToBottom; + return this; + } + + public ScaleLayoutManager build() { + return new ScaleLayoutManager(this); + } + } +} + diff --git a/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/ScrollHelper.java b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/ScrollHelper.java new file mode 100644 index 0000000..8839dec --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/ScrollHelper.java @@ -0,0 +1,24 @@ +package com.common.commonlib.view.viewpagerlayoutmanager; + +import android.view.View; + +import androidx.recyclerview.widget.RecyclerView; + +public class ScrollHelper { + /* package */ + static void smoothScrollToPosition(RecyclerView recyclerView, ViewPagerLayoutManager viewPagerLayoutManager, int targetPosition) { + final int delta = viewPagerLayoutManager.getOffsetToPosition(targetPosition); + if (viewPagerLayoutManager.getOrientation() == RecyclerView.VERTICAL) { + recyclerView.smoothScrollBy(0, delta); + } else { + recyclerView.smoothScrollBy(delta, 0); + } + } + + public static void smoothScrollToTargetView(RecyclerView recyclerView, View targetView) { + final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); + if (!(layoutManager instanceof ViewPagerLayoutManager)) return; + final int targetPosition = ((ViewPagerLayoutManager) layoutManager).getLayoutPositionOfView(targetView); + smoothScrollToPosition(recyclerView, (ViewPagerLayoutManager) layoutManager, targetPosition); + } +} diff --git a/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/ViewPagerLayoutManager.java b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/ViewPagerLayoutManager.java new file mode 100644 index 0000000..a1b07a9 --- /dev/null +++ b/library-common/src/main/java/com/common/commonlib/view/viewpagerlayoutmanager/ViewPagerLayoutManager.java @@ -0,0 +1,936 @@ +package com.common.commonlib.view.viewpagerlayoutmanager; + +import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; + +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.SparseArray; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Interpolator; + +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; + +@SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue"}) +public abstract class ViewPagerLayoutManager extends LinearLayoutManager { + + public static final int DETERMINE_BY_MAX_AND_MIN = -1; + + public static final int HORIZONTAL = OrientationHelper.HORIZONTAL; + + public static final int VERTICAL = OrientationHelper.VERTICAL; + protected static final int INVALID_SIZE = Integer.MAX_VALUE; + private static final int DIRECTION_NO_WHERE = -1; + private static final int DIRECTION_FORWARD = 0; + private static final int DIRECTION_BACKWARD = 1; + protected int mDecoratedMeasurement; + protected int mDecoratedMeasurementInOther; + protected int mSpaceMain; + protected int mSpaceInOther; + /** + * The offset of property which will change while scrolling + */ + protected float mOffset; + /** + * Many calculations are made depending on orientation. To keep it clean, this interface + * helps {@link LinearLayoutManager} make those decisions. + * Based on {@link #mOrientation}, an implementation is lazily created in + * method. + */ + protected OrientationHelper mOrientationHelper; + protected float mInterval; //the mInterval of each item's mOffset + /** + * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL} + */ + int mOrientation; + /* package */ OnPageChangeListener onPageChangeListener; + private SparseArray positionCache = new SparseArray<>(); + /** + * Defines if layout should be calculated from end to start. + */ + private boolean mReverseLayout = false; + /** + * This keeps the final value for how LayoutManager should start laying out views. + * It is calculated by checking {@link #getReverseLayout()} and View's layout direction. + * {@link #onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)} is run. + */ + private boolean mShouldReverseLayout = false; + /** + * Works the same way as {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}. + * see {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)} + */ + private boolean mSmoothScrollbarEnabled = true; + /** + * When LayoutManager needs to scroll to a position, it sets this variable and requests a + * layout which will check this variable and re-layout accordingly. + */ + private int mPendingScrollPosition = NO_POSITION; + private SavedState mPendingSavedState = null; + private boolean mRecycleChildrenOnDetach; + + private boolean mInfinite = false; + + private boolean mEnableBringCenterToFront; + + private int mLeftItems; + + private int mRightItems; + + /** + * max visible item count + */ + private int mMaxVisibleItemCount = DETERMINE_BY_MAX_AND_MIN; + + private Interpolator mSmoothScrollInterpolator; + + private int mDistanceToBottom = INVALID_SIZE; + + /** + * use for handle focus + */ + private View currentFocusView; + + /** + * Creates a horizontal ViewPagerLayoutManager + */ + public ViewPagerLayoutManager(Context context) { + this(context, HORIZONTAL, false); + } + + /** + * @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL} + * @param reverseLayout When set to true, layouts from end to start + */ + public ViewPagerLayoutManager(Context context, int orientation, boolean reverseLayout) { + super(context); + setOrientation(orientation); + setReverseLayout(reverseLayout); + setAutoMeasureEnabled(true); + setItemPrefetchEnabled(false); + } + + /** + * @return the mInterval of each item's mOffset + */ + protected abstract float setInterval(); + + protected abstract void setItemViewProperty(View itemView, float targetOffset); + + /** + * cause elevation is not support below api 21, + * so you can set your elevation here for supporting it below api 21 + * or you can just setElevation in {@link #setItemViewProperty(View, float)} + */ + protected float setViewElevation(View itemView, float targetOffset) { + return 0; + } + + @Override + public RecyclerView.LayoutParams generateDefaultLayoutParams() { + return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + } + + /** + * Returns whether LayoutManager will recycle its children when it is detached from + * RecyclerView. + * + * @return true if LayoutManager will recycle its children when it is detached from + * RecyclerView. + */ + public boolean getRecycleChildrenOnDetach() { + return mRecycleChildrenOnDetach; + } + + /** + * Set whether LayoutManager will recycle its children when it is detached from + * RecyclerView. + *

+ * If you are using a {@link RecyclerView.RecycledViewPool}, it might be a good idea to set + * this flag to true so that views will be available to other RecyclerViews + * immediately. + *

+ * Note that, setting this flag will result in a performance drop if RecyclerView + * is restored. + * + * @param recycleChildrenOnDetach Whether children should be recycled in detach or not. + */ + public void setRecycleChildrenOnDetach(boolean recycleChildrenOnDetach) { + mRecycleChildrenOnDetach = recycleChildrenOnDetach; + } + + @Override + public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) { + super.onDetachedFromWindow(view, recycler); + if (mRecycleChildrenOnDetach) { + removeAndRecycleAllViews(recycler); + recycler.clear(); + } + } + + @Override + public Parcelable onSaveInstanceState() { + if (mPendingSavedState != null) { + return new SavedState(mPendingSavedState); + } + SavedState savedState = new SavedState(); + savedState.position = mPendingScrollPosition; + savedState.offset = mOffset; + savedState.isReverseLayout = mShouldReverseLayout; + return savedState; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + if (state instanceof SavedState) { + mPendingSavedState = new SavedState((SavedState) state); + requestLayout(); + } + } + + /** + * @return true if {@link #getOrientation()} is {@link #HORIZONTAL} + */ + @Override + public boolean canScrollHorizontally() { + return mOrientation == HORIZONTAL; + } + + /** + * @return true if {@link #getOrientation()} is {@link #VERTICAL} + */ + @Override + public boolean canScrollVertically() { + return mOrientation == VERTICAL; + } + + /** + * Returns the current orientation of the layout. + * + * @return Current orientation, either {@link #HORIZONTAL} or {@link #VERTICAL} + * @see #setOrientation(int) + */ + public int getOrientation() { + return mOrientation; + } + + /** + * Sets the orientation of the layout. {@link ViewPagerLayoutManager} + * will do its best to keep scroll position. + * + * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL} + */ + public void setOrientation(int orientation) { + if (orientation != HORIZONTAL && orientation != VERTICAL) { + throw new IllegalArgumentException("invalid orientation:" + orientation); + } + assertNotInLayoutOrScroll(null); + if (orientation == mOrientation) { + return; + } + mOrientation = orientation; + mOrientationHelper = null; + mDistanceToBottom = INVALID_SIZE; + removeAllViews(); + } + + /** + * Returns the max visible item count, {@link #DETERMINE_BY_MAX_AND_MIN} means it haven't been set now + * And it will use {@link #maxRemoveOffset()} and {@link #minRemoveOffset()} to handle the range + * + * @return Max visible item count + */ + public int getMaxVisibleItemCount() { + return mMaxVisibleItemCount; + } + + /** + * Set the max visible item count, {@link #DETERMINE_BY_MAX_AND_MIN} means it haven't been set now + * And it will use {@link #maxRemoveOffset()} and {@link #minRemoveOffset()} to handle the range + * + * @param mMaxVisibleItemCount Max visible item count + */ + public void setMaxVisibleItemCount(int mMaxVisibleItemCount) { + assertNotInLayoutOrScroll(null); + if (this.mMaxVisibleItemCount == mMaxVisibleItemCount) return; + this.mMaxVisibleItemCount = mMaxVisibleItemCount; + removeAllViews(); + } + + /** + * Calculates the view layout order. (e.g. from end to start or start to end) + * RTL layout support is applied automatically. So if layout is RTL and + * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left. + */ + private void resolveShouldLayoutReverse() { + // A == B is the same result, but we rather keep it readable + if (mOrientation == VERTICAL || !isLayoutRTL()) { + mShouldReverseLayout = mReverseLayout; + } else { + mShouldReverseLayout = !mReverseLayout; + } + } + + /** + * Returns if views are laid out from the opposite direction of the layout. + * + * @return If layout is reversed or not. + * @see #setReverseLayout(boolean) + */ + public boolean getReverseLayout() { + return mReverseLayout; + } + + public void setReverseLayout(boolean reverseLayout) { + assertNotInLayoutOrScroll(null); + if (reverseLayout == mReverseLayout) { + return; + } + mReverseLayout = reverseLayout; + removeAllViews(); + } + + public void setSmoothScrollInterpolator(Interpolator smoothScrollInterpolator) { + this.mSmoothScrollInterpolator = smoothScrollInterpolator; + } + + @Override + public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) { + final int offsetPosition; + + // fix wrong scroll direction when infinite enable + if (mInfinite) { + final int currentPosition = getCurrentPosition(); + final int total = getItemCount(); + final int targetPosition; + if (position < currentPosition) { + int d1 = currentPosition - position; + int d2 = total - currentPosition + position; + targetPosition = d1 < d2 ? (currentPosition - d1) : (currentPosition + d2); + } else { + int d1 = position - currentPosition; + int d2 = currentPosition + total - position; + targetPosition = d1 < d2 ? (currentPosition + d1) : (currentPosition - d2); + } + + offsetPosition = getOffsetToPosition(targetPosition); + } else { + offsetPosition = getOffsetToPosition(position); + } + + if (mOrientation == VERTICAL) { + recyclerView.smoothScrollBy(0, offsetPosition, mSmoothScrollInterpolator); + } else { + recyclerView.smoothScrollBy(offsetPosition, 0, mSmoothScrollInterpolator); + } + } + + @Override + public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { + if (state.getItemCount() == 0) { + removeAndRecycleAllViews(recycler); + mOffset = 0; + return; + } + + ensureLayoutState(); + resolveShouldLayoutReverse(); + + //make sure properties are correct while measure more than once + View scrap = getMeasureView(recycler, state, 0); + if (scrap == null) { + removeAndRecycleAllViews(recycler); + mOffset = 0; + return; + } + + measureChildWithMargins(scrap, 0, 0); + mDecoratedMeasurement = mOrientationHelper.getDecoratedMeasurement(scrap); + mDecoratedMeasurementInOther = mOrientationHelper.getDecoratedMeasurementInOther(scrap); + mSpaceMain = (mOrientationHelper.getTotalSpace() - mDecoratedMeasurement) / 2; + if (mDistanceToBottom == INVALID_SIZE) { + mSpaceInOther = (mOrientationHelper.getTotalSpaceInOther() - mDecoratedMeasurementInOther) / 2; + } else { + mSpaceInOther = mOrientationHelper.getTotalSpaceInOther() - mDecoratedMeasurementInOther - mDistanceToBottom; + } + + mInterval = setInterval(); + setUp(); + if (mInterval == 0) { + mLeftItems = 1; + mRightItems = 1; + } else { + mLeftItems = (int) Math.abs(minRemoveOffset() / mInterval) + 1; + mRightItems = (int) Math.abs(maxRemoveOffset() / mInterval) + 1; + } + + if (mPendingSavedState != null) { + mShouldReverseLayout = mPendingSavedState.isReverseLayout; + mPendingScrollPosition = mPendingSavedState.position; + mOffset = mPendingSavedState.offset; + } + + if (mPendingScrollPosition != NO_POSITION) { + mOffset = mShouldReverseLayout ? + mPendingScrollPosition * -mInterval : mPendingScrollPosition * mInterval; + } + + layoutItems(recycler); + } + + private View getMeasureView(RecyclerView.Recycler recycler, RecyclerView.State state, int index) { + if (index >= state.getItemCount() || index < 0) return null; + try { + return recycler.getViewForPosition(index); + } catch (Exception e) { + return getMeasureView(recycler, state, index + 1); + } + } + + @Override + public void onLayoutCompleted(RecyclerView.State state) { + super.onLayoutCompleted(state); + mPendingSavedState = null; + mPendingScrollPosition = NO_POSITION; + } + + @Override + public boolean onAddFocusables(RecyclerView recyclerView, ArrayList views, int direction, int focusableMode) { + final int currentPosition = getCurrentPosition(); + final View currentView = findViewByPosition(currentPosition); + if (currentView == null) return true; + if (recyclerView.hasFocus()) { + final int movement = getMovement(direction); + if (movement != DIRECTION_NO_WHERE) { + final int targetPosition = movement == DIRECTION_BACKWARD ? + currentPosition - 1 : currentPosition + 1; + ScrollHelper.smoothScrollToPosition(recyclerView, this, targetPosition); + } + } else { + currentView.addFocusables(views, direction, focusableMode); + } + return true; + } + + @Override + public View onFocusSearchFailed(View focused, int focusDirection, RecyclerView.Recycler recycler, RecyclerView.State state) { + return null; + } + + private int getMovement(int direction) { + if (mOrientation == VERTICAL) { + if (direction == View.FOCUS_UP) { + return mShouldReverseLayout ? DIRECTION_FORWARD : DIRECTION_BACKWARD; + } else if (direction == View.FOCUS_DOWN) { + return mShouldReverseLayout ? DIRECTION_BACKWARD : DIRECTION_FORWARD; + } else { + return DIRECTION_NO_WHERE; + } + } else { + if (direction == View.FOCUS_LEFT) { + return mShouldReverseLayout ? DIRECTION_FORWARD : DIRECTION_BACKWARD; + } else if (direction == View.FOCUS_RIGHT) { + return mShouldReverseLayout ? DIRECTION_BACKWARD : DIRECTION_FORWARD; + } else { + return DIRECTION_NO_WHERE; + } + } + } + + void ensureLayoutState() { + if (mOrientationHelper == null) { + mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation); + } + } + + /** + * You can set up your own properties here or change the exist properties like mSpaceMain and mSpaceInOther + */ + protected void setUp() { + + } + + private float getProperty(int position) { + return mShouldReverseLayout ? position * -mInterval : position * mInterval; + } + + @Override + public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) { + removeAllViews(); + mOffset = 0; + } + + @Override + public void scrollToPosition(int position) { + if (!mInfinite && (position < 0 || position >= getItemCount())) return; + mPendingScrollPosition = position; + mOffset = mShouldReverseLayout ? position * -mInterval : position * mInterval; + requestLayout(); + } + + @Override + public int computeHorizontalScrollOffset(RecyclerView.State state) { + return computeScrollOffset(); + } + + @Override + public int computeVerticalScrollOffset(RecyclerView.State state) { + return computeScrollOffset(); + } + + @Override + public int computeHorizontalScrollExtent(RecyclerView.State state) { + return computeScrollExtent(); + } + + @Override + public int computeVerticalScrollExtent(RecyclerView.State state) { + return computeScrollExtent(); + } + + @Override + public int computeHorizontalScrollRange(RecyclerView.State state) { + return computeScrollRange(); + } + + @Override + public int computeVerticalScrollRange(RecyclerView.State state) { + return computeScrollRange(); + } + + private int computeScrollOffset() { + if (getChildCount() == 0) { + return 0; + } + + if (!mSmoothScrollbarEnabled) { + return !mShouldReverseLayout ? + getCurrentPosition() : getItemCount() - getCurrentPosition() - 1; + } + + final float realOffset = getOffsetOfRightAdapterPosition(); + return !mShouldReverseLayout ? (int) realOffset : (int) ((getItemCount() - 1) * mInterval + realOffset); + } + + private int computeScrollExtent() { + if (getChildCount() == 0) { + return 0; + } + + if (!mSmoothScrollbarEnabled) { + return 1; + } + + return (int) mInterval; + } + + private int computeScrollRange() { + if (getChildCount() == 0) { + return 0; + } + + if (!mSmoothScrollbarEnabled) { + return getItemCount(); + } + + return (int) (getItemCount() * mInterval); + } + + @Override + public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) { + if (mOrientation == VERTICAL) { + return 0; + } + return scrollBy(dx, recycler, state); + } + + @Override + public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { + if (mOrientation == HORIZONTAL) { + return 0; + } + return scrollBy(dy, recycler, state); + } + + private int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { + if (getChildCount() == 0 || dy == 0) { + return 0; + } + ensureLayoutState(); + int willScroll = dy; + + float realDx = dy / getDistanceRatio(); + if (Math.abs(realDx) < 0.00000001f) { + return 0; + } + float targetOffset = mOffset + realDx; + + //handle the boundary + if (!mInfinite && targetOffset < getMinOffset()) { + willScroll -= (targetOffset - getMinOffset()) * getDistanceRatio(); + } else if (!mInfinite && targetOffset > getMaxOffset()) { + willScroll = (int) ((getMaxOffset() - mOffset) * getDistanceRatio()); + } + + realDx = willScroll / getDistanceRatio(); + + mOffset += realDx; + + //handle recycle + layoutItems(recycler); + + return willScroll; + } + + private void layoutItems(RecyclerView.Recycler recycler) { + detachAndScrapAttachedViews(recycler); + positionCache.clear(); + + final int itemCount = getItemCount(); + if (itemCount == 0) return; + + // make sure that current position start from 0 to 1 + final int currentPos = mShouldReverseLayout ? + -getCurrentPositionOffset() : getCurrentPositionOffset(); + int start = currentPos - mLeftItems; + int end = currentPos + mRightItems; + + // handle max visible count + if (useMaxVisibleCount()) { + boolean isEven = mMaxVisibleItemCount % 2 == 0; + if (isEven) { + int offset = mMaxVisibleItemCount / 2; + start = currentPos - offset + 1; + end = currentPos + offset + 1; + } else { + int offset = (mMaxVisibleItemCount - 1) / 2; + start = currentPos - offset; + end = currentPos + offset + 1; + } + } + + if (!mInfinite) { + if (start < 0) { + start = 0; + if (useMaxVisibleCount()) end = mMaxVisibleItemCount; + } + if (end > itemCount) end = itemCount; + } + + float lastOrderWeight = Float.MIN_VALUE; + for (int i = start; i < end; i++) { + if (useMaxVisibleCount() || !removeCondition(getProperty(i) - mOffset)) { + // start and end base on current position, + // so we need to calculate the adapter position + int adapterPosition = i; + if (i >= itemCount) { + adapterPosition %= itemCount; + } else if (i < 0) { + int delta = (-adapterPosition) % itemCount; + if (delta == 0) delta = itemCount; + adapterPosition = itemCount - delta; + } + final View scrap = recycler.getViewForPosition(adapterPosition); + measureChildWithMargins(scrap, 0, 0); + resetViewProperty(scrap); + // we need i to calculate the real offset of current view + final float targetOffset = getProperty(i) - mOffset; + layoutScrap(scrap, targetOffset); + final float orderWeight = mEnableBringCenterToFront ? + setViewElevation(scrap, targetOffset) : adapterPosition; + if (orderWeight > lastOrderWeight) { + addView(scrap); + } else { + addView(scrap, 0); + } + if (i == currentPos) currentFocusView = scrap; + lastOrderWeight = orderWeight; + positionCache.put(i, scrap); + } + } + + currentFocusView.requestFocus(); + } + + private boolean useMaxVisibleCount() { + return mMaxVisibleItemCount != DETERMINE_BY_MAX_AND_MIN; + } + + private boolean removeCondition(float targetOffset) { + return targetOffset > maxRemoveOffset() || targetOffset < minRemoveOffset(); + } + + private void resetViewProperty(View v) { + v.setRotation(0); + v.setRotationY(0); + v.setRotationX(0); + v.setScaleX(1f); + v.setScaleY(1f); + v.setAlpha(1f); + } + + /* package */ float getMaxOffset() { + return !mShouldReverseLayout ? (getItemCount() - 1) * mInterval : 0; + } + + /* package */ float getMinOffset() { + return !mShouldReverseLayout ? 0 : -(getItemCount() - 1) * mInterval; + } + + private void layoutScrap(View scrap, float targetOffset) { + final int left = calItemLeft(scrap, targetOffset); + final int top = calItemTop(scrap, targetOffset); + if (mOrientation == VERTICAL) { + layoutDecorated(scrap, mSpaceInOther + left, mSpaceMain + top, + mSpaceInOther + left + mDecoratedMeasurementInOther, mSpaceMain + top + mDecoratedMeasurement); + } else { + layoutDecorated(scrap, mSpaceMain + left, mSpaceInOther + top, + mSpaceMain + left + mDecoratedMeasurement, mSpaceInOther + top + mDecoratedMeasurementInOther); + } + setItemViewProperty(scrap, targetOffset); + } + + protected int calItemLeft(View itemView, float targetOffset) { + return mOrientation == VERTICAL ? 0 : (int) targetOffset; + } + + protected int calItemTop(View itemView, float targetOffset) { + return mOrientation == VERTICAL ? (int) targetOffset : 0; + } + + /** + * when the target offset reach this, + * the view will be removed and recycled in {@link #layoutItems(RecyclerView.Recycler)} + */ + protected float maxRemoveOffset() { + return mOrientationHelper.getTotalSpace() - mSpaceMain; + } + + /** + * when the target offset reach this, + * the view will be removed and recycled in {@link #layoutItems(RecyclerView.Recycler)} + */ + protected float minRemoveOffset() { + return -mDecoratedMeasurement - mOrientationHelper.getStartAfterPadding() - mSpaceMain; + } + + protected float getDistanceRatio() { + return 1f; + } + + public int getCurrentPosition() { + if (getItemCount() == 0) return 0; + + int position = getCurrentPositionOffset(); + if (!mInfinite) return Math.abs(position); + + position = !mShouldReverseLayout ? + //take care of position = getItemCount() + (position >= 0 ? + position % getItemCount() : + getItemCount() + position % getItemCount()) : + (position > 0 ? + getItemCount() - position % getItemCount() : + -position % getItemCount()); + return position == getItemCount() ? 0 : position; + } + + @Override + public View findViewByPosition(int position) { + final int itemCount = getItemCount(); + if (itemCount == 0) return null; + for (int i = 0; i < positionCache.size(); i++) { + final int key = positionCache.keyAt(i); + if (key >= 0) { + if (position == key % itemCount) return positionCache.valueAt(i); + } else { + int delta = key % itemCount; + if (delta == 0) delta = -itemCount; + if (itemCount + delta == position) return positionCache.valueAt(i); + } + } + return null; + } + + public int getLayoutPositionOfView(View v) { + for (int i = 0; i < positionCache.size(); i++) { + int key = positionCache.keyAt(i); + View value = positionCache.get(key); + if (value == v) return key; + } + return -1; + } + + /* package */ int getCurrentPositionOffset() { + if (mInterval == 0) return 0; + return Math.round(mOffset / mInterval); + } + + /** + * Sometimes we need to get the right offset of matching adapter position + * cause when {@link #mInfinite} is set true, there will be no limitation of {@link #mOffset} + */ + private float getOffsetOfRightAdapterPosition() { + if (mShouldReverseLayout) + return mInfinite ? + (mOffset <= 0 ? + (mOffset % (mInterval * getItemCount())) : + (getItemCount() * -mInterval + mOffset % (mInterval * getItemCount()))) : + mOffset; + else + return mInfinite ? + (mOffset >= 0 ? + (mOffset % (mInterval * getItemCount())) : + (getItemCount() * mInterval + mOffset % (mInterval * getItemCount()))) : + mOffset; + } + + /** + * used by {@link CenterSnapHelper} to center the current view + * + * @return the dy between center and current position + */ + public int getOffsetToCenter() { + if (mInfinite) + return (int) ((getCurrentPositionOffset() * mInterval - mOffset) * getDistanceRatio()); + return (int) ((getCurrentPosition() * + (!mShouldReverseLayout ? mInterval : -mInterval) - mOffset) * getDistanceRatio()); + } + + public int getOffsetToPosition(int position) { + if (mInfinite) + return (int) (((getCurrentPositionOffset() + + (!mShouldReverseLayout ? position - getCurrentPositionOffset() : -getCurrentPositionOffset() - position)) * + mInterval - mOffset) * getDistanceRatio()); + return (int) ((position * + (!mShouldReverseLayout ? mInterval : -mInterval) - mOffset) * getDistanceRatio()); + } + + public void setOnPageChangeListener(OnPageChangeListener onPageChangeListener) { + this.onPageChangeListener = onPageChangeListener; + } + + public boolean getInfinite() { + return mInfinite; + } + + public void setInfinite(boolean enable) { + assertNotInLayoutOrScroll(null); + if (enable == mInfinite) { + return; + } + mInfinite = enable; + requestLayout(); + } + + public int getDistanceToBottom() { + return mDistanceToBottom == INVALID_SIZE ? + (mOrientationHelper.getTotalSpaceInOther() - mDecoratedMeasurementInOther) / 2 : mDistanceToBottom; + } + + public void setDistanceToBottom(int mDistanceToBottom) { + assertNotInLayoutOrScroll(null); + if (this.mDistanceToBottom == mDistanceToBottom) return; + this.mDistanceToBottom = mDistanceToBottom; + removeAllViews(); + } + + public boolean getEnableBringCenterToFront() { + return mEnableBringCenterToFront; + } + + public void setEnableBringCenterToFront(boolean bringCenterToTop) { + assertNotInLayoutOrScroll(null); + if (mEnableBringCenterToFront == bringCenterToTop) { + return; + } + this.mEnableBringCenterToFront = bringCenterToTop; + requestLayout(); + } + + /** + * Returns the current state of the smooth scrollbar feature. It is enabled by default. + * + * @return True if smooth scrollbar is enabled, false otherwise. + * @see #setSmoothScrollbarEnabled(boolean) + */ + public boolean getSmoothScrollbarEnabled() { + return mSmoothScrollbarEnabled; + } + + /** + * When smooth scrollbar is enabled, the position and size of the scrollbar thumb is computed + * based on the number of visible pixels in the visible items. This however assumes that all + * list items have similar or equal widths or heights (depending on list orientation). + * If you use a list in which items have different dimensions, the scrollbar will change + * appearance as the user scrolls through the list. To avoid this issue, you need to disable + * this property. + *

+ * When smooth scrollbar is disabled, the position and size of the scrollbar thumb is based + * solely on the number of items in the adapter and the position of the visible items inside + * the adapter. This provides a stable scrollbar as the user navigates through a list of items + * with varying widths / heights. + * + * @param enabled Whether or not to enable smooth scrollbar. + * @see #setSmoothScrollbarEnabled(boolean) + */ + public void setSmoothScrollbarEnabled(boolean enabled) { + mSmoothScrollbarEnabled = enabled; + } + + public interface OnPageChangeListener { + void onPageSelected(int position); + + void onPageScrollStateChanged(int state); + } + + private static class SavedState implements Parcelable { + public static final Creator CREATOR + = new Creator() { + @Override + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + int position; + float offset; + boolean isReverseLayout; + + SavedState() { + + } + + SavedState(Parcel in) { + position = in.readInt(); + offset = in.readFloat(); + isReverseLayout = in.readInt() == 1; + } + + public SavedState(SavedState other) { + position = other.position; + offset = other.offset; + isReverseLayout = other.isReverseLayout; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(position); + dest.writeFloat(offset); + dest.writeInt(isReverseLayout ? 1 : 0); + } + } +} diff --git a/library-common/src/main/res/drawable-anydpi/ic_check_box_checked.xml b/library-common/src/main/res/drawable-anydpi/ic_check_box_checked.xml new file mode 100644 index 0000000..f2e1d31 --- /dev/null +++ b/library-common/src/main/res/drawable-anydpi/ic_check_box_checked.xml @@ -0,0 +1,11 @@ + + + diff --git a/library-common/src/main/res/drawable-anydpi/ic_check_box_normal.xml b/library-common/src/main/res/drawable-anydpi/ic_check_box_normal.xml new file mode 100644 index 0000000..57d07d4 --- /dev/null +++ b/library-common/src/main/res/drawable-anydpi/ic_check_box_normal.xml @@ -0,0 +1,11 @@ + + + diff --git a/library-common/src/main/res/drawable-hdpi/ic_add.png b/library-common/src/main/res/drawable-hdpi/ic_add.png new file mode 100644 index 0000000..40768c0 Binary files /dev/null and b/library-common/src/main/res/drawable-hdpi/ic_add.png differ diff --git a/library-common/src/main/res/drawable-hdpi/ic_back.png b/library-common/src/main/res/drawable-hdpi/ic_back.png new file mode 100644 index 0000000..b677fc1 Binary files /dev/null and b/library-common/src/main/res/drawable-hdpi/ic_back.png differ diff --git a/library-common/src/main/res/drawable-hdpi/ic_check_box_checked.png b/library-common/src/main/res/drawable-hdpi/ic_check_box_checked.png new file mode 100644 index 0000000..9c9770f Binary files /dev/null and b/library-common/src/main/res/drawable-hdpi/ic_check_box_checked.png differ diff --git a/library-common/src/main/res/drawable-hdpi/ic_check_box_normal.png b/library-common/src/main/res/drawable-hdpi/ic_check_box_normal.png new file mode 100644 index 0000000..cb5ca01 Binary files /dev/null and b/library-common/src/main/res/drawable-hdpi/ic_check_box_normal.png differ diff --git a/library-common/src/main/res/drawable-hdpi/ic_pic_delete.png b/library-common/src/main/res/drawable-hdpi/ic_pic_delete.png new file mode 100644 index 0000000..47b9aca Binary files /dev/null and b/library-common/src/main/res/drawable-hdpi/ic_pic_delete.png differ diff --git a/library-common/src/main/res/drawable-hdpi/ic_record_delete.png b/library-common/src/main/res/drawable-hdpi/ic_record_delete.png new file mode 100644 index 0000000..47b9aca Binary files /dev/null and b/library-common/src/main/res/drawable-hdpi/ic_record_delete.png differ diff --git a/library-common/src/main/res/drawable-hdpi/ic_right.png b/library-common/src/main/res/drawable-hdpi/ic_right.png new file mode 100644 index 0000000..8a5244b Binary files /dev/null and b/library-common/src/main/res/drawable-hdpi/ic_right.png differ diff --git a/library-common/src/main/res/drawable-xhdpi/ic_add.png b/library-common/src/main/res/drawable-xhdpi/ic_add.png new file mode 100644 index 0000000..b9b15a3 Binary files /dev/null and b/library-common/src/main/res/drawable-xhdpi/ic_add.png differ diff --git a/library-common/src/main/res/drawable-xhdpi/ic_back.png b/library-common/src/main/res/drawable-xhdpi/ic_back.png new file mode 100644 index 0000000..9980628 Binary files /dev/null and b/library-common/src/main/res/drawable-xhdpi/ic_back.png differ diff --git a/library-common/src/main/res/drawable-xhdpi/ic_check_box_checked.png b/library-common/src/main/res/drawable-xhdpi/ic_check_box_checked.png new file mode 100644 index 0000000..2d6a04b Binary files /dev/null and b/library-common/src/main/res/drawable-xhdpi/ic_check_box_checked.png differ diff --git a/library-common/src/main/res/drawable-xhdpi/ic_check_box_normal.png b/library-common/src/main/res/drawable-xhdpi/ic_check_box_normal.png new file mode 100644 index 0000000..81d9f74 Binary files /dev/null and b/library-common/src/main/res/drawable-xhdpi/ic_check_box_normal.png differ diff --git a/library-common/src/main/res/drawable-xhdpi/ic_pic_delete.png b/library-common/src/main/res/drawable-xhdpi/ic_pic_delete.png new file mode 100644 index 0000000..0e560d0 Binary files /dev/null and b/library-common/src/main/res/drawable-xhdpi/ic_pic_delete.png differ diff --git a/library-common/src/main/res/drawable-xhdpi/ic_record_delete.png b/library-common/src/main/res/drawable-xhdpi/ic_record_delete.png new file mode 100644 index 0000000..0e560d0 Binary files /dev/null and b/library-common/src/main/res/drawable-xhdpi/ic_record_delete.png differ diff --git a/library-common/src/main/res/drawable-xhdpi/ic_right.png b/library-common/src/main/res/drawable-xhdpi/ic_right.png new file mode 100644 index 0000000..fb61a0f Binary files /dev/null and b/library-common/src/main/res/drawable-xhdpi/ic_right.png differ diff --git a/library-common/src/main/res/drawable-xxhdpi/ic_add.png b/library-common/src/main/res/drawable-xxhdpi/ic_add.png new file mode 100644 index 0000000..f7eb245 Binary files /dev/null and b/library-common/src/main/res/drawable-xxhdpi/ic_add.png differ diff --git a/library-common/src/main/res/drawable-xxhdpi/ic_back.png b/library-common/src/main/res/drawable-xxhdpi/ic_back.png new file mode 100644 index 0000000..e646cf2 Binary files /dev/null and b/library-common/src/main/res/drawable-xxhdpi/ic_back.png differ diff --git a/library-common/src/main/res/drawable-xxhdpi/ic_check_box_checked.png b/library-common/src/main/res/drawable-xxhdpi/ic_check_box_checked.png new file mode 100644 index 0000000..368a961 Binary files /dev/null and b/library-common/src/main/res/drawable-xxhdpi/ic_check_box_checked.png differ diff --git a/library-common/src/main/res/drawable-xxhdpi/ic_check_box_normal.png b/library-common/src/main/res/drawable-xxhdpi/ic_check_box_normal.png new file mode 100644 index 0000000..0764b18 Binary files /dev/null and b/library-common/src/main/res/drawable-xxhdpi/ic_check_box_normal.png differ diff --git a/library-common/src/main/res/drawable-xxhdpi/ic_pic_delete.png b/library-common/src/main/res/drawable-xxhdpi/ic_pic_delete.png new file mode 100644 index 0000000..13fb409 Binary files /dev/null and b/library-common/src/main/res/drawable-xxhdpi/ic_pic_delete.png differ diff --git a/library-common/src/main/res/drawable-xxhdpi/ic_record_delete.png b/library-common/src/main/res/drawable-xxhdpi/ic_record_delete.png new file mode 100644 index 0000000..13fb409 Binary files /dev/null and b/library-common/src/main/res/drawable-xxhdpi/ic_record_delete.png differ diff --git a/library-common/src/main/res/drawable-xxhdpi/ic_right.png b/library-common/src/main/res/drawable-xxhdpi/ic_right.png new file mode 100644 index 0000000..45fe9b3 Binary files /dev/null and b/library-common/src/main/res/drawable-xxhdpi/ic_right.png differ diff --git a/library-common/src/main/res/drawable/mike.png b/library-common/src/main/res/drawable/mike.png new file mode 100644 index 0000000..30b74af Binary files /dev/null and b/library-common/src/main/res/drawable/mike.png differ diff --git a/library-common/src/main/res/layout/common_title_view.xml b/library-common/src/main/res/layout/common_title_view.xml new file mode 100644 index 0000000..2d87323 --- /dev/null +++ b/library-common/src/main/res/layout/common_title_view.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library-common/src/main/res/layout/item_grid_pic_add.xml b/library-common/src/main/res/layout/item_grid_pic_add.xml new file mode 100644 index 0000000..0d12457 --- /dev/null +++ b/library-common/src/main/res/layout/item_grid_pic_add.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/library-common/src/main/res/layout/item_grid_pic_show.xml b/library-common/src/main/res/layout/item_grid_pic_show.xml new file mode 100644 index 0000000..ff7f15f --- /dev/null +++ b/library-common/src/main/res/layout/item_grid_pic_show.xml @@ -0,0 +1,31 @@ + + + + + + + + + diff --git a/library-common/src/main/res/values/attrs.xml b/library-common/src/main/res/values/attrs.xml new file mode 100644 index 0000000..4f898bb --- /dev/null +++ b/library-common/src/main/res/values/attrs.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library-common/src/main/res/values/colors.xml b/library-common/src/main/res/values/colors.xml new file mode 100644 index 0000000..e987809 --- /dev/null +++ b/library-common/src/main/res/values/colors.xml @@ -0,0 +1,34 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + #FF0000 + + #FF5151 + #FFAA60 + + #666666 + #333333 + #999999 + #F5F5F5 + #E9E9E9 + + #0786F0 + #3BA6FF + #0786F0 + #FFAA61 + + #2E69FF + #FF973B + + #333333 + #F5F5F5 + #666666 + #999999 + + \ No newline at end of file diff --git a/library-common/src/main/res/values/dimens.xml b/library-common/src/main/res/values/dimens.xml new file mode 100644 index 0000000..119ad45 --- /dev/null +++ b/library-common/src/main/res/values/dimens.xml @@ -0,0 +1,52 @@ + + 0.5dp + 1dp + 2dp + 3dp + 4dp + 8dp + 5dp + 6dp + 7dp + 9dp + 10dp + 11dp + 12dp + 13dp + 14dp + 15dp + 16dp + 17dp + 18dp + 19dp + 20dp + 21dp + 22dp + 23dp + 24dp + 26dp + 27dp + 28dp + 30dp + 29dp + 40dp + 42dp + 46dp + 60dp + 65dp + 70dp + 72dp + 74dp + 79dp + 80dp + 90dp + 84dp + 100dp + 115dp + 152dp + 160dp + 16dp + 48dp + 15dp + 185dp + \ No newline at end of file diff --git a/library-common/src/main/res/values/strings.xml b/library-common/src/main/res/values/strings.xml new file mode 100644 index 0000000..5b8533e --- /dev/null +++ b/library-common/src/main/res/values/strings.xml @@ -0,0 +1,8 @@ + + + 编辑 + 保存 + 部分权限未授权,请手动授予,若不授予权限,则无法使用app + 设置 + 取消 + \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 1db5fda..b73dbd8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,3 +3,4 @@ include ':app' //, ':libuvccamera-release' include ':library-push' include ':library-ijkplayer' include ':library-serialPort' +include ':library-common'