├── .gitattributes ├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── gradle.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── lanyixin │ │ └── myapplication │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── lanyixin │ │ │ └── myapplication │ │ │ ├── MainActivity.kt │ │ │ ├── MyApplication.kt │ │ │ ├── TxtActivity.java │ │ │ ├── api │ │ │ ├── ApiService.kt │ │ │ ├── ResultBase.java │ │ │ └── RetrofitManager.kt │ │ │ ├── contract │ │ │ └── DemoContract.kt │ │ │ ├── format │ │ │ ├── CFUtils.java │ │ │ ├── CacheHelper.java │ │ │ ├── CacheManager.java │ │ │ ├── PriceUtils.java │ │ │ └── RateBean.java │ │ │ ├── model │ │ │ └── DemoModel.kt │ │ │ └── presenter │ │ │ └── DemoPresenter.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ └── activity_txt.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── raw │ │ ├── default_coin.json │ │ ├── default_coin_en.json │ │ ├── default_coin_tw.json │ │ ├── hexie │ │ ├── huilv.json │ │ ├── huilv_count.json │ │ ├── img.json │ │ ├── plat.json │ │ ├── price.json │ │ ├── price_en.json │ │ ├── price_hk.json │ │ └── price_tw.json │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── example │ └── lanyixin │ └── myapplication │ └── ExampleUnitTest.kt ├── build.gradle ├── common_lib ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── kotlinmvp │ │ ├── Extensions.kt │ │ ├── base │ │ ├── BaseActivity.kt │ │ ├── BaseFragment.kt │ │ ├── BaseFragmentAdapter.kt │ │ ├── BasePresenter.kt │ │ ├── IBaseView.kt │ │ └── IPresenter.kt │ │ ├── glide │ │ ├── CustomAppGlideModule.kt │ │ ├── CustomBaseGlideUrlLoader.kt │ │ └── GlideRoundTransform.kt │ │ ├── net │ │ ├── BaseResponse.kt │ │ └── exception │ │ │ ├── ApiException.kt │ │ │ ├── ErrorStatus.kt │ │ │ └── ExceptionHandle.kt │ │ ├── rx │ │ └── scheduler │ │ │ ├── BaseScheduler.kt │ │ │ ├── ComputationMainScheduler.kt │ │ │ ├── IoMainScheduler.kt │ │ │ ├── NewThreadMainScheduler.kt │ │ │ ├── SchedulerUtils.kt │ │ │ ├── SingleMainScheduler.kt │ │ │ └── TrampolineMainScheduler.kt │ │ ├── utils │ │ ├── AppUtils.kt │ │ ├── CleanLeakUtils.kt │ │ ├── DisplayManager.kt │ │ ├── GsonUtils.java │ │ ├── NetworkUtil.kt │ │ └── Preference.kt │ │ └── view │ │ ├── LoadingView.java │ │ ├── ViewAnimUtils.kt │ │ └── recyclerview │ │ ├── MultipleType.kt │ │ ├── ViewHolder.kt │ │ └── adapter │ │ ├── CommonAdapter.kt │ │ ├── OnItemClickListener.kt │ │ └── OnItemLongClickListener.kt │ └── res │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── ids.xml │ ├── strings.xml │ └── styles.xml ├── config.gradle ├── demo.gif ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── multiple-status-view ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── classic │ │ └── common │ │ └── MultipleStatusView.java │ └── res │ ├── layout │ ├── empty_view.xml │ ├── error_view.xml │ ├── loading_view.xml │ └── no_network_view.xml │ └── values │ ├── attrs.xml │ ├── ids.xml │ ├── strings.xml │ └── styles.xml └── settings.gradle /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches/build_file_checksums.ser 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | .DS_Store 9 | /build 10 | /captures 11 | .externalNativeBuild 12 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 18 | 19 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 50 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 本demo纯学习之用 不可作为任何商业用途 2 | # 一套完整的数字货币与法币汇率转换以及货币符号匹配展示规则demo 3 | # KotlinMvp 纯demo 无杂质 的MVP基础框架 4 | 5 | ![](https://github.com/bayshier/MVPKotlin_Commonlib/blob/master/demo.gif) 6 | 7 | #### 1. 法币价格数据展示规则 8 | - x >= 1000 0 0000 无小数 //容错,暂时没有可能发生 9 | - 100 <= x < 1000 0 0000 2位小数 //例:$12345.12 10 | - 0.1<= x < 100 4小数 //例:$0.1234 11 | - 0.0001 <= x < 0.1 6位小数 //例:$0.001234 12 | - 0.00000001 <= x < 0.0001 8位小数 //例:$0.00001234 13 | - x < 0.00000001 展示为0 //容错,暂时没有可能发生 14 | - 没有数据时用"--"表示,有货币符号的数据不要带货币符号 //例:-- 15 | - 小数直接截断不4舍5入,小数点末位为0的不省略 //例:$54321.10 16 | - 小数点全部为0的,隐藏 //例:$54321 17 | 18 |

19 | #### 2. 非法币价格数据展示规则 20 | - x >= 1000 0 0000 无小数 //容错,暂时没有可能发生 21 | - 1000 <= x < 100000000 4位小数 //例:12345.1234 Ht 22 | - 0.0001 <= x < 1000 6位小数 //例: 12.123456 Eth 23 | - 0.000001<= x < 0.0001 8位小数 //例: ฿0.00001234 24 | - 0.0000000001 <= x < 0.000001 10位小数 //例:฿0.0000001234 25 | - x < 0.0000000001 展示为0 //容错,暂时没有可能发生 26 | - 没有数据时用"--"表示,有货币符号的数据不要带货币符号 //例:-- 27 | - 小数直接截断不4舍5入,小数点末位为0的不省略 //例:0.0123400 Eth 28 | - 小数点全部为0的,隐藏 //例:4321 Eth 29 | - 币作为单位时,前面留一个空格 //例:1.2345 Eth 30 | - 币作为单位时,第一位字母大写,后面都是小写 //例: 1.2345 Eth 31 | 32 |

33 | #### 3. 涨跌幅百分比(+/-x%)数据展示规则 34 | - x >= 10000 无小数点 //容错,小机率发生 35 | - 0.01 <= x < 10000 保留2位小数 36 | - x < 0.01 展示为0 37 | - *没有数据时用"--"表示 //例:-- 38 | - *x=0时展示“0.00%”, 不展示"+/-"符号 //例:0.00% 39 | - 小数直接截断不4舍5入,小数点末位为0的不省略 //例:12.10% 40 | - 小数点全部为0的,展示 //例:12.00% 41 | > *在旧版中,返回数据不能辨别x=0与没有数据,如果还是不能解决,按x=0处理,web端展示为"0%",客户端为”0.00%“。 42 | 43 |

44 | #### 4. 基数词头的适配规则 45 | - 万 104 //默认>=105 时适配,法币价格>=106 时才适配 46 | - 亿 108 47 | - 万亿 1012 //国标;台,韩,日标准中称为“兆” 48 | - 支持基数词头适配的字段: 49 | 流通市值 | 流通数量 | 成交额 | 总市值 | 法币价格 50 | - 基数词头适配后,保留2位小数 //例:123.12万 51 | - 小数直接截断不4舍5入,小数点末位为0的不省略 //例:123.10万 52 | - 小数点全部为0的,隐藏 //例:123万 53 | 54 |

55 | #### 5. 基数的三位逗号分隔符的适配规则 56 | - *在简体中文语境下,没有适配基数词头的数字不采用三位逗号分隔。//例:54321; 5,432.12万 57 | - 在简体中文语境下,非行情数据展示的数字不采用逗号分隔。//例:阅读数 54321;关注数 5432.12万 58 | > *行情数据在其它非简体中文语境中,采用三位逗号分隔机制 59 | 60 |

61 | #### 6. 陆,港,台,日,韩,英文的基数词头的规范 62 | NUM | CN | HK | TW | JP | KR | EN 63 | ---|---|---|---|---|---|--- 64 | 104 |1万 | 1萬 | 1萬 | 1万 | 1만 | 10K,1K=103 65 | 108 |1亿 | 1億 | 1億 | 1億 | 1억 | 100M,1M=106 66 | 1012 |1万亿 | 1萬億 | 1兆 | 1兆 | 1조 | 1000B,1B=109 67 | 68 |

69 | #### 7. 货币选顶中的货币符号 70 | 代码 | 货币 | 符号 | 范例 71 | ---|---|---|--- 72 | xn5 73 | > *除ETH,BTC外,其它适用法币展示规则 74 | 75 |

76 | #### 8. 交易对中的其它法币符号 77 | - ##### 约等于法币价值的稳定代币, 可以使用法币符号替代,适用法币数据展示规则 78 | 代码 | 货币 | 对应法币 | 可替代符号 79 | ---|---|---|--- 80 | BITCNY | 比特元 | 人民币 | ¥ 81 | CNYT | 未知 | 人民币 | ¥ 82 | CNH | 离岸人民币 | 人民币 | ¥ 83 | BITEUR |未知 | 欧元 | € 84 | BITUSD | 未知 | 美元 | $ 85 | CK.USD | 未知 | 美元 | $ 86 | CKUSD | 未知 | 美元 | $ 87 | USDT | 泰达币 | 美元 | $ 88 | GUSD | 双子星美元 | 美元 | $ 89 | TUSD | 未知 | 美元 | $ 90 | USDC | 未知 | 美元 | $ 91 | PAX | 未知 | 美元 | $ 92 | 93 | - ##### 交易对中的其它法币符号,适用法币数据展示规则 94 | 代码 | 货币 | 符号 | 范例 95 | ---|---|---|--- 96 | CLP | 智利比索 | CLP | CLP100 97 | COP | 哥伦比亚比索 | Col$ | Col$100 98 | ILS | 以色列新谢克尔 | ₪ | ₪100 99 | ISK | 冰岛克郎 | kr | kr100 100 | MYR | 马元 | RM | RM100 101 | NGN | 尼日利亚奈拉 | ₦ | ₦100 102 | NZD | 新西兰元 | NZ$ | NZ$100 103 | PEN | 秘鲁新索尔 | S/. | S/.100 104 | PHP | 菲律宾比索 | ₱ | ₱100 105 | PLN | 波兰兹罗提 | zł | zł100 106 | SGD | 新加坡元 | SG$ | SG$100 107 | THB | 泰铢* | ฿ | ฿100 108 | TRY | 土耳其里拉 | ₺ | ₺100 109 | UAH | 乌克兰格里夫纳 | ₴ | ₴100 110 | VND | 越南盾 | ₫ | ₫100 111 | ZAR | 南非兰特 | R | R100 112 | 113 | > *比特币货币符号฿是借用泰铢法币符号,会与泰铢符号产生岐义,泰铁符号使用"T฿"来区分。 114 | 115 | - 主价格为可切换的价格 (当前价格切换中的货币见“条目7”) 116 | - 副价格为原价 (全球指数默认是USD,交易对为平台价) 117 | - 当主,副价格为同一货币单位时,副价格展示为USD; 当同为USD时,副价格展示为CNY。(这条规则包含且不限于使用货币符号为单位的稳定代币) 118 | 119 |

120 | #### 10. 交易所数据请求统一的国家代码 121 | 代码 | 国家 | EN | TW | HK | JP 122 | ---|---|---|---|---|--- 123 | cn | 中国 | China | 中國 | 中國 | 中国 124 | uk | 英国 | United Kingdom | 英國 | 英國 | イギリス 125 | us | 美国 | United States | 美國 | 美國 | アメリカ 126 | jp | 日本 | Japan | 日本 | 日本 | 日本 127 | kr | 韩国 | Korea | 韓國 | 韓國 | 韓国 128 | hk | 中国香港 | China Hong Kong | 中國香港 | 中國香港 | 香港、中国 129 | pl | 波兰 | Poland | 波蘭 | 波蘭 | ポーランド 130 | ca | 加拿大 | Canada | 加拿大 | 加拿大 | カナダ 131 | au | 澳洲 | Australia | 澳洲 | 澳洲 | オーストラリア 132 | th | 泰国 | Thailand | 泰國 | 泰國 | タイ 133 | in | 印度 | India | 印度 | 印度 | インド 134 | de | 德国 | Germany | 德國 | 德國 | ドイツ 135 | ua | 乌克兰 | Ukraine | 烏克蘭 | 烏克蘭 | ウクライナ 136 | nz | 新西兰 | new Zealand | 紐西蘭 | 新西蘭 | ニュージーランド 137 | ru | 俄罗斯 | Russia | 俄羅斯 | 俄羅斯 | ロシア 138 | dk | 丹麦 | Denmark | 丹麥 | 丹麥 | デンマーク 139 | sc | 塞舌尔 | Seychelles | 塞席爾 | 塞舌爾 | セイシェル 140 | nl | 荷兰 | Netherlands | 荷蘭 | 荷蘭 | オランダ 141 | es | 西班牙 | Spain | 西班牙 | 西班牙 | スペイン 142 | mv | 马耳他 | Malta | 馬爾他 | 馬爾他 | マルタ 143 | tz | 坦桑尼亚 | Tanzania | 坦尚尼亞 | 坦桑尼亞 | タンザニア 144 | mx | 墨西哥 | Mexico | 墨西哥 | 墨西哥 | メキシコ 145 | ch | 瑞士 | Switzerland | 瑞士 | 瑞士 | スイス 146 | br | 巴西 | Brazil | 巴西 | 巴西 | ブラジル 147 | tr | 土耳其 | Turkey | 土耳其 | 土耳其 | トルコ 148 | il | 以色列 | Israel | 以色列 | 以色列 | イスラエル 149 | tw | 中国台湾 | China Taiwan | 台灣 | 中國台灣 | 台湾、中国 150 | hr | 克罗地亚 | Croatia | 克羅埃西亞 | 克羅地亞 | クロアチア 151 | sg | 新加坡 | Singapore | 新加坡 | 新加坡 | シンガポール 152 | cl | 智利 | Chile | 智利 | 智利 | チリ 153 | ph | 菲律宾 | Philippines | 菲律賓 | 菲律賓 | フィリピン 154 | za | 南非 | South Africa | 南非 | 南非 | 南アフリカ 155 | mn | 蒙古国 | Mongolia | 蒙古 | 蒙古 | モンゴル 156 | ee | 爱沙尼亚 | Estonia | 愛沙尼亞 | 愛沙尼亞 | エストニア 157 | jo | 约旦 | Jordan | 約旦 | 約旦 | ヨルダン 158 | it | 意大利 | Italy | 義大利 | 意大利 | イタリア 159 | vn | 越南 | Vietnam | 越南 | 越南 | ベトナム 160 | ae | 阿联酋 | United Arab Emirates | 阿聯 | 阿聯酋 | アラブ首長国連邦 161 | ky | 开曼群岛 | Cayman Islands | 開曼群島 | 開曼群島 | ケイマン諸島 162 | my | 马来西亚 | Malaysia | 馬來西亞 | 馬來西亞 | マレーシア 163 | ws | 萨摩亚 | Samoa | 薩摩亞 | 薩摩亞 | サモア 164 | unknown | 未知 | unknown | 未知 | 未知 | 不明 165 | > 地域选项的命名一律不能称为“国家”,必须展示为“国家/地区”;
166 | 台湾地区命名必须使用“中国台湾”(台湾正体译文除外);
167 | 168 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 28 9 | defaultConfig { 10 | applicationId "com.example.lanyixin.myapplication" 11 | minSdkVersion 21 12 | targetSdkVersion 28 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | multiDexEnabled true 17 | } 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | compileOptions { 25 | sourceCompatibility JavaVersion.VERSION_1_8 26 | targetCompatibility JavaVersion.VERSION_1_8 27 | } 28 | } 29 | 30 | dependencies { 31 | api fileTree(dir: 'libs', include: ['*.jar']) 32 | api 'com.android.support:multidex:1.0.1' 33 | api"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 34 | api 'com.android.support:appcompat-v7:28.0.0' 35 | api 'com.android.support.constraint:constraint-layout:1.1.3' 36 | testImplementation 'junit:junit:4.12' 37 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 38 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 39 | // spanbuilder 40 | api 'com.zrq:spanbuilder:1.0.5' 41 | api ('com.zzhoujay.richtext:richtext:3.0.7') 42 | api 'com.android.support:design:27.1.1' 43 | api project(':common_lib') 44 | } 45 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/lanyixin/myapplication/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.lanyixin.myapplication 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("com.example.lanyixin.myapplication", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/lanyixin/myapplication/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.lanyixin.myapplication 2 | 3 | import android.content.Intent 4 | import android.os.Build 5 | import android.text.Editable 6 | import android.text.TextWatcher 7 | import android.util.Log 8 | import com.example.lanyixin.myapplication.api.ResultBase 9 | import com.example.lanyixin.myapplication.format.CFUtils 10 | import com.kotlinmvp.mvp.contract.DemoContract 11 | import com.kotlinmvp.base.BaseActivity 12 | import com.kotlinmvp.mvp.presenter.DemoPresenter 13 | import kotlinx.android.synthetic.main.activity_main.* 14 | import java.lang.StringBuilder 15 | import java.util.* 16 | import java.util.regex.Pattern 17 | 18 | class MainActivity : BaseActivity(), DemoContract.View { 19 | 20 | override fun initData() { 21 | mPresenter.requestHotWordData(); 22 | } 23 | 24 | override fun initView() { 25 | 26 | title = "一套完整的数字货币与法币汇率转换以及货币符号匹配展示规则demo" 27 | 28 | val res = this.getResources() 29 | val config = res.getConfiguration() 30 | var locale = res.getConfiguration().locale 31 | locale = Locale.SIMPLIFIED_CHINESE 32 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 33 | config.setLocale(locale) 34 | } else { 35 | config.locale = locale 36 | } 37 | this 38 | .getResources() 39 | .updateConfiguration( 40 | config, 41 | this.getResources().getDisplayMetrics() 42 | ) 43 | 44 | switch1.setOnCheckedChangeListener { buttonView, isChecked -> 45 | val res = this.getResources() 46 | val config = res.getConfiguration() 47 | var locale = res.getConfiguration().locale 48 | 49 | if (isChecked) { 50 | switch1.text = Locale.ENGLISH.language 51 | locale = Locale.ENGLISH 52 | } else { 53 | switch1.text = Locale.SIMPLIFIED_CHINESE.language 54 | locale = Locale.SIMPLIFIED_CHINESE 55 | } 56 | 57 | Log.i("df", locale.getCountry() + " " + locale.getLanguage()) 58 | 59 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 60 | config.setLocale(locale) 61 | } else { 62 | config.locale = locale 63 | } 64 | 65 | this 66 | .getResources() 67 | .updateConfiguration( 68 | config, 69 | this.getResources().getDisplayMetrics() 70 | ) 71 | } 72 | 73 | floatingActionButton.setOnClickListener { 74 | val intent = Intent() 75 | intent.setClass(this@MainActivity, TxtActivity::class.java!!) 76 | startActivity(intent) 77 | } 78 | 79 | editText.addTextChangedListener(object : TextWatcher { 80 | override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { 81 | 82 | } 83 | 84 | override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { 85 | 86 | } 87 | 88 | override fun afterTextChanged(s: Editable) { 89 | val str = s.toString() 90 | 91 | if (str.isEmpty()) return 92 | if (str.length > 30) return 93 | if (!isNumeric(str)) return 94 | 95 | val vol = java.lang.Double.valueOf(str) 96 | num.text = str 97 | 98 | val builder = CFUtils.Builder(application) 99 | 100 | //法币 101 | fb1.text = builder.setnumber(vol) 102 | .setlega(true) 103 | .setword(true) 104 | .setformat(true) 105 | .build() 106 | .cfRules(application) 107 | fb2.text = builder.setnumber(vol) 108 | .setlega(true) 109 | .setword(true) 110 | .setformat(false) 111 | .build() 112 | .cfRules(application) 113 | fb3.text = builder.setnumber(vol) 114 | .setlega(true) 115 | .setword(false) 116 | .setformat(true) 117 | .build() 118 | .cfRules(application) 119 | fb4.text = builder.setnumber(vol) 120 | .setlega(true) 121 | .setword(false) 122 | .setformat(false) 123 | .build() 124 | .cfRules(application) 125 | 126 | //虚拟货币 127 | xn1.text = builder.setnumber(vol) 128 | .setlega(false) 129 | .setword(true) 130 | .setformat(true) 131 | .build() 132 | .cfRules(application) 133 | xn2.text = builder.setnumber(vol) 134 | .setlega(false) 135 | .setword(true) 136 | .setformat(false) 137 | .build() 138 | .cfRules(application) 139 | xn3.text = builder.setnumber(vol) 140 | .setlega(false) 141 | .setword(false) 142 | .setformat(true) 143 | .build() 144 | .cfRules(application) 145 | xn4.text = builder.setnumber(vol) 146 | .setlega(false) 147 | .setword(false) 148 | .setformat(false) 149 | .build() 150 | .cfRules(application) 151 | 152 | xn5.text = CFUtils.percentage(vol * 100) 153 | } 154 | }) 155 | 156 | } 157 | 158 | //判断是否是数字 159 | fun isNumeric(str: String): Boolean { 160 | //Pattern pattern = Pattern.compile("-?[0-9]+.?[0-9]+");//这个有问题,一位的整数不能通过 161 | val pattern = Pattern.compile("^(\\-|\\+)?\\d+(\\.\\d+)?$")//这个是对的 162 | val isNum = pattern.matcher(str) 163 | return isNum.matches() 164 | } 165 | 166 | override fun start() { 167 | } 168 | 169 | override fun showError(errorMsg: String, errorCode: Int) { 170 | } 171 | 172 | override fun setHotWordData(string: ResultBase>) { 173 | 174 | val stringBuffer = StringBuilder() 175 | 176 | for (item in string.data) { 177 | println(item) 178 | stringBuffer.append(item + " ") 179 | } 180 | test.text = stringBuffer.toString() 181 | } 182 | 183 | override fun showLoading() { 184 | } 185 | 186 | override fun dismissLoading() { 187 | } 188 | 189 | override fun layoutId(): Int = R.layout.activity_main 190 | 191 | private val mPresenter by lazy { DemoPresenter() } 192 | 193 | init { 194 | mPresenter.attachView(this) 195 | } 196 | 197 | } 198 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/lanyixin/myapplication/MyApplication.kt: -------------------------------------------------------------------------------- 1 | package com.example.lanyixin.myapplication 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import android.content.Context 6 | import android.os.Bundle 7 | import android.util.Log 8 | import com.facebook.stetho.Stetho 9 | import com.orhanobut.logger.AndroidLogAdapter 10 | import com.orhanobut.logger.Logger 11 | import com.orhanobut.logger.PrettyFormatStrategy 12 | import kotlin.properties.Delegates 13 | 14 | 15 | class MyApplication : Application(){ 16 | 17 | 18 | companion object { 19 | 20 | private val TAG = "MyApplication" 21 | 22 | var context: Context by Delegates.notNull() 23 | private set 24 | 25 | } 26 | 27 | override fun onCreate() { 28 | super.onCreate() 29 | context = applicationContext 30 | initConfig() 31 | registerActivityLifecycleCallbacks(mActivityLifecycleCallbacks) 32 | 33 | //Stetho 34 | Stetho.initialize( 35 | Stetho.newInitializerBuilder(context) 36 | .enableDumpapp(Stetho.defaultDumperPluginsProvider(context)) 37 | .enableWebKitInspector(Stetho.defaultInspectorModulesProvider(context)) 38 | .build() 39 | ) 40 | 41 | } 42 | 43 | /** 44 | * 初始化配置 45 | */ 46 | private fun initConfig() { 47 | 48 | val formatStrategy = PrettyFormatStrategy.newBuilder() 49 | .showThreadInfo(false) // 隐藏线程信息 默认:显示 50 | .methodCount(0) // 决定打印多少行(每一行代表一个方法)默认:2 51 | .methodOffset(7) // (Optional) Hides internal method calls up to offset. Default 5 52 | .tag("hao_zz") // (Optional) Global tag for every log. Default PRETTY_LOGGER 53 | .build() 54 | Logger.addLogAdapter(object : AndroidLogAdapter(formatStrategy) { 55 | override fun isLoggable(priority: Int, tag: String?): Boolean { 56 | return BuildConfig.DEBUG 57 | } 58 | }) 59 | } 60 | 61 | 62 | private val mActivityLifecycleCallbacks = object : Application.ActivityLifecycleCallbacks { 63 | override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { 64 | Log.d(TAG, "onCreated: " + activity.componentName.className) 65 | } 66 | 67 | override fun onActivityStarted(activity: Activity) { 68 | Log.d(TAG, "onStart: " + activity.componentName.className) 69 | } 70 | 71 | override fun onActivityResumed(activity: Activity) { 72 | 73 | } 74 | 75 | override fun onActivityPaused(activity: Activity) { 76 | 77 | } 78 | 79 | override fun onActivityStopped(activity: Activity) { 80 | 81 | } 82 | 83 | override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { 84 | 85 | } 86 | 87 | override fun onActivityDestroyed(activity: Activity) { 88 | Log.d(TAG, "onDestroy: " + activity.componentName.className) 89 | } 90 | } 91 | 92 | 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/lanyixin/myapplication/api/ApiService.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.api 2 | 3 | import com.example.lanyixin.myapplication.api.ResultBase 4 | import retrofit2.http.GET 5 | import java.util.* 6 | import io.reactivex.Observable 7 | 8 | interface ApiService{ 9 | 10 | @GET("api/coin/hotsearch") 11 | fun getHotsearch(): Observable>> 12 | 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/lanyixin/myapplication/api/ResultBase.java: -------------------------------------------------------------------------------- 1 | package com.example.lanyixin.myapplication.api; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * @author easin on 2018/6/22 18:25 7 | */ 8 | 9 | public class ResultBase implements Serializable { 10 | /** 11 | * status : true 12 | * code : 0 13 | * action : add 14 | * msg : 关注成功 15 | * data : 16 | */ 17 | 18 | private String status; 19 | private String code; 20 | private String action; 21 | private String msg; 22 | private T data; 23 | 24 | public String getStatus() { 25 | return status; 26 | } 27 | 28 | public void setStatus(String status) { 29 | this.status = status; 30 | } 31 | 32 | public String getCode() { 33 | return code; 34 | } 35 | 36 | public void setCode(String code) { 37 | this.code = code; 38 | } 39 | 40 | public String getAction() { 41 | return action; 42 | } 43 | 44 | public void setAction(String action) { 45 | this.action = action; 46 | } 47 | 48 | public String getMsg() { 49 | return msg; 50 | } 51 | 52 | public void setMsg(String msg) { 53 | this.msg = msg; 54 | } 55 | 56 | public T getData() { 57 | return data; 58 | } 59 | 60 | public void setData(T data) { 61 | this.data = data; 62 | } 63 | 64 | 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/lanyixin/myapplication/api/RetrofitManager.kt: -------------------------------------------------------------------------------- 1 | package com.example.lanyixin.myapplication.api 2 | 3 | import android.content.Context 4 | import android.support.constraint.solver.Cache 5 | import com.example.lanyixin.myapplication.MyApplication 6 | import com.facebook.stetho.okhttp3.StethoInterceptor 7 | import com.kotlinmvp.api.ApiService 8 | import com.kotlinmvp.utils.AppUtils 9 | import com.kotlinmvp.utils.NetworkUtil 10 | import com.kotlinmvp.utils.Preference 11 | import okhttp3.CacheControl 12 | import okhttp3.Interceptor 13 | import okhttp3.OkHttpClient 14 | import okhttp3.Request 15 | import okhttp3.logging.HttpLoggingInterceptor 16 | import retrofit2.Retrofit 17 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory 18 | import retrofit2.converter.gson.GsonConverterFactory 19 | import java.io.File 20 | import java.util.concurrent.TimeUnit 21 | 22 | object RetrofitManager { 23 | 24 | val service: ApiService by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { 25 | getRetrofit().create(ApiService::class.java) 26 | } 27 | 28 | private var token: String by Preference(MyApplication.context, "token", "") 29 | 30 | /** 31 | * 设置头 32 | */ 33 | private fun addHeaderInterceptor(): Interceptor { 34 | return Interceptor { chain -> 35 | val originalRequest = chain.request() 36 | val requestBuilder = originalRequest.newBuilder() 37 | // Provide your custom header here 38 | .header("token", token) 39 | .method(originalRequest.method(), originalRequest.body()) 40 | val request = requestBuilder.build() 41 | chain.proceed(request) 42 | } 43 | } 44 | 45 | /** 46 | * 设置缓存 47 | */ 48 | private fun addCacheInterceptor(context: Context): Interceptor { 49 | return Interceptor { chain -> 50 | var request = chain.request() 51 | if (!NetworkUtil.isNetworkAvailable(context)) { 52 | request = request.newBuilder() 53 | .cacheControl(CacheControl.FORCE_CACHE) 54 | .build() 55 | } 56 | val response = chain.proceed(request) 57 | if (NetworkUtil.isNetworkAvailable(context)) { 58 | val maxAge = 0 59 | // 有网络时 设置缓存超时时间0个小时 ,意思就是不读取缓存数据,只对get有用,post没有缓冲 60 | response.newBuilder() 61 | .header("Cache-Control", "public, max-age=" + maxAge) 62 | .removeHeader("Retrofit")// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效 63 | .build() 64 | } else { 65 | // 无网络时,设置超时为4周 只对get有用,post没有缓冲 66 | val maxStale = 60 * 60 * 24 * 28 67 | response.newBuilder() 68 | .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale) 69 | .removeHeader("nyn") 70 | .build() 71 | } 72 | response 73 | } 74 | } 75 | 76 | private fun getRetrofit(): Retrofit { 77 | // 获取retrofit的实例 78 | return Retrofit.Builder() 79 | .baseUrl("https://app.fxh.io/") //自己配置 80 | .client(getOkHttpClient(MyApplication.context)) 81 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 82 | .addConverterFactory(GsonConverterFactory.create()) 83 | .build() 84 | } 85 | 86 | private fun getOkHttpClient(context: Context): OkHttpClient { 87 | 88 | //添加一个log拦截器,打印所有的log 89 | val httpLoggingInterceptor = HttpLoggingInterceptor() 90 | //可以设置请求过滤的水平,body,basic,headers 91 | httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY 92 | 93 | //设置 请求的缓存的大小跟位置 94 | // val cacheFile = File(context.cacheDir, "cache") 95 | // val cache = Cache(cacheFile, 1024 * 1024 * 50) //50Mb 缓存的大小 96 | 97 | return OkHttpClient.Builder() 98 | // .addInterceptor(addQueryParameterInterceptor()) //参数添加 99 | .addInterceptor(addHeaderInterceptor()) // token过滤 100 | // .addInterceptor(addCacheInterceptor()) 101 | .addInterceptor(httpLoggingInterceptor) //日志,所有的请求响应度看到 102 | // .cache(cache) //添加缓存 103 | .addNetworkInterceptor(StethoInterceptor())//抓包 104 | .connectTimeout(60L, TimeUnit.SECONDS) 105 | .readTimeout(60L, TimeUnit.SECONDS) 106 | .writeTimeout(60L, TimeUnit.SECONDS) 107 | .build() 108 | } 109 | 110 | 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/lanyixin/myapplication/contract/DemoContract.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.mvp.contract 2 | 3 | import com.example.lanyixin.myapplication.api.ResultBase 4 | import com.kotlinmvp.base.IBaseView 5 | import com.kotlinmvp.base.IPresenter 6 | 7 | 8 | /** 9 | * 契约类 10 | */ 11 | interface DemoContract { 12 | 13 | interface View : IBaseView { 14 | 15 | fun showError(errorMsg: String,errorCode:Int) 16 | 17 | /** 18 | * 获取数据 19 | */ 20 | fun setHotWordData(string: ResultBase>) 21 | } 22 | 23 | 24 | interface Presenter : IPresenter { 25 | /** 26 | * 显示数据 27 | */ 28 | fun requestHotWordData() 29 | 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/lanyixin/myapplication/format/CacheHelper.java: -------------------------------------------------------------------------------- 1 | package com.example.lanyixin.myapplication.format; 2 | 3 | import android.content.Context; 4 | import android.support.v4.util.ArrayMap; 5 | import android.text.TextUtils; 6 | import com.example.lanyixin.myapplication.R; 7 | import com.google.gson.reflect.TypeToken; 8 | import com.kotlinmvp.utils.GsonUtils; 9 | 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | public class CacheHelper { 14 | 15 | static CacheHelper mInstance; 16 | 17 | /** 18 | * Application#getApplicationContext 19 | */ 20 | public static CacheHelper getInstance(Context context) { 21 | if (mInstance == null) { 22 | mInstance = new CacheHelper(context); 23 | } 24 | return mInstance; 25 | } 26 | 27 | public CacheHelper(Context context) { 28 | initHuilv(context); 29 | mLocalIcon = new ArrayMap(); 30 | initLocalIcons(); 31 | } 32 | 33 | /** 34 | * 汇率生成 35 | */ 36 | private Map mHuilvCache = new ArrayMap<>(); 37 | 38 | public void putHuilv(String key, double num) { 39 | this.mHuilvCache.put(key, num); 40 | } 41 | 42 | public double getHuilv(String key) { 43 | Double obj = mHuilvCache.get(key); 44 | return obj != null ? obj.doubleValue() : 0; 45 | } 46 | 47 | // 初始化汇率集合 48 | public void initHuilv(Context context) { 49 | //保存的汇率列表 50 | List rateBeans = CacheManager.get("rate_list"); 51 | if (isEmpty(rateBeans)) { 52 | rateBeans = 53 | GsonUtils.changeGsonToList(PriceUtils.getConfigJson(R.raw.huilv,context), 54 | new TypeToken>() { 55 | }.getType()); 56 | } 57 | for (RateBean rateBean : rateBeans) { 58 | putHuilv(rateBean.getSymbol().toUpperCase(), rateBean.getRate()); 59 | } 60 | } 61 | 62 | public static boolean isEmpty(List paramList) { 63 | if ((paramList == null) || paramList.isEmpty()) { 64 | return true; 65 | } 66 | return false; 67 | } 68 | 69 | // 当前的货币单位 70 | private String mCurrenPriceShowType = "CNY"; 71 | 72 | // 根据货币单位 获取货币符号 73 | public String getUnit(String market) { 74 | if ("BTC".equalsIgnoreCase(market)) { 75 | return "฿"; 76 | } else if ("ETH".equalsIgnoreCase(market)) { 77 | return "ETH"; 78 | } else if ("EUR".equalsIgnoreCase(market) || "BITEUR".equalsIgnoreCase(market)) { 79 | return "€"; 80 | } else if ("CNY".equalsIgnoreCase(market) 81 | || "BITCNY".equalsIgnoreCase(market) 82 | || "QC".equalsIgnoreCase(market) 83 | || "CNH".equalsIgnoreCase(market) 84 | || "CNYT".equalsIgnoreCase(market)) { 85 | return "¥"; 86 | } else if ("KRW".equalsIgnoreCase(market)) { 87 | return "₩"; 88 | } else if ("RUB".equalsIgnoreCase(market)) { 89 | return "\u20BD"; 90 | } else if ("JPY".equalsIgnoreCase(market)) { 91 | return "円"; 92 | } else if ("AUD".equalsIgnoreCase(market)) { 93 | return "A$"; 94 | } else if ("GBP".equalsIgnoreCase(market)) { 95 | return "£"; 96 | } else if ("SGD".equalsIgnoreCase(market)) { 97 | return "S$"; 98 | } else if ("USD".equalsIgnoreCase(market) 99 | || "PAX".equalsIgnoreCase(market) 100 | || "USDT".equalsIgnoreCase(market) 101 | || "GUSD".equalsIgnoreCase(market) 102 | || "TUSD".equalsIgnoreCase(market) 103 | || "USDC".equalsIgnoreCase(market) 104 | || "BITUSD".equalsIgnoreCase(market) 105 | || "CK.USD".equalsIgnoreCase(market) 106 | || "CKUSD".equalsIgnoreCase(market)) { 107 | return "$"; 108 | } else if ("INR".equalsIgnoreCase(market)) { 109 | return "₹"; 110 | } else if ("CHF".equalsIgnoreCase(market)) { 111 | return "₣"; 112 | } else if ("CAD".equalsIgnoreCase(market)) { 113 | return "C$"; 114 | } else if ("IDR".equalsIgnoreCase(market)) { 115 | return "Rp"; 116 | } else if ("BRL".equalsIgnoreCase(market)) { 117 | return "R$"; 118 | } else if ("MXN".equalsIgnoreCase(market)) { 119 | return "MXN"; 120 | } else if ("CNY".equalsIgnoreCase(market) || "CN".equalsIgnoreCase(market)) { 121 | return "¥"; 122 | } else if ("HKD".equalsIgnoreCase(market)) { 123 | return "HK$"; 124 | } else if ("CLP".equalsIgnoreCase(market)) { 125 | return "CLP"; 126 | } else if ("COP".equalsIgnoreCase(market)) { 127 | return "Col$"; 128 | } else if ("ILS".equalsIgnoreCase(market)) { 129 | return "₪"; 130 | } else if ("ISK".equalsIgnoreCase(market)) { 131 | return "kr"; 132 | } else if ("MYR".equalsIgnoreCase(market)) { 133 | return "RM"; 134 | } else if ("NGN".equalsIgnoreCase(market)) { 135 | return "₦"; 136 | } else if ("NZD".equalsIgnoreCase(market)) { 137 | return "NZ$"; 138 | } else if ("PEN".equalsIgnoreCase(market)) { 139 | return "S/."; 140 | } else if ("PHP".equalsIgnoreCase(market)) { 141 | return "₱"; 142 | } else if ("PLN".equalsIgnoreCase(market)) { 143 | return "zł"; 144 | } else if ("THB".equalsIgnoreCase(market)) { 145 | return "T฿"; 146 | } else if ("TRY".equalsIgnoreCase(market)) { 147 | return "₺"; 148 | } else if ("TWD".equalsIgnoreCase(market)) { 149 | return "NT$"; 150 | } else if ("UAH".equalsIgnoreCase(market)) { 151 | return "₴"; 152 | } else if ("VND".equalsIgnoreCase(market)) { 153 | return "₫"; 154 | } else { 155 | return market; 156 | } 157 | } 158 | 159 | /** 160 | * 汇率换算 161 | * 162 | * @param currencyValue 金额 163 | * @param market1 的货币类型 164 | * @param market2 要兑换的货币类型 165 | */ 166 | public double currencyExchange(Context context,double currencyValue, String market1, 167 | String market2) { 168 | 169 | if (market1.equalsIgnoreCase(market2)) { 170 | // 传入的货币==用户设置的货币 171 | return currencyValue; 172 | } else { 173 | // 传入的是美元返回1,否者尝试取得汇率 174 | double rate = market1.equalsIgnoreCase("USD") ? 1 : 175 | CacheHelper.getInstance(context).getHuilv(market1.toUpperCase()); 176 | if (rate == 0) { 177 | return Double.NaN; 178 | } 179 | // 得到以美元为基准的转化后的原始金额数值 180 | double usdValue = currencyValue / rate; 181 | if ("USD".equalsIgnoreCase(market2)) { 182 | return usdValue; 183 | } 184 | // 不是美元 获取要越换的汇率 185 | rate = CacheHelper.getInstance(context).getHuilv(market2.toUpperCase()); 186 | if (rate == 0) { 187 | return Double.NaN; 188 | } 189 | return usdValue * rate; 190 | } 191 | } 192 | 193 | //国家国旗 194 | private Map mLocalIcon; 195 | 196 | private void initLocalIcons() { 197 | mLocalIcon.put("asny", "ic_aishaniya.png"); 198 | mLocalIcon.put("alq", "ic_alq.png"); 199 | mLocalIcon.put("aus", "ic_aus.png"); 200 | mLocalIcon.put("brazil", "ic_brazil.png"); 201 | mLocalIcon.put("pola", "ic_pola.png"); 202 | mLocalIcon.put("danmark", "ic_danmarki.png"); 203 | mLocalIcon.put("de", "ic_de.png"); 204 | mLocalIcon.put("els", "ic_els.png"); 205 | mLocalIcon.put("philippines", "ic_feilvbin.png"); 206 | mLocalIcon.put("kr", "ic_kr.png"); 207 | mLocalIcon.put("holland", "ic_holland.png"); 208 | mLocalIcon.put("can", "ic_can.png"); 209 | mLocalIcon.put("cayman", "ic_cayman.png"); 210 | mLocalIcon.put("kldy", "ic_kldy.png"); 211 | mLocalIcon.put("malta", "ic_malta.png"); 212 | mLocalIcon.put("malaysia", "ic_malaysia.png"); 213 | mLocalIcon.put("us", "ic_us.png"); 214 | mLocalIcon.put("mengu", "ic_mengu.png"); 215 | mLocalIcon.put("mesica", "ic_mesica.png"); 216 | mLocalIcon.put("nanfei", "ic_nanfei.png"); 217 | mLocalIcon.put("jp", "ic_jp.png"); 218 | mLocalIcon.put("switzerland", "ic_switzerland.png"); 219 | mLocalIcon.put("sser", "ic_sser.png"); 220 | mLocalIcon.put("samora", "ic_samora.png"); 221 | mLocalIcon.put("thailand", "ic_thailand.png"); 222 | mLocalIcon.put("tw", "ic_tw.png"); 223 | mLocalIcon.put("tsny", "ic_tsny.png"); 224 | mLocalIcon.put("teq", "ic_teq.png"); 225 | mLocalIcon.put("ic_unknow", "ic_unknow.png"); 226 | mLocalIcon.put("wkl", "ic_wkl.png"); 227 | mLocalIcon.put("hongkong", "ic_hongkong.png"); 228 | mLocalIcon.put("spain", "ic_spain.png"); 229 | mLocalIcon.put("singepo", "ic_singepo.png"); 230 | mLocalIcon.put("newzeland", "ic_newzeland.png"); 231 | mLocalIcon.put("ydl", "ic_ydl.png"); 232 | mLocalIcon.put("india", "ic_india.png"); 233 | mLocalIcon.put("uk", "ic_uk.png"); 234 | mLocalIcon.put("ysl", "ic_yiselie.png"); 235 | mLocalIcon.put("yuedan", "ic_yuedan.png"); 236 | mLocalIcon.put("vietnam", "ic_yuenan.png"); 237 | mLocalIcon.put("zl", "ic_zl.png"); 238 | mLocalIcon.put("cn", "ic_cn.png"); 239 | } 240 | 241 | public String getLocalIcon(String key) { 242 | return mLocalIcon.get(key); 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/lanyixin/myapplication/format/CacheManager.java: -------------------------------------------------------------------------------- 1 | package com.example.lanyixin.myapplication.format; 2 | 3 | import android.text.TextUtils; 4 | import com.orhanobut.logger.Logger; 5 | 6 | import java.io.*; 7 | import java.security.MessageDigest; 8 | 9 | /** 10 | * 文件序列化缓存管理器
11 | * 缓存路径:data/data//Cache_Serialization 12 | * 13 | * @author weige 14 | */ 15 | public class CacheManager { 16 | 17 | /** 18 | * 缓存数据目录 19 | */ 20 | private static final String CACHE_SERIALIZATION_DIR = "Cache_Serialization"; 21 | 22 | private static final String CACHE_VERSION = "CACHE_VERSION"; 23 | 24 | private static final String TAG = "CacheManager"; 25 | 26 | private static File cacheDir; 27 | 28 | private CacheManager() { 29 | } 30 | 31 | private static void deleteOldCacheDir(final File oldCacheDir) { 32 | // new MyAsyncTask() { 33 | // @Override 34 | // protected Void doInBackground(Void... params) { 35 | // deleteFile(oldCacheDir); 36 | // return null; 37 | // } 38 | // }.execute(); 39 | } 40 | 41 | public static String md5(String str) { 42 | try { 43 | MessageDigest md5 = MessageDigest.getInstance("MD5"); 44 | md5.update(str.getBytes("UTF-8")); 45 | byte[] encryption = md5.digest(); 46 | 47 | StringBuilder strBuf = new StringBuilder(); 48 | for (byte b : encryption) { 49 | if (Integer.toHexString(0xff & b).length() == 1) { 50 | strBuf.append("0").append(Integer.toHexString(0xff & b)); 51 | } else { 52 | strBuf.append(Integer.toHexString(0xff & b)); 53 | } 54 | } 55 | return strBuf.toString(); 56 | } catch (Exception e) { 57 | Logger.e(TAG, "makeKey# error=", e); 58 | return ""; 59 | } 60 | } 61 | 62 | /** 63 | * 存储 64 | * 65 | * @param key 唯一key 66 | * @param data data 67 | */ 68 | public static void save(String key, Object data) { 69 | if (TextUtils.isEmpty(key)) { 70 | return; 71 | } 72 | String md5 = md5(key); 73 | Logger.i(TAG, "save# key=" + key + ", md5=" + md5); 74 | serialization(md5, data); 75 | } 76 | 77 | /** 78 | * 读取 79 | * 80 | * @param key 唯一key 81 | * @return Object 82 | */ 83 | @SuppressWarnings("unchecked") 84 | public static T get(String key) { 85 | if (TextUtils.isEmpty(key)) { 86 | return null; 87 | } 88 | String md5 = md5(key); 89 | Logger.i(TAG, "get# key=" + key + ", md5=" + md5); 90 | return (T) deserialization(md5); 91 | } 92 | 93 | /** 94 | * 删除 95 | * 96 | * @param key 唯一key 97 | */ 98 | public static void delete(String key) { 99 | if (TextUtils.isEmpty(key)) { 100 | return; 101 | } 102 | String md5 = md5(key); 103 | Logger.i(TAG, "delete# key=" + key + ", md5=" + md5); 104 | File file = new File(cacheDir, md5); 105 | deleteFile(file); 106 | } 107 | 108 | 109 | /** 110 | * serialize to file 111 | * 112 | * @param key 文件名 113 | * @param obj Object 114 | */ 115 | private static void serialization(String key, Object obj) { 116 | File file = new File(cacheDir, key); 117 | if (file.exists()) { 118 | deleteFile(file); 119 | } 120 | ObjectOutputStream out = null; 121 | try { 122 | out = new ObjectOutputStream(new FileOutputStream(file)); 123 | out.writeObject(obj); 124 | } catch (Throwable e) { 125 | deleteFile(file); 126 | Logger.e(TAG, "serialization#", e); 127 | } finally { 128 | if (out != null) { 129 | try { 130 | out.close(); 131 | } catch (Throwable e) { 132 | Logger.e(TAG, "serialization#", e); 133 | } 134 | } 135 | } 136 | } 137 | 138 | /** 139 | * deserialization from file 140 | * 141 | * @param key 文件名 142 | * @return Object 143 | */ 144 | private static Object deserialization(String key) { 145 | File file = new File(cacheDir, key); 146 | if (!file.exists()) { 147 | return null; 148 | } 149 | ObjectInputStream in = null; 150 | try { 151 | in = new ObjectInputStream(new FileInputStream(file)); 152 | return in.readObject(); 153 | } catch (Throwable e) { 154 | deleteFile(file); 155 | Logger.e(TAG, "deserialization#", e); 156 | } finally { 157 | if (in != null) { 158 | try { 159 | in.close(); 160 | } catch (Throwable e) { 161 | Logger.e(TAG, "deserialization#", e); 162 | } 163 | } 164 | } 165 | return null; 166 | } 167 | 168 | /** 169 | * delete file 170 | * 171 | * @param file file 172 | * @return true if delete success 173 | */ 174 | private static boolean deleteFile(File file) { 175 | if (!file.exists()) { 176 | return true; 177 | } 178 | if (file.isDirectory()) { 179 | File[] files = file.listFiles(); 180 | for (File f : files) { 181 | deleteFile(f); 182 | } 183 | } 184 | return file.delete(); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/lanyixin/myapplication/format/PriceUtils.java: -------------------------------------------------------------------------------- 1 | package com.example.lanyixin.myapplication.format; 2 | 3 | 4 | import android.content.Context; 5 | import com.example.lanyixin.myapplication.R; 6 | 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.UnsupportedEncodingException; 10 | import java.util.Locale; 11 | 12 | 13 | public class PriceUtils { 14 | 15 | /** 16 | * 获取配置json 位置:Raw Resource 17 | */ 18 | public static String getConfigJson(int res, Context context) { 19 | try { 20 | InputStream is = context.getResources().openRawResource(res); 21 | byte[] buf = new byte[is.available()];//读取整个文件 22 | is.read(buf); 23 | is.close(); 24 | return byteToString(buf); 25 | } catch (IOException e) { 26 | e.printStackTrace(); 27 | } 28 | return ""; 29 | } 30 | 31 | public static String byteToString(byte[] data) { 32 | String result = ""; 33 | try { 34 | result = new String(data, "utf-8"); 35 | } catch (UnsupportedEncodingException e) { 36 | } 37 | return result; 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/lanyixin/myapplication/format/RateBean.java: -------------------------------------------------------------------------------- 1 | package com.example.lanyixin.myapplication.format; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Created by jiongjiong on 2018/12/11. 7 | */ 8 | public class RateBean implements Serializable { 9 | private String symbol; 10 | 11 | private double rate; 12 | 13 | public String getSymbol() { 14 | return symbol; 15 | } 16 | 17 | public void setSymbol(String symbol) { 18 | this.symbol = symbol; 19 | } 20 | 21 | public double getRate() { 22 | return rate; 23 | } 24 | 25 | public void setRate(double rate) { 26 | this.rate = rate; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/lanyixin/myapplication/model/DemoModel.kt: -------------------------------------------------------------------------------- 1 | package com.example.lanyixin.myapplication.model 2 | 3 | import com.example.lanyixin.myapplication.api.ResultBase 4 | import com.example.lanyixin.myapplication.api.RetrofitManager 5 | import com.kotlinmvp.rx.scheduler.SchedulerUtils 6 | import java.util.* 7 | import kotlin.collections.ArrayList 8 | import io.reactivex.Observable 9 | 10 | class DemoModel { 11 | 12 | /** 13 | * 请求数据 14 | */ 15 | fun requestHotWordData(): Observable>> { 16 | return RetrofitManager.service.getHotsearch() 17 | .compose(SchedulerUtils.ioToMain()) 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/lanyixin/myapplication/presenter/DemoPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.mvp.presenter 2 | 3 | import com.example.lanyixin.myapplication.model.DemoModel 4 | import com.kotlinmvp.mvp.contract.DemoContract 5 | import com.kotlinmvp.base.BasePresenter 6 | import com.kotlinmvp.net.exception.ExceptionHandle 7 | 8 | 9 | class DemoPresenter : BasePresenter(), DemoContract.Presenter { 10 | 11 | private val mModel by lazy { DemoModel() } 12 | 13 | /** 14 | * 获取数据 15 | */ 16 | override fun requestHotWordData() { 17 | 18 | addSubscription(disposable = mModel.requestHotWordData() 19 | .subscribe({ string -> 20 | mRootView?.apply { 21 | setHotWordData(string) 22 | } 23 | }, { throwable -> 24 | mRootView?.apply { 25 | //处理异常 26 | showError(ExceptionHandle.handleException(throwable),ExceptionHandle.errorCode) 27 | } 28 | })) 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 22 | 32 | 42 | 51 | 60 | 69 | 79 | 88 | 98 | 107 | 118 | 128 | 138 | 148 | 158 | 166 | 176 | 184 | 192 | 200 | 201 | 211 | 221 | 231 | 232 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_txt.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayshier/MVPKotlin_Commonlib/d9aa24e8f99520d99ec5d3cacd5936fc1ecdba53/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayshier/MVPKotlin_Commonlib/d9aa24e8f99520d99ec5d3cacd5936fc1ecdba53/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayshier/MVPKotlin_Commonlib/d9aa24e8f99520d99ec5d3cacd5936fc1ecdba53/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayshier/MVPKotlin_Commonlib/d9aa24e8f99520d99ec5d3cacd5936fc1ecdba53/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayshier/MVPKotlin_Commonlib/d9aa24e8f99520d99ec5d3cacd5936fc1ecdba53/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayshier/MVPKotlin_Commonlib/d9aa24e8f99520d99ec5d3cacd5936fc1ecdba53/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayshier/MVPKotlin_Commonlib/d9aa24e8f99520d99ec5d3cacd5936fc1ecdba53/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayshier/MVPKotlin_Commonlib/d9aa24e8f99520d99ec5d3cacd5936fc1ecdba53/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayshier/MVPKotlin_Commonlib/d9aa24e8f99520d99ec5d3cacd5936fc1ecdba53/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayshier/MVPKotlin_Commonlib/d9aa24e8f99520d99ec5d3cacd5936fc1ecdba53/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/raw/default_coin.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "true", 3 | "code": "0", 4 | "msg": "", 5 | "time": 1517537524, 6 | "plat": [ 7 | { 8 | "code": "huobipro", 9 | "IsSelect": true, 10 | "title": "HuobiPro" 11 | }, 12 | { 13 | "code": "binance", 14 | "IsSelect": true, 15 | "title": "币安" 16 | }, 17 | { 18 | "code": "okex", 19 | "IsSelect": true, 20 | "title": "OKex" 21 | }, 22 | { 23 | "code": "upbit", 24 | "IsSelect": true, 25 | "title": "Upbit" 26 | }, 27 | { 28 | "code": "bitfinex", 29 | "IsSelect": true, 30 | "title": "Bitfinex" 31 | }, 32 | { 33 | "code": "bittrex", 34 | "IsSelect": true, 35 | "title": "Bittrex" 36 | }, 37 | { 38 | "code": "poloniex", 39 | "IsSelect": true, 40 | "title": "Poloniex" 41 | }, 42 | { 43 | "code": "bithumb", 44 | "IsSelect": true, 45 | "title": "Bithumb" 46 | }, 47 | { 48 | "code": "zb", 49 | "IsSelect": false, 50 | "title": "ZB中币" 51 | }, 52 | { 53 | "code": "kucoin", 54 | "IsSelect": true, 55 | "title": "KuCoin" 56 | }, 57 | { 58 | "code": "hitbtc", 59 | "IsSelect": true, 60 | "title": "Hitbtc" 61 | }, 62 | { 63 | "code": "gdax", 64 | "IsSelect": false, 65 | "title": "Gdax" 66 | }, 67 | { 68 | "code": "gate-io", 69 | "IsSelect": false, 70 | "title": "Gate-IO" 71 | }, 72 | { 73 | "code": "coinw", 74 | "IsSelect": false, 75 | "title": "Coinw" 76 | }, 77 | { 78 | "code": "bibox", 79 | "IsSelect": true, 80 | "title": "Bibox" 81 | }, 82 | { 83 | "code": "kraken", 84 | "IsSelect": true, 85 | "title": "Kraken" 86 | }, 87 | { 88 | "code": "yobit", 89 | "IsSelect": false, 90 | "title": "Yobit" 91 | }, 92 | { 93 | "code": "bit-z", 94 | "IsSelect": false, 95 | "title": "Bit-z" 96 | }, 97 | { 98 | "code": "liqui", 99 | "IsSelect": false, 100 | "title": "Liqui" 101 | }, 102 | { 103 | "code": "etherdelta", 104 | "IsSelect": false, 105 | "title": "Etherdelta" 106 | } 107 | ], 108 | "coin": [ 109 | { 110 | "code": "bitcoin", 111 | "IsSelect": true, 112 | "title": "BTC" 113 | }, 114 | { 115 | "code": "ethereum", 116 | "IsSelect": true, 117 | "title": "ETH" 118 | }, 119 | { 120 | "code": "eos", 121 | "IsSelect": true, 122 | "title": "EOS" 123 | }, 124 | { 125 | "code": "bqt", 126 | "IsSelect": true, 127 | "title": "BQT" 128 | }, 129 | { 130 | "code": "ripple", 131 | "IsSelect": true, 132 | "title": "XRP" 133 | }, 134 | { 135 | "code": "bitcoin-cash", 136 | "IsSelect": true, 137 | "title": "BCH" 138 | } 139 | ] 140 | } -------------------------------------------------------------------------------- /app/src/main/res/raw/default_coin_en.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "true", 3 | "code": "0", 4 | "msg": "", 5 | "time":1517537524, 6 | "plat": [ 7 | { 8 | "code": "huobipro", 9 | "IsSelect": true, 10 | "title": "HuobiPro" 11 | }, 12 | { 13 | "code": "binance", 14 | "IsSelect": true, 15 | "title": "Binance" 16 | }, 17 | { 18 | "code": "okex", 19 | "IsSelect": true, 20 | "title": "OKex" 21 | }, 22 | { 23 | "code": "upbit", 24 | "IsSelect": true, 25 | "title": "Upbit" 26 | }, 27 | { 28 | "code": "bitfinex", 29 | "IsSelect": true, 30 | "title": "Bitfinex" 31 | }, 32 | { 33 | "code": "bittrex", 34 | "IsSelect": true, 35 | "title": "Bittrex" 36 | }, 37 | { 38 | "code": "poloniex", 39 | "IsSelect": true, 40 | "title": "Poloniex" 41 | }, 42 | { 43 | "code": "bithumb", 44 | "IsSelect": true, 45 | "title": "Bithumb" 46 | }, 47 | { 48 | "code": "zb", 49 | "IsSelect": false, 50 | "title": "ZB-Com" 51 | }, 52 | { 53 | "code": "kucoin", 54 | "IsSelect": true, 55 | "title": "KuCoin" 56 | }, 57 | { 58 | "code": "hitbtc", 59 | "IsSelect": true, 60 | "title": "Hitbtc" 61 | }, 62 | { 63 | "code": "gdax", 64 | "IsSelect": false, 65 | "title": "Gdax" 66 | }, 67 | { 68 | "code": "gate-io", 69 | "IsSelect": false, 70 | "title": "Gate-IO" 71 | }, 72 | { 73 | "code": "coinw", 74 | "IsSelect": false, 75 | "title": "Coinw" 76 | }, 77 | { 78 | "code": "bibox", 79 | "IsSelect": true, 80 | "title": "Bibox" 81 | }, 82 | { 83 | "code": "kraken", 84 | "IsSelect": true, 85 | "title": "Kraken" 86 | }, 87 | { 88 | "code": "yobit", 89 | "IsSelect": false, 90 | "title": "Yobit" 91 | }, 92 | { 93 | "code": "bit-z", 94 | "IsSelect": false, 95 | "title": "Bit-z" 96 | }, 97 | { 98 | "code": "liqui", 99 | "IsSelect": false, 100 | "title": "Liqui" 101 | }, 102 | { 103 | "code": "etherdelta", 104 | "IsSelect": false, 105 | "title": "Etherdelta" 106 | } 107 | ], 108 | "coin": [ 109 | { 110 | "code": "bitcoin", 111 | "IsSelect": true, 112 | "title": "BTC" 113 | }, 114 | { 115 | "code": "ethereum", 116 | "IsSelect": true, 117 | "title": "ETH" 118 | }, 119 | { 120 | "code": "ripple", 121 | "IsSelect": true, 122 | "title": "XRP" 123 | }, 124 | { 125 | "code": "bitcoin-cash", 126 | "IsSelect": true, 127 | "title": "BCH" 128 | }, 129 | { 130 | "code": "litecoin", 131 | "IsSelect": false, 132 | "title": "LTC" 133 | } 134 | ] 135 | } -------------------------------------------------------------------------------- /app/src/main/res/raw/default_coin_tw.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "true", 3 | "code": "0", 4 | "msg": "", 5 | "time":1517537524, 6 | "plat": [ 7 | { 8 | "code": "huobipro", 9 | "IsSelect": true, 10 | "title": "HuobiPro" 11 | }, 12 | { 13 | "code": "binance", 14 | "IsSelect": true, 15 | "title": "幣安" 16 | }, 17 | { 18 | "code": "okex", 19 | "IsSelect": true, 20 | "title": "OKex" 21 | }, 22 | { 23 | "code": "upbit", 24 | "IsSelect": true, 25 | "title": "Upbit" 26 | }, 27 | { 28 | "code": "bitfinex", 29 | "IsSelect": true, 30 | "title": "Bitfinex" 31 | }, 32 | { 33 | "code": "bittrex", 34 | "IsSelect": true, 35 | "title": "Bittrex" 36 | }, 37 | { 38 | "code": "poloniex", 39 | "IsSelect": true, 40 | "title": "Poloniex" 41 | }, 42 | { 43 | "code": "bithumb", 44 | "IsSelect": true, 45 | "title": "Bithumb" 46 | }, 47 | { 48 | "code": "zb", 49 | "IsSelect": false, 50 | "title": "ZB中幣" 51 | }, 52 | { 53 | "code": "kucoin", 54 | "IsSelect": true, 55 | "title": "KuCoin" 56 | }, 57 | { 58 | "code": "hitbtc", 59 | "IsSelect": true, 60 | "title": "Hitbtc" 61 | }, 62 | { 63 | "code": "gdax", 64 | "IsSelect": false, 65 | "title": "Gdax" 66 | }, 67 | { 68 | "code": "gate-io", 69 | "IsSelect": false, 70 | "title": "Gate-IO" 71 | }, 72 | { 73 | "code": "coinw", 74 | "IsSelect": false, 75 | "title": "Coinw" 76 | }, 77 | { 78 | "code": "bibox", 79 | "IsSelect": true, 80 | "title": "Bibox" 81 | }, 82 | { 83 | "code": "kraken", 84 | "IsSelect": true, 85 | "title": "Kraken" 86 | }, 87 | { 88 | "code": "yobit", 89 | "IsSelect": false, 90 | "title": "Yobit" 91 | }, 92 | { 93 | "code": "bit-z", 94 | "IsSelect": false, 95 | "title": "Bit-z" 96 | }, 97 | { 98 | "code": "liqui", 99 | "IsSelect": false, 100 | "title": "Liqui" 101 | }, 102 | { 103 | "code": "etherdelta", 104 | "IsSelect": false, 105 | "title": "Etherdelta" 106 | } 107 | ], 108 | "coin": [ 109 | { 110 | "code": "bitcoin", 111 | "IsSelect": true, 112 | "title": "BTC" 113 | }, 114 | { 115 | "code": "ethereum", 116 | "IsSelect": true, 117 | "title": "ETH" 118 | }, 119 | { 120 | "code": "ripple", 121 | "IsSelect": true, 122 | "title": "XRP" 123 | }, 124 | { 125 | "code": "bitcoin-cash", 126 | "IsSelect": true, 127 | "title": "BCH" 128 | }, 129 | { 130 | "code": "litecoin", 131 | "IsSelect": false, 132 | "title": "LTC" 133 | } 134 | ] 135 | } -------------------------------------------------------------------------------- /app/src/main/res/raw/hexie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayshier/MVPKotlin_Commonlib/d9aa24e8f99520d99ec5d3cacd5936fc1ecdba53/app/src/main/res/raw/hexie -------------------------------------------------------------------------------- /app/src/main/res/raw/huilv.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "symbol": "aed", 4 | "rate": 3.679717397703856 5 | }, 6 | { 7 | "symbol": "ars", 8 | "rate": 38.37298541826554 9 | }, 10 | { 11 | "symbol": "aud", 12 | "rate": 1.3880984439416444 13 | }, 14 | { 15 | "symbol": "bch", 16 | "rate": 0.009135712468608025 17 | }, 18 | { 19 | "symbol": "bgn", 20 | "rate": 1.7133556069562237 21 | }, 22 | { 23 | "symbol": "bob", 24 | "rate": 6.8601221101735606 25 | }, 26 | { 27 | "symbol": "brl", 28 | "rate": 3.9261876717707107 29 | }, 30 | { 31 | "symbol": "btc", 32 | "rate": 0.00029046145967623593 33 | }, 34 | { 35 | "symbol": "cad", 36 | "rate": 1.341021858656296 37 | }, 38 | { 39 | "symbol": "chf", 40 | "rate": 0.989021857383048 41 | }, 42 | { 43 | "symbol": "clp", 44 | "rate": 680.2721088435375 45 | }, 46 | { 47 | "symbol": "cny", 48 | "rate": 6.925207756232687 49 | }, 50 | { 51 | "symbol": "cop", 52 | "rate": 3192.3383878691143 53 | }, 54 | { 55 | "symbol": "czk", 56 | "rate": 22.779562176814963 57 | }, 58 | { 59 | "symbol": "dkk", 60 | "rate": 6.565988181221274 61 | }, 62 | { 63 | "symbol": "eth", 64 | "rate": 0.011018868626010269 65 | }, 66 | { 67 | "symbol": "eur", 68 | "rate": 0.8796312585764047 69 | }, 70 | { 71 | "symbol": "gbp", 72 | "rate": 0.7948936034911728 73 | }, 74 | { 75 | "symbol": "hkd", 76 | "rate": 7.817385866166354 77 | }, 78 | { 79 | "symbol": "hrk", 80 | "rate": 6.491820306413918 81 | }, 82 | { 83 | "symbol": "huf", 84 | "rate": 286.77946659019216 85 | }, 86 | { 87 | "symbol": "idr", 88 | "rate": 14692.918013517485 89 | }, 90 | { 91 | "symbol": "ils", 92 | "rate": 3.751078435050077 93 | }, 94 | { 95 | "symbol": "inr", 96 | "rate": 72.25433526011561 97 | }, 98 | { 99 | "symbol": "isk", 100 | "rate": 122.39902080783354 101 | }, 102 | { 103 | "symbol": "jpy", 104 | "rate": 113.08379509216329 105 | }, 106 | { 107 | "symbol": "krw", 108 | "rate": 1135.0737797956867 109 | }, 110 | { 111 | "symbol": "ltc", 112 | "rate": 0.04077666031030164 113 | }, 114 | { 115 | "symbol": "mur", 116 | "rate": 34.91132523390588 117 | }, 118 | { 119 | "symbol": "mxn", 120 | "rate": 20.32520325203252 121 | }, 122 | { 123 | "symbol": "myr", 124 | "rate": 4.189359028068705 125 | }, 126 | { 127 | "symbol": "ngn", 128 | "rate": 370.7823507601038 129 | }, 130 | { 131 | "symbol": "nok", 132 | "rate": 8.55431993156544 133 | }, 134 | { 135 | "symbol": "nzd", 136 | "rate": 1.4534883720930234 137 | }, 138 | { 139 | "symbol": "pen", 140 | "rate": 3.363492650768558 141 | }, 142 | { 143 | "symbol": "php", 144 | "rate": 53.022269353128316 145 | }, 146 | { 147 | "symbol": "pkr", 148 | "rate": 138.58093126385808 149 | }, 150 | { 151 | "symbol": "pln", 152 | "rate": 3.7852979029449614 153 | }, 154 | { 155 | "symbol": "ron", 156 | "rate": 4.101218061764344 157 | }, 158 | { 159 | "symbol": "rub", 160 | "rate": 66.8985817500669 161 | }, 162 | { 163 | "symbol": "sek", 164 | "rate": 9.124087591240876 165 | }, 166 | { 167 | "symbol": "sgd", 168 | "rate": 1.3738150844896277 169 | }, 170 | { 171 | "symbol": "thb", 172 | "rate": 32.89473684210526 173 | }, 174 | { 175 | "symbol": "try", 176 | "rate": 5.3415950002670805 177 | }, 178 | { 179 | "symbol": "twd", 180 | "rate": 30.8641975308642 181 | }, 182 | { 183 | "symbol": "uah", 184 | "rate": 27.693159789531983 185 | }, 186 | { 187 | "symbol": "vnd", 188 | "rate": 23332.32226603514 189 | }, 190 | { 191 | "symbol": "zar", 192 | "rate": 14.471780028943561 193 | } 194 | ] -------------------------------------------------------------------------------- /app/src/main/res/raw/huilv_count.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "img": "ic_zhongguo.webp", 4 | "code": "CNY" 5 | }, 6 | { 7 | "img": "ic_meiguo.webp", 8 | "code": "USD" 9 | }, 10 | { 11 | "img": "bitcoin.webp", 12 | "code": "BTC" 13 | }, 14 | { 15 | "img": "ethereum.webp", 16 | "code": "ETH" 17 | }, 18 | { 19 | "img": "ic_riben.webp", 20 | "code": "JPY" 21 | }, 22 | { 23 | "img": "ic_hanguo.webp", 24 | "code": "KRW" 25 | }, 26 | { 27 | "img": "ic_xianggang.webp", 28 | "code": "HKD" 29 | }, 30 | { 31 | "img": "ic_taiwan.webp", 32 | "code": "TWD" 33 | }, 34 | { 35 | "img": "ic_eluosi.webp", 36 | "code": "RUB" 37 | }, 38 | 39 | { 40 | "img": "ic_yindu.webp", 41 | "code": "INR" 42 | }, 43 | 44 | 45 | { 46 | "img": "ic_aodaliya.webp", 47 | "code": "AUD" 48 | }, 49 | { 50 | "img": "eu.webp", 51 | "code": "EUR" 52 | }, 53 | { 54 | "img": "fr.webp", 55 | "code": "CHF" 56 | }, 57 | { 58 | "img": "ic_yingguo.webp", 59 | "code": "GBP" 60 | }, 61 | { 62 | "img": "ic_jianada.webp", 63 | "code": "CAD" 64 | }, 65 | { 66 | "img": "id.webp", 67 | "code": "IDR" 68 | }, 69 | { 70 | "img": "ic_baxi.webp", 71 | "code": "BRL" 72 | }, 73 | { 74 | "img": "ic_moxige.webp", 75 | "code": "MXN" 76 | } 77 | ] -------------------------------------------------------------------------------- /app/src/main/res/raw/plat.json: -------------------------------------------------------------------------------- 1 | {"coinsbank": "coinsbank.webp", 2 | "poloniex": "poloniex.webp", 3 | "bitmex": "bitmex.webp", 4 | "liqui": "liqui.webp", 5 | "bithumb": "bithumb.webp", 6 | "bitflyer": "bitflyer.webp", 7 | "chaoex": "chaoex.webp", 8 | "okcoin": "okcoin.webp", 9 | "btcbox": "btcbox.webp", 10 | "acx": "acx.webp", 11 | "allcoin": "allcoin.webp", 12 | "bcex": "bcex.webp", 13 | "btcc": "btcc.webp", 14 | "coinx": "coinx.webp", 15 | "exx": "exx.webp", 16 | "coolcoin": "coolcoin.webp", 17 | "zb": "zb.webp", 18 | "hitbtc": "hitbtc.webp", 19 | "gemini": "gemini.webp", 20 | "kraken": "kraken.webp", 21 | "hashtoken": "hashtoken.webp", 22 | "bitstar": "bitstar.webp", 23 | "lbank": "lbank.webp", 24 | "upbit": "upbit.webp", 25 | "huobi": "huobi.webp", 26 | "coinegg": "coinegg.webp", 27 | "okex": "okex.webp", 28 | "zaif": "zaif.webp", 29 | "coinnest": "coinnest.webp", 30 | "bitstamp": "bitstamp.webp", 31 | "coinexchange": "coinexchange.webp", 32 | "uncoinex": "uncoinex.webp", 33 | "ceo": "ceo.webp", 34 | "quoine": "quoine.webp", 35 | "kucoin": "kucoin.webp", 36 | "bter": "bter.webp", 37 | "chinawkb": "chinawkb.webp", 38 | "aex": "aex.webp", 39 | "bigone": "bigone.webp", 40 | "bittrex": "bittrex.webp", 41 | "cex": "cex.webp", 42 | "korbit": "korbit.webp", 43 | "lakebtc": "lakebtc.webp", 44 | "gatecoin": "gatecoin.webp", 45 | "coinone": "coinone.webp", 46 | "binance": "binance.webp", 47 | "bit-z": "bit_z.webp", 48 | "kex": "kex.webp", 49 | "gdax": "gdax.webp", 50 | "c-cex": "c_cex.webp", 51 | "dragonex": "dragonex.webp", 52 | "yobit": "yobit.webp", 53 | "wex": "wex.webp", 54 | "worldcryptocap": "worldcryptocap.webp", 55 | "btc38": "btc38.webp", 56 | "lykke": "lykke.webp", 57 | "bitfinex": "bitfinex.webp", 58 | "qbtc": "qbtc.webp", 59 | "coincheck": "coincheck.webp", 60 | "shuzibi": "shuzibi.webp", 61 | "livecoin": "livecoin.webp", 62 | "gate": "gate.webp", 63 | "mercatox": "mercatox.webp", 64 | "itbit": "itbit.webp" 65 | } -------------------------------------------------------------------------------- /app/src/main/res/raw/price.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "人民币", 4 | "code": "CNY" 5 | }, 6 | { 7 | "name": "美元", 8 | "code": "USD" 9 | }, 10 | { 11 | "name": "比特币", 12 | "code": "BTC" 13 | }, 14 | { 15 | "name": "以太坊", 16 | "code": "ETH" 17 | }, 18 | { 19 | "name": "日元", 20 | "code": "JPY" 21 | }, 22 | { 23 | "name": "韩币", 24 | "code": "KRW" 25 | }, 26 | { 27 | "name": "港币", 28 | "code": "HKD" 29 | }, 30 | { 31 | "name": "新台币", 32 | "code": "TWD" 33 | }, 34 | { 35 | "name": "卢布", 36 | "code": "RUB" 37 | }, 38 | 39 | { 40 | "name": "卢比", 41 | "code": "INR" 42 | }, 43 | 44 | 45 | { 46 | "name": "澳元", 47 | "code": "AUD" 48 | }, 49 | { 50 | "name": "欧元", 51 | "code": "EUR" 52 | }, 53 | { 54 | "name": "法郎", 55 | "code": "CHF" 56 | }, 57 | { 58 | "name": "英镑", 59 | "code": "GBP" 60 | }, 61 | { 62 | "name": "加元", 63 | "code": "CAD" 64 | }, 65 | { 66 | "name": "印尼盾", 67 | "code": "IDR" 68 | }, 69 | { 70 | "name": "巴西雷亚尔", 71 | "code": "BRL" 72 | }, 73 | { 74 | "name": "墨西哥比索", 75 | "code": "MXN" 76 | } 77 | ] -------------------------------------------------------------------------------- /app/src/main/res/raw/price_en.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "", 4 | "code": "CNY" 5 | }, 6 | { 7 | "name": "", 8 | "code": "USD" 9 | }, 10 | { 11 | "name": "", 12 | "code": "BTC" 13 | }, 14 | { 15 | "name": "", 16 | "code": "ETH" 17 | }, 18 | { 19 | "name": "", 20 | "code": "JPY" 21 | }, 22 | { 23 | "name": "", 24 | "code": "KRW" 25 | }, 26 | { 27 | "name": "", 28 | "code": "HKD" 29 | }, 30 | { 31 | "name": "", 32 | "code": "TWD" 33 | }, 34 | { 35 | "name": "", 36 | "code": "RUB" 37 | }, 38 | { 39 | "name": "", 40 | "code": "INR" 41 | }, 42 | { 43 | "name": "", 44 | "code": "AUD" 45 | }, 46 | { 47 | "name": "", 48 | "code": "EUR" 49 | }, 50 | { 51 | "name": "", 52 | "code": "CHF" 53 | }, 54 | { 55 | "name": "", 56 | "code": "GBP" 57 | }, 58 | { 59 | "name": "", 60 | "code": "CAD" 61 | }, 62 | { 63 | "name": "", 64 | "code": "IDR" 65 | }, 66 | { 67 | "name": "", 68 | "code": "BRL" 69 | }, 70 | { 71 | "name": "", 72 | "code": "MXN" 73 | } 74 | ] -------------------------------------------------------------------------------- /app/src/main/res/raw/price_hk.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "人民幣", 4 | "code": "CNY" 5 | }, 6 | { 7 | "name": "美元", 8 | "code": "USD" 9 | }, 10 | { 11 | "name": "比特幣", 12 | "code": "BTC" 13 | }, 14 | { 15 | "name": "以太坊", 16 | "code": "ETH" 17 | }, 18 | { 19 | "name": "日元", 20 | "code": "JPY" 21 | }, 22 | { 23 | "name": "韓元", 24 | "code": "KRW" 25 | }, 26 | { 27 | "name": "港元", 28 | "code": "HKD" 29 | }, 30 | { 31 | "name": "新台幣", 32 | "code": "TWD" 33 | }, 34 | { 35 | "name": "盧布", 36 | "code": "RUB" 37 | }, 38 | { 39 | "name": "盧比", 40 | "code": "INR" 41 | }, 42 | { 43 | "name": "澳元", 44 | "code": "AUD" 45 | }, 46 | { 47 | "name": "歐元", 48 | "code": "EUR" 49 | }, 50 | { 51 | "name": "法郎", 52 | "code": "CHF" 53 | }, 54 | { 55 | "name": "英鎊", 56 | "code": "GBP" 57 | }, 58 | { 59 | "name": "加元", 60 | "code": "CAD" 61 | }, 62 | { 63 | "name": "印尼盾", 64 | "code": "IDR" 65 | }, 66 | { 67 | "name": "巴西雷亞爾", 68 | "code": "BRL" 69 | }, 70 | { 71 | "name": "墨西哥披索", 72 | "code": "MXN" 73 | } 74 | ] -------------------------------------------------------------------------------- /app/src/main/res/raw/price_tw.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "人民幣", 4 | "code": "CNY" 5 | }, 6 | { 7 | "name": "美元", 8 | "code": "USD" 9 | }, 10 | { 11 | "name": "比特幣", 12 | "code": "BTC" 13 | }, 14 | { 15 | "name": "以太坊", 16 | "code": "ETH" 17 | }, 18 | { 19 | "name": "日元", 20 | "code": "JPY" 21 | }, 22 | { 23 | "name": "韓元", 24 | "code": "KRW" 25 | }, 26 | { 27 | "name": "港元", 28 | "code": "HKD" 29 | }, 30 | { 31 | "name": "新台幣", 32 | "code": "TWD" 33 | }, 34 | { 35 | "name": "盧布", 36 | "code": "RUB" 37 | }, 38 | { 39 | "name": "盧比", 40 | "code": "INR" 41 | }, 42 | { 43 | "name": "澳元", 44 | "code": "AUD" 45 | }, 46 | { 47 | "name": "歐元", 48 | "code": "EUR" 49 | }, 50 | { 51 | "name": "法郎", 52 | "code": "CHF" 53 | }, 54 | { 55 | "name": "英鎊", 56 | "code": "GBP" 57 | }, 58 | { 59 | "name": "加元", 60 | "code": "CAD" 61 | }, 62 | { 63 | "name": "印尼盾", 64 | "code": "IDR" 65 | }, 66 | { 67 | "name": "巴西雷亞爾", 68 | "code": "BRL" 69 | }, 70 | { 71 | "name": "墨西哥披索", 72 | "code": "MXN" 73 | } 74 | ] -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MVPKotlin_Commonlib 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/example/lanyixin/myapplication/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.lanyixin.myapplication 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | apply from: "config.gradle" 3 | buildscript { 4 | ext.kotlin_version = '1.3.11' 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.2.1' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /common_lib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /common_lib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' //扩展插件 6 | 7 | apply plugin: 'kotlin-kapt' //kapt3插件 8 | 9 | android { 10 | 11 | compileSdkVersion rootProject.ext.android.compileSdkVersion 12 | buildToolsVersion '28.0.3' 13 | 14 | defaultConfig { 15 | minSdkVersion rootProject.ext.android.minSdkVersion 16 | targetSdkVersion rootProject.ext.android.targetSdkVersion 17 | versionCode rootProject.ext.android.versionCode 18 | versionName rootProject.ext.android.versionName 19 | } 20 | 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | } 28 | 29 | dependencies { 30 | implementation fileTree(include: ['*.jar'], dir: 'libs') 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 33 | androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.1') { 34 | exclude group: 'com.android.support', module: 'support-annotations' 35 | } 36 | // Support库 37 | api rootProject.ext.supportLibs 38 | // 网络请求库 39 | api rootProject.ext.networkLibs 40 | // RxJava2 41 | api rootProject.ext.rxJavaLibs 42 | api rootProject.ext.otherLibs 43 | // APT dependencies(Kotlin内置的注解处理器) 44 | kapt rootProject.ext.annotationProcessorLibs 45 | 46 | //kotlin 支持库 47 | api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 48 | //GlideOkHttp 49 | api(rootProject.ext.glideOkhttp) { 50 | exclude group: 'glide-parent' 51 | } 52 | //Logger 53 | api 'com.orhanobut:logger:2.1.1' 54 | //运行时权限 55 | api'pub.devrel:easypermissions:1.2.0' 56 | 57 | api project(':multiple-status-view') 58 | 59 | } 60 | -------------------------------------------------------------------------------- /common_lib/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | #Glide的混淆规则 24 | -keep public class * implements com.bumptech.glide.module.GlideModule 25 | -keep public class * extends com.bumptech.glide.AppGlideModule 26 | -keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { 27 | **[] $VALUES; 28 | public *; 29 | } 30 | 31 | # Platform calls Class.forName on types which do not exist on Android to determine platform. 32 | -dontnote retrofit2.Platform 33 | # Platform used when running on Java 8 VMs. Will not be used at runtime. 34 | -dontwarn retrofit2.Platform$Java8 35 | # Retain generic type information for use by reflection by converters and adapters. 36 | -keepattributes Signature 37 | # Retain declared checked exceptions for use by a Proxy instance. 38 | -keepattributes Exceptions 39 | -dontwarn org.xmlpull.v1.** 40 | -dontwarn okhttp3.** 41 | -keep class okhttp3.** { *; } 42 | -dontwarn okio.** 43 | -dontwarn javax.annotation.Nullable 44 | -dontwarn javax.annotation.ParametersAreNonnullByDefault 45 | -------------------------------------------------------------------------------- /common_lib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp 2 | 3 | import android.content.Context 4 | import android.support.v4.app.Fragment 5 | import android.view.View 6 | import android.widget.ImageView 7 | import android.widget.Toast 8 | 9 | /** 10 | */ 11 | 12 | fun Fragment.showToast(content: String): Toast { 13 | val toast = Toast.makeText(this.activity?.applicationContext, content, Toast.LENGTH_SHORT) 14 | toast.show() 15 | return toast 16 | } 17 | 18 | fun Context.showToast(content: String,context: Context): Toast { 19 | val toast = Toast.makeText(context, content, Toast.LENGTH_SHORT) 20 | toast.show() 21 | return toast 22 | } 23 | 24 | 25 | fun View.dip2px(dipValue: Float): Int { 26 | val scale = this.resources.displayMetrics.density 27 | return (dipValue * scale + 0.5f).toInt() 28 | } 29 | 30 | fun View.px2dip(pxValue: Float): Int { 31 | val scale = this.resources.displayMetrics.density 32 | return (pxValue / scale + 0.5f).toInt() 33 | } 34 | 35 | fun durationFormat(duration: Long?): String { 36 | val minute = duration!! / 60 37 | val second = duration % 60 38 | return if (minute <= 9) { 39 | if (second <= 9) { 40 | "0$minute' 0$second''" 41 | } else { 42 | "0$minute' $second''" 43 | } 44 | } else { 45 | if (second <= 9) { 46 | "$minute' 0$second''" 47 | } else { 48 | "$minute' $second''" 49 | } 50 | } 51 | } 52 | 53 | /** 54 | * 数据流量格式化 55 | */ 56 | fun Context.dataFormat(total: Long): String { 57 | var result: String 58 | var speedReal: Int = (total / (1024)).toInt() 59 | result = if (speedReal < 512) { 60 | speedReal.toString() + " KB" 61 | } else { 62 | val mSpeed = speedReal / 1024.0 63 | (Math.round(mSpeed * 100) / 100.0).toString() + " MB" 64 | } 65 | return result 66 | } 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.base 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.support.v7.app.AppCompatActivity 6 | import android.util.Log 7 | import android.view.View 8 | import android.view.inputmethod.InputMethodManager 9 | import android.widget.EditText 10 | import android.widget.Toast 11 | import com.classic.common.MultipleStatusView 12 | import io.reactivex.annotations.NonNull 13 | import pub.devrel.easypermissions.AppSettingsDialog 14 | import pub.devrel.easypermissions.EasyPermissions 15 | 16 | 17 | 18 | abstract class BaseActivity : AppCompatActivity(),EasyPermissions.PermissionCallbacks { 19 | /** 20 | * 多种状态的 View 的切换 21 | */ 22 | protected var mLayoutStatusView: MultipleStatusView? = null 23 | 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | setContentView(layoutId()) 27 | initData() 28 | initView() 29 | start() 30 | initListener() 31 | } 32 | 33 | private fun initListener() { 34 | mLayoutStatusView?.setOnClickListener(mRetryClickListener) 35 | } 36 | 37 | open val mRetryClickListener: View.OnClickListener = View.OnClickListener { 38 | start() 39 | } 40 | 41 | 42 | /** 43 | * 加载布局 44 | */ 45 | abstract fun layoutId(): Int 46 | 47 | /** 48 | * 初始化数据 49 | */ 50 | abstract fun initData() 51 | 52 | /** 53 | * 初始化 View 54 | */ 55 | abstract fun initView() 56 | 57 | /** 58 | * 开始请求 59 | */ 60 | abstract fun start() 61 | 62 | 63 | /** 64 | * 打卡软键盘 65 | */ 66 | fun openKeyBord(mEditText: EditText, mContext: Context) { 67 | val imm = mContext.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 68 | imm.showSoftInput(mEditText, InputMethodManager.RESULT_SHOWN) 69 | imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY) 70 | } 71 | 72 | /** 73 | * 关闭软键盘 74 | */ 75 | fun closeKeyBord(mEditText: EditText, mContext: Context) { 76 | val imm = mContext.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 77 | imm.hideSoftInputFromWindow(mEditText.windowToken, 0) 78 | } 79 | 80 | 81 | override fun onDestroy() { 82 | super.onDestroy() 83 | } 84 | 85 | 86 | /** 87 | * 重写要申请权限的Activity或者Fragment的onRequestPermissionsResult()方法, 88 | * 在里面调用EasyPermissions.onRequestPermissionsResult(),实现回调。 89 | * 90 | * @param requestCode 权限请求的识别码 91 | * @param permissions 申请的权限 92 | * @param grantResults 授权结果 93 | */ 94 | override fun onRequestPermissionsResult(requestCode: Int, @NonNull permissions: Array, @NonNull grantResults: IntArray) { 95 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 96 | EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) 97 | } 98 | 99 | /** 100 | * 当权限被成功申请的时候执行回调 101 | * 102 | * @param requestCode 权限请求的识别码 103 | * @param perms 申请的权限的名字 104 | */ 105 | override fun onPermissionsGranted(requestCode: Int, perms: List) { 106 | Log.i("EasyPermissions", "获取成功的权限$perms") 107 | } 108 | 109 | /** 110 | * 当权限申请失败的时候执行的回调 111 | * 112 | * @param requestCode 权限请求的识别码 113 | * @param perms 申请的权限的名字 114 | */ 115 | override fun onPermissionsDenied(requestCode: Int, perms: List) { 116 | //处理权限名字字符串 117 | val sb = StringBuffer() 118 | for (str in perms) { 119 | sb.append(str) 120 | sb.append("\n") 121 | } 122 | sb.replace(sb.length - 2, sb.length, "") 123 | //用户点击拒绝并不在询问时候调用 124 | if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { 125 | Toast.makeText(this, "已拒绝权限" + sb + "并不再询问", Toast.LENGTH_SHORT).show() 126 | AppSettingsDialog.Builder(this) 127 | .setRationale("此功能需要" + sb + "权限,否则无法正常使用,是否打开设置") 128 | .setPositiveButton("好") 129 | .setNegativeButton("不行") 130 | .build() 131 | .show() 132 | } 133 | } 134 | 135 | } 136 | 137 | 138 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/base/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.base 2 | 3 | import android.os.Bundle 4 | import android.support.annotation.LayoutRes 5 | import android.support.v4.app.Fragment 6 | import android.util.Log 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.widget.Toast 11 | import com.classic.common.MultipleStatusView 12 | import io.reactivex.annotations.NonNull 13 | import pub.devrel.easypermissions.AppSettingsDialog 14 | import pub.devrel.easypermissions.EasyPermissions 15 | 16 | 17 | 18 | abstract class BaseFragment: Fragment(),EasyPermissions.PermissionCallbacks{ 19 | 20 | /** 21 | * 视图是否加载完毕 22 | */ 23 | private var isViewPrepare = false 24 | /** 25 | * 数据是否加载过了 26 | */ 27 | private var hasLoadData = false 28 | /** 29 | * 多种状态的 View 的切换 30 | */ 31 | protected var mLayoutStatusView: MultipleStatusView? = null 32 | 33 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 34 | return inflater.inflate(getLayoutId(),null) 35 | } 36 | 37 | 38 | 39 | override fun setUserVisibleHint(isVisibleToUser: Boolean) { 40 | super.setUserVisibleHint(isVisibleToUser) 41 | if (isVisibleToUser) { 42 | lazyLoadDataIfPrepared() 43 | } 44 | } 45 | 46 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 47 | super.onViewCreated(view, savedInstanceState) 48 | isViewPrepare = true 49 | initView() 50 | lazyLoadDataIfPrepared() 51 | //多种状态切换的view 重试点击事件 52 | mLayoutStatusView?.setOnClickListener(mRetryClickListener) 53 | } 54 | 55 | private fun lazyLoadDataIfPrepared() { 56 | if (userVisibleHint && isViewPrepare && !hasLoadData) { 57 | lazyLoad() 58 | hasLoadData = true 59 | } 60 | } 61 | 62 | open val mRetryClickListener: View.OnClickListener = View.OnClickListener { 63 | lazyLoad() 64 | } 65 | 66 | 67 | /** 68 | * 加载布局 69 | */ 70 | @LayoutRes 71 | abstract fun getLayoutId():Int 72 | 73 | /** 74 | * 初始化 ViewI 75 | */ 76 | abstract fun initView() 77 | 78 | /** 79 | * 懒加载 80 | */ 81 | abstract fun lazyLoad() 82 | 83 | override fun onDestroy() { 84 | super.onDestroy() 85 | } 86 | 87 | 88 | /** 89 | * 重写要申请权限的Activity或者Fragment的onRequestPermissionsResult()方法, 90 | * 在里面调用EasyPermissions.onRequestPermissionsResult(),实现回调。 91 | * 92 | * @param requestCode 权限请求的识别码 93 | * @param permissions 申请的权限 94 | * @param grantResults 授权结果 95 | */ 96 | override fun onRequestPermissionsResult(requestCode: Int, @NonNull permissions: Array, @NonNull grantResults: IntArray) { 97 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 98 | EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) 99 | } 100 | 101 | /** 102 | * 当权限被成功申请的时候执行回调 103 | * 104 | * @param requestCode 权限请求的识别码 105 | * @param perms 申请的权限的名字 106 | */ 107 | override fun onPermissionsGranted(requestCode: Int, perms: List) { 108 | Log.i("EasyPermissions", "获取成功的权限$perms") 109 | } 110 | 111 | /** 112 | * 当权限申请失败的时候执行的回调 113 | * 114 | * @param requestCode 权限请求的识别码 115 | * @param perms 申请的权限的名字 116 | */ 117 | override fun onPermissionsDenied(requestCode: Int, perms: List) { 118 | //处理权限名字字符串 119 | val sb = StringBuffer() 120 | for (str in perms) { 121 | sb.append(str) 122 | sb.append("\n") 123 | } 124 | sb.replace(sb.length - 2, sb.length, "") 125 | //用户点击拒绝并不在询问时候调用 126 | if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { 127 | Toast.makeText(activity, "已拒绝权限" + sb + "并不再询问", Toast.LENGTH_SHORT).show() 128 | AppSettingsDialog.Builder(this) 129 | .setRationale("此功能需要" + sb + "权限,否则无法正常使用,是否打开设置") 130 | .setPositiveButton("好") 131 | .setNegativeButton("不行") 132 | .build() 133 | .show() 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/base/BaseFragmentAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.base 2 | 3 | import android.annotation.SuppressLint 4 | import android.support.v4.app.Fragment 5 | import android.support.v4.app.FragmentManager 6 | import android.support.v4.app.FragmentPagerAdapter 7 | 8 | 9 | /** 10 | * 该类内的每一个生成的 Fragment 都将保存在内存之中, 11 | * 因此适用于那些相对静态的页,数量也比较少的那种; 12 | * 如果需要处理有很多页,并且数据动态性较大、占用内存较多的情况, 13 | * 应该使用FragmentStatePagerAdapter。 14 | */ 15 | class BaseFragmentAdapter : FragmentPagerAdapter { 16 | 17 | private var fragmentList: List? = ArrayList() 18 | private var mTitles: List? = null 19 | 20 | constructor(fm: FragmentManager, fragmentList: List) : super(fm) { 21 | this.fragmentList = fragmentList 22 | } 23 | 24 | constructor(fm: FragmentManager, fragmentList: List, mTitles: List) : super(fm) { 25 | this.mTitles = mTitles 26 | setFragments(fm, fragmentList, mTitles) 27 | } 28 | 29 | //刷新fragment 30 | @SuppressLint("CommitTransaction") 31 | private fun setFragments(fm: FragmentManager, fragments: List, mTitles: List) { 32 | this.mTitles = mTitles 33 | if (this.fragmentList != null) { 34 | val ft = fm.beginTransaction() 35 | fragmentList?.forEach { 36 | ft.remove(it) 37 | } 38 | ft?.commitAllowingStateLoss() 39 | fm.executePendingTransactions() 40 | } 41 | this.fragmentList = fragments 42 | notifyDataSetChanged() 43 | } 44 | 45 | override fun getPageTitle(position: Int): CharSequence { 46 | return if (null != mTitles) mTitles!![position] else "" 47 | } 48 | 49 | override fun getItem(position: Int): Fragment { 50 | return fragmentList!![position] 51 | } 52 | 53 | override fun getCount(): Int { 54 | return fragmentList!!.size 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/base/BasePresenter.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.base 2 | 3 | import io.reactivex.disposables.CompositeDisposable 4 | import io.reactivex.disposables.Disposable 5 | 6 | 7 | 8 | /** 9 | * 11/16. 10 | * 11 | */ 12 | open class BasePresenter : IPresenter { 13 | 14 | var mRootView: T? = null 15 | private set 16 | 17 | private var compositeDisposable = CompositeDisposable() 18 | 19 | 20 | override fun attachView(mRootView: T) { 21 | this.mRootView = mRootView 22 | } 23 | 24 | override fun detachView() { 25 | mRootView = null 26 | 27 | //保证activity结束时取消所有正在执行的订阅 28 | if (!compositeDisposable.isDisposed) { 29 | compositeDisposable.clear() 30 | } 31 | 32 | } 33 | 34 | private val isViewAttached: Boolean 35 | get() = mRootView != null 36 | 37 | fun checkViewAttached() { 38 | if (!isViewAttached) throw MvpViewNotAttachedException() 39 | } 40 | 41 | fun addSubscription(disposable: Disposable) { 42 | compositeDisposable.add(disposable) 43 | } 44 | 45 | private class MvpViewNotAttachedException internal constructor() : RuntimeException("Please call IPresenter.attachView(IBaseView) before" + " requesting data to the IPresenter") 46 | 47 | 48 | } -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/base/IBaseView.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.base 2 | 3 | interface IBaseView { 4 | 5 | fun showLoading() 6 | 7 | fun dismissLoading() 8 | 9 | } 10 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/base/IPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.base 2 | 3 | 4 | 5 | 6 | 7 | interface IPresenter { 8 | 9 | fun attachView(mRootView: V) 10 | 11 | fun detachView() 12 | 13 | } 14 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/glide/CustomAppGlideModule.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.glide 2 | 3 | import android.content.Context 4 | 5 | import com.bumptech.glide.Glide 6 | import com.bumptech.glide.GlideBuilder 7 | import com.bumptech.glide.Registry 8 | import com.bumptech.glide.annotation.GlideModule 9 | import com.bumptech.glide.load.engine.cache.LruResourceCache 10 | import com.bumptech.glide.module.AppGlideModule 11 | 12 | import java.io.InputStream 13 | 14 | /** 15 | * desc: 16 | */ 17 | @GlideModule 18 | class CustomAppGlideModule : AppGlideModule() { 19 | 20 | /** 21 | * 通过GlideBuilder设置默认的结构(Engine,BitmapPool ,ArrayPool,MemoryCache等等). 22 | * 23 | * @param context 24 | * @param builder 25 | */ 26 | override fun applyOptions(context: Context, builder: GlideBuilder) { 27 | 28 | //重新设置内存限制 29 | builder.setMemoryCache(LruResourceCache(10 * 1024 * 1024)) 30 | 31 | } 32 | 33 | 34 | /** 35 | * 清单解析的开启 36 | * 37 | * 38 | * 这里不开启,避免添加相同的modules两次 39 | * 40 | * @return 41 | */ 42 | override fun isManifestParsingEnabled(): Boolean { 43 | return false 44 | } 45 | 46 | /** 47 | * 48 | * 为App注册一个自定义的String类型的BaseGlideUrlLoader 49 | * @param context 50 | * @param glide 51 | * @param registry 52 | */ 53 | override fun registerComponents(context: Context, glide: Glide, registry: Registry) { 54 | registry.append(String::class.java, InputStream::class.java, CustomBaseGlideUrlLoader.Factory()) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/glide/CustomBaseGlideUrlLoader.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.glide 2 | 3 | import com.bumptech.glide.load.Options 4 | import com.bumptech.glide.load.model.* 5 | import com.bumptech.glide.load.model.stream.BaseGlideUrlLoader 6 | import java.io.InputStream 7 | import java.util.regex.Pattern 8 | 9 | /** 10 | * desc: 11 | */ 12 | 13 | class CustomBaseGlideUrlLoader(concreteLoader: ModelLoader, modelCache: ModelCache) : BaseGlideUrlLoader(concreteLoader, modelCache) { 14 | 15 | /** 16 | * If the URL contains a special variable width indicator (eg "__w-200-400-800__") 17 | * we get the buckets from the URL (200, 400 and 800 in the example) and replace 18 | * the URL with the best bucket for the requested width (the bucket immediately 19 | * larger than the requested width). 20 | * 21 | * 控制加载的图片的大小 22 | */ 23 | override fun getUrl(model: String, width: Int, height: Int, options: Options): String { 24 | val m = PATTERN.matcher(model) 25 | var bestBucket: Int 26 | if (m.find()) { 27 | val found = m.group(1).split("-".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 28 | for (bucketStr in found) { 29 | bestBucket = Integer.parseInt(bucketStr) 30 | if (bestBucket >= width) { 31 | // the best bucket is the first immediately bigger than the requested width 32 | break 33 | } 34 | } 35 | 36 | } 37 | return model 38 | } 39 | 40 | override fun handles(s: String): Boolean { 41 | return true 42 | } 43 | 44 | /** 45 | * 工厂来构建CustomBaseGlideUrlLoader对象 46 | */ 47 | class Factory : ModelLoaderFactory { 48 | override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader { 49 | return CustomBaseGlideUrlLoader(multiFactory.build(GlideUrl::class.java, InputStream::class.java), urlCache) 50 | } 51 | 52 | override fun teardown() { 53 | 54 | } 55 | } 56 | 57 | companion object { 58 | 59 | private val urlCache = ModelCache(150) 60 | /** 61 | * Url的匹配规则 62 | */ 63 | private val PATTERN = Pattern.compile("__w-((?:-?\\d+)+)__") 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/glide/GlideRoundTransform.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package com.kotlinmvp.glide 4 | 5 | import android.content.res.Resources 6 | import android.graphics.Bitmap 7 | import android.graphics.BitmapShader 8 | import android.graphics.Canvas 9 | import android.graphics.Paint 10 | import android.graphics.RectF 11 | import android.graphics.Shader 12 | 13 | import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool 14 | import com.bumptech.glide.load.resource.bitmap.BitmapTransformation 15 | 16 | import java.security.MessageDigest 17 | 18 | /** 19 | * desc: 20 | * 1.永远不要把transform()传给你的原始resource或原始Bitmap给recycle()了,更不要放回BitmapPool,因为这些都自动完成了。值得注意的是,任何从BitmapPool取出的用于自定义图片变换的辅助Bitmap,如果不经过transform()方法返回,就必须主动放回BitmapPool或者调用recycle()回收。 21 | * 2.如果你从BitmapPool拿出多个Bitmap或不使用你从BitmapPool拿出的一个Bitmap,一定要返回extras给BitmapPool。 22 | * 3.如果你的图片处理没有替换原始resource(例如由于一张图片已经匹配了你想要的尺寸,你需要提前返回), transform()`方法就返回原始resource或原始Bitmap。 23 | */ 24 | 25 | class GlideRoundTransform @JvmOverloads constructor(dp: Int = 4) : BitmapTransformation() { 26 | 27 | private var radius = 0f 28 | 29 | init { 30 | this.radius = Resources.getSystem().displayMetrics.density * dp 31 | } 32 | 33 | override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap? { 34 | return roundCrop(pool, toTransform) 35 | } 36 | 37 | 38 | override fun updateDiskCacheKey(messageDigest: MessageDigest) { 39 | 40 | } 41 | 42 | 43 | private fun roundCrop(pool: BitmapPool, source: Bitmap?): Bitmap? { 44 | if (source == null) return null 45 | 46 | var result: Bitmap? = pool.get(source.width, source.height, Bitmap.Config.ARGB_8888) 47 | if (result == null) { 48 | result = Bitmap.createBitmap(source.width, source.height, Bitmap.Config.ARGB_8888) 49 | } 50 | 51 | val canvas = Canvas(result!!) 52 | val paint = Paint() 53 | paint.shader = BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) 54 | paint.isAntiAlias = true 55 | val rectF = RectF(0f, 0f, source.width.toFloat(), source.height.toFloat()) 56 | canvas.drawRoundRect(rectF, radius, radius, paint) 57 | return result 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/net/BaseResponse.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.net 2 | 3 | /** 4 | * 11/16. 5 | * 封装返回的数据 6 | */ 7 | class BaseResponse(val code :Int, 8 | val msg:String, 9 | val data:T) -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/net/exception/ApiException.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.net.exception 2 | 3 | /** 4 | * desc: 5 | */ 6 | class ApiException : RuntimeException { 7 | 8 | private var code: Int? = null 9 | 10 | 11 | constructor(throwable: Throwable, code: Int) : super(throwable) { 12 | this.code = code 13 | } 14 | 15 | constructor(message: String) : super(Throwable(message)) 16 | } -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/net/exception/ErrorStatus.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.net.exception 2 | 3 | /** 4 | * desc: 5 | */ 6 | object ErrorStatus { 7 | /** 8 | * 响应成功 9 | */ 10 | @JvmField 11 | val SUCCESS = 0 12 | 13 | /** 14 | * 未知错误 15 | */ 16 | @JvmField 17 | val UNKNOWN_ERROR = 1002 18 | 19 | /** 20 | * 服务器内部错误 21 | */ 22 | @JvmField 23 | val SERVER_ERROR = 1003 24 | 25 | /** 26 | * 网络连接超时 27 | */ 28 | @JvmField 29 | val NETWORK_ERROR = 1004 30 | 31 | /** 32 | * API解析异常(或者第三方数据结构更改)等其他异常 33 | */ 34 | @JvmField 35 | val API_ERROR = 1005 36 | 37 | } -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/net/exception/ExceptionHandle.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.net.exception 2 | 3 | import com.google.gson.JsonParseException 4 | import com.orhanobut.logger.Logger 5 | 6 | import org.json.JSONException 7 | 8 | import java.net.ConnectException 9 | 10 | import java.net.SocketTimeoutException 11 | import java.net.UnknownHostException 12 | import java.text.ParseException 13 | 14 | /** 15 | * desc: 异常处理类 16 | */ 17 | 18 | class ExceptionHandle { 19 | 20 | 21 | companion object { 22 | var errorCode = ErrorStatus.UNKNOWN_ERROR 23 | var errorMsg = "请求失败,请稍后重试" 24 | 25 | fun handleException(e: Throwable): String { 26 | e.printStackTrace() 27 | if (e is SocketTimeoutException) {//网络超时 28 | Logger.e("TAG", "网络连接异常: " + e.message) 29 | errorMsg = "网络连接异常" 30 | errorCode = ErrorStatus.NETWORK_ERROR 31 | } else if (e is ConnectException) { //均视为网络错误 32 | Logger.e("TAG", "网络连接异常: " + e.message) 33 | errorMsg = "网络连接异常" 34 | errorCode = ErrorStatus.NETWORK_ERROR 35 | } else if (e is JsonParseException 36 | || e is JSONException 37 | || e is ParseException) { //均视为解析错误 38 | Logger.e("TAG", "数据解析异常: " + e.message) 39 | errorMsg = "数据解析异常" 40 | errorCode = ErrorStatus.SERVER_ERROR 41 | } else if (e is ApiException) {//服务器返回的错误信息 42 | errorMsg = e.message.toString() 43 | errorCode = ErrorStatus.SERVER_ERROR 44 | } else if (e is UnknownHostException) { 45 | Logger.e("TAG", "网络连接异常: " + e.message) 46 | errorMsg = "网络连接异常" 47 | errorCode = ErrorStatus.NETWORK_ERROR 48 | } else if (e is IllegalArgumentException) { 49 | errorMsg = "参数错误" 50 | errorCode = ErrorStatus.SERVER_ERROR 51 | } else {//未知错误 52 | try { 53 | Logger.e("TAG", "错误: " + e.message) 54 | } catch (e1: Exception) { 55 | Logger.e("TAG", "未知错误Debug调试 ") 56 | } 57 | 58 | errorMsg = "未知错误,可能抛锚了吧~" 59 | errorCode = ErrorStatus.UNKNOWN_ERROR 60 | } 61 | return errorMsg 62 | } 63 | 64 | } 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/rx/scheduler/BaseScheduler.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.rx.scheduler 2 | 3 | import io.reactivex.* 4 | import org.reactivestreams.Publisher 5 | 6 | /** 7 | * . 8 | * desc:RxJava2.x 5中基础相应类型 9 | */ 10 | 11 | 12 | 13 | abstract class BaseScheduler protected constructor(private val subscribeOnScheduler: Scheduler, 14 | private val observeOnScheduler: Scheduler) : ObservableTransformer, 15 | SingleTransformer, 16 | MaybeTransformer, 17 | CompletableTransformer, 18 | FlowableTransformer { 19 | 20 | override fun apply(upstream: Completable): CompletableSource { 21 | return upstream.subscribeOn(subscribeOnScheduler) 22 | .observeOn(observeOnScheduler) 23 | } 24 | 25 | override fun apply(upstream: Flowable): Publisher { 26 | return upstream.subscribeOn(subscribeOnScheduler) 27 | .observeOn(observeOnScheduler) 28 | } 29 | 30 | override fun apply(upstream: Maybe): MaybeSource { 31 | return upstream.subscribeOn(subscribeOnScheduler) 32 | .observeOn(observeOnScheduler) 33 | } 34 | 35 | override fun apply(upstream: Observable): ObservableSource { 36 | return upstream.subscribeOn(subscribeOnScheduler) 37 | .observeOn(observeOnScheduler) 38 | } 39 | 40 | override fun apply(upstream: Single): SingleSource { 41 | return upstream.subscribeOn(subscribeOnScheduler) 42 | .observeOn(observeOnScheduler) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/rx/scheduler/ComputationMainScheduler.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.rx.scheduler 2 | 3 | import io.reactivex.android.schedulers.AndroidSchedulers 4 | import io.reactivex.schedulers.Schedulers 5 | /** 6 | * . 7 | * desc: 8 | */ 9 | 10 | 11 | class ComputationMainScheduler private constructor() : BaseScheduler(Schedulers.computation(), AndroidSchedulers.mainThread()) 12 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/rx/scheduler/IoMainScheduler.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.rx.scheduler 2 | 3 | import io.reactivex.android.schedulers.AndroidSchedulers 4 | import io.reactivex.schedulers.Schedulers 5 | 6 | /** 7 | * . 8 | * desc: 9 | */ 10 | class IoMainScheduler : BaseScheduler(Schedulers.io(), AndroidSchedulers.mainThread()) 11 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/rx/scheduler/NewThreadMainScheduler.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.rx.scheduler 2 | 3 | import io.reactivex.android.schedulers.AndroidSchedulers 4 | import io.reactivex.schedulers.Schedulers 5 | 6 | /** 7 | * . 8 | * desc: 9 | */ 10 | 11 | 12 | class NewThreadMainScheduler private constructor() : BaseScheduler(Schedulers.newThread(), AndroidSchedulers.mainThread()) 13 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/rx/scheduler/SchedulerUtils.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.rx.scheduler 2 | 3 | /** 4 | * . 5 | * desc: 6 | */ 7 | 8 | object SchedulerUtils { 9 | 10 | fun ioToMain(): IoMainScheduler { 11 | return IoMainScheduler() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/rx/scheduler/SingleMainScheduler.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.rx.scheduler 2 | 3 | import io.reactivex.android.schedulers.AndroidSchedulers 4 | import io.reactivex.schedulers.Schedulers 5 | 6 | /** 7 | * . 8 | * desc: 9 | */ 10 | 11 | 12 | class SingleMainScheduler private constructor() : BaseScheduler(Schedulers.single(), AndroidSchedulers.mainThread()) 13 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/rx/scheduler/TrampolineMainScheduler.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.rx.scheduler 2 | 3 | import io.reactivex.android.schedulers.AndroidSchedulers 4 | import io.reactivex.schedulers.Schedulers 5 | 6 | /** 7 | * . 8 | * desc: 9 | */ 10 | 11 | 12 | class TrampolineMainScheduler private constructor() : BaseScheduler(Schedulers.trampoline(), AndroidSchedulers.mainThread()) 13 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/utils/AppUtils.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.utils 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.ActivityManager 5 | import android.content.Context 6 | import android.content.pm.PackageManager 7 | import android.os.Build 8 | import java.security.MessageDigest 9 | 10 | /** 11 | * 12/6. 12 | * desc: APP 相关的工具类 13 | */ 14 | 15 | class AppUtils private constructor() { 16 | 17 | 18 | init { 19 | throw Error("Do not need instantiate!") 20 | } 21 | 22 | companion object { 23 | 24 | private val DEBUG = true 25 | private val TAG = "AppUtils" 26 | 27 | 28 | /** 29 | * 得到软件版本号 30 | * 31 | * @param context 上下文 32 | * @return 当前版本Code 33 | */ 34 | fun getVerCode(context: Context): Int { 35 | var verCode = -1 36 | try { 37 | val packageName = context.packageName 38 | verCode = context.packageManager 39 | .getPackageInfo(packageName, 0).versionCode 40 | } catch (e: PackageManager.NameNotFoundException) { 41 | e.printStackTrace() 42 | } 43 | 44 | return verCode 45 | } 46 | 47 | 48 | /** 49 | * 获取应用运行的最大内存 50 | * 51 | * @return 最大内存 52 | */ 53 | val maxMemory: Long 54 | get() = Runtime.getRuntime().maxMemory() / 1024 55 | 56 | 57 | /** 58 | * 得到软件显示版本信息 59 | * 60 | * @param context 上下文 61 | * @return 当前版本信息 62 | */ 63 | fun getVerName(context: Context): String { 64 | var verName = "" 65 | try { 66 | val packageName = context.packageName 67 | verName = context.packageManager 68 | .getPackageInfo(packageName, 0).versionName 69 | } catch (e: PackageManager.NameNotFoundException) { 70 | e.printStackTrace() 71 | } 72 | 73 | return verName 74 | } 75 | 76 | 77 | @SuppressLint("PackageManagerGetSignatures") 78 | /** 79 | * 获取应用签名 80 | * 81 | * @param context 上下文 82 | * @param pkgName 包名 83 | * @return 返回应用的签名 84 | */ 85 | fun getSign(context: Context, pkgName: String): String? { 86 | return try { 87 | @SuppressLint("PackageManagerGetSignatures") val pis = context.packageManager 88 | .getPackageInfo(pkgName, 89 | PackageManager.GET_SIGNATURES) 90 | hexDigest(pis.signatures[0].toByteArray()) 91 | } catch (e: PackageManager.NameNotFoundException) { 92 | e.printStackTrace() 93 | null 94 | } 95 | 96 | } 97 | 98 | /** 99 | * 将签名字符串转换成需要的32位签名 100 | * 101 | * @param paramArrayOfByte 签名byte数组 102 | * @return 32位签名字符串 103 | */ 104 | private fun hexDigest(paramArrayOfByte: ByteArray): String { 105 | val hexDigits = charArrayOf(48.toChar(), 49.toChar(), 50.toChar(), 51.toChar(), 52.toChar(), 53.toChar(), 54.toChar(), 55.toChar(), 56.toChar(), 57.toChar(), 97.toChar(), 98.toChar(), 99.toChar(), 100.toChar(), 101.toChar(), 102.toChar()) 106 | try { 107 | val localMessageDigest = MessageDigest.getInstance("MD5") 108 | localMessageDigest.update(paramArrayOfByte) 109 | val arrayOfByte = localMessageDigest.digest() 110 | val arrayOfChar = CharArray(32) 111 | var i = 0 112 | var j = 0 113 | while (true) { 114 | if (i >= 16) { 115 | return String(arrayOfChar) 116 | } 117 | val k = arrayOfByte[i].toInt() 118 | arrayOfChar[j] = hexDigits[0xF and k.ushr(4)] 119 | arrayOfChar[++j] = hexDigits[k and 0xF] 120 | i++ 121 | j++ 122 | } 123 | } catch (e: Exception) { 124 | e.printStackTrace() 125 | } 126 | 127 | return "" 128 | } 129 | 130 | 131 | /** 132 | * 获取设备的可用内存大小 133 | * 134 | * @param context 应用上下文对象context 135 | * @return 当前内存大小 136 | */ 137 | fun getDeviceUsableMemory(context: Context): Int { 138 | val am = context.getSystemService( 139 | Context.ACTIVITY_SERVICE) as ActivityManager 140 | val mi = ActivityManager.MemoryInfo() 141 | am.getMemoryInfo(mi) 142 | // 返回当前系统的可用内存 143 | return (mi.availMem / (1024 * 1024)).toInt() 144 | } 145 | 146 | 147 | fun getMobileModel(): String { 148 | var model: String? = Build.MODEL 149 | model = model?.trim { it <= ' ' } ?: "" 150 | return model 151 | } 152 | 153 | /** 154 | * 获取手机系统SDK版本 155 | * 156 | * @return 如API 17 则返回 17 157 | */ 158 | val sdkVersion: Int 159 | get() = android.os.Build.VERSION.SDK_INT 160 | } 161 | 162 | 163 | } -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/utils/CleanLeakUtils.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.utils 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import android.view.inputmethod.InputMethodManager 6 | import com.orhanobut.logger.Logger 7 | 8 | import java.lang.reflect.Field 9 | 10 | /** 11 | * desc: 12 | */ 13 | 14 | object CleanLeakUtils { 15 | 16 | fun fixInputMethodManagerLeak(destContext: Context?) { 17 | if (destContext == null) { 18 | return 19 | } 20 | val inputMethodManager = destContext.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 21 | 22 | val viewArray = arrayOf("mCurRootView", "mServedView", "mNextServedView") 23 | var filed: Field 24 | var filedObject: Any? 25 | 26 | for (view in viewArray) { 27 | try { 28 | filed = inputMethodManager.javaClass.getDeclaredField(view) 29 | if (!filed.isAccessible) { 30 | filed.isAccessible = true 31 | } 32 | filedObject = filed.get(inputMethodManager) 33 | if (filedObject != null && filedObject is View) { 34 | val fileView = filedObject as View? 35 | if (fileView!!.context === destContext) { // 被InputMethodManager持有引用的context是想要目标销毁的 36 | filed.set(inputMethodManager, null) // 置空,破坏掉path to gc节点 37 | } else { 38 | break// 不是想要目标销毁的,即为又进了另一层界面了,不要处理,避免影响原逻辑,也就不用继续for循环了 39 | } 40 | } 41 | } catch (t: Throwable) { 42 | t.printStackTrace() 43 | } 44 | 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/utils/DisplayManager.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.utils 2 | 3 | import android.content.Context 4 | import android.util.DisplayMetrics 5 | 6 | /** 7 | * desc: 8 | */ 9 | 10 | object DisplayManager { 11 | init { 12 | 13 | } 14 | 15 | private var displayMetrics: DisplayMetrics? = null 16 | 17 | private var screenWidth: Int? = null 18 | 19 | private var screenHeight: Int? = null 20 | 21 | private var screenDpi: Int? = null 22 | 23 | fun init(context: Context) { 24 | displayMetrics = context.resources.displayMetrics 25 | screenWidth = displayMetrics?.widthPixels 26 | screenHeight = displayMetrics?.heightPixels 27 | screenDpi = displayMetrics?.densityDpi 28 | } 29 | 30 | 31 | //UI图的大小 32 | private const val STANDARD_WIDTH = 1080 33 | private const val STANDARD_HEIGHT = 1920 34 | 35 | 36 | fun getScreenWidth(): Int? { 37 | return screenWidth 38 | } 39 | 40 | fun getScreenHeight(): Int? { 41 | return screenHeight 42 | } 43 | 44 | 45 | /** 46 | * 传入UI图中问题的高度,单位像素 47 | * @param size 48 | * @return 49 | */ 50 | fun getPaintSize(size: Int): Int? { 51 | return getRealHeight(size) 52 | } 53 | 54 | /** 55 | * 输入UI图的尺寸,输出实际的px 56 | * 57 | * @param px ui图中的大小 58 | * @return 59 | */ 60 | fun getRealWidth(px: Int): Int? { 61 | //ui图的宽度 62 | return getRealWidth(px, STANDARD_WIDTH.toFloat()) 63 | } 64 | 65 | /** 66 | * 输入UI图的尺寸,输出实际的px,第二个参数是父布局 67 | * 68 | * @param px ui图中的大小 69 | * @param parentWidth 父view在ui图中的高度 70 | * @return 71 | */ 72 | fun getRealWidth(px: Int, parentWidth: Float): Int? { 73 | return (px / parentWidth * getScreenWidth()!!).toInt() 74 | } 75 | 76 | /** 77 | * 输入UI图的尺寸,输出实际的px 78 | * 79 | * @param px ui图中的大小 80 | * @return 81 | */ 82 | fun getRealHeight(px: Int): Int? { 83 | //ui图的宽度 84 | return getRealHeight(px, STANDARD_HEIGHT.toFloat()) 85 | } 86 | 87 | /** 88 | * 输入UI图的尺寸,输出实际的px,第二个参数是父布局 89 | * 90 | * @param px ui图中的大小 91 | * @param parentHeight 父view在ui图中的高度 92 | * @return 93 | */ 94 | fun getRealHeight(px: Int, parentHeight: Float): Int? { 95 | return (px / parentHeight * getScreenHeight()!!).toInt() 96 | } 97 | 98 | /** 99 | * dip转px 100 | * @param dipValue 101 | * @return int 102 | */ 103 | fun dip2px(dipValue: Float): Int? { 104 | val scale = displayMetrics?.density 105 | return (dipValue * scale!! + 0.5f).toInt() 106 | } 107 | } -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/utils/GsonUtils.java: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.utils; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import java.lang.reflect.Type; 6 | import java.util.List; 7 | 8 | /** 9 | */ 10 | public class GsonUtils { 11 | 12 | public static String createGsonString(Object object) { 13 | Gson gson = new Gson(); 14 | String gsonString = gson.toJson(object); 15 | return gsonString; 16 | } 17 | 18 | public static T changeGsonToBean(String gsonString, Class cls) { 19 | Gson gson = new Gson(); 20 | T t = gson.fromJson(gsonString, cls); 21 | return t; 22 | } 23 | 24 | public static List changeGsonToList(String gsonString, Type type) { 25 | Gson gson = new Gson(); 26 | List list = gson.fromJson(gsonString, type); 27 | return list; 28 | } 29 | 30 | } 31 | 32 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/utils/NetworkUtil.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.utils 2 | 3 | import android.content.Context 4 | import android.net.ConnectivityManager 5 | import android.net.NetworkInfo 6 | import android.telephony.TelephonyManager 7 | import java.io.IOException 8 | import java.net.HttpURLConnection 9 | import java.net.NetworkInterface 10 | import java.net.SocketException 11 | import java.net.URL 12 | 13 | 14 | /** 15 | */ 16 | class NetworkUtil{ 17 | 18 | companion object { 19 | 20 | var NET_CNNT_BAIDU_OK = 1 // NetworkAvailable 21 | var NET_CNNT_BAIDU_TIMEOUT = 2 // no NetworkAvailable 22 | var NET_NOT_PREPARE = 3 // Net no ready 23 | var NET_ERROR = 4 //net error 24 | private val TIMEOUT = 3000 // TIMEOUT 25 | /** 26 | * check NetworkAvailable 27 | * 28 | * @param context 29 | * @return 30 | */ 31 | @JvmStatic 32 | fun isNetworkAvailable(context: Context): Boolean { 33 | val manager = context.applicationContext.getSystemService( 34 | Context.CONNECTIVITY_SERVICE) as ConnectivityManager 35 | val info = manager.activeNetworkInfo 36 | return !(null == info || !info.isAvailable) 37 | } 38 | 39 | /** 40 | * 得到ip地址 41 | * 42 | * @return 43 | */ 44 | @JvmStatic 45 | fun getLocalIpAddress(): String { 46 | var ret = "" 47 | try { 48 | val en = NetworkInterface.getNetworkInterfaces() 49 | while (en.hasMoreElements()) { 50 | val enumIpAddress = en.nextElement().inetAddresses 51 | while (enumIpAddress.hasMoreElements()) { 52 | val netAddress = enumIpAddress.nextElement() 53 | if (!netAddress.isLoopbackAddress) { 54 | ret = netAddress.hostAddress.toString() 55 | } 56 | } 57 | } 58 | } catch (ex: SocketException) { 59 | ex.printStackTrace() 60 | } 61 | 62 | return ret 63 | } 64 | 65 | 66 | /** 67 | * ping "http://www.baidu.com" 68 | * 69 | * @return 70 | */ 71 | @JvmStatic 72 | private fun pingNetWork(): Boolean { 73 | var result = false 74 | var httpUrl: HttpURLConnection? = null 75 | try { 76 | httpUrl = URL("http://www.baidu.com") 77 | .openConnection() as HttpURLConnection 78 | httpUrl.connectTimeout = TIMEOUT 79 | httpUrl.connect() 80 | result = true 81 | } catch (e: IOException) { 82 | } finally { 83 | if (null != httpUrl) { 84 | httpUrl.disconnect() 85 | } 86 | } 87 | return result 88 | } 89 | 90 | /** 91 | * check is3G 92 | * 93 | * @param context 94 | * @return boolean 95 | */ 96 | @JvmStatic 97 | fun is3G(context: Context): Boolean { 98 | val connectivityManager = context 99 | .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 100 | val activeNetInfo = connectivityManager.activeNetworkInfo 101 | return activeNetInfo != null && activeNetInfo.type == ConnectivityManager.TYPE_MOBILE 102 | } 103 | 104 | /** 105 | * isWifi 106 | * 107 | * @param context 108 | * @return boolean 109 | */ 110 | @JvmStatic 111 | fun isWifi(context: Context): Boolean { 112 | val connectivityManager = context 113 | .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 114 | val activeNetInfo = connectivityManager.activeNetworkInfo 115 | return activeNetInfo != null && activeNetInfo.type == ConnectivityManager.TYPE_WIFI 116 | } 117 | 118 | /** 119 | * is2G 120 | * 121 | * @param context 122 | * @return boolean 123 | */ 124 | @JvmStatic 125 | fun is2G(context: Context): Boolean { 126 | val connectivityManager = context 127 | .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 128 | val activeNetInfo = connectivityManager.activeNetworkInfo 129 | return activeNetInfo != null && (activeNetInfo.subtype == TelephonyManager.NETWORK_TYPE_EDGE 130 | || activeNetInfo.subtype == TelephonyManager.NETWORK_TYPE_GPRS || activeNetInfo 131 | .subtype == TelephonyManager.NETWORK_TYPE_CDMA) 132 | } 133 | 134 | /** 135 | * is wifi on 136 | */ 137 | @JvmStatic 138 | fun isWifiEnabled(context: Context): Boolean { 139 | val mgrConn = context 140 | .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 141 | val mgrTel = context 142 | .getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager 143 | return mgrConn.activeNetworkInfo != null && mgrConn 144 | .activeNetworkInfo.state == NetworkInfo.State.CONNECTED || mgrTel 145 | .networkType == TelephonyManager.NETWORK_TYPE_UMTS 146 | } 147 | 148 | } 149 | 150 | 151 | 152 | } -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/utils/Preference.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.utils 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.content.SharedPreferences 6 | import java.io.* 7 | import kotlin.reflect.KProperty 8 | 9 | /** 10 | * 12/11. 11 | * desc:kotlin委托属性+SharedPreference实例 12 | */ 13 | class Preference constructor(val context: Context, val name: String, private val default: T) { 14 | 15 | private val mcontext: Context 16 | 17 | private val file_name = "kotlin_mvp_file" 18 | 19 | private val prefs: SharedPreferences 20 | 21 | init { 22 | this.mcontext = context 23 | prefs = mcontext.getSharedPreferences(file_name, Context.MODE_PRIVATE) 24 | } 25 | 26 | 27 | /** 28 | * 删除全部数据 29 | */ 30 | fun clearPreference() { 31 | prefs.edit().clear().apply() 32 | } 33 | 34 | /** 35 | * 根据key删除存储数据 36 | */ 37 | fun clearPreference(key: String) { 38 | prefs.edit().remove(key).apply() 39 | } 40 | 41 | 42 | operator fun getValue(thisRef: Any?, property: KProperty<*>): T { 43 | return getSharedPreferences(name, default) 44 | } 45 | 46 | operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { 47 | putSharedPreferences(name, value) 48 | } 49 | 50 | @SuppressLint("CommitPrefEdits") 51 | private fun putSharedPreferences(name: String, value: T) = with(prefs.edit()) { 52 | when (value) { 53 | is Long -> putLong(name, value) 54 | is String -> putString(name, value) 55 | is Int -> putInt(name, value) 56 | is Boolean -> putBoolean(name, value) 57 | is Float -> putFloat(name, value) 58 | else -> putString(name, serialize(value)) 59 | }.apply() 60 | } 61 | 62 | @Suppress("UNCHECKED_CAST") 63 | private fun getSharedPreferences(name: String, default: T): T = with(prefs) { 64 | val res: Any = when (default) { 65 | is Long -> getLong(name, default) 66 | is String -> getString(name, default) 67 | is Int -> getInt(name, default) 68 | is Boolean -> getBoolean(name, default) 69 | is Float -> getFloat(name, default) 70 | else -> deSerialization(getString(name, serialize(default))) 71 | } 72 | return res as T 73 | } 74 | 75 | 76 | /** 77 | * 序列化对象 78 | 79 | * @param person 80 | * * 81 | * @return 82 | * * 83 | * @throws IOException 84 | */ 85 | @Throws(IOException::class) 86 | private fun serialize(obj: A): String { 87 | val byteArrayOutputStream = ByteArrayOutputStream() 88 | val objectOutputStream = ObjectOutputStream( 89 | byteArrayOutputStream 90 | ) 91 | objectOutputStream.writeObject(obj) 92 | var serStr = byteArrayOutputStream.toString("ISO-8859-1") 93 | serStr = java.net.URLEncoder.encode(serStr, "UTF-8") 94 | objectOutputStream.close() 95 | byteArrayOutputStream.close() 96 | return serStr 97 | } 98 | 99 | /** 100 | * 反序列化对象 101 | 102 | * @param str 103 | * * 104 | * @return 105 | * * 106 | * @throws IOException 107 | * * 108 | * @throws ClassNotFoundException 109 | */ 110 | @Suppress("UNCHECKED_CAST") 111 | @Throws(IOException::class, ClassNotFoundException::class) 112 | private fun deSerialization(str: String): A { 113 | val redStr = java.net.URLDecoder.decode(str, "UTF-8") 114 | val byteArrayInputStream = ByteArrayInputStream( 115 | redStr.toByteArray(charset("ISO-8859-1")) 116 | ) 117 | val objectInputStream = ObjectInputStream( 118 | byteArrayInputStream 119 | ) 120 | val obj = objectInputStream.readObject() as A 121 | objectInputStream.close() 122 | byteArrayInputStream.close() 123 | return obj 124 | } 125 | 126 | 127 | /** 128 | * 查询某个key是否已经存在 129 | * 130 | * @param key 131 | * @return 132 | */ 133 | fun contains(key: String): Boolean { 134 | return prefs.contains(key) 135 | } 136 | 137 | /** 138 | * 返回所有的键值对 139 | * 140 | * @param context 141 | * @return 142 | */ 143 | fun getAll(): Map { 144 | return prefs.all 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/view/LoadingView.java: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.view; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Paint; 7 | import android.graphics.Path; 8 | import android.graphics.PointF; 9 | import android.graphics.RectF; 10 | import android.os.Handler; 11 | import android.os.Message; 12 | import android.util.AttributeSet; 13 | import android.view.View; 14 | import com.kotlinmvp.R; 15 | 16 | 17 | import java.lang.ref.WeakReference; 18 | 19 | /** 20 | * 12/4. 21 | * desc:LoadingView 加载动画 22 | */ 23 | 24 | public class LoadingView extends View { 25 | 26 | private int mOuterCircleRadius; 27 | private int mOuterCircleColor; 28 | private int mInnerTriangleRadius; 29 | private int mInnerTriangleColor; 30 | private int mBackgroundColor; 31 | private int mStrokeWidth; 32 | private boolean mIsNeedBackground; 33 | 34 | private Paint mPaint; 35 | private Paint mTrianglePaint; 36 | private Paint mBackGroundPaint; 37 | private boolean isReverse = false; 38 | private int mProgress = 0; 39 | private int mStartAngle = -90; 40 | private int mRotateAngle = 0; 41 | private int mDel = 30; 42 | private RectF mRectF; 43 | private RectF mRoundRectF; 44 | private Path mPath; 45 | private PointF mRotateCenter; 46 | private MyHandler mHandler; 47 | 48 | 49 | public LoadingView(Context context) { 50 | this(context, null); 51 | 52 | } 53 | 54 | public LoadingView(Context context, AttributeSet attrs) { 55 | this(context, attrs, 0); 56 | } 57 | 58 | public LoadingView(Context context, AttributeSet attrs, int defStyleAttr) { 59 | super(context, attrs, defStyleAttr); 60 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LoadingView); 61 | mOuterCircleRadius = typedArray.getDimensionPixelSize(R.styleable.LoadingView_outerCircleRadius, 50); 62 | mOuterCircleColor = typedArray.getColor(R.styleable.LoadingView_outerCircleColor, 0xFF228B22); 63 | mInnerTriangleRadius = typedArray.getDimensionPixelSize(R.styleable.LoadingView_innerTriangleRadius, 25); 64 | mInnerTriangleColor = typedArray.getColor(R.styleable.LoadingView_innerTriangleColor, 0xFF228B22); 65 | mIsNeedBackground = typedArray.getBoolean(R.styleable.LoadingView_isNeedBackground, true); 66 | mBackgroundColor = typedArray.getColor(R.styleable.LoadingView_backgroundColor, 0xBB222222); 67 | mStrokeWidth = typedArray.getDimensionPixelSize(R.styleable.LoadingView_strokeWidth, 5); 68 | 69 | typedArray.recycle(); 70 | 71 | init(); 72 | } 73 | 74 | private void init() { 75 | //设置画进度圈的画笔 76 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 77 | mPaint.setColor(mOuterCircleColor); 78 | mPaint.setStrokeWidth(mStrokeWidth); 79 | mPaint.setStyle(Paint.Style.STROKE); 80 | 81 | //设置画三角形的画笔 82 | mTrianglePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 83 | mTrianglePaint.setColor(mInnerTriangleColor); 84 | 85 | //设置画背景的画笔 86 | mBackGroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 87 | mBackGroundPaint.setColor(mBackgroundColor); 88 | 89 | mPath = new Path(); 90 | mRotateCenter = new PointF(); 91 | mRoundRectF = new RectF(); 92 | mHandler = new MyHandler(this); 93 | } 94 | 95 | 96 | @Override 97 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 98 | super.onSizeChanged(w, h, oldw, oldh); 99 | //判断外圆的半径 100 | mOuterCircleRadius = (int) (Math.min(mOuterCircleRadius, 101 | (Math.min(w - getPaddingRight() - getPaddingLeft(), h - getPaddingTop() - getPaddingBottom()) - 4 * mPaint.getStrokeWidth()) / 2)); 102 | if (mOuterCircleRadius < 0) { 103 | mStrokeWidth = Math.min(w - getPaddingRight() - getPaddingLeft(), h - getPaddingTop() - getPaddingBottom()) / 2; 104 | mOuterCircleRadius = Math.min(w - getPaddingRight() - getPaddingLeft(), h - getPaddingTop() - getPaddingBottom()) / 4; 105 | } 106 | float left = (w - 2 * mOuterCircleRadius) / 2; 107 | float top = (h - 2 * mOuterCircleRadius) / 2; 108 | float diameter = 2 * mOuterCircleRadius; 109 | mRectF = new RectF(left, top, left + diameter, top + diameter); 110 | 111 | //判断内圆的半径大小 112 | mInnerTriangleRadius = (mInnerTriangleRadius < mOuterCircleRadius) ? mInnerTriangleRadius : 3 * mOuterCircleRadius / 5; 113 | if (mInnerTriangleRadius < 0) { 114 | mInnerTriangleRadius = 0; 115 | } 116 | //计算内圆的圆心,圆心应该和外圆圆心相同 117 | float centerX = left + mOuterCircleRadius; 118 | float centerY = top + mOuterCircleRadius; 119 | //计算内圆的内接三角形的三个定点组成的path 120 | mPath.moveTo(centerX - mInnerTriangleRadius / 2, (float) (centerY - Math.sqrt(3) * mInnerTriangleRadius / 2)); 121 | mPath.lineTo(centerX + mInnerTriangleRadius, centerY); 122 | mPath.lineTo(centerX - mInnerTriangleRadius / 2, (float) (centerY + Math.sqrt(3) * mInnerTriangleRadius / 2)); 123 | mPath.close(); 124 | 125 | mRotateCenter.set(getMeasuredWidth() / 2, getMeasuredHeight() / 2); 126 | mRoundRectF.left = 0; 127 | mRoundRectF.top = 0; 128 | mRoundRectF.right = w; 129 | mRoundRectF.bottom = h; 130 | } 131 | 132 | @Override 133 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 134 | setMeasuredDimension(measureSize(widthMeasureSpec, 140), measureSize(heightMeasureSpec, 140)); 135 | } 136 | 137 | private int measureSize(int measureSpec, int defaultSize) { 138 | int specMode = MeasureSpec.getMode(measureSpec); 139 | int specSize = MeasureSpec.getSize(measureSpec); 140 | 141 | int resultSize = defaultSize; 142 | switch (specMode) { 143 | case MeasureSpec.EXACTLY: 144 | resultSize = specSize; 145 | break; 146 | case MeasureSpec.AT_MOST: 147 | case MeasureSpec.UNSPECIFIED: 148 | resultSize = Math.min(specSize, resultSize); 149 | break; 150 | } 151 | return resultSize; 152 | } 153 | 154 | 155 | @Override 156 | protected void onDraw(Canvas canvas) { 157 | if (mIsNeedBackground) { 158 | canvas.drawRoundRect(mRoundRectF, 8, 8, mBackGroundPaint); 159 | } 160 | 161 | if (isReverse) { 162 | mProgress -= mDel; 163 | mStartAngle += mDel; 164 | if (mStartAngle >= 270) { 165 | mStartAngle = -90; 166 | isReverse = false; 167 | } 168 | mRotateAngle += mDel; 169 | if (mRotateAngle >= 360) { 170 | mRotateAngle = 0; 171 | } 172 | canvas.save(); 173 | canvas.rotate(mRotateAngle, mRotateCenter.x, mRotateCenter.y); 174 | canvas.drawPath(mPath, mTrianglePaint); 175 | canvas.restore(); 176 | } else { 177 | mProgress += mDel; 178 | if (mProgress >= 360) { 179 | isReverse = true; 180 | } 181 | canvas.drawPath(mPath, mTrianglePaint); 182 | } 183 | 184 | canvas.drawArc(mRectF, mStartAngle, mProgress, false, mPaint); 185 | mHandler.sendEmptyMessageDelayed(MyHandler.REFRESH_VIEW, 80); 186 | } 187 | 188 | 189 | private static class MyHandler extends Handler { 190 | static final int REFRESH_VIEW = 0; 191 | private final WeakReference mLoadingViewWeakReference; 192 | 193 | private MyHandler(LoadingView loadingView) { 194 | mLoadingViewWeakReference = new WeakReference<>(loadingView); 195 | } 196 | 197 | @Override 198 | public void handleMessage(Message msg) { 199 | if (mLoadingViewWeakReference.get() != null) { 200 | switch (msg.what) { 201 | case REFRESH_VIEW: 202 | mLoadingViewWeakReference.get().postInvalidate(); 203 | break; 204 | } 205 | } 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/view/ViewAnimUtils.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.view 2 | 3 | import android.animation.Animator 4 | import android.animation.AnimatorListenerAdapter 5 | import android.annotation.TargetApi 6 | import android.content.Context 7 | import android.os.Build 8 | import android.support.annotation.ColorRes 9 | import android.support.annotation.RequiresApi 10 | import android.support.v4.content.ContextCompat 11 | import android.view.View 12 | import android.view.ViewAnimationUtils 13 | import android.view.animation.AccelerateDecelerateInterpolator 14 | 15 | /** 16 | * 12/1. 17 | * desc: View 动画工具类 18 | */ 19 | 20 | object ViewAnimUtils { 21 | 22 | interface OnRevealAnimationListener { 23 | fun onRevealHide() 24 | 25 | fun onRevealShow() 26 | } 27 | 28 | 29 | 30 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 31 | fun animateRevealShow( 32 | context: Context, view: View, 33 | startRadius: Int, @ColorRes color: Int, 34 | listener: OnRevealAnimationListener) { 35 | val cx = (view.left + view.right) / 2 36 | val cy = (view.top + view.bottom) / 2 37 | 38 | val finalRadius = Math.hypot(view.width.toDouble(), view.height.toDouble()).toFloat() 39 | 40 | // 设置圆形显示动画 41 | val anim = ViewAnimationUtils.createCircularReveal(view, cx, cy, startRadius.toFloat(), finalRadius) 42 | anim.duration = 300 43 | anim.interpolator = AccelerateDecelerateInterpolator() 44 | anim.addListener(object : AnimatorListenerAdapter() { 45 | override fun onAnimationEnd(animation: Animator) { 46 | super.onAnimationEnd(animation) 47 | view.visibility = View.VISIBLE 48 | listener.onRevealShow() 49 | } 50 | 51 | override fun onAnimationStart(animation: Animator) { 52 | super.onAnimationStart(animation) 53 | view.setBackgroundColor(ContextCompat.getColor(context, color)) 54 | } 55 | }) 56 | 57 | anim.start() 58 | } 59 | 60 | // 圆圈凝聚效果 61 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 62 | fun animateRevealHide( 63 | context: Context, view: View, 64 | finalRadius: Int, @ColorRes color: Int, 65 | listener: OnRevealAnimationListener 66 | ) { 67 | val cx = (view.left + view.right) / 2 68 | val cy = (view.top + view.bottom) / 2 69 | val initialRadius = view.width 70 | // 与入场动画的区别就是圆圈起始和终止的半径相反 71 | val anim = ViewAnimationUtils.createCircularReveal(view, cx, cy, initialRadius.toFloat(), finalRadius.toFloat()) 72 | anim.duration = 300 73 | anim.interpolator = AccelerateDecelerateInterpolator() 74 | anim.addListener(object : AnimatorListenerAdapter() { 75 | override fun onAnimationStart(animation: Animator) { 76 | super.onAnimationStart(animation) 77 | view.setBackgroundColor(ContextCompat.getColor(context, color)) 78 | } 79 | 80 | override fun onAnimationEnd(animation: Animator) { 81 | super.onAnimationEnd(animation) 82 | listener.onRevealHide() 83 | view.visibility = View.INVISIBLE 84 | } 85 | }) 86 | anim.start() 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/view/recyclerview/MultipleType.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.view.recyclerview 2 | 3 | /** 4 | * 11/22. 5 | * desc: 多布局条目类型 6 | */ 7 | 8 | interface MultipleType { 9 | fun getLayoutId(item: T, position: Int): Int 10 | } 11 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/view/recyclerview/ViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.view.recyclerview 2 | 3 | import android.annotation.SuppressLint 4 | import android.support.v7.widget.RecyclerView 5 | import android.util.SparseArray 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.ImageView 9 | import android.widget.TextView 10 | 11 | @Suppress("UNCHECKED_CAST") 12 | /** 13 | * 11/22. 14 | * desc: 15 | */ 16 | 17 | class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 18 | 19 | //用于缓存已找的界面 20 | private var mView: SparseArray?=null 21 | 22 | init { 23 | mView = SparseArray() 24 | } 25 | 26 | fun getView(viewId: Int): T { 27 | //对已有的view做缓存 28 | var view: View? = mView?.get(viewId) 29 | //使用缓存的方式减少findViewById的次数 30 | if (view == null) { 31 | view = itemView.findViewById(viewId) 32 | mView?.put(viewId, view) 33 | } 34 | return view as T 35 | } 36 | 37 | 38 | fun getViewGroup(viewId: Int): T { 39 | //对已有的view做缓存 40 | var view: View? = mView?.get(viewId) 41 | //使用缓存的方式减少findViewById的次数 42 | if (view == null) { 43 | view = itemView.findViewById(viewId) 44 | mView?.put(viewId, view) 45 | } 46 | return view as T 47 | } 48 | 49 | @SuppressLint("SetTextI18n") 50 | //通用的功能进行封装 设置文本 设置条目点击事件 设置图片 51 | fun setText(viewId: Int, text: CharSequence): ViewHolder { 52 | val view = getView(viewId) 53 | view.text = "" + text 54 | //希望可以链式调用 55 | return this 56 | } 57 | 58 | fun setHintText(viewId: Int, text: CharSequence): ViewHolder { 59 | val view = getView(viewId) 60 | view.hint = "" + text 61 | return this 62 | } 63 | 64 | /** 65 | * 设置本地图片 66 | * 67 | * @param viewId 68 | * @param resId 69 | * @return 70 | */ 71 | fun setImageResource(viewId: Int, resId: Int): ViewHolder { 72 | val iv = getView(viewId) 73 | iv.setImageResource(resId) 74 | return this 75 | } 76 | 77 | /** 78 | * 加载图片资源路径 79 | * 80 | * @param viewId 81 | * @param imageLoader 82 | * @return 83 | */ 84 | fun setImagePath(viewId: Int, imageLoader: HolderImageLoader): ViewHolder { 85 | val iv = getView(viewId) 86 | imageLoader.loadImage(iv, imageLoader.path) 87 | return this 88 | } 89 | 90 | abstract class HolderImageLoader(val path: String) { 91 | 92 | /** 93 | * 需要去复写这个方法加载图片 94 | * 95 | * @param iv 96 | * @param path 97 | */ 98 | abstract fun loadImage(iv: ImageView, path: String) 99 | } 100 | 101 | /** 102 | * 设置View的Visibility 103 | */ 104 | fun setViewVisibility(viewId: Int, visibility: Int): ViewHolder { 105 | getView(viewId).visibility = visibility 106 | return this 107 | } 108 | 109 | /** 110 | * 设置条目点击事件 111 | */ 112 | fun setOnItemClickListener(listener: View.OnClickListener) { 113 | itemView.setOnClickListener(listener) 114 | } 115 | 116 | /** 117 | * 设置条目长按事件 118 | */ 119 | fun setOnItemLongClickListener(listener: View.OnLongClickListener) { 120 | itemView.setOnLongClickListener(listener) 121 | } 122 | 123 | } -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/view/recyclerview/adapter/CommonAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.view.recyclerview.adapter 2 | 3 | import android.content.Context 4 | import android.support.v7.widget.RecyclerView 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | 9 | import com.kotlinmvp.view.recyclerview.MultipleType 10 | import com.kotlinmvp.view.recyclerview.ViewHolder 11 | 12 | /** 13 | * 11/22. 14 | * desc: 通用的 Adapter 15 | */ 16 | 17 | abstract class CommonAdapter(var mContext: Context, var mData: ArrayList, //条目布局 18 | private var mLayoutId: Int) : RecyclerView.Adapter() { 19 | protected var mInflater: LayoutInflater? = null 20 | private var mTypeSupport: MultipleType? = null 21 | 22 | //使用接口回调点击事件 23 | private var mItemClickListener: OnItemClickListener? = null 24 | 25 | //使用接口回调点击事件 26 | private var mItemLongClickListener: OnItemLongClickListener? = null 27 | 28 | init { 29 | mInflater = LayoutInflater.from(mContext) 30 | } 31 | 32 | //需要多布局 33 | constructor(context: Context, data: ArrayList, typeSupport: MultipleType) : this(context, data, -1) { 34 | this.mTypeSupport = typeSupport 35 | } 36 | 37 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 38 | if (mTypeSupport != null) { 39 | //需要多布局 40 | mLayoutId = viewType 41 | } 42 | //创建view 43 | val view = mInflater?.inflate(mLayoutId, parent, false) 44 | return ViewHolder(view!!) 45 | } 46 | 47 | override fun getItemViewType(position: Int): Int { 48 | //多布局问题 49 | return mTypeSupport?.getLayoutId(mData[position], position) ?: super.getItemViewType(position) 50 | } 51 | 52 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 53 | //绑定数据 54 | bindData(holder, mData[position], position) 55 | 56 | // if (mItemClickListener != null) { 57 | // holder.itemView.setOnClickListener { mItemClickListener!!.onItemClick(mData[position], position) } 58 | // } 59 | // //长按点击事件 60 | // if (mItemLongClickListener != null) { 61 | // holder.itemView.setOnLongClickListener { mItemLongClickListener!!.onItemLongClick(mData[position], position) } 62 | // } 63 | //条目点击事件 64 | mItemClickListener?.let { 65 | holder.itemView.setOnClickListener { mItemClickListener!!.onItemClick(mData[position], position) } 66 | } 67 | 68 | //长按点击事件 69 | mItemLongClickListener?.let { 70 | holder.itemView.setOnLongClickListener { mItemLongClickListener!!.onItemLongClick(mData[position], position) } 71 | } 72 | } 73 | 74 | /** 75 | * 将必要参数传递出去 76 | * 77 | * @param holder 78 | * @param data 79 | * @param position 80 | */ 81 | protected abstract fun bindData(holder: ViewHolder, data: T, position: Int) 82 | 83 | override fun getItemCount(): Int { 84 | return mData.size 85 | } 86 | 87 | fun setOnItemClickListener(itemClickListener: OnItemClickListener) { 88 | this.mItemClickListener = itemClickListener 89 | } 90 | 91 | fun setOnItemLongClickListener(itemLongClickListener: OnItemLongClickListener) { 92 | this.mItemLongClickListener = itemLongClickListener 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/view/recyclerview/adapter/OnItemClickListener.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.view.recyclerview.adapter 2 | 3 | 4 | interface OnItemClickListener { 5 | 6 | fun onItemClick(obj: Any?, position: Int) 7 | } 8 | -------------------------------------------------------------------------------- /common_lib/src/main/java/com/kotlinmvp/view/recyclerview/adapter/OnItemLongClickListener.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinmvp.view.recyclerview.adapter 2 | 3 | /** 4 | * 5 | * Description: Adapter条目的长按事件 6 | */ 7 | interface OnItemLongClickListener { 8 | 9 | fun onItemLongClick(obj: Any?, position: Int): Boolean 10 | 11 | } 12 | -------------------------------------------------------------------------------- /common_lib/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /common_lib/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | #549cf8 5 | #EF5362 6 | 7 | #9a9a9a 8 | 9 | #5000 10 | 11 | 12 | #EF5362 13 | #FE6D4B 14 | #FFCF47 15 | #9FD661 16 | #3FD0AD 17 | #2BBDF3 18 | #00b0ff 19 | #AC8FEF 20 | #EE85C1 21 | 22 | #ff4081 23 | #dd2c00 24 | #ee447AA4 25 | 26 | 27 | 28 | 29 | 30 | #CCFFFFFF 31 | 32 | #EEEEEE 33 | 34 | 35 | #FFFFFF 36 | #000000 37 | 38 | #87CEEB 39 | 40 | 41 | #000000 42 | 43 | #ddd 44 | #aaa 45 | 46 | @color/color_lighter_gray 47 | 48 | #00000000 49 | 50 | #7FFFD4 51 | 52 | #5000 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /common_lib/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 16dp 3 | 51dp 4 | 5 | 6 | 10dp 7 | 15dp 8 | 10dp 9 | 5dp 10 | 11 | 10sp 12 | 12sp 13 | 14sp 14 | 16sp 15 | 18sp 16 | 17 | 0.5dp 18 | 19 | -------------------------------------------------------------------------------- /common_lib/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /common_lib/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Kotlin Mvp 3 | 4 | 5 | 6 | 亿 7 | 万亿 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /common_lib/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 19 | 20 | 21 | 34 | 35 | 36 | 40 | 41 | 48 | 49 | 55 | 56 | 57 | 64 | 65 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /config.gradle: -------------------------------------------------------------------------------- 1 | ext{ 2 | 3 | android = [ 4 | compileSdkVersion: 27, 5 | buildToolsVersion: "27.0.3", 6 | minSdkVersion : 19, 7 | targetSdkVersion : 27, 8 | versionCode : 6, 9 | versionName : "1.3.0" 10 | ] 11 | 12 | dependVersion = [ 13 | androidSupportSdkVersion: "27.1.1", 14 | retrofitSdkVersion : "2.3.0", 15 | glideSdkVersion : "4.7.1", 16 | rxJava : "2.1.5", 17 | rxAndroid : "2.0.1" 18 | ] 19 | 20 | supportDeps = [ 21 | //-------- support ------- 22 | supportv4 : "com.android.support:support-v4:$dependVersion.androidSupportSdkVersion", 23 | appcompatv7 : "com.android.support:appcompat-v7:$dependVersion.androidSupportSdkVersion", 24 | cardview : "com.android.support:cardview-v7:$dependVersion.androidSupportSdkVersion", 25 | design : "com.android.support:design:$dependVersion.androidSupportSdkVersion", 26 | annotations : "com.android.support:support-annotations:$dependVersion.androidSupportSdkVersion" 27 | ] 28 | 29 | 30 | retrofit = [ 31 | //------ retrofit和RxJava --------- 32 | retrofit : "com.squareup.retrofit2:retrofit:$dependVersion.retrofitSdkVersion", 33 | retrofitConverterGson : "com.squareup.retrofit2:converter-gson:$dependVersion.retrofitSdkVersion", 34 | retrofitAdapterRxjava2 : "com.squareup.retrofit2:adapter-rxjava2:$dependVersion.retrofitSdkVersion", 35 | okhttp3LoggerInterceptor: 'com.squareup.okhttp3:logging-interceptor:3.4.1', 36 | 37 | //stetho 38 | stetho : 'com.facebook.stetho:stetho:1.3.1', 39 | stetho_okhttp3 : 'com.facebook.stetho:stetho-okhttp3:1.3.1' 40 | ] 41 | 42 | rxJava = [ 43 | rxJava : "io.reactivex.rxjava2:rxjava:$dependVersion.rxJava", 44 | rxAndroid : "io.reactivex.rxjava2:rxandroid:$dependVersion.rxAndroid" 45 | ] 46 | 47 | 48 | glide = "com.github.bumptech.glide:glide:$dependVersion.glideSdkVersion" 49 | glideCompiler = "com.github.bumptech.glide:compiler:$dependVersion.glideSdkVersion" 50 | glideOkhttp = "com.github.bumptech.glide:okhttp3-integration:$dependVersion.glideSdkVersion" 51 | 52 | 53 | supportLibs = supportDeps.values() 54 | networkLibs = retrofit.values() 55 | rxJavaLibs = rxJava.values() 56 | otherLibs = [glide] 57 | 58 | // APT 59 | annotationProcessorLibs =[glideCompiler] 60 | 61 | } -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayshier/MVPKotlin_Commonlib/d9aa24e8f99520d99ec5d3cacd5936fc1ecdba53/demo.gif -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # Kotlin code style for this project: "official" or "obsolete": 15 | kotlin.code.style=official 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayshier/MVPKotlin_Commonlib/d9aa24e8f99520d99ec5d3cacd5936fc1ecdba53/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /multiple-status-view/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.iml -------------------------------------------------------------------------------- /multiple-status-view/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | 5 | compileSdkVersion rootProject.ext.android.compileSdkVersion 6 | buildToolsVersion '28.0.3' 7 | 8 | defaultConfig { 9 | minSdkVersion rootProject.ext.android.minSdkVersion 10 | targetSdkVersion rootProject.ext.android.targetSdkVersion 11 | versionCode rootProject.ext.android.versionCode 12 | versionName rootProject.ext.android.versionName 13 | } 14 | 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { } -------------------------------------------------------------------------------- /multiple-status-view/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\AndroidStudio\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /multiple-status-view/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /multiple-status-view/src/main/java/com/classic/common/MultipleStatusView.java: -------------------------------------------------------------------------------- 1 | package com.classic.common; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.util.AttributeSet; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.RelativeLayout; 10 | 11 | import java.util.ArrayList; 12 | 13 | /** 14 | * 类描述: 一个方便在多种状态切换的view 15 | * 16 | * 创建时间: 2016/1/15 10:20. 17 | */ 18 | @SuppressWarnings("unused") 19 | public class MultipleStatusView extends RelativeLayout { 20 | private static final String TAG = "MultipleStatusView"; 21 | 22 | private static final RelativeLayout.LayoutParams DEFAULT_LAYOUT_PARAMS = 23 | new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, 24 | RelativeLayout.LayoutParams.MATCH_PARENT); 25 | 26 | public static final int STATUS_CONTENT = 0x00; 27 | public static final int STATUS_LOADING = 0x01; 28 | public static final int STATUS_EMPTY = 0x02; 29 | public static final int STATUS_ERROR = 0x03; 30 | public static final int STATUS_NO_NETWORK = 0x04; 31 | 32 | private static final int NULL_RESOURCE_ID = -1; 33 | 34 | private View mEmptyView; 35 | private View mErrorView; 36 | private View mLoadingView; 37 | private View mNoNetworkView; 38 | private View mContentView; 39 | private int mEmptyViewResId; 40 | private int mErrorViewResId; 41 | private int mLoadingViewResId; 42 | private int mNoNetworkViewResId; 43 | private int mContentViewResId; 44 | 45 | private int mViewStatus; 46 | private LayoutInflater mInflater; 47 | private OnClickListener mOnRetryClickListener; 48 | 49 | private final ArrayList mOtherIds = new ArrayList<>(); 50 | 51 | public MultipleStatusView(Context context) { 52 | this(context, null); 53 | } 54 | 55 | public MultipleStatusView(Context context, AttributeSet attrs) { 56 | this(context, attrs, 0); 57 | } 58 | 59 | public MultipleStatusView(Context context, AttributeSet attrs, int defStyleAttr) { 60 | super(context, attrs, defStyleAttr); 61 | final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MultipleStatusView, defStyleAttr, 0); 62 | mEmptyViewResId = a.getResourceId(R.styleable.MultipleStatusView_emptyView, R.layout.empty_view); 63 | mErrorViewResId = a.getResourceId(R.styleable.MultipleStatusView_errorView, R.layout.error_view); 64 | mLoadingViewResId = a.getResourceId(R.styleable.MultipleStatusView_loadingView, R.layout.loading_view); 65 | mNoNetworkViewResId = a.getResourceId(R.styleable.MultipleStatusView_noNetworkView, R.layout.no_network_view); 66 | mContentViewResId = a.getResourceId(R.styleable.MultipleStatusView_contentView, NULL_RESOURCE_ID); 67 | a.recycle(); 68 | mInflater = LayoutInflater.from(getContext()); 69 | } 70 | 71 | @Override protected void onFinishInflate() { 72 | super.onFinishInflate(); 73 | showContent(); 74 | } 75 | 76 | @Override protected void onDetachedFromWindow() { 77 | super.onDetachedFromWindow(); 78 | clear(mEmptyView, mLoadingView, mErrorView, mNoNetworkView); 79 | if (null != mOtherIds) { 80 | mOtherIds.clear(); 81 | } 82 | if (null != mOnRetryClickListener) { 83 | mOnRetryClickListener = null; 84 | } 85 | mInflater = null; 86 | } 87 | 88 | /** 89 | * 获取当前状态 90 | */ 91 | public int getViewStatus() { 92 | return mViewStatus; 93 | } 94 | 95 | /** 96 | * 设置重试点击事件 97 | * 98 | * @param onRetryClickListener 重试点击事件 99 | */ 100 | public void setOnRetryClickListener(OnClickListener onRetryClickListener) { 101 | this.mOnRetryClickListener = onRetryClickListener; 102 | } 103 | 104 | /** 105 | * 显示空视图 106 | */ 107 | public final void showEmpty() { 108 | showEmpty(mEmptyViewResId, DEFAULT_LAYOUT_PARAMS); 109 | } 110 | 111 | /** 112 | * 显示空视图 113 | * @param layoutId 自定义布局文件 114 | * @param layoutParams 布局参数 115 | */ 116 | public final void showEmpty(int layoutId, ViewGroup.LayoutParams layoutParams) { 117 | showEmpty(inflateView(layoutId), layoutParams); 118 | } 119 | 120 | /** 121 | * 显示空视图 122 | * @param view 自定义视图 123 | * @param layoutParams 布局参数 124 | */ 125 | public final void showEmpty(View view, ViewGroup.LayoutParams layoutParams) { 126 | checkNull(view, "Empty view is null!"); 127 | mViewStatus = STATUS_EMPTY; 128 | if (null == mEmptyView) { 129 | mEmptyView = view; 130 | View emptyRetryView = mEmptyView.findViewById(R.id.empty_retry_view); 131 | if (null != mOnRetryClickListener && null != emptyRetryView) { 132 | emptyRetryView.setOnClickListener(mOnRetryClickListener); 133 | } 134 | mOtherIds.add(mEmptyView.getId()); 135 | addView(mEmptyView, 0, layoutParams); 136 | } 137 | showViewById(mEmptyView.getId()); 138 | } 139 | 140 | /** 141 | * 显示错误视图 142 | */ 143 | public final void showError() { 144 | showError(mErrorViewResId, DEFAULT_LAYOUT_PARAMS); 145 | } 146 | 147 | /** 148 | * 显示错误视图 149 | * @param layoutId 自定义布局文件 150 | * @param layoutParams 布局参数 151 | */ 152 | public final void showError(int layoutId, ViewGroup.LayoutParams layoutParams) { 153 | showError(inflateView(layoutId), layoutParams); 154 | } 155 | 156 | /** 157 | * 显示错误视图 158 | * @param view 自定义视图 159 | * @param layoutParams 布局参数 160 | */ 161 | public final void showError(View view, ViewGroup.LayoutParams layoutParams) { 162 | checkNull(view, "Error view is null!"); 163 | mViewStatus = STATUS_ERROR; 164 | if (null == mErrorView) { 165 | mErrorView = view; 166 | View errorRetryView = mErrorView.findViewById(R.id.error_retry_view); 167 | if (null != mOnRetryClickListener && null != errorRetryView) { 168 | errorRetryView.setOnClickListener(mOnRetryClickListener); 169 | } 170 | mOtherIds.add(mErrorView.getId()); 171 | addView(mErrorView, 0, layoutParams); 172 | } 173 | showViewById(mErrorView.getId()); 174 | } 175 | 176 | /** 177 | * 显示加载中视图 178 | */ 179 | public final void showLoading() { 180 | showLoading(mLoadingViewResId, DEFAULT_LAYOUT_PARAMS); 181 | } 182 | 183 | /** 184 | * 显示加载中视图 185 | * @param layoutId 自定义布局文件 186 | * @param layoutParams 布局参数 187 | */ 188 | public final void showLoading(int layoutId, ViewGroup.LayoutParams layoutParams) { 189 | showLoading(inflateView(layoutId), layoutParams); 190 | } 191 | 192 | /** 193 | * 显示加载中视图 194 | * @param view 自定义视图 195 | * @param layoutParams 布局参数 196 | */ 197 | public final void showLoading(View view, ViewGroup.LayoutParams layoutParams) { 198 | checkNull(view, "Loading view is null!"); 199 | mViewStatus = STATUS_LOADING; 200 | if (null == mLoadingView) { 201 | mLoadingView = view; 202 | mOtherIds.add(mLoadingView.getId()); 203 | addView(mLoadingView, 0, layoutParams); 204 | } 205 | showViewById(mLoadingView.getId()); 206 | } 207 | 208 | /** 209 | * 显示无网络视图 210 | */ 211 | public final void showNoNetwork() { 212 | showNoNetwork(mNoNetworkViewResId, DEFAULT_LAYOUT_PARAMS); 213 | } 214 | 215 | /** 216 | * 显示无网络视图 217 | * @param layoutId 自定义布局文件 218 | * @param layoutParams 布局参数 219 | */ 220 | public final void showNoNetwork(int layoutId, ViewGroup.LayoutParams layoutParams) { 221 | showNoNetwork(inflateView(layoutId), layoutParams); 222 | } 223 | 224 | /** 225 | * 显示无网络视图 226 | * @param view 自定义视图 227 | * @param layoutParams 布局参数 228 | */ 229 | public final void showNoNetwork(View view, ViewGroup.LayoutParams layoutParams) { 230 | checkNull(view, "No network view is null!"); 231 | mViewStatus = STATUS_NO_NETWORK; 232 | if (null == mNoNetworkView) { 233 | mNoNetworkView = view; 234 | View noNetworkRetryView = mNoNetworkView.findViewById(R.id.no_network_retry_view); 235 | if (null != mOnRetryClickListener && null != noNetworkRetryView) { 236 | noNetworkRetryView.setOnClickListener(mOnRetryClickListener); 237 | } 238 | mOtherIds.add(mNoNetworkView.getId()); 239 | addView(mNoNetworkView, 0, layoutParams); 240 | } 241 | showViewById(mNoNetworkView.getId()); 242 | } 243 | 244 | /** 245 | * 显示内容视图 246 | */ 247 | public final void showContent() { 248 | mViewStatus = STATUS_CONTENT; 249 | if (null == mContentView && mContentViewResId != NULL_RESOURCE_ID) { 250 | mContentView = mInflater.inflate(mContentViewResId, null); 251 | addView(mContentView, 0, DEFAULT_LAYOUT_PARAMS); 252 | } 253 | showContentView(); 254 | } 255 | 256 | private void showContentView() { 257 | final int childCount = getChildCount(); 258 | for (int i = 0; i < childCount; i++) { 259 | View view = getChildAt(i); 260 | view.setVisibility(mOtherIds.contains(view.getId()) ? View.GONE : View.VISIBLE); 261 | } 262 | } 263 | 264 | private View inflateView(int layoutId) { 265 | return mInflater.inflate(layoutId, null); 266 | } 267 | 268 | private void showViewById(int viewId) { 269 | final int childCount = getChildCount(); 270 | for (int i = 0; i < childCount; i++) { 271 | View view = getChildAt(i); 272 | view.setVisibility(view.getId() == viewId ? View.VISIBLE : View.GONE); 273 | } 274 | } 275 | 276 | private void checkNull(Object object, String hint) { 277 | if (null == object) { 278 | throw new NullPointerException(hint); 279 | } 280 | } 281 | 282 | private void clear(View... views) { 283 | if (null == views) { 284 | return; 285 | } 286 | try { 287 | for (View view : views) { 288 | if (null != view) { 289 | removeView(view); 290 | } 291 | } 292 | } catch (Exception e) { 293 | e.printStackTrace(); 294 | } 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /multiple-status-view/src/main/res/layout/empty_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /multiple-status-view/src/main/res/layout/error_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /multiple-status-view/src/main/res/layout/loading_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 13 | -------------------------------------------------------------------------------- /multiple-status-view/src/main/res/layout/no_network_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /multiple-status-view/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /multiple-status-view/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /multiple-status-view/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | multiple-status-view 3 | 4 | 暂无数据 5 | 加载失败 6 | 网络异常 7 | 8 | -------------------------------------------------------------------------------- /multiple-status-view/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app','common_lib', ':multiple-status-view' 2 | --------------------------------------------------------------------------------