├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── byl │ │ └── mvvm │ │ ├── App.kt │ │ ├── api │ │ ├── ApiService.kt │ │ ├── HttpUtil.kt │ │ ├── URLConstant.kt │ │ ├── error │ │ │ ├── ErrorResult.kt │ │ │ └── ErrorUtil.kt │ │ ├── interceptor │ │ │ └── LoggingInterceptor.kt │ │ ├── response │ │ │ └── BaseResult.kt │ │ └── retrofit │ │ │ ├── RetrofitClient.kt │ │ │ └── SSLContextSecurity.kt │ │ ├── event │ │ ├── Event.kt │ │ ├── EventCode.kt │ │ └── EventMessage.kt │ │ ├── ui │ │ ├── SplashActivity.kt │ │ ├── base │ │ │ ├── BaseActivity.kt │ │ │ ├── BaseAdapter.kt │ │ │ ├── BaseFragment.kt │ │ │ ├── BaseViewHolder.kt │ │ │ └── BaseViewModel.kt │ │ ├── common │ │ │ └── model │ │ │ │ └── TestModel.kt │ │ └── main │ │ │ ├── MainActivity.kt │ │ │ ├── MainFragment.kt │ │ │ ├── TestEventActivity.kt │ │ │ ├── adapter │ │ │ ├── ArticleListAdapter.kt │ │ │ └── FragmentPageAdapter.java │ │ │ ├── model │ │ │ ├── ArticleBean.kt │ │ │ └── ArticleListBean.kt │ │ │ └── vm │ │ │ ├── MainActivityViewModel.kt │ │ │ └── MainFragmentViewModel.kt │ │ ├── utils │ │ ├── GenericParadigmUtil.java │ │ ├── LogUtil.kt │ │ ├── StatusBarUtil.java │ │ ├── SysUtils.kt │ │ └── ToastUtil.kt │ │ └── widget │ │ └── ViewClickDelay.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_main.xml │ ├── activity_splash.xml │ ├── activity_test_event.xml │ ├── fragment_main.xml │ └── item_article.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 │ ├── values-v23 │ └── styles.xml │ ├── values │ ├── attr.xml │ ├── colors.xml │ ├── ids.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ ├── file_path.xml │ └── network_security_config.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | xmlns:android 17 | 18 | ^$ 19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 | xmlns:.* 28 | 29 | ^$ 30 | 31 | 32 | BY_NAME 33 | 34 |
35 |
36 | 37 | 38 | 39 | .*:id 40 | 41 | http://schemas.android.com/apk/res/android 42 | 43 | 44 | 45 |
46 |
47 | 48 | 49 | 50 | .*:name 51 | 52 | http://schemas.android.com/apk/res/android 53 | 54 | 55 | 56 |
57 |
58 | 59 | 60 | 61 | name 62 | 63 | ^$ 64 | 65 | 66 | 67 |
68 |
69 | 70 | 71 | 72 | style 73 | 74 | ^$ 75 | 76 | 77 | 78 |
79 |
80 | 81 | 82 | 83 | .* 84 | 85 | ^$ 86 | 87 | 88 | BY_NAME 89 | 90 |
91 |
92 | 93 | 94 | 95 | .* 96 | 97 | http://schemas.android.com/apk/res/android 98 | 99 | 100 | ANDROID_ATTRIBUTE_ORDER 101 | 102 |
103 |
104 | 105 | 106 | 107 | .* 108 | 109 | .* 110 | 111 | 112 | BY_NAME 113 | 114 |
115 |
116 |
117 |
118 | 119 | 121 |
122 |
-------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | 39 | 40 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MVVM 2 | Kotlin+MVVM+Retrofit+协程+ViewBinding+EventBus 3 | 4 | 注意:使用ViewBinding需要AndroidStudio版本为4.0+ 5 | 6 | 项目框架整体架构图: 7 | 8 | ![架构图](https://img-blog.csdnimg.cn/20200601152544441.png) 9 | 10 | ## 本框架的特点: 11 | 12 | 1.使用Kotlin语言 13 | 14 | 2.使用MVVM+协程开发模式,相较于常用的MVP+RXJava开发模式,会减省大量的MvpView的创建及大量的接口回调,并且不再需要Presenter的注册和注销,减少内存泄漏风险 15 | 16 | 3.ViewBinding(根据xml自动生成),你将不再需要进行findViewById的繁琐工作,比ButterKinfer更加方便 17 | 18 | 4.关于消息传递,github上有基于LiveData的LiveEventBus(https://github.com/JeremyLiao/LiveEventBus),优点是具有生命周期感知能力,不需要主动注册和注销,但缺点是书写相对麻烦,且无法统一配置,衍生版SmartEventBus虽然支持定制,但配置依然麻烦,而本项目选择继续使用EventBus的原因,则是因为EventBus的强大以及它的稳定性和灵活性,且方便统一配置(下面有讲到); 19 | 20 | ## Example 21 | 22 | ## 编写Activity(只需要传入对应的ViewModel和ViewBinding即可): 23 | 24 | class TestActivity : BaseActivity() { 25 | 26 | 27 | } 28 | 29 | Fragment同! 30 | 31 | ## 编写Adapter(只需要传入数据model类型和item的ViewBinding即可): 32 | 33 | class ArticleListAdapter(context: Activity, listDatas: ArrayList) : 34 | BaseAdapter(context, listDatas) { 35 | 36 | override fun convert(v: ItemArticleBinding, t: ArticleBean, position: Int) { 37 | Glide.with(mContext).load(t.envelopePic).into(v.ivCover) 38 | v.tvTitle.text = t.title 39 | v.tvDes.text = t.desc 40 | } 41 | 42 | } 43 | 44 | ## 添加接口(ApiService): 45 | 46 | @GET("test") 47 | suspend fun test(@QueryMap options: HashMap): BaseResult 48 | 49 | 注意:suspend不可缺少! 50 | 51 | ## 创建ViewModel: 52 | 53 | class MainViewModel : BaseViewModel() { 54 | 55 | var articlesData = MutableLiveData() 56 | 57 | fun getArticleList(page: Int, isShowLoading: Boolean) { 58 | launch({ httpUtil.getArticleList(page) }, articlesData, isShowLoading) 59 | } 60 | 61 | } 62 | 63 | ## 调用接口: 64 | 在Activity或Fragment中直接通过传入的ViewModel调用: 65 | 66 | vm.getArticleList()//调用接口 67 | 68 | vm.articlesData.observe(this, Observer {//返回结果 69 | 70 | }) 71 | 72 | ## 消息传递: 73 | 74 | 本项目中,像EventBus的注册与注销,以及消息接收全部放在了BaseActivity中,并提供了一个对外的消息处理方法,利用消息Code来区分不同消息,在需要使用消息的界面,重写该方法即可: 75 | 76 | 发送消息:App.post(EventMessage(EventCode.REFRESH)) 77 | 78 | /** 79 | * 接收消息 80 | */ 81 | override fun handleEvent(msg: EventMessage) { 82 | super.handleEvent(msg) 83 | if (msg.code == EventCode.REFRESH) { 84 | ToastUtil.showToast(mContext, "主页:刷新") 85 | page = 0 86 | vm.getArticleList(page,false) 87 | } 88 | } 89 | 90 | 这样做的好处是 91 | 92 | 1:不在需要你去手动在每个界面去注册和注销EventBus,你只用关心什么时候post消息,和什么时间接受消息即可,大大减少出错几率,并提高代码可读性; 93 | 94 | 2:可以随时更换消息传递框架,方便快捷; 95 | 96 | 当然,缺点,只有一个,就是发送消息所有活动界面都会收到,但这个所谓的缺点其实完全可以忽略! 97 | 98 | 该框架已应用到自己公司项目中,运行良好,如果后续发现有坑的地方,会及时更新! 99 | 100 | ## 2021.5.19 更新内容: 101 | 102 | 1.使用协程请求接口时,不再需要withContext-IO,有suspend关键字即可; 103 | 104 | 2.将UI更新部分,放在了viewmodel中进行,在ui中仅调用接口请求方法即可,例: 105 | 106 | class MainActivityViewModel : BaseViewModel() { 107 | 108 | var articlesData = MutableLiveData() 109 | 110 | fun getArticleList(page: Int, isShowLoading: Boolean = false) { 111 | launch({ httpUtil.getArticleList(page) }, articlesData, isShowLoading) 112 | } 113 | 114 | override fun observe(activity: Activity, owner: LifecycleOwner, viewBinding: ViewBinding) { 115 | val mContext = activity as MainActivity 116 | val vb = viewBinding as ActivityMainBinding 117 | articlesData.observe(owner, Observer { 118 | vb.refreshLayout.finishRefresh() 119 | vb.refreshLayout.finishLoadMore() 120 | if (mContext.page == 0) mContext.list!!.clear() 121 | it.datas?.let { it1 -> mContext.list!!.addAll(it1) } 122 | mContext.adapter!!.notifyDataSetChanged() 123 | }) 124 | errorData.observe(owner, Observer { 125 | vb.refreshLayout.finishRefresh() 126 | vb.refreshLayout.finishLoadMore() 127 | }) 128 | } 129 | } 130 | 131 | observe方法在BaseActivity和BaseFragment中调用,子ViewModel中重写即可,重点是有两个强制转化: 132 | 133 | val mContext = activity as MainActivity 134 | val vb = viewBinding as ActivityMainBinding 135 | 136 | mContext也可以是Fragment,即获取该ui界面声明的变量,vb则是当前ui的ViewBinding! 137 | 138 | 当然,这不是强制的,你也可以选择不使用这种方式,依然在ui界面更新ui! 139 | 140 | 第二种方式:在BaseViewModel中传入VB泛型,这样就不需要再传入ViewBinding强转了(可以对比一下第一种和第二种写法): 141 | 142 | abstract class BaseActivity, VB : ViewBinding> : AppCompatActivity() { 143 | lateinit var mContext: FragmentActivity 144 | lateinit var vm: VM 145 | lateinit var vb: VB 146 | 147 | private var loadingDialog: ProgressDialog? = null 148 | 149 | @Suppress("UNCHECKED_CAST") 150 | override fun onCreate(savedInstanceState: Bundle?) { 151 | super.onCreate(savedInstanceState) 152 | initResources() 153 | var pathfinders = ArrayList() 154 | pathfinders.add(GenericParadigmUtil.Pathfinder(0, 0)) 155 | val clazzVM = GenericParadigmUtil.parseGenericParadigm(javaClass, pathfinders) as Class 156 | vm = ViewModelProvider(this).get(clazzVM) 157 | 158 | pathfinders = ArrayList() 159 | pathfinders.add(GenericParadigmUtil.Pathfinder(0, 1)) 160 | val clazzVB = GenericParadigmUtil.parseGenericParadigm(javaClass, pathfinders) 161 | val method = clazzVB.getMethod("inflate", LayoutInflater::class.java) 162 | vb = method.invoke(null, layoutInflater) as VB 163 | 164 | vm.binding(vb) 165 | vm.observe(this, this) 166 | 167 | setContentView(vb.root) 168 | 169 | ... 170 | 171 | open class BaseViewModel : ViewModel() { 172 | 173 | lateinit var vb: VB 174 | 175 | fun binding(vb: VB) { 176 | this.vb = vb 177 | } 178 | 179 | open fun observe(activity: Activity, owner: LifecycleOwner) { 180 | 181 | } 182 | 183 | open fun observe(fragment: Fragment, owner: LifecycleOwner) { 184 | 185 | } 186 | 187 | class MainActivityViewModel : BaseViewModel() { 188 | 189 | var articlesData = MutableLiveData() 190 | 191 | fun getArticleList(page: Int, isShowLoading: Boolean = false) { 192 | launch({ httpUtil.getArticleList(page) }, articlesData, isShowLoading) 193 | } 194 | 195 | override fun observe(activity: Activity, owner: LifecycleOwner) { 196 | val mContext = activity as MainActivity 197 | articlesData.observe(owner, Observer { 198 | vb.refreshLayout.finishRefresh() 199 | vb.refreshLayout.finishLoadMore() 200 | if (mContext.page == 0) mContext.list!!.clear() 201 | it.datas?.let { it1 -> mContext.list!!.addAll(it1) } 202 | mContext.adapter!!.notifyDataSetChanged() 203 | }) 204 | errorData.observe(owner, Observer { 205 | vb.refreshLayout.finishRefresh() 206 | vb.refreshLayout.finishLoadMore() 207 | }) 208 | } 209 | } 210 | 211 | ## 2020.9.23 简化Adapter 212 | 213 | 子Adapter继承BaseAdapter,不需要再强转ViewBinding了: 214 | 215 | BaseAdapter: 216 | 217 | override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { 218 | convert(holder.v as VB, listDatas[position], position) 219 | } 220 | 221 | abstract fun convert(v: VB, t: T, position: Int) 222 | 223 | 子类Adapter: 224 | 225 | override fun convert(v: ItemArticleBinding, t: ArticleBean, position: Int) { 226 | Glide.with(mContext).load(t.envelopePic).into(v.ivCover) 227 | v.tvTitle.text = t.title 228 | v.tvDes.text = t.desc 229 | } 230 | 231 | 直接传入item对应的ViewBinding对象,更加简单便捷! 232 | 233 | 234 | ## 2020.08.31 235 | 236 | 关于BaseAdapter,这里解释下原来的说明,为什么recycleview的item高度要设置为wrap? 237 | 238 | 由于item的ViewBindding也是通过反射得到,但得到后itemView的宽高会自动被系统设为wrap,所以这里需要重新赋值宽高,之前的做法是将父容器宽高给了item,这里有问题,item的父容器就是RecyclerView,所以如果RecyclerView设置了宽高后,item显示就出问题了,因此,现在修改为item重置自身宽高,宽度match_parent,高度wrap_content,此时就要注意,item的最外层父布局的的宽高同样为match_parent和wrap_content,这适用于大多数item的布局,如果确实有需求要对item设置固定宽高,建议在子Adapter中通过代码动态设置宽高! 239 | 240 | vb.root.layoutParams = RecyclerView.LayoutParams( 241 | RecyclerView.LayoutParams.MATCH_PARENT, 242 | RecyclerView.LayoutParams.WRAP_CONTENT 243 | ) 244 | 245 | ## 2020.06.15 246 | 247 | 在使用viewpager+fragment过程中发现,某些机型应用在按返回键退出时,fragment中的contentView未销毁: 248 | 249 | if (null == contentView) { 250 | contentView = v.root 251 | //... 252 | } 253 | return contentView 254 | 255 | 导致再次打开app时,fragment并未重建,直接用的原来缓存在内存中的View致使页面出现问题,对于这种情况,目前的解决办法是在Fragment销毁时,将contentView=null: 256 | 257 | override fun onDestroyView() { 258 | super.onDestroyView() 259 | contentView = null 260 | } 261 | 262 | 263 | ## 2020.06.05: 264 | 接口调用流程简化,新增接口只需要在ApiService中添加后,即可直接在ViewModel中通过httpUtil调用,一步到位! 265 | 266 | 另附上文件上传案例代码,需要时以作参考: 267 | 268 | fun uploadFile(path: String) { 269 | val file = File(path) 270 | val map: HashMap = LinkedHashMap() 271 | val requestBody: RequestBody = RequestBody.create(MediaType.parse("image/*"), file) 272 | map["file\"; filename=\"" + file.name] = requestBody//file为后台规定参数 273 | map["name"] = RequestBody.create(MediaType.parse("text/plain"), file.name) 274 | map["arg1"] = RequestBody.create(MediaType.parse("text/plain"), "arg1")//普通参数 275 | map["arg2"] = RequestBody.create(MediaType.parse("text/plain"), "arg2") 276 | 277 | //签名(根据服务器规则) 278 | val params = LinkedHashMap() 279 | params["name"] = file.name 280 | params["arg1"] = "arg1" 281 | params["arg2"] = "arg2" 282 | val sign: String = getSign(params) 283 | map["sign"] = RequestBody.create(MediaType.parse("text/plain"), sign) 284 | 285 | launch({ httpUtil.upLoadFile(map) }, uploadData) 286 | } 287 | 288 | ApiService: 289 | 290 | @Multipart 291 | @POST("/upload") 292 | suspend fun upLoadFile(@PartMap map: HashMap): BaseResult 293 | 294 | 295 | 296 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 29 7 | buildToolsVersion "29.0.3" 8 | 9 | defaultConfig { 10 | applicationId "com.byl.mvvm" 11 | minSdkVersion 21 12 | targetSdkVersion 29 13 | versionCode 1 14 | versionName "1.0" 15 | flavorDimensions versionName 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | kotlinOptions { 30 | jvmTarget = '1.8' 31 | } 32 | 33 | buildFeatures { 34 | viewBinding = true 35 | } 36 | 37 | sourceSets { 38 | main { 39 | jniLibs.srcDirs = ['libs'] 40 | } 41 | } 42 | } 43 | 44 | dependencies { 45 | implementation fileTree(dir: "libs", include: ["*.jar"]) 46 | 47 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 48 | implementation 'androidx.appcompat:appcompat:1.1.0' 49 | implementation 'androidx.core:core-ktx:1.2.0' 50 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 51 | implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0' 52 | implementation 'androidx.recyclerview:recyclerview:1.1.0' 53 | 54 | implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' 55 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' 56 | implementation 'com.google.android.material:material:1.1.0' 57 | 58 | 59 | //glide图片加载框架 60 | final GLIDE_VERSION = '4.11.0' 61 | implementation "com.github.bumptech.glide:glide:$GLIDE_VERSION" 62 | implementation "com.github.bumptech.glide:okhttp3-integration:$GLIDE_VERSION" 63 | 64 | //网络请求框架 65 | final RETROFIT_VERSION = '2.8.1' 66 | implementation "com.squareup.retrofit2:retrofit:$RETROFIT_VERSION" 67 | implementation "com.squareup.retrofit2:converter-gson:$RETROFIT_VERSION" 68 | 69 | //网络请求cookie缓存 70 | implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1' 71 | 72 | //PhotoView 73 | implementation 'com.github.chrisbanes:PhotoView:2.3.0' 74 | 75 | //刷新框架 76 | implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.2' 77 | implementation 'com.scwang.smartrefresh:SmartRefreshHeader:1.1.2' 78 | 79 | //EventBus 80 | implementation 'org.greenrobot:eventbus:3.2.0' 81 | 82 | } -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 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 | 32 | 33 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/App.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import com.byl.mvvm.event.Event 6 | import com.byl.mvvm.event.EventMessage 7 | import com.scwang.smartrefresh.layout.SmartRefreshLayout 8 | import com.scwang.smartrefresh.layout.api.RefreshLayout 9 | import com.scwang.smartrefresh.layout.constant.SpinnerStyle 10 | import com.scwang.smartrefresh.layout.footer.ClassicsFooter 11 | import com.scwang.smartrefresh.layout.header.ClassicsHeader 12 | 13 | 14 | class App : Application() { 15 | 16 | companion object { 17 | var DEBUG: Boolean = false 18 | lateinit var instance: App 19 | 20 | fun post(eventMessage: EventMessage) { 21 | Event.getInstance().post(eventMessage) 22 | } 23 | } 24 | 25 | override fun onCreate() { 26 | super.onCreate() 27 | instance = this 28 | DEBUG = true 29 | } 30 | 31 | init { 32 | //设置全局默认配置(优先级最低,会被其他设置覆盖) 33 | SmartRefreshLayout.setDefaultRefreshInitializer { _: Context?, layout: RefreshLayout -> 34 | //开始设置全局的基本参数 35 | layout.setFooterHeight(40f) 36 | layout.setDisableContentWhenLoading(false) 37 | layout.setDisableContentWhenRefresh(true) //是否在刷新的时候禁止列表的操作 38 | layout.setDisableContentWhenLoading(true) //是否在加载的时候禁止列表的操作 39 | layout.setEnableOverScrollBounce(false) 40 | } 41 | SmartRefreshLayout.setDefaultRefreshHeaderCreator { context: Context?, layout: RefreshLayout? -> 42 | ClassicsHeader(context) 43 | .setSpinnerStyle(SpinnerStyle.Translate) 44 | .setTextSizeTitle(13f) 45 | .setDrawableArrowSize(15f) 46 | .setDrawableProgressSize(15f) 47 | .setDrawableMarginRight(10f) 48 | .setFinishDuration(0) 49 | } 50 | SmartRefreshLayout.setDefaultRefreshFooterCreator { context: Context?, layout: RefreshLayout? -> 51 | ClassicsFooter(context) 52 | .setSpinnerStyle(SpinnerStyle.Translate) 53 | .setTextSizeTitle(13f) 54 | .setDrawableArrowSize(15f) 55 | .setDrawableProgressSize(15f) 56 | .setDrawableMarginRight(10f) 57 | .setFinishDuration(0) 58 | } 59 | } 60 | 61 | 62 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/api/ApiService.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.api 2 | 3 | import com.byl.mvvm.api.response.BaseResult 4 | import com.byl.mvvm.ui.common.model.TestModel 5 | import com.byl.mvvm.ui.main.model.ArticleListBean 6 | import retrofit2.http.* 7 | 8 | 9 | interface ApiService { 10 | 11 | @GET("test") 12 | suspend fun test(@QueryMap options: HashMap): BaseResult 13 | 14 | @GET("article/listproject/{page}/json") 15 | suspend fun getArticleList(@Path("page") page: Int): BaseResult 16 | 17 | 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/api/HttpUtil.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.api 2 | 3 | import com.byl.mvvm.api.retrofit.RetrofitClient 4 | 5 | class HttpUtil { 6 | 7 | private val mService by lazy { RetrofitClient.getInstance().create() } 8 | 9 | //suspend fun test(options: LinkedHashMap) = mService.test(options) 10 | 11 | //suspend fun getArticleList(page: Int) = mService.getArticleList(page) 12 | 13 | 14 | companion object { 15 | @Volatile 16 | private var httpUtil: HttpUtil? = null 17 | 18 | fun getInstance() = httpUtil ?: synchronized(this) { 19 | httpUtil ?: HttpUtil().also { httpUtil = it } 20 | } 21 | } 22 | 23 | //可以直接在BaseViewModel中获取取ApiService对象,简化接口调用 24 | fun getService(): ApiService { 25 | return mService 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/api/URLConstant.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.api 2 | 3 | import com.byl.mvvm.App 4 | 5 | class URLConstant { 6 | companion object { 7 | 8 | private const val BASE_URL_DEBUG: String = "https://www.wanandroid.com/" 9 | private const val BASE_URL_RELEASE: String = "https://www.wanandroid.com/" 10 | 11 | val BASE_URL: String = if (App.DEBUG) BASE_URL_DEBUG else BASE_URL_RELEASE 12 | 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/api/error/ErrorResult.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.api.error 2 | 3 | class ErrorResult @JvmOverloads constructor( 4 | var code: Int = 0, 5 | var errMsg: String? = "", 6 | var show: Boolean = false, 7 | var index: Int = 0//表示api类型(确定是哪个api) 8 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/api/error/ErrorUtil.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.api.error 2 | 3 | import retrofit2.HttpException 4 | 5 | object ErrorUtil { 6 | 7 | fun getError(e: Throwable): ErrorResult { 8 | val errorResult = ErrorResult() 9 | if (e is HttpException) { 10 | errorResult.code = e.code() 11 | } 12 | errorResult.errMsg = e.message 13 | if (errorResult.errMsg.isNullOrEmpty()) errorResult.errMsg = "网络请求失败,请重试" 14 | return errorResult 15 | } 16 | 17 | fun getError(apiIndex: Int, e: Throwable): ErrorResult { 18 | val errorResult = ErrorResult() 19 | errorResult.index = apiIndex 20 | if (e is HttpException) { 21 | errorResult.code = e.code() 22 | } 23 | errorResult.errMsg = e.message 24 | if (errorResult.errMsg.isNullOrEmpty()) errorResult.errMsg = "网络请求失败,请重试" 25 | return errorResult 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/api/interceptor/LoggingInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.api.interceptor 2 | 3 | import com.byl.mvvm.api.URLConstant 4 | import com.byl.mvvm.utils.LogUtil.showHttpApiLog 5 | import com.byl.mvvm.utils.LogUtil.showHttpHeaderLog 6 | import com.byl.mvvm.utils.LogUtil.showHttpLog 7 | import okhttp3.Interceptor 8 | import okhttp3.RequestBody 9 | import okhttp3.Response 10 | import okio.Buffer 11 | import java.io.IOException 12 | import java.net.URLDecoder 13 | 14 | 15 | class LoggingInterceptor : Interceptor { 16 | @Throws(IOException::class) 17 | override fun intercept(chain: Interceptor.Chain): Response { 18 | val request = chain.request() 19 | val httpUrl = request.url() 20 | val t1 = System.nanoTime() //请求发起的时间 21 | val response = chain.proceed(request) 22 | val t2 = System.nanoTime() //收到响应的时间 23 | val responseBody = response.peekBody(1024 * 1024.toLong()) 24 | if (httpUrl.toString().contains(".png") 25 | || httpUrl.toString().contains(".jpg") 26 | || httpUrl.toString().contains(".jpeg") 27 | || httpUrl.toString().contains(".gif") 28 | ) { 29 | return response 30 | } 31 | 32 | var api = httpUrl.toString().replace(URLConstant.BASE_URL, "") 33 | if (api.contains("?")) { 34 | api = api.substring(0, api.indexOf("?")) 35 | } 36 | val result = responseBody.string() 37 | showHttpHeaderLog( 38 | String.format( 39 | "%n%s%n%s", 40 | " ", 41 | request.headers().toString() 42 | ) 43 | ) 44 | if (request.method() == "POST" || request.method() == "PUT") { 45 | if (api.contains("UpLoadFile")) showHttpApiLog( 46 | String.format( 47 | "%s%n%s%n%s%n%s%n%s%n", 48 | "请求URL>>$httpUrl", 49 | "API>>$api", 50 | "请求方法>>" + request.method(), 51 | "请求参数>>" + request.body().toString(), 52 | "请求耗时>>" + String.format("%.1f", (t2 - t1) / 1e6) + "ms" 53 | ) 54 | ) else showHttpApiLog( 55 | java.lang.String.format( 56 | "%s%n%s%n%s%n%s%n%s%n", 57 | "请求URL>>$httpUrl", 58 | "API>>$api", 59 | "请求方法>>" + request.method(), 60 | "请求参数>>" + URLDecoder.decode(bodyToString(request.body()), "UTF-8"), 61 | "请求耗时>>" + String.format("%.1f", (t2 - t1) / 1e6) + "ms" 62 | ) 63 | ) 64 | } else { 65 | showHttpApiLog( 66 | String.format( 67 | "%s%n%s%n%s%n%s%n", 68 | "请求URL>>$httpUrl", 69 | "API>>$api", 70 | "请求方法>>" + request.method(), 71 | "请求耗时>>" + String.format("%.1f", (t2 - t1) / 1e6) + "ms" 72 | ) 73 | ) 74 | } 75 | 76 | if (result.length > 4000) { 77 | val chunkCount = result.length / 4000 // integer division 78 | for (i in 0..chunkCount) { 79 | val max = 4000 * (i + 1) 80 | if (max >= result.length) { 81 | showHttpLog( 82 | String.format( 83 | "%s%n%s%n%s%n", 84 | "请求结果>>>" + result.substring(4000 * i), 85 | " ", 86 | " " 87 | ) 88 | ) 89 | } else { 90 | showHttpLog( 91 | String.format( 92 | "%s%n%s%n%s%n", 93 | "请求结果>>>" + result.substring(4000 * i, max), 94 | " ", 95 | " " 96 | ) 97 | ) 98 | } 99 | } 100 | } else { 101 | showHttpLog(String.format("%s%n%s%n%s%n", "请求结果>>>$result", " ", "")) 102 | } 103 | return response 104 | } 105 | 106 | fun bodyToString(request: RequestBody?): String? { 107 | return try { 108 | val buffer = Buffer() 109 | if (request != null) request.writeTo(buffer) else return "" 110 | buffer.readUtf8() 111 | } catch (e: IOException) { 112 | "did not work" 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/api/response/BaseResult.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.api.response 2 | 3 | open class BaseResult { 4 | val errorMsg: String? = null 5 | val errorCode: Int = 0 6 | val data: T? = null 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/api/retrofit/RetrofitClient.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.api.retrofit 2 | 3 | import com.byl.mvvm.App 4 | import com.byl.mvvm.api.ApiService 5 | import com.byl.mvvm.api.URLConstant 6 | import com.byl.mvvm.api.interceptor.LoggingInterceptor 7 | import com.franmontiel.persistentcookiejar.PersistentCookieJar 8 | import com.franmontiel.persistentcookiejar.cache.SetCookieCache 9 | import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor 10 | import okhttp3.OkHttpClient 11 | import retrofit2.Retrofit 12 | import retrofit2.converter.gson.GsonConverterFactory 13 | import java.util.concurrent.TimeUnit 14 | 15 | class RetrofitClient { 16 | 17 | companion object { 18 | fun getInstance() = 19 | SingletonHolder.INSTANCE 20 | 21 | private lateinit var retrofit: Retrofit 22 | } 23 | 24 | private object SingletonHolder { 25 | val INSTANCE = RetrofitClient() 26 | } 27 | 28 | private var cookieJar: PersistentCookieJar = PersistentCookieJar( 29 | SetCookieCache(), 30 | SharedPrefsCookiePersistor(App.instance) 31 | ) 32 | 33 | init { 34 | retrofit = Retrofit.Builder() 35 | .client(getOkHttpClient()) 36 | .addConverterFactory(GsonConverterFactory.create()) 37 | .baseUrl(URLConstant.BASE_URL) 38 | .build() 39 | } 40 | 41 | private fun getOkHttpClient(): OkHttpClient { 42 | return OkHttpClient.Builder() 43 | .connectTimeout(10, TimeUnit.SECONDS) 44 | .writeTimeout(10, TimeUnit.SECONDS) 45 | .cookieJar(cookieJar) 46 | .addInterceptor(LoggingInterceptor()) 47 | .sslSocketFactory(SSLContextSecurity.createIgnoreVerifySSL("TLS")) 48 | .build() 49 | } 50 | 51 | fun create(): ApiService = retrofit.create( 52 | ApiService::class.java 53 | ) 54 | 55 | 56 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/api/retrofit/SSLContextSecurity.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.api.retrofit 2 | 3 | import java.security.cert.CertificateException 4 | import java.security.cert.X509Certificate 5 | import javax.net.ssl.* 6 | import javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier 7 | import javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory 8 | 9 | object SSLContextSecurity { 10 | 11 | fun createIgnoreVerifySSL(sslVersion: String): SSLSocketFactory { 12 | var sc = SSLContext.getInstance(sslVersion); 13 | val trustAllCerts: Array = arrayOf(object : X509TrustManager { 14 | @Throws(CertificateException::class) 15 | override fun checkClientTrusted( 16 | chain: Array, authType: String 17 | ) { 18 | } 19 | 20 | @Throws(CertificateException::class) 21 | override fun checkServerTrusted( 22 | chain: Array, 23 | authType: String 24 | ) { 25 | } 26 | 27 | override fun getAcceptedIssuers(): Array { 28 | return arrayOfNulls(0) 29 | } 30 | }) 31 | 32 | sc!!.init(null, trustAllCerts, java.security.SecureRandom()) 33 | 34 | // Create all-trusting host name verifier 35 | val allHostsValid = HostnameVerifier { _, _ -> true } 36 | 37 | setDefaultSSLSocketFactory(sc.socketFactory); 38 | setDefaultHostnameVerifier(allHostsValid); 39 | return sc.socketFactory; 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/event/Event.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.event 2 | 3 | import org.greenrobot.eventbus.EventBus 4 | 5 | object Event { 6 | fun getInstance(): EventBus { 7 | return EventBus.getDefault() 8 | } 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/event/EventCode.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.event 2 | 3 | /** 4 | * 事件类型 5 | */ 6 | enum class EventCode { 7 | LOGIN_OUT,//退出登录 8 | LOGIN_SUCCESS,//登录成功 9 | REFRESH,//刷新 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/event/EventMessage.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.event 2 | 3 | class EventMessage @JvmOverloads constructor( 4 | var code: EventCode, 5 | var msg: String = "", 6 | var arg1: Int = 0, 7 | var arg2: Int = 0, 8 | var obj: Any? = null 9 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/ui/SplashActivity.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.ui 2 | 3 | import android.Manifest 4 | import android.content.Intent 5 | import android.content.pm.PackageManager 6 | import android.os.Build 7 | import android.os.Handler 8 | import android.widget.Toast 9 | import androidx.core.content.ContextCompat 10 | import com.byl.mvvm.databinding.ActivitySplashBinding 11 | import com.byl.mvvm.ui.base.BaseActivity 12 | import com.byl.mvvm.ui.base.BaseViewModel 13 | import com.byl.mvvm.ui.main.MainActivity 14 | import com.byl.mvvm.utils.LogUtil 15 | import com.byl.mvvm.utils.StatusBarUtil 16 | import com.byl.mvvm.utils.SysUtils 17 | 18 | 19 | class SplashActivity : BaseActivity, ActivitySplashBinding>() { 20 | 21 | 22 | override fun initView() { 23 | StatusBarUtil.immersive(this) 24 | StatusBarUtil.darkMode(this) 25 | if (!this.isTaskRoot) { 26 | val mainIntent = intent 27 | val action = mainIntent.action 28 | if (mainIntent.hasCategory(Intent.CATEGORY_LAUNCHER) && action == Intent.ACTION_MAIN) { 29 | finish() 30 | return 31 | } 32 | } 33 | 34 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !hasPermission()) { 35 | requestPermissions( 36 | arrayOf( 37 | Manifest.permission.READ_EXTERNAL_STORAGE, 38 | Manifest.permission.WRITE_EXTERNAL_STORAGE 39 | ), 1001 40 | ) 41 | } else { 42 | init() 43 | } 44 | } 45 | 46 | private fun init() { 47 | SysUtils.initFiles() 48 | Handler().postDelayed({ 49 | startActivity(Intent(mContext, MainActivity::class.java)) 50 | finish() 51 | }, 2000) 52 | } 53 | 54 | override fun initClick() { 55 | 56 | } 57 | 58 | override fun initData() { 59 | 60 | } 61 | 62 | override fun onRequestPermissionsResult( 63 | requestCode: Int, 64 | permissions: Array, 65 | grantResults: IntArray 66 | ) { 67 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 68 | when (requestCode) { 69 | 1001 -> { 70 | if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 71 | init() 72 | } else { 73 | Toast.makeText(mContext, "您拒绝了文件权限", Toast.LENGTH_SHORT).show() 74 | } 75 | return 76 | } 77 | } 78 | } 79 | 80 | private fun hasPermission(): Boolean { 81 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) 82 | ContextCompat.checkSelfPermission( 83 | mContext, 84 | Manifest.permission_group.STORAGE 85 | ) == PackageManager.PERMISSION_GRANTED 86 | else 87 | true 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/ui/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.ui.base 2 | 3 | import android.app.ProgressDialog 4 | import android.content.res.Configuration 5 | import android.content.res.Resources 6 | import android.os.Build 7 | import android.os.Bundle 8 | import android.view.LayoutInflater 9 | import androidx.annotation.RequiresApi 10 | import androidx.appcompat.app.AppCompatActivity 11 | import androidx.fragment.app.FragmentActivity 12 | import androidx.lifecycle.Observer 13 | import androidx.lifecycle.ViewModelProvider 14 | import androidx.viewbinding.ViewBinding 15 | import com.byl.mvvm.api.error.ErrorResult 16 | import com.byl.mvvm.databinding.ActivitySplashBinding 17 | import com.byl.mvvm.event.EventCode 18 | import com.byl.mvvm.event.EventMessage 19 | import com.byl.mvvm.utils.GenericParadigmUtil 20 | import com.byl.mvvm.utils.LogUtil 21 | import com.byl.mvvm.utils.ToastUtil 22 | import org.greenrobot.eventbus.EventBus 23 | import org.greenrobot.eventbus.Subscribe 24 | import java.lang.reflect.ParameterizedType 25 | 26 | 27 | abstract class BaseActivity, VB : ViewBinding> : AppCompatActivity() { 28 | lateinit var mContext: FragmentActivity 29 | lateinit var vm: VM 30 | lateinit var vb: VB 31 | 32 | private var loadingDialog: ProgressDialog? = null 33 | 34 | @Suppress("UNCHECKED_CAST") 35 | override fun onCreate(savedInstanceState: Bundle?) { 36 | super.onCreate(savedInstanceState) 37 | initResources() 38 | var pathfinders = ArrayList() 39 | pathfinders.add(GenericParadigmUtil.Pathfinder(0, 0)) 40 | val clazzVM = GenericParadigmUtil.parseGenericParadigm(javaClass, pathfinders) as Class 41 | vm = ViewModelProvider(this).get(clazzVM) 42 | 43 | pathfinders = ArrayList() 44 | pathfinders.add(GenericParadigmUtil.Pathfinder(0, 1)) 45 | val clazzVB = GenericParadigmUtil.parseGenericParadigm(javaClass, pathfinders) 46 | val method = clazzVB.getMethod("inflate", LayoutInflater::class.java) 47 | vb = method.invoke(null, layoutInflater) as VB 48 | 49 | vm.binding(vb) 50 | vm.observe(this, this) 51 | 52 | setContentView(vb.root) 53 | 54 | mContext = this 55 | init() 56 | initView() 57 | initClick() 58 | initData() 59 | LogUtil.e(getClassName()) 60 | } 61 | 62 | /** 63 | * 防止系统字体影响到app的字体 64 | * 65 | * @return 66 | */ 67 | open fun initResources(): Resources? { 68 | val res: Resources = super.getResources() 69 | val config = Configuration() 70 | config.setToDefaults() 71 | res.updateConfiguration(config, res.displayMetrics) 72 | return res 73 | } 74 | 75 | override fun onDestroy() { 76 | super.onDestroy() 77 | EventBus.getDefault().unregister(this) 78 | } 79 | 80 | //事件传递 81 | @Subscribe 82 | fun onEventMainThread(msg: EventMessage) { 83 | handleEvent(msg) 84 | } 85 | 86 | open fun getClassName(): String? { 87 | val className = "BaseActivity" 88 | try { 89 | return javaClass.name 90 | } catch (e: Exception) { 91 | } 92 | return className 93 | } 94 | 95 | abstract fun initView() 96 | 97 | abstract fun initClick() 98 | 99 | abstract fun initData() 100 | 101 | private fun init() { 102 | EventBus.getDefault().register(this) 103 | //loading 104 | (vm as BaseViewModel<*>).isShowLoading.observe(this, Observer { 105 | if (it) showLoading() else dismissLoading() 106 | }) 107 | //错误信息 108 | (vm as BaseViewModel<*>).errorData.observe(this, Observer { 109 | if (it.show) ToastUtil.showToast(mContext, it.errMsg) 110 | errorResult(it) 111 | }) 112 | } 113 | 114 | fun showLoading() { 115 | if (loadingDialog == null) { 116 | loadingDialog = ProgressDialog(this) 117 | } 118 | loadingDialog!!.show() 119 | } 120 | 121 | fun dismissLoading() { 122 | loadingDialog?.dismiss() 123 | loadingDialog = null 124 | } 125 | 126 | /** 127 | * 消息、事件接收回调 128 | */ 129 | open fun handleEvent(msg: EventMessage) { 130 | if (msg.code == EventCode.LOGIN_OUT) { 131 | finish() 132 | } 133 | } 134 | 135 | /** 136 | * 接口请求错误回调 137 | */ 138 | open fun errorResult(errorResult: ErrorResult) {} 139 | 140 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/ui/base/BaseAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.ui.base 2 | 3 | import android.app.Activity 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.RecyclerView 7 | import androidx.viewbinding.ViewBinding 8 | import com.byl.mvvm.widget.clicks 9 | import java.lang.reflect.ParameterizedType 10 | 11 | /** 12 | * 通过传入ViewBinding,不再需要写具体xml资源,省略onBindViewHolder中findviewById 13 | * 注意点:item的最外层布局高度要设为wrap_content, 14 | * 如果item有需求要设置为固定宽高,可以在子类的convert方法里,通过代码设置 15 | */ 16 | abstract class BaseAdapter( 17 | var mContext: Activity, 18 | var listDatas: ArrayList 19 | ) : RecyclerView.Adapter() { 20 | 21 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { 22 | val type = javaClass.genericSuperclass as ParameterizedType 23 | val clazz = type.actualTypeArguments[0] as Class 24 | val method = clazz.getMethod("inflate", LayoutInflater::class.java) 25 | var vb = method.invoke(null, LayoutInflater.from(mContext)) as VB 26 | vb.root.layoutParams = RecyclerView.LayoutParams( 27 | RecyclerView.LayoutParams.MATCH_PARENT, 28 | RecyclerView.LayoutParams.WRAP_CONTENT 29 | ) 30 | return BaseViewHolder(vb, vb.root) 31 | } 32 | 33 | override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { 34 | holder.itemView.clicks { 35 | itemClick?.let { it(position) } 36 | } 37 | holder.itemView.setOnLongClickListener { 38 | itemLongClick?.let { it1 -> it1(position) } 39 | true 40 | } 41 | 42 | convert(holder.v as VB, listDatas[position], position) 43 | } 44 | 45 | abstract fun convert(v: VB, t: T, position: Int) 46 | 47 | override fun getItemCount(): Int { 48 | return listDatas.size 49 | } 50 | 51 | 52 | private var itemClick: ((Int) -> Unit)? = null 53 | private var itemLongClick: ((Int) -> Unit)? = null 54 | 55 | 56 | fun itemClick(itemClick: (Int) -> Unit) { 57 | this.itemClick = itemClick 58 | } 59 | 60 | fun itemLongClick(itemLongClick: (Int) -> Unit) { 61 | this.itemLongClick = itemLongClick 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/ui/base/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.ui.base 2 | 3 | import android.app.ProgressDialog 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.Toast 9 | import androidx.appcompat.app.AppCompatActivity 10 | import androidx.fragment.app.Fragment 11 | import androidx.fragment.app.FragmentActivity 12 | import androidx.lifecycle.Observer 13 | import androidx.lifecycle.ViewModelProvider 14 | import androidx.viewbinding.ViewBinding 15 | import com.byl.mvvm.api.error.ErrorResult 16 | import com.byl.mvvm.event.EventMessage 17 | import com.byl.mvvm.utils.GenericParadigmUtil 18 | import com.byl.mvvm.utils.LogUtil 19 | import com.byl.mvvm.utils.ToastUtil 20 | import org.greenrobot.eventbus.EventBus 21 | import org.greenrobot.eventbus.Subscribe 22 | import java.lang.reflect.ParameterizedType 23 | 24 | 25 | abstract class BaseFragment, VB : ViewBinding> : Fragment() { 26 | 27 | lateinit var mContext: FragmentActivity 28 | var contentView: View? = null 29 | lateinit var vm: VM 30 | lateinit var vb: VB 31 | private var loadingDialog: ProgressDialog? = null 32 | 33 | //Fragment的View加载完毕的标记 34 | private var isViewCreated = false 35 | 36 | //Fragment对用户可见的标记 37 | private var isUIVisible = false 38 | var isVisibleToUser = false 39 | 40 | @Suppress("UNCHECKED_CAST") 41 | override fun onCreate(savedInstanceState: Bundle?) { 42 | super.onCreate(savedInstanceState) 43 | mContext = context as FragmentActivity 44 | 45 | var pathfinders = ArrayList() 46 | pathfinders.add(GenericParadigmUtil.Pathfinder(0, 0)) 47 | val clazzVM = GenericParadigmUtil.parseGenericParadigm(javaClass, pathfinders) as Class 48 | vm = ViewModelProvider(this).get(clazzVM) 49 | 50 | pathfinders = ArrayList() 51 | pathfinders.add(GenericParadigmUtil.Pathfinder(0, 1)) 52 | val clazzVB = GenericParadigmUtil.parseGenericParadigm(javaClass, pathfinders) 53 | val method = clazzVB.getMethod("inflate", LayoutInflater::class.java) 54 | vb = method.invoke(null, layoutInflater) as VB 55 | 56 | vm.binding(vb) 57 | vm.observe(this, this) 58 | 59 | } 60 | 61 | override fun onCreateView( 62 | inflater: LayoutInflater, 63 | container: ViewGroup?, 64 | savedInstanceState: Bundle? 65 | ): View? { 66 | if (null == contentView) { 67 | contentView = vb.root 68 | init() 69 | initView() 70 | initClick() 71 | initData() 72 | LogUtil.e(getClassName()) 73 | } 74 | 75 | return contentView 76 | } 77 | 78 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 79 | super.onViewCreated(view, savedInstanceState) 80 | isViewCreated = true 81 | lazyLoad() 82 | } 83 | 84 | private fun init() { 85 | EventBus.getDefault().register(this) 86 | //loading 87 | vm.isShowLoading.observe(this, Observer { 88 | if (it) showLoading() else dismissLoding() 89 | }) 90 | //错误信息 91 | vm.errorData.observe(this, Observer { 92 | if (it.show) ToastUtil.showToast(mContext, it.errMsg) 93 | errorResult(it) 94 | }) 95 | } 96 | 97 | override fun onDestroy() { 98 | super.onDestroy() 99 | EventBus.getDefault().unregister(this) 100 | } 101 | 102 | //事件传递 103 | @Subscribe 104 | fun onEventMainThread(msg: EventMessage) { 105 | handleEvent(msg) 106 | } 107 | 108 | open fun getClassName(): String? { 109 | val className = "BaseFragment" 110 | try { 111 | return javaClass.name 112 | } catch (e: Exception) { 113 | } 114 | return className 115 | } 116 | 117 | abstract fun initView() 118 | 119 | abstract fun initClick() 120 | 121 | abstract fun initData() 122 | 123 | override fun setUserVisibleHint(isVisibleToUser: Boolean) { 124 | super.setUserVisibleHint(isVisibleToUser) 125 | this.isVisibleToUser = isVisibleToUser 126 | //isVisibleToUser这个boolean值表示:该Fragment的UI 用户是否可见 127 | if (isVisibleToUser) { 128 | isUIVisible = true 129 | lazyLoad() 130 | } else { 131 | isUIVisible = false 132 | } 133 | } 134 | 135 | fun lazyLoad() { 136 | //这里进行双重标记判断,是因为setUserVisibleHint会多次回调,并且会在onCreateView执行前回调,必须确保onCreateView加载完毕且页面可见,才加载数据 137 | if (isViewCreated && isUIVisible) { 138 | lazyLoadData() 139 | //数据加载完毕,恢复标记,防止重复加载 140 | isViewCreated = false 141 | isUIVisible = false 142 | } 143 | } 144 | 145 | //需要懒加载的数据,重写此方法 146 | abstract fun lazyLoadData() 147 | 148 | private fun showLoading() { 149 | if (loadingDialog == null) { 150 | loadingDialog = ProgressDialog(mContext) 151 | } 152 | loadingDialog!!.show() 153 | } 154 | 155 | private fun dismissLoding() { 156 | loadingDialog?.dismiss() 157 | loadingDialog = null 158 | } 159 | 160 | /** 161 | * 消息、事件接收回调 162 | */ 163 | open fun handleEvent(msg: EventMessage) {} 164 | 165 | /** 166 | * 接口请求错误回调 167 | */ 168 | open fun errorResult(errorResult: ErrorResult) {} 169 | 170 | override fun onDestroyView() { 171 | super.onDestroyView() 172 | contentView = null 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/ui/base/BaseViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.ui.base 2 | 3 | import android.view.View 4 | import androidx.recyclerview.widget.RecyclerView 5 | import androidx.viewbinding.ViewBinding 6 | 7 | open class BaseViewHolder(var v: ViewBinding, itemView: View) : RecyclerView.ViewHolder(itemView) -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/ui/base/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.ui.base 2 | 3 | import android.app.Activity 4 | import android.view.LayoutInflater 5 | import androidx.fragment.app.Fragment 6 | import androidx.lifecycle.* 7 | import androidx.viewbinding.ViewBinding 8 | import com.byl.mvvm.api.HttpUtil 9 | import com.byl.mvvm.api.error.ErrorResult 10 | import com.byl.mvvm.api.error.ErrorUtil 11 | import com.byl.mvvm.api.response.BaseResult 12 | import com.byl.mvvm.utils.LogUtil 13 | import kotlinx.coroutines.CoroutineScope 14 | import kotlinx.coroutines.launch 15 | import java.lang.reflect.ParameterizedType 16 | import java.net.URLEncoder 17 | import java.security.MessageDigest 18 | import java.security.NoSuchAlgorithmException 19 | 20 | 21 | open class BaseViewModel : ViewModel() { 22 | 23 | private val AUTH_SECRET = "123456"//前后台协议密钥 24 | val httpUtil by lazy { HttpUtil.getInstance().getService() } 25 | var isShowLoading = MutableLiveData()//是否显示loading 26 | var errorData = MutableLiveData()//错误信息 27 | lateinit var vb: VB 28 | 29 | fun binding(vb: VB) { 30 | this.vb = vb 31 | } 32 | 33 | open fun observe(activity: Activity, owner: LifecycleOwner) { 34 | 35 | } 36 | 37 | open fun observe(fragment: Fragment, owner: LifecycleOwner) { 38 | 39 | } 40 | 41 | private fun showLoading() { 42 | isShowLoading.value = true 43 | } 44 | 45 | private fun dismissLoading() { 46 | isShowLoading.value = false 47 | } 48 | 49 | private fun showError(error: ErrorResult) { 50 | errorData.value = error 51 | } 52 | 53 | /** 54 | * 无参 55 | */ 56 | open fun signNoParams(): LinkedHashMap { 57 | var params = LinkedHashMap() 58 | params["sign"] = getSign(params) 59 | return params 60 | } 61 | 62 | /** 63 | * 有参 64 | */ 65 | open fun signParams(params: LinkedHashMap): LinkedHashMap { 66 | params["sign"] = getSign(params) 67 | return params 68 | } 69 | 70 | 71 | /** 72 | * 签名 73 | */ 74 | private fun getSign(params: LinkedHashMap): String { 75 | val sb = StringBuilder() 76 | params.forEach { 77 | val key = it.key 78 | var value = "" 79 | if (!it.value.isNullOrEmpty()) { 80 | value = URLEncoder.encode(it.value as String?).replace("\\+", "%20") 81 | } 82 | sb.append("$key=$value&") 83 | } 84 | val s = sb.toString().substring(0, sb.toString().length - 1).toLowerCase() + AUTH_SECRET 85 | return encryption(s) 86 | } 87 | 88 | 89 | /** 90 | * MD5加密 91 | * 92 | * @param plainText 明文 93 | * @return 32位密文 94 | */ 95 | private fun encryption(plainText: String): String { 96 | var re_md5 = "" 97 | try { 98 | val md: MessageDigest = MessageDigest.getInstance("MD5") 99 | md.update(plainText.toByteArray()) 100 | val b: ByteArray = md.digest() 101 | var i: Int 102 | val buf = StringBuffer("") 103 | for (offset in b.indices) { 104 | i = b[offset].toInt() 105 | if (i < 0) i += 256 106 | if (i < 16) buf.append("0") 107 | buf.append(Integer.toHexString(i)) 108 | } 109 | re_md5 = buf.toString() 110 | } catch (e: NoSuchAlgorithmException) { 111 | e.printStackTrace() 112 | } 113 | return re_md5 114 | } 115 | 116 | /** 117 | * 请求接口,可定制是否显示loading和错误提示 118 | * block:闭包(功能代码块,定义了其,为返回值为BaseResult的协程), 119 | * 相当于 val block={ suspend { httpUtil.getArticleList(page) } } 120 | * val result=block() 121 | */ 122 | fun launch( 123 | block: suspend CoroutineScope.() -> BaseResult,//请求接口方法,T表示data实体泛型,调用时可将data对应的bean传入即可 124 | liveData: MutableLiveData, 125 | isShowLoading: Boolean = false, 126 | isShowError: Boolean = true 127 | ) { 128 | if (isShowLoading) showLoading() 129 | viewModelScope.launch { 130 | try { 131 | val result = block() 132 | if (result.errorCode == 0) {//请求成功 133 | liveData.value = result.data 134 | } else { 135 | LogUtil.e("请求错误>>" + result.errorMsg) 136 | showError(ErrorResult(result.errorCode, result.errorMsg, isShowError)) 137 | } 138 | } catch (e: Throwable) {//接口请求失败 139 | LogUtil.e("请求异常>>" + e.message) 140 | val errorResult = ErrorUtil.getError(e) 141 | errorResult.show = isShowError 142 | showError(errorResult) 143 | } finally {//请求结束 144 | dismissLoading() 145 | } 146 | } 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/ui/common/model/TestModel.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.ui.common.model 2 | 3 | class TestModel { 4 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/ui/main/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.ui.main 2 | 3 | import android.content.Intent 4 | import androidx.recyclerview.widget.LinearLayoutManager 5 | import com.byl.mvvm.databinding.ActivityMainBinding 6 | import com.byl.mvvm.event.EventCode 7 | import com.byl.mvvm.event.EventMessage 8 | import com.byl.mvvm.ui.base.BaseActivity 9 | import com.byl.mvvm.ui.main.adapter.ArticleListAdapter 10 | import com.byl.mvvm.ui.main.model.ArticleBean 11 | import com.byl.mvvm.ui.main.vm.MainActivityViewModel 12 | import com.byl.mvvm.utils.ToastUtil 13 | 14 | class MainActivity : BaseActivity() { 15 | 16 | var adapter: ArticleListAdapter? = null 17 | var list: ArrayList? = null 18 | var page: Int = 0 19 | 20 | 21 | override fun initView() { 22 | list = ArrayList() 23 | adapter = ArticleListAdapter(mContext, list!!) 24 | adapter!!.itemClick { 25 | startActivity(Intent(mContext, TestEventActivity::class.java)) 26 | } 27 | vb.mRecyclerView.layoutManager = LinearLayoutManager(mContext) 28 | vb.mRecyclerView.adapter = adapter 29 | 30 | vb.refreshLayout.setOnRefreshListener {//下拉刷新 31 | page = 0 32 | vm.getArticleList(page) 33 | } 34 | vb.refreshLayout.setOnLoadMoreListener {//上拉加载 35 | vm.getArticleList(++page) 36 | } 37 | } 38 | 39 | override fun initClick() { 40 | 41 | } 42 | 43 | override fun initData() { 44 | vm.getArticleList(page, true) 45 | } 46 | 47 | /** 48 | * 接收消息 49 | */ 50 | override fun handleEvent(msg: EventMessage) { 51 | super.handleEvent(msg) 52 | if (msg.code == EventCode.REFRESH) { 53 | ToastUtil.showToast(mContext, "主页:刷新") 54 | page = 0 55 | vm.getArticleList(page) 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/ui/main/MainFragment.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.ui.main 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.recyclerview.widget.LinearLayoutManager 6 | import com.byl.mvvm.databinding.FragmentMainBinding 7 | import com.byl.mvvm.ui.base.BaseFragment 8 | import com.byl.mvvm.ui.main.adapter.ArticleListAdapter 9 | import com.byl.mvvm.ui.main.model.ArticleBean 10 | import com.byl.mvvm.ui.main.vm.MainFragmentViewModel 11 | 12 | class MainFragment : BaseFragment() { 13 | 14 | var id: Int? = 0 15 | var adapter: ArticleListAdapter? = null 16 | var list: ArrayList? = null 17 | var page: Int = 0 18 | 19 | companion object { 20 | fun getInstance(id: Int): MainFragment { 21 | val fragment = MainFragment() 22 | val b = Bundle() 23 | b.putInt("id", id) 24 | fragment.arguments = b 25 | return fragment 26 | } 27 | } 28 | 29 | override fun initView() { 30 | id = arguments?.get("id") as Int? 31 | list = ArrayList() 32 | adapter = ArticleListAdapter(mContext, list!!) 33 | adapter!!.itemClick { 34 | startActivity(Intent(mContext, TestEventActivity::class.java)) 35 | } 36 | vb.mRecyclerView.layoutManager = LinearLayoutManager(mContext) 37 | vb.mRecyclerView.adapter = adapter 38 | 39 | vb.refreshLayout.setOnRefreshListener {//下拉刷新 40 | page = 0 41 | vm.getArticleList(page) 42 | } 43 | vb.refreshLayout.setOnLoadMoreListener {//上拉加载 44 | vm.getArticleList(++page) 45 | } 46 | } 47 | 48 | override fun initClick() { 49 | 50 | } 51 | 52 | override fun initData() { 53 | 54 | } 55 | 56 | override fun lazyLoadData() { 57 | vm.getArticleList(page) 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/ui/main/TestEventActivity.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.ui.main 2 | 3 | 4 | import androidx.fragment.app.Fragment 5 | import com.byl.mvvm.App 6 | import com.byl.mvvm.databinding.ActivityTestEventBinding 7 | import com.byl.mvvm.event.EventCode 8 | import com.byl.mvvm.event.EventMessage 9 | import com.byl.mvvm.ui.base.BaseActivity 10 | import com.byl.mvvm.ui.base.BaseViewModel 11 | import com.byl.mvvm.ui.main.adapter.FragmentPageAdapter 12 | import com.byl.mvvm.widget.clicks 13 | import java.util.* 14 | 15 | 16 | class TestEventActivity : 17 | BaseActivity, ActivityTestEventBinding>() { 18 | 19 | private val fragments: ArrayList = ArrayList() 20 | private val titles = arrayOf("最新", "热门", "我的") 21 | 22 | override fun initView() { 23 | 24 | } 25 | 26 | override fun initClick() { 27 | vb.btn.clicks { 28 | App.post(EventMessage(EventCode.REFRESH)) 29 | } 30 | } 31 | 32 | override fun initData() { 33 | for (i in titles.indices) { 34 | fragments.add(MainFragment.getInstance(i)) 35 | val tab = vb.tabLayout.newTab() 36 | tab.text = titles[i] 37 | vb.tabLayout.addTab(tab) 38 | } 39 | vb.tabLayout.setupWithViewPager(vb.viewPager, false) 40 | var pagerAdapter = FragmentPageAdapter(mContext, supportFragmentManager, fragments, titles) 41 | vb.viewPager.adapter = pagerAdapter 42 | 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/ui/main/adapter/ArticleListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.ui.main.adapter 2 | 3 | import android.app.Activity 4 | import com.bumptech.glide.Glide 5 | import com.byl.mvvm.databinding.ItemArticleBinding 6 | import com.byl.mvvm.ui.base.BaseAdapter 7 | import com.byl.mvvm.ui.base.BaseViewHolder 8 | import com.byl.mvvm.ui.main.model.ArticleBean 9 | 10 | 11 | class ArticleListAdapter(context: Activity, listDatas: ArrayList) : 12 | BaseAdapter(context, listDatas) { 13 | 14 | override fun convert(v: ItemArticleBinding, t: ArticleBean, position: Int) { 15 | Glide.with(mContext).load(t.envelopePic).into(v.ivCover) 16 | v.tvTitle.text = t.title 17 | v.tvDes.text = t.desc 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/ui/main/adapter/FragmentPageAdapter.java: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.ui.main.adapter; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.fragment.app.Fragment; 6 | import androidx.fragment.app.FragmentManager; 7 | import androidx.fragment.app.FragmentStatePagerAdapter; 8 | 9 | import java.util.List; 10 | 11 | 12 | public class FragmentPageAdapter extends FragmentStatePagerAdapter { 13 | private Context mContext; 14 | private List mFragments; 15 | private String[] mFragmentTitles; 16 | 17 | public FragmentPageAdapter(Context context, FragmentManager fm, List fragments, String[] titles) { 18 | super(fm); 19 | this.mContext = context; 20 | mFragments = fragments; 21 | mFragmentTitles = titles; 22 | } 23 | 24 | @Override 25 | public Fragment getItem(int position) { 26 | return mFragments.get(position); 27 | } 28 | 29 | @Override 30 | public int getCount() { 31 | return mFragments.size(); 32 | } 33 | 34 | @Override 35 | public CharSequence getPageTitle(int position) { 36 | return mFragmentTitles[position]; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/ui/main/model/ArticleBean.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.ui.main.model 2 | 3 | class ArticleBean { 4 | var apkLink: String? = null 5 | var author: String? = null 6 | var chapterId: Int = 0 7 | var chapterName: String? = null 8 | var isCollect: Boolean = false 9 | var courseId: Int = 0 10 | var desc: String? = null 11 | var envelopePic: String? = null 12 | var id: Int = 0 13 | var originId: Int = -1 14 | var link: String? = null 15 | var niceDate: String? = null 16 | var origin: String? = null 17 | var projectLink: String? = null 18 | var publishTime: Long = 0 19 | var title: String? = null 20 | var visible: Int = 0 21 | var zan: Int = 0 22 | var isFresh: Boolean = false 23 | var isShowImage: Boolean = true 24 | // 分类name 25 | var navigationName: String? = null 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/ui/main/model/ArticleListBean.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.ui.main.model 2 | 3 | class ArticleListBean { 4 | var curPage: Int = 0 5 | var offset: Int = 0 6 | var over: Boolean = true 7 | var pageCount: Int = 0 8 | var size: Int = 0 9 | var total: Int = 0 10 | var datas: List? = null 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/ui/main/vm/MainActivityViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.ui.main.vm 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Activity 5 | import androidx.lifecycle.LifecycleOwner 6 | import androidx.lifecycle.MutableLiveData 7 | import androidx.lifecycle.Observer 8 | import androidx.viewbinding.ViewBinding 9 | import com.byl.mvvm.databinding.ActivityMainBinding 10 | import com.byl.mvvm.ui.base.BaseViewModel 11 | import com.byl.mvvm.ui.main.MainActivity 12 | import com.byl.mvvm.ui.main.model.ArticleListBean 13 | 14 | class MainActivityViewModel : BaseViewModel() { 15 | 16 | var articlesData = MutableLiveData() 17 | 18 | fun getArticleList(page: Int, isShowLoading: Boolean = false) { 19 | launch({ httpUtil.getArticleList(page) }, articlesData, isShowLoading) 20 | } 21 | 22 | @SuppressLint("NotifyDataSetChanged") 23 | override fun observe(activity: Activity, owner: LifecycleOwner) { 24 | val mContext = activity as MainActivity 25 | articlesData.observe(owner, Observer { 26 | vb.refreshLayout.finishRefresh() 27 | vb.refreshLayout.finishLoadMore() 28 | if (mContext.page == 0) mContext.list!!.clear() 29 | it.datas?.let { it1 -> mContext.list!!.addAll(it1) } 30 | mContext.adapter!!.notifyDataSetChanged() 31 | }) 32 | errorData.observe(owner, Observer { 33 | vb.refreshLayout.finishRefresh() 34 | vb.refreshLayout.finishLoadMore() 35 | }) 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/ui/main/vm/MainFragmentViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.ui.main.vm 2 | 3 | import android.annotation.SuppressLint 4 | import androidx.fragment.app.Fragment 5 | import androidx.lifecycle.LifecycleOwner 6 | import androidx.lifecycle.MutableLiveData 7 | import androidx.lifecycle.Observer 8 | import com.byl.mvvm.databinding.FragmentMainBinding 9 | import com.byl.mvvm.ui.base.BaseViewModel 10 | import com.byl.mvvm.ui.main.MainFragment 11 | import com.byl.mvvm.ui.main.model.ArticleListBean 12 | 13 | class MainFragmentViewModel : BaseViewModel() { 14 | 15 | var articlesData = MutableLiveData() 16 | 17 | fun getArticleList(page: Int, isShowLoading: Boolean = false) { 18 | launch({ httpUtil.getArticleList(page) }, articlesData, isShowLoading) 19 | } 20 | 21 | @SuppressLint("NotifyDataSetChanged") 22 | override fun observe(fragment: Fragment, owner: LifecycleOwner) { 23 | val mContext = fragment as MainFragment 24 | articlesData.observe(owner, Observer { 25 | vb.refreshLayout.finishRefresh() 26 | vb.refreshLayout.finishLoadMore() 27 | if (mContext.page == 0) mContext.list!!.clear() 28 | it.datas?.let { it1 -> mContext.list!!.addAll(it1) } 29 | mContext.adapter!!.notifyDataSetChanged() 30 | }) 31 | errorData.observe(owner, Observer { 32 | vb.refreshLayout.finishRefresh() 33 | vb.refreshLayout.finishLoadMore() 34 | }) 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/utils/GenericParadigmUtil.java: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.utils; 2 | 3 | import java.lang.reflect.ParameterizedType; 4 | import java.lang.reflect.Type; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * @author by zhuxiaoan on 2019/4/25 0025. 10 | */ 11 | public class GenericParadigmUtil { 12 | 13 | public static Class parseGenericParadigm(Object object, int position) { 14 | if (object == null) { 15 | return null; 16 | } 17 | 18 | return GenericParadigmUtil.parseGenericParadigm(object.getClass(), position); 19 | } 20 | 21 | public static Class parseGenericParadigm(Class clazz, int position) { 22 | if (clazz == null) { 23 | return null; 24 | } 25 | List pathfinders = new ArrayList(1); 26 | pathfinders.add(new ConsistentPathfinder(Integer.MAX_VALUE, position)); 27 | return GenericParadigmUtil.parseGenericParadigm(clazz, pathfinders); 28 | } 29 | 30 | public static Class parseGenericParadigm(Object object, List pathfinders) { 31 | if (object == null) { 32 | return null; 33 | } 34 | return GenericParadigmUtil.parseGenericParadigm(object.getClass(), pathfinders); 35 | } 36 | 37 | public static Class parseGenericParadigm(Class clazz, List pathfinders) { 38 | 39 | if (!GenericParadigmUtil.isGenericParadigm(clazz) || pathfinders == null || pathfinders.isEmpty()) { 40 | return null; 41 | } 42 | assertPathfinder(pathfinders); 43 | Pathfinder pathfinder = pathfinders.get(0); 44 | boolean isConsistentPathfinder = pathfinder instanceof ConsistentPathfinder; 45 | int size = pathfinders.size(); 46 | Type type = clazz.getGenericSuperclass(); 47 | 48 | return GenericParadigmUtil.getGenericClassPlus(type, 0, size, isConsistentPathfinder, pathfinders); 49 | } 50 | 51 | public static Class parseInterfaceGenericParadigm(Object object, int who, int position) { 52 | if (object == null) { 53 | return null; 54 | } 55 | 56 | return GenericParadigmUtil.parseInterfaceGenericParadigm(object.getClass(), who, position); 57 | } 58 | 59 | public static Class parseInterfaceGenericParadigm(Class clazz, int who, int position) { 60 | if (clazz == null) { 61 | return null; 62 | } 63 | List pathfinders = new ArrayList(1); 64 | pathfinders.add(new ConsistentPathfinder(Integer.MAX_VALUE, position)); 65 | return GenericParadigmUtil.parseInterfaceGenericParadigm(clazz, who, pathfinders); 66 | } 67 | 68 | public static Class parseInterfaceGenericParadigm(Object object, int who, List pathfinders) { 69 | if (object == null) { 70 | return null; 71 | } 72 | return GenericParadigmUtil.parseInterfaceGenericParadigm(object.getClass(), who, pathfinders); 73 | } 74 | 75 | public static Class parseInterfaceGenericParadigm(Class clazz, int who, List pathfinders) { 76 | if (!GenericParadigmUtil.isInterfaceGenericParadigm(clazz) || pathfinders == null || pathfinders.isEmpty()) { 77 | return null; 78 | } 79 | Type[] genericInterfaces = clazz.getGenericInterfaces(); 80 | int length = genericInterfaces.length; 81 | if (who < 0 || who >= length) { 82 | return null; 83 | } 84 | assertPathfinder(pathfinders); 85 | Pathfinder pathfinder = pathfinders.get(0); 86 | boolean isConsistentPathfinder = pathfinder instanceof ConsistentPathfinder; 87 | int size = pathfinders.size(); 88 | Type type = genericInterfaces[who]; 89 | 90 | return GenericParadigmUtil.getGenericClassPlus(type, 0, size, isConsistentPathfinder, pathfinders); 91 | } 92 | 93 | public static boolean isInterfaceGenericParadigm(Object object) { 94 | if (object == null) { 95 | return false; 96 | } 97 | return GenericParadigmUtil.isInterfaceGenericParadigm(object.getClass()); 98 | } 99 | 100 | public static boolean isInterfaceGenericParadigm(Class clazz) { 101 | if (clazz == null) { 102 | return false; 103 | } 104 | Type[] genericInterfaces = 105 | clazz.getGenericInterfaces(); 106 | return genericInterfaces != null && genericInterfaces.length > 0; 107 | } 108 | 109 | public static boolean isGenericParadigm(Object object) { 110 | if (object == null) { 111 | return false; 112 | } 113 | return GenericParadigmUtil.isGenericParadigm(object.getClass()); 114 | } 115 | 116 | public static boolean isGenericParadigm(Class clazz) { 117 | if (clazz == null) { 118 | return false; 119 | } 120 | Type genericSuperclass = clazz.getGenericSuperclass(); 121 | return genericSuperclass instanceof ParameterizedType; 122 | } 123 | 124 | private static Class getGenericClassPlus(Type type, int level, int size, boolean isConsistentPathfinder, List pathfinders) { 125 | if (isConsistentPathfinder || level < size) { 126 | // 得到指路人指明前进的道路 127 | Pathfinder pathfinder = isConsistentPathfinder ? pathfinders.get(0) : pathfinders.get(level); 128 | if (type instanceof ParameterizedType) { 129 | Type[] types = ((ParameterizedType) type).getActualTypeArguments(); 130 | int length = types.length; 131 | int position = pathfinder.position; 132 | if (position < 0 || position >= length) { 133 | return null; 134 | } 135 | return getGenericClassPlus(types[position], level + 1, size, isConsistentPathfinder, pathfinders); 136 | } 137 | } 138 | 139 | if (type instanceof Class) { 140 | return (Class) type; 141 | } else if (type instanceof ParameterizedType) { 142 | return (Class) ((ParameterizedType) type).getRawType(); 143 | } 144 | return null; 145 | } 146 | 147 | @Deprecated 148 | private static Class getGenericClass(Type type, int level, int size, boolean isConsistentPathfinder, List pathfinders) { 149 | if (isConsistentPathfinder) { 150 | // 特殊, 找到泛型的最深处类型 151 | if (type instanceof Class) { 152 | return (Class) type; 153 | } 154 | } else { 155 | // 指定指路人 156 | if (level >= size) { 157 | if (type instanceof Class) { 158 | return (Class) type; 159 | } else if (type instanceof ParameterizedType) { 160 | return (Class) ((ParameterizedType) type).getRawType(); 161 | } 162 | } 163 | } 164 | 165 | // 得到指路人指明前进的道路 166 | Pathfinder pathfinder = isConsistentPathfinder ? pathfinders.get(0) : pathfinders.get(level); 167 | if (type instanceof ParameterizedType) { 168 | Type[] types = ((ParameterizedType) type).getActualTypeArguments(); 169 | int length = types.length; 170 | int position = pathfinder.position; 171 | if (position < 0 || position >= length) { 172 | return null; 173 | } 174 | return getGenericClass(types[position], level + 1, size, isConsistentPathfinder, pathfinders); 175 | } else { 176 | return null; 177 | } 178 | } 179 | 180 | private static void assertPathfinder(List pathfinders) { 181 | if (pathfinders == null || pathfinders.isEmpty()) { 182 | throw new IllegalArgumentException("Oh, No, It`s not have Pathfinder..."); 183 | } 184 | Pathfinder pathfinder = pathfinders.get(0); 185 | boolean isConsistentPathfinder = pathfinder instanceof ConsistentPathfinder; 186 | if (!isConsistentPathfinder) { 187 | int size = pathfinders.size(); 188 | for (int level = 0; level < size; level++) { 189 | pathfinder = pathfinders.get(level); 190 | if (level != pathfinder.depth) { 191 | throw new IllegalArgumentException("Oh, No, Pathfinders is incomplete..."); 192 | } 193 | } 194 | } 195 | } 196 | 197 | private static class ConsistentPathfinder extends Pathfinder { 198 | public ConsistentPathfinder(int in_depth, int in_position) { 199 | super.depth = in_depth; 200 | super.position = in_position; 201 | } 202 | } 203 | 204 | public static class Pathfinder { 205 | public int depth; 206 | public int position; 207 | 208 | public Pathfinder() { 209 | } 210 | 211 | public Pathfinder(int in_depth, int in_position) { 212 | this.depth = in_depth; 213 | this.position = in_position; 214 | } 215 | } 216 | 217 | } 218 | 219 | -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/utils/LogUtil.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.utils 2 | 3 | import android.util.Log 4 | import com.byl.mvvm.BuildConfig 5 | 6 | object LogUtil { 7 | private const val TAG = "mvvm_log" 8 | private const val TAG_NET = "mvvm_net" 9 | 10 | fun i(message: String?) { 11 | if (BuildConfig.DEBUG) Log.i(TAG, message) 12 | } 13 | 14 | fun e(message: String?) { 15 | if (BuildConfig.DEBUG) Log.e(TAG, message) 16 | } 17 | 18 | fun showHttpHeaderLog(message: String?) { 19 | if (BuildConfig.DEBUG) Log.d(TAG_NET, message) 20 | } 21 | 22 | fun showHttpApiLog(message: String?) { 23 | if (BuildConfig.DEBUG) Log.w(TAG_NET, message) 24 | } 25 | 26 | fun showHttpLog(message: String?) { 27 | if (BuildConfig.DEBUG) Log.i(TAG_NET, message) 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/utils/StatusBarUtil.java: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.utils; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.graphics.Color; 7 | import android.os.Build; 8 | import android.util.DisplayMetrics; 9 | import android.util.Log; 10 | import android.view.Display; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.view.Window; 14 | import android.view.WindowManager; 15 | import android.widget.LinearLayout; 16 | 17 | import androidx.annotation.ColorInt; 18 | import androidx.annotation.FloatRange; 19 | import androidx.annotation.RequiresApi; 20 | import androidx.coordinatorlayout.widget.CoordinatorLayout; 21 | import androidx.drawerlayout.widget.DrawerLayout; 22 | 23 | 24 | import com.byl.mvvm.R; 25 | 26 | import java.lang.reflect.Field; 27 | import java.lang.reflect.Method; 28 | import java.util.regex.Pattern; 29 | 30 | 31 | /** 32 | * Created by Jaeger on 16/2/14. 33 | *

34 | * Email: chjie.jaeger@gmail.com 35 | * GitHub: https://github.com/laobie 36 | */ 37 | public class StatusBarUtil { 38 | 39 | public static final int DEFAULT_STATUS_BAR_ALPHA = 0; 40 | private static final int FAKE_STATUS_BAR_VIEW_ID = R.id.statusbarutil_fake_status_bar_view; 41 | private static final int FAKE_TRANSLUCENT_VIEW_ID = R.id.statusbarutil_translucent_view; 42 | private static final int TAG_KEY_HAVE_SET_OFFSET = -123; 43 | 44 | /** 45 | * 设置状态栏颜色 46 | * 47 | * @param activity 需要设置的 activity 48 | * @param color 状态栏颜色值 49 | */ 50 | public static void setColor(Activity activity, @ColorInt int color) { 51 | if (activity == null) return; 52 | setColor(activity, color, DEFAULT_STATUS_BAR_ALPHA); 53 | } 54 | 55 | /** 56 | * 设置状态栏颜色 57 | * 58 | * @param activity 需要设置的activity 59 | * @param color 状态栏颜色值 60 | * @param statusBarAlpha 状态栏透明度 61 | */ 62 | 63 | public static void setColor(Activity activity, @ColorInt int color, int statusBarAlpha) { 64 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 65 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 66 | activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 67 | activity.getWindow().setStatusBarColor(calculateStatusColor(color, statusBarAlpha)); 68 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 69 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 70 | ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView(); 71 | View fakeStatusBarView = decorView.findViewById(FAKE_STATUS_BAR_VIEW_ID); 72 | if (fakeStatusBarView != null) { 73 | if (fakeStatusBarView.getVisibility() == View.GONE) { 74 | fakeStatusBarView.setVisibility(View.VISIBLE); 75 | } 76 | fakeStatusBarView.setBackgroundColor(calculateStatusColor(color, statusBarAlpha)); 77 | } else { 78 | decorView.addView(createStatusBarView(activity, color, statusBarAlpha)); 79 | } 80 | setRootView(activity); 81 | } 82 | } 83 | 84 | /** 85 | * 为滑动返回界面设置状态栏颜色 86 | * 87 | * @param activity 需要设置的activity 88 | * @param color 状态栏颜色值 89 | */ 90 | public static void setColorForSwipeBack(Activity activity, int color) { 91 | setColorForSwipeBack(activity, color, DEFAULT_STATUS_BAR_ALPHA); 92 | } 93 | 94 | /** 95 | * 为滑动返回界面设置状态栏颜色 96 | * 97 | * @param activity 需要设置的activity 98 | * @param color 状态栏颜色值 99 | * @param statusBarAlpha 状态栏透明度 100 | */ 101 | public static void setColorForSwipeBack(Activity activity, @ColorInt int color, int statusBarAlpha) { 102 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 103 | 104 | ViewGroup contentView = ((ViewGroup) activity.findViewById(android.R.id.content)); 105 | View rootView = contentView.getChildAt(0); 106 | int statusBarHeight = getStatusBarHeight(activity); 107 | if (rootView != null && rootView instanceof CoordinatorLayout) { 108 | final CoordinatorLayout coordinatorLayout = (CoordinatorLayout) rootView; 109 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 110 | coordinatorLayout.setFitsSystemWindows(false); 111 | contentView.setBackgroundColor(calculateStatusColor(color, statusBarAlpha)); 112 | boolean isNeedRequestLayout = contentView.getPaddingTop() < statusBarHeight; 113 | if (isNeedRequestLayout) { 114 | contentView.setPadding(0, statusBarHeight, 0, 0); 115 | coordinatorLayout.post(new Runnable() { 116 | @Override 117 | public void run() { 118 | coordinatorLayout.requestLayout(); 119 | } 120 | }); 121 | } 122 | } else { 123 | coordinatorLayout.setStatusBarBackgroundColor(calculateStatusColor(color, statusBarAlpha)); 124 | } 125 | } else { 126 | contentView.setPadding(0, statusBarHeight, 0, 0); 127 | contentView.setBackgroundColor(calculateStatusColor(color, statusBarAlpha)); 128 | } 129 | setTransparentForWindow(activity); 130 | } 131 | } 132 | 133 | /** 134 | * 设置状态栏纯色 不加半透明效果 135 | * 136 | * @param activity 需要设置的 activity 137 | * @param color 状态栏颜色值 138 | */ 139 | public static void setColorNoTranslucent(Activity activity, @ColorInt int color) { 140 | setColor(activity, color, 0); 141 | } 142 | 143 | /** 144 | * 设置状态栏颜色(5.0以下无半透明效果,不建议使用) 145 | * 146 | * @param activity 需要设置的 activity 147 | * @param color 状态栏颜色值 148 | */ 149 | @Deprecated 150 | public static void setColorDiff(Activity activity, @ColorInt int color) { 151 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 152 | return; 153 | } 154 | transparentStatusBar(activity); 155 | ViewGroup contentView = (ViewGroup) activity.findViewById(android.R.id.content); 156 | // 移除半透明矩形,以免叠加 157 | View fakeStatusBarView = contentView.findViewById(FAKE_STATUS_BAR_VIEW_ID); 158 | if (fakeStatusBarView != null) { 159 | if (fakeStatusBarView.getVisibility() == View.GONE) { 160 | fakeStatusBarView.setVisibility(View.VISIBLE); 161 | } 162 | fakeStatusBarView.setBackgroundColor(color); 163 | } else { 164 | contentView.addView(createStatusBarView(activity, color)); 165 | } 166 | setRootView(activity); 167 | } 168 | 169 | /** 170 | * 使状态栏半透明 171 | *

172 | * 适用于图片作为背景的界面,此时需要图片填充到状态栏 173 | * 174 | * @param activity 需要设置的activity 175 | */ 176 | public static void setTranslucent(Activity activity) { 177 | setTranslucent(activity, DEFAULT_STATUS_BAR_ALPHA); 178 | } 179 | 180 | /** 181 | * 使状态栏半透明 182 | *

183 | * 适用于图片作为背景的界面,此时需要图片填充到状态栏 184 | * 185 | * @param activity 需要设置的activity 186 | * @param statusBarAlpha 状态栏透明度 187 | */ 188 | public static void setTranslucent(Activity activity, int statusBarAlpha) { 189 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 190 | return; 191 | } 192 | setTransparent(activity); 193 | addTranslucentView(activity, statusBarAlpha); 194 | } 195 | 196 | /** 197 | * 针对根布局是 CoordinatorLayout, 使状态栏半透明 198 | *

199 | * 适用于图片作为背景的界面,此时需要图片填充到状态栏 200 | * 201 | * @param activity 需要设置的activity 202 | * @param statusBarAlpha 状态栏透明度 203 | */ 204 | public static void setTranslucentForCoordinatorLayout(Activity activity, int statusBarAlpha) { 205 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 206 | return; 207 | } 208 | transparentStatusBar(activity); 209 | addTranslucentView(activity, statusBarAlpha); 210 | } 211 | 212 | /** 213 | * 设置状态栏全透明 214 | * 215 | * @param activity 需要设置的activity 216 | */ 217 | public static void setTransparent(Activity activity) { 218 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 219 | return; 220 | } 221 | transparentStatusBar(activity); 222 | setRootView(activity); 223 | } 224 | 225 | /** 226 | * 使状态栏透明(5.0以上半透明效果,不建议使用) 227 | *

228 | * 适用于图片作为背景的界面,此时需要图片填充到状态栏 229 | * 230 | * @param activity 需要设置的activity 231 | */ 232 | @Deprecated 233 | public static void setTranslucentDiff(Activity activity) { 234 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 235 | // 设置状态栏透明 236 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 237 | setRootView(activity); 238 | } 239 | } 240 | 241 | /** 242 | * 为DrawerLayout 布局设置状态栏变色 243 | * 244 | * @param activity 需要设置的activity 245 | * @param drawerLayout DrawerLayout 246 | * @param color 状态栏颜色值 247 | */ 248 | public static void setColorForDrawerLayout(Activity activity, DrawerLayout drawerLayout, @ColorInt int color) { 249 | setColorForDrawerLayout(activity, drawerLayout, color, DEFAULT_STATUS_BAR_ALPHA); 250 | } 251 | 252 | /** 253 | * 为DrawerLayout 布局设置状态栏颜色,纯色 254 | * 255 | * @param activity 需要设置的activity 256 | * @param drawerLayout DrawerLayout 257 | * @param color 状态栏颜色值 258 | */ 259 | public static void setColorNoTranslucentForDrawerLayout(Activity activity, DrawerLayout drawerLayout, @ColorInt int color) { 260 | setColorForDrawerLayout(activity, drawerLayout, color, 0); 261 | } 262 | 263 | /** 264 | * 为DrawerLayout 布局设置状态栏变色 265 | * 266 | * @param activity 需要设置的activity 267 | * @param drawerLayout DrawerLayout 268 | * @param color 状态栏颜色值 269 | * @param statusBarAlpha 状态栏透明度 270 | */ 271 | public static void setColorForDrawerLayout(Activity activity, DrawerLayout drawerLayout, @ColorInt int color, 272 | int statusBarAlpha) { 273 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 274 | return; 275 | } 276 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 277 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 278 | activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 279 | activity.getWindow().setStatusBarColor(Color.TRANSPARENT); 280 | } else { 281 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 282 | } 283 | // 生成一个状态栏大小的矩形 284 | // 添加 statusBarView 到布局中 285 | ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0); 286 | View fakeStatusBarView = contentLayout.findViewById(FAKE_STATUS_BAR_VIEW_ID); 287 | if (fakeStatusBarView != null) { 288 | if (fakeStatusBarView.getVisibility() == View.GONE) { 289 | fakeStatusBarView.setVisibility(View.VISIBLE); 290 | } 291 | fakeStatusBarView.setBackgroundColor(color); 292 | } else { 293 | contentLayout.addView(createStatusBarView(activity, color), 0); 294 | } 295 | // 内容布局不是 LinearLayout 时,设置padding top 296 | if (!(contentLayout instanceof LinearLayout) && contentLayout.getChildAt(1) != null) { 297 | contentLayout.getChildAt(1) 298 | .setPadding(contentLayout.getPaddingLeft(), getStatusBarHeight(activity) + contentLayout.getPaddingTop(), 299 | contentLayout.getPaddingRight(), contentLayout.getPaddingBottom()); 300 | } 301 | // 设置属性 302 | setDrawerLayoutProperty(drawerLayout, contentLayout); 303 | addTranslucentView(activity, statusBarAlpha); 304 | } 305 | 306 | /** 307 | * 设置 DrawerLayout 属性 308 | * 309 | * @param drawerLayout DrawerLayout 310 | * @param drawerLayoutContentLayout DrawerLayout 的内容布局 311 | */ 312 | private static void setDrawerLayoutProperty(DrawerLayout drawerLayout, ViewGroup drawerLayoutContentLayout) { 313 | ViewGroup drawer = (ViewGroup) drawerLayout.getChildAt(1); 314 | drawerLayout.setFitsSystemWindows(false); 315 | drawerLayoutContentLayout.setFitsSystemWindows(false); 316 | drawerLayoutContentLayout.setClipToPadding(true); 317 | drawer.setFitsSystemWindows(false); 318 | } 319 | 320 | /** 321 | * 为DrawerLayout 布局设置状态栏变色(5.0以下无半透明效果,不建议使用) 322 | * 323 | * @param activity 需要设置的activity 324 | * @param drawerLayout DrawerLayout 325 | * @param color 状态栏颜色值 326 | */ 327 | @Deprecated 328 | public static void setColorForDrawerLayoutDiff(Activity activity, DrawerLayout drawerLayout, @ColorInt int color) { 329 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 330 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 331 | // 生成一个状态栏大小的矩形 332 | ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0); 333 | View fakeStatusBarView = contentLayout.findViewById(FAKE_STATUS_BAR_VIEW_ID); 334 | if (fakeStatusBarView != null) { 335 | if (fakeStatusBarView.getVisibility() == View.GONE) { 336 | fakeStatusBarView.setVisibility(View.VISIBLE); 337 | } 338 | fakeStatusBarView.setBackgroundColor(calculateStatusColor(color, DEFAULT_STATUS_BAR_ALPHA)); 339 | } else { 340 | // 添加 statusBarView 到布局中 341 | contentLayout.addView(createStatusBarView(activity, color), 0); 342 | } 343 | // 内容布局不是 LinearLayout 时,设置padding top 344 | if (!(contentLayout instanceof LinearLayout) && contentLayout.getChildAt(1) != null) { 345 | contentLayout.getChildAt(1).setPadding(0, getStatusBarHeight(activity), 0, 0); 346 | } 347 | // 设置属性 348 | setDrawerLayoutProperty(drawerLayout, contentLayout); 349 | } 350 | } 351 | 352 | /** 353 | * 为 DrawerLayout 布局设置状态栏透明 354 | * 355 | * @param activity 需要设置的activity 356 | * @param drawerLayout DrawerLayout 357 | */ 358 | public static void setTranslucentForDrawerLayout(Activity activity, DrawerLayout drawerLayout) { 359 | setTranslucentForDrawerLayout(activity, drawerLayout, DEFAULT_STATUS_BAR_ALPHA); 360 | } 361 | 362 | /** 363 | * 为 DrawerLayout 布局设置状态栏透明 364 | * 365 | * @param activity 需要设置的activity 366 | * @param drawerLayout DrawerLayout 367 | */ 368 | public static void setTranslucentForDrawerLayout(Activity activity, DrawerLayout drawerLayout, int statusBarAlpha) { 369 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 370 | return; 371 | } 372 | setTransparentForDrawerLayout(activity, drawerLayout); 373 | addTranslucentView(activity, statusBarAlpha); 374 | } 375 | 376 | /** 377 | * 为 DrawerLayout 布局设置状态栏透明 378 | * 379 | * @param activity 需要设置的activity 380 | * @param drawerLayout DrawerLayout 381 | */ 382 | public static void setTransparentForDrawerLayout(Activity activity, DrawerLayout drawerLayout) { 383 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 384 | return; 385 | } 386 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 387 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 388 | activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 389 | activity.getWindow().setStatusBarColor(Color.TRANSPARENT); 390 | } else { 391 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 392 | } 393 | 394 | ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0); 395 | // 内容布局不是 LinearLayout 时,设置padding top 396 | if (!(contentLayout instanceof LinearLayout) && contentLayout.getChildAt(1) != null) { 397 | contentLayout.getChildAt(1).setPadding(0, getStatusBarHeight(activity), 0, 0); 398 | } 399 | 400 | // 设置属性 401 | setDrawerLayoutProperty(drawerLayout, contentLayout); 402 | } 403 | 404 | /** 405 | * 为 DrawerLayout 布局设置状态栏透明(5.0以上半透明效果,不建议使用) 406 | * 407 | * @param activity 需要设置的activity 408 | * @param drawerLayout DrawerLayout 409 | */ 410 | @Deprecated 411 | public static void setTranslucentForDrawerLayoutDiff(Activity activity, DrawerLayout drawerLayout) { 412 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 413 | // 设置状态栏透明 414 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 415 | // 设置内容布局属性 416 | ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0); 417 | contentLayout.setFitsSystemWindows(true); 418 | contentLayout.setClipToPadding(true); 419 | // 设置抽屉布局属性 420 | ViewGroup vg = (ViewGroup) drawerLayout.getChildAt(1); 421 | vg.setFitsSystemWindows(false); 422 | // 设置 DrawerLayout 属性 423 | drawerLayout.setFitsSystemWindows(false); 424 | } 425 | } 426 | 427 | /** 428 | * 为头部是 ImageView 的界面设置状态栏全透明 429 | * 430 | * @param activity 需要设置的activity 431 | * @param needOffsetView 需要向下偏移的 View 432 | */ 433 | public static void setTransparentForImageView(Activity activity, View needOffsetView) { 434 | setTranslucentForImageView(activity, 0, needOffsetView); 435 | } 436 | 437 | /** 438 | * 为头部是 ImageView 的界面设置状态栏透明(使用默认透明度) 439 | * 440 | * @param activity 需要设置的activity 441 | * @param needOffsetView 需要向下偏移的 View 442 | */ 443 | public static void setTranslucentForImageView(Activity activity, View needOffsetView) { 444 | setTranslucentForImageView(activity, DEFAULT_STATUS_BAR_ALPHA, needOffsetView); 445 | } 446 | 447 | /** 448 | * 为头部是 ImageView 的界面设置状态栏透明 449 | * 450 | * @param activity 需要设置的activity 451 | * @param statusBarAlpha 状态栏透明度 452 | * @param needOffsetView 需要向下偏移的 View 453 | */ 454 | public static void setTranslucentForImageView(Activity activity, int statusBarAlpha, View needOffsetView) { 455 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 456 | return; 457 | } 458 | setTransparentForWindow(activity); 459 | addTranslucentView(activity, statusBarAlpha); 460 | if (needOffsetView != null) { 461 | Object haveSetOffset = needOffsetView.getTag(TAG_KEY_HAVE_SET_OFFSET); 462 | if (haveSetOffset != null && (Boolean) haveSetOffset) { 463 | return; 464 | } 465 | ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) needOffsetView.getLayoutParams(); 466 | layoutParams.setMargins(layoutParams.leftMargin, layoutParams.topMargin + getStatusBarHeight(activity), 467 | layoutParams.rightMargin, layoutParams.bottomMargin); 468 | needOffsetView.setTag(TAG_KEY_HAVE_SET_OFFSET, true); 469 | } 470 | } 471 | 472 | /** 473 | * 为 fragment 头部是 ImageView 的设置状态栏透明 474 | * 475 | * @param activity fragment 对应的 activity 476 | * @param needOffsetView 需要向下偏移的 View 477 | */ 478 | public static void setTranslucentForImageViewInFragment(Activity activity, View needOffsetView) { 479 | setTranslucentForImageViewInFragment(activity, DEFAULT_STATUS_BAR_ALPHA, needOffsetView); 480 | } 481 | 482 | /** 483 | * 为 fragment 头部是 ImageView 的设置状态栏透明 484 | * 485 | * @param activity fragment 对应的 activity 486 | * @param needOffsetView 需要向下偏移的 View 487 | */ 488 | public static void setTransparentForImageViewInFragment(Activity activity, View needOffsetView) { 489 | setTranslucentForImageViewInFragment(activity, 0, needOffsetView); 490 | } 491 | 492 | /** 493 | * 为 fragment 头部是 ImageView 的设置状态栏透明 494 | * 495 | * @param activity fragment 对应的 activity 496 | * @param statusBarAlpha 状态栏透明度 497 | * @param needOffsetView 需要向下偏移的 View 498 | */ 499 | public static void setTranslucentForImageViewInFragment(Activity activity, int statusBarAlpha, View needOffsetView) { 500 | setTranslucentForImageView(activity, statusBarAlpha, needOffsetView); 501 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 502 | clearPreviousSetting(activity); 503 | } 504 | } 505 | 506 | /** 507 | * 隐藏伪状态栏 View 508 | * 509 | * @param activity 调用的 Activity 510 | */ 511 | public static void hideFakeStatusBarView(Activity activity) { 512 | ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView(); 513 | View fakeStatusBarView = decorView.findViewById(FAKE_STATUS_BAR_VIEW_ID); 514 | if (fakeStatusBarView != null) { 515 | fakeStatusBarView.setVisibility(View.GONE); 516 | } 517 | View fakeTranslucentView = decorView.findViewById(FAKE_TRANSLUCENT_VIEW_ID); 518 | if (fakeTranslucentView != null) { 519 | fakeTranslucentView.setVisibility(View.GONE); 520 | } 521 | } 522 | 523 | /////////////////////////////////////////////////////////////////////////////////// 524 | 525 | @TargetApi(Build.VERSION_CODES.KITKAT) 526 | private static void clearPreviousSetting(Activity activity) { 527 | ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView(); 528 | View fakeStatusBarView = decorView.findViewById(FAKE_STATUS_BAR_VIEW_ID); 529 | if (fakeStatusBarView != null) { 530 | decorView.removeView(fakeStatusBarView); 531 | ViewGroup rootView = (ViewGroup) ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0); 532 | rootView.setPadding(0, 0, 0, 0); 533 | } 534 | } 535 | 536 | /** 537 | * 添加半透明矩形条 538 | * 539 | * @param activity 需要设置的 activity 540 | * @param statusBarAlpha 透明值 541 | */ 542 | private static void addTranslucentView(Activity activity, int statusBarAlpha) { 543 | ViewGroup contentView = (ViewGroup) activity.findViewById(android.R.id.content); 544 | View fakeTranslucentView = contentView.findViewById(FAKE_TRANSLUCENT_VIEW_ID); 545 | if (fakeTranslucentView != null) { 546 | if (fakeTranslucentView.getVisibility() == View.GONE) { 547 | fakeTranslucentView.setVisibility(View.VISIBLE); 548 | } 549 | fakeTranslucentView.setBackgroundColor(Color.argb(statusBarAlpha, 0, 0, 0)); 550 | } else { 551 | contentView.addView(createTranslucentStatusBarView(activity, statusBarAlpha)); 552 | } 553 | } 554 | 555 | /** 556 | * 生成一个和状态栏大小相同的彩色矩形条 557 | * 558 | * @param activity 需要设置的 activity 559 | * @param color 状态栏颜色值 560 | * @return 状态栏矩形条 561 | */ 562 | private static View createStatusBarView(Activity activity, @ColorInt int color) { 563 | return createStatusBarView(activity, color, 0); 564 | } 565 | 566 | /** 567 | * 生成一个和状态栏大小相同的半透明矩形条 568 | * 569 | * @param activity 需要设置的activity 570 | * @param color 状态栏颜色值 571 | * @param alpha 透明值 572 | * @return 状态栏矩形条 573 | */ 574 | private static View createStatusBarView(Activity activity, @ColorInt int color, int alpha) { 575 | // 绘制一个和状态栏一样高的矩形 576 | View statusBarView = new View(activity); 577 | LinearLayout.LayoutParams params = 578 | new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity)); 579 | statusBarView.setLayoutParams(params); 580 | statusBarView.setBackgroundColor(calculateStatusColor(color, alpha)); 581 | statusBarView.setId(FAKE_STATUS_BAR_VIEW_ID); 582 | return statusBarView; 583 | } 584 | 585 | /** 586 | * 设置根布局参数 587 | */ 588 | private static void setRootView(Activity activity) { 589 | ViewGroup parent = (ViewGroup) activity.findViewById(android.R.id.content); 590 | for (int i = 0, count = parent.getChildCount(); i < count; i++) { 591 | View childView = parent.getChildAt(i); 592 | if (childView instanceof ViewGroup) { 593 | childView.setFitsSystemWindows(true); 594 | ((ViewGroup) childView).setClipToPadding(true); 595 | } 596 | } 597 | } 598 | 599 | /** 600 | * 设置透明 601 | */ 602 | private static void setTransparentForWindow(Activity activity) { 603 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 604 | activity.getWindow().setStatusBarColor(Color.TRANSPARENT); 605 | activity.getWindow() 606 | .getDecorView() 607 | .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); 608 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 609 | activity.getWindow() 610 | .setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 611 | } 612 | } 613 | 614 | /** 615 | * 使状态栏透明 616 | */ 617 | @TargetApi(Build.VERSION_CODES.KITKAT) 618 | private static void transparentStatusBar(Activity activity) { 619 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 620 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 621 | activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 622 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); 623 | activity.getWindow().setStatusBarColor(Color.TRANSPARENT); 624 | } else { 625 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 626 | } 627 | } 628 | 629 | /** 630 | * 创建半透明矩形 View 631 | * 632 | * @param alpha 透明值 633 | * @return 半透明 View 634 | */ 635 | private static View createTranslucentStatusBarView(Activity activity, int alpha) { 636 | // 绘制一个和状态栏一样高的矩形 637 | View statusBarView = new View(activity); 638 | LinearLayout.LayoutParams params = 639 | new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity)); 640 | statusBarView.setLayoutParams(params); 641 | statusBarView.setBackgroundColor(Color.argb(alpha, 0, 0, 0)); 642 | statusBarView.setId(FAKE_TRANSLUCENT_VIEW_ID); 643 | return statusBarView; 644 | } 645 | 646 | /** 647 | * 获取状态栏高度 648 | * 649 | * @param context context 650 | * @return 状态栏高度 651 | */ 652 | public static int getStatusBarHeight(Context context) { 653 | // 获得状态栏高度 654 | int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); 655 | return context.getResources().getDimensionPixelSize(resourceId); 656 | } 657 | 658 | /** 659 | * 计算状态栏颜色 660 | * 661 | * @param color color值 662 | * @param alpha alpha值 663 | * @return 最终的状态栏颜色 664 | */ 665 | private static int calculateStatusColor(@ColorInt int color, int alpha) { 666 | if (alpha == 0) { 667 | return color; 668 | } 669 | float a = 1 - alpha / 255f; 670 | int red = color >> 16 & 0xff; 671 | int green = color >> 8 & 0xff; 672 | int blue = color & 0xff; 673 | red = (int) (red * a + 0.5); 674 | green = (int) (green * a + 0.5); 675 | blue = (int) (blue * a + 0.5); 676 | return 0xff << 24 | red << 16 | green << 8 | blue; 677 | } 678 | 679 | public static boolean setMiuiStatusBarDarkMode(Activity activity, boolean darkmode) { 680 | Class clazz = activity.getWindow().getClass(); 681 | try { 682 | int darkModeFlag = 0; 683 | Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams"); 684 | Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE"); 685 | darkModeFlag = field.getInt(layoutParams); 686 | Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class); 687 | extraFlagField.invoke(activity.getWindow(), darkmode ? darkModeFlag : 0, darkModeFlag); 688 | return true; 689 | } catch (Exception e) { 690 | e.printStackTrace(); 691 | } 692 | return false; 693 | } 694 | 695 | public static boolean setMeizuStatusBarDarkMode(Activity activity, boolean darkmode) { 696 | boolean result = false; 697 | if (activity != null) { 698 | try { 699 | WindowManager.LayoutParams lp = activity.getWindow().getAttributes(); 700 | Field darkFlag = WindowManager.LayoutParams.class.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON"); 701 | Field meizuFlags = WindowManager.LayoutParams.class.getDeclaredField("meizuFlags"); 702 | darkFlag.setAccessible(true); 703 | meizuFlags.setAccessible(true); 704 | int bit = darkFlag.getInt(null); 705 | int value = meizuFlags.getInt(lp); 706 | if (darkmode) { 707 | value |= bit; 708 | } else { 709 | value &= ~bit; 710 | } 711 | meizuFlags.setInt(lp, value); 712 | activity.getWindow().setAttributes(lp); 713 | result = true; 714 | } catch (Exception e) { 715 | } 716 | } 717 | return result; 718 | } 719 | 720 | 721 | public static int DEFAULT_COLOR = 0; 722 | public static float DEFAULT_ALPHA = 0;//Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 0.2f : 0.3f; 723 | public static final int MIN_API = 19; 724 | 725 | // 726 | public static void immersive(Activity activity) { 727 | immersive(activity, DEFAULT_COLOR, DEFAULT_ALPHA); 728 | } 729 | 730 | public static void immersive(Activity activity, int color, @FloatRange(from = 0.0, to = 1.0) float alpha) { 731 | immersive(activity.getWindow(), color, alpha); 732 | } 733 | 734 | public static void immersive(Activity activity, int color) { 735 | immersive(activity.getWindow(), color, 1f); 736 | } 737 | 738 | public static void immersive(Window window) { 739 | immersive(window, DEFAULT_COLOR, DEFAULT_ALPHA); 740 | } 741 | 742 | public static void immersive(Window window, int color) { 743 | immersive(window, color, 1f); 744 | } 745 | 746 | public static void immersive(Window window, int color, @FloatRange(from = 0.0, to = 1.0) float alpha) { 747 | if (Build.VERSION.SDK_INT >= 21) { 748 | window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 749 | window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 750 | window.setStatusBarColor(mixtureColor(color, alpha)); 751 | 752 | int systemUiVisibility = window.getDecorView().getSystemUiVisibility(); 753 | systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; 754 | systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE; 755 | window.getDecorView().setSystemUiVisibility(systemUiVisibility); 756 | } else if (Build.VERSION.SDK_INT >= 19) { 757 | window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 758 | setTranslucentView((ViewGroup) window.getDecorView(), color, alpha); 759 | } else if (Build.VERSION.SDK_INT >= MIN_API && Build.VERSION.SDK_INT > 16) { 760 | int systemUiVisibility = window.getDecorView().getSystemUiVisibility(); 761 | systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; 762 | systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE; 763 | window.getDecorView().setSystemUiVisibility(systemUiVisibility); 764 | } 765 | } 766 | // 767 | 768 | // 769 | public static void darkMode(Activity activity, boolean dark) { 770 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 771 | if (Build.MODEL.equals("MI 5") && isMIUI6Later()) 772 | darkModeForMIUI6(activity.getWindow(), dark); 773 | else 774 | darkModeForM(activity.getWindow(), dark); 775 | } else if (isFlyme4Later()) { 776 | darkModeForFlyme4(activity.getWindow(), dark); 777 | } else if (isMIUI6Later()) { 778 | darkModeForMIUI6(activity.getWindow(), dark); 779 | } 780 | } 781 | 782 | /** 783 | * 设置状态栏darkMode,字体颜色及icon变黑(目前支持MIUI6以上,Flyme4以上,Android M以上) 784 | */ 785 | public static void darkMode(Activity activity) { 786 | darkMode(activity.getWindow(), DEFAULT_COLOR, DEFAULT_ALPHA); 787 | } 788 | 789 | public static void darkMode(Activity activity, int color, @FloatRange(from = 0.0, to = 1.0) float alpha) { 790 | darkMode(activity.getWindow(), color, alpha); 791 | } 792 | 793 | /** 794 | * 设置状态栏darkMode,字体颜色及icon变黑(目前支持MIUI6以上,Flyme4以上,Android M以上) 795 | */ 796 | public static void darkMode(Window window, int color, @FloatRange(from = 0.0, to = 1.0) float alpha) { 797 | if (isFlyme4Later()) { 798 | darkModeForFlyme4(window, true); 799 | immersive(window, color, alpha); 800 | } else if (isMIUI6Later()) { 801 | darkModeForMIUI6(window, true); 802 | immersive(window, color, alpha); 803 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 804 | darkModeForM(window, true); 805 | immersive(window, color, alpha); 806 | } else if (Build.VERSION.SDK_INT >= 19) { 807 | window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 808 | setTranslucentView((ViewGroup) window.getDecorView(), color, alpha); 809 | } else { 810 | immersive(window, color, alpha); 811 | } 812 | // if (Build.VERSION.SDK_INT >= 21) { 813 | // window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 814 | // window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 815 | // window.setStatusBarColor(Color.TRANSPARENT); 816 | // } else if (Build.VERSION.SDK_INT >= 19) { 817 | // window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 818 | // } 819 | 820 | // setTranslucentView((ViewGroup) window.getDecorView(), color, alpha); 821 | } 822 | 823 | //-------------------------> 824 | 825 | /** 826 | * android 6.0设置字体颜色 827 | */ 828 | @RequiresApi(Build.VERSION_CODES.M) 829 | private static void darkModeForM(Window window, boolean dark) { 830 | // window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 831 | // window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 832 | // window.setStatusBarColor(Color.TRANSPARENT); 833 | 834 | int systemUiVisibility = window.getDecorView().getSystemUiVisibility(); 835 | if (dark) { 836 | systemUiVisibility |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; 837 | } else { 838 | systemUiVisibility &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; 839 | } 840 | window.getDecorView().setSystemUiVisibility(systemUiVisibility); 841 | } 842 | 843 | /** 844 | * 设置Flyme4+的darkMode,darkMode时候字体颜色及icon变黑 845 | * http://open-wiki.flyme.cn/index.php?title=Flyme%E7%B3%BB%E7%BB%9FAPI 846 | */ 847 | public static boolean darkModeForFlyme4(Window window, boolean dark) { 848 | boolean result = false; 849 | if (window != null) { 850 | try { 851 | WindowManager.LayoutParams e = window.getAttributes(); 852 | Field darkFlag = WindowManager.LayoutParams.class.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON"); 853 | Field meizuFlags = WindowManager.LayoutParams.class.getDeclaredField("meizuFlags"); 854 | darkFlag.setAccessible(true); 855 | meizuFlags.setAccessible(true); 856 | int bit = darkFlag.getInt(null); 857 | int value = meizuFlags.getInt(e); 858 | if (dark) { 859 | value |= bit; 860 | } else { 861 | value &= ~bit; 862 | } 863 | 864 | meizuFlags.setInt(e, value); 865 | window.setAttributes(e); 866 | result = true; 867 | } catch (Exception var8) { 868 | Log.e("StatusBar", "darkIcon: failed"); 869 | } 870 | } 871 | 872 | return result; 873 | } 874 | 875 | /** 876 | * 设置MIUI6+的状态栏是否为darkMode,darkMode时候字体颜色及icon变黑 877 | * http://dev.xiaomi.com/doc/p=4769/ 878 | */ 879 | public static boolean darkModeForMIUI6(Window window, boolean darkmode) { 880 | Class clazz = window.getClass(); 881 | try { 882 | int darkModeFlag = 0; 883 | Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams"); 884 | Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE"); 885 | darkModeFlag = field.getInt(layoutParams); 886 | Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class); 887 | extraFlagField.invoke(window, darkmode ? darkModeFlag : 0, darkModeFlag); 888 | return true; 889 | } catch (Exception e) { 890 | e.printStackTrace(); 891 | return false; 892 | } 893 | } 894 | 895 | /** 896 | * 判断是否Flyme4以上 897 | */ 898 | public static boolean isFlyme4Later() { 899 | return Build.FINGERPRINT.contains("Flyme_OS_4") 900 | || Build.VERSION.INCREMENTAL.contains("Flyme_OS_4") 901 | || Pattern.compile("Flyme OS [4|5]", Pattern.CASE_INSENSITIVE).matcher(Build.DISPLAY).find(); 902 | } 903 | 904 | /** 905 | * 判断是否为MIUI6以上 906 | */ 907 | public static boolean isMIUI6Later() { 908 | try { 909 | Class clz = Class.forName("android.os.SystemProperties"); 910 | Method mtd = clz.getMethod("get", String.class); 911 | String val = (String) mtd.invoke(null, "ro.miui.ui.version.name"); 912 | val = val.replaceAll("[vV]", ""); 913 | int version = Integer.parseInt(val); 914 | return version >= 6; 915 | } catch (Exception e) { 916 | return false; 917 | } 918 | } 919 | // 920 | 921 | 922 | /** 923 | * 增加View的paddingTop,增加的值为状态栏高度 924 | */ 925 | public static void setPadding(Context context, View view) { 926 | if (Build.VERSION.SDK_INT >= MIN_API) { 927 | view.setPadding(view.getPaddingLeft(), view.getPaddingTop() + getStatusBarHeight(context), 928 | view.getPaddingRight(), view.getPaddingBottom()); 929 | } 930 | } 931 | 932 | public static void setPadding0(Context context, View view) { 933 | if (Build.VERSION.SDK_INT >= MIN_API) { 934 | view.setPadding(0, 0, 0, 0); 935 | } 936 | } 937 | 938 | public static void setPadding1(Context context, View view) { 939 | if (Build.VERSION.SDK_INT >= MIN_API) { 940 | view.setPadding(0, getStatusBarHeight(context), 0, 0); 941 | } 942 | } 943 | 944 | /** 945 | * 增加View的paddingTop,增加的值为状态栏高度 (智能判断,并设置高度) 946 | */ 947 | public static void setPaddingSmart(Context context, View view) { 948 | if (Build.VERSION.SDK_INT >= MIN_API) { 949 | ViewGroup.LayoutParams lp = view.getLayoutParams(); 950 | if (lp != null && lp.height > 0) { 951 | lp.height += getStatusBarHeight(context);//增高 952 | } 953 | view.setPadding(view.getPaddingLeft(), view.getPaddingTop() + getStatusBarHeight(context), 954 | view.getPaddingRight(), view.getPaddingBottom()); 955 | } 956 | } 957 | 958 | /** 959 | * 增加View的高度以及paddingTop,增加的值为状态栏高度.一般是在沉浸式全屏给ToolBar用的 960 | */ 961 | public static void setHeightAndPadding(Context context, View view) { 962 | if (Build.VERSION.SDK_INT >= MIN_API) { 963 | ViewGroup.LayoutParams lp = view.getLayoutParams(); 964 | lp.height += getStatusBarHeight(context);//增高 965 | view.setPadding(view.getPaddingLeft(), view.getPaddingTop() + getStatusBarHeight(context), 966 | view.getPaddingRight(), view.getPaddingBottom()); 967 | } 968 | } 969 | 970 | /** 971 | * 增加View上边距(MarginTop)一般是给高度为 WARP_CONTENT 的小控件用的 972 | */ 973 | public static void setMargin(Context context, View view) { 974 | if (Build.VERSION.SDK_INT >= MIN_API) { 975 | ViewGroup.LayoutParams lp = view.getLayoutParams(); 976 | if (lp instanceof ViewGroup.MarginLayoutParams) { 977 | ((ViewGroup.MarginLayoutParams) lp).topMargin += getStatusBarHeight(context);//增高 978 | } 979 | view.setLayoutParams(lp); 980 | } 981 | } 982 | 983 | /** 984 | * 创建假的透明栏 985 | */ 986 | public static void setTranslucentView(ViewGroup container, int color, @FloatRange(from = 0.0, to = 1.0) float alpha) { 987 | if (Build.VERSION.SDK_INT >= 19) { 988 | int mixtureColor = mixtureColor(color, alpha); 989 | View translucentView = container.findViewById(android.R.id.custom); 990 | if (translucentView == null && mixtureColor != 0) { 991 | translucentView = new View(container.getContext()); 992 | translucentView.setId(android.R.id.custom); 993 | ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams( 994 | ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(container.getContext())); 995 | container.addView(translucentView, lp); 996 | } 997 | if (translucentView != null) { 998 | translucentView.setBackgroundColor(mixtureColor); 999 | } 1000 | } 1001 | } 1002 | 1003 | public static int mixtureColor(int color, @FloatRange(from = 0.0, to = 1.0) float alpha) { 1004 | int a = (color & 0xff000000) == 0 ? 0xff : color >>> 24; 1005 | return (color & 0x00ffffff) | (((int) (a * alpha)) << 24); 1006 | } 1007 | 1008 | /** 1009 | * 判断底部navigator是否存在 1010 | * 1011 | * @return 1012 | * @paramwindowManager 1013 | */ 1014 | 1015 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) 1016 | public static boolean hasNavBar(WindowManager windowManager) { 1017 | 1018 | Display d = windowManager.getDefaultDisplay(); 1019 | 1020 | DisplayMetrics realDisplayMetrics = new DisplayMetrics(); 1021 | 1022 | d.getRealMetrics(realDisplayMetrics); 1023 | 1024 | int realHeight = realDisplayMetrics.heightPixels; 1025 | 1026 | int realWidth = realDisplayMetrics.widthPixels; 1027 | 1028 | DisplayMetrics displayMetrics = new DisplayMetrics(); 1029 | 1030 | d.getMetrics(displayMetrics); 1031 | 1032 | int displayHeight = displayMetrics.heightPixels; 1033 | 1034 | int displayWidth = displayMetrics.widthPixels; 1035 | 1036 | return (realWidth - displayWidth) > 0 || (realHeight - displayHeight) > 0; 1037 | 1038 | } 1039 | 1040 | 1041 | /** 1042 | * 设置Android状态栏的字体颜色,状态栏为亮色的时候字体和图标是黑色,状态栏为暗色的时候字体和图标为白色 1043 | * 1044 | * @param dark 状态栏字体和图标是否为深色 1045 | */ 1046 | protected static void setStatusBarFontDark(Activity mActivity, boolean dark) { 1047 | // 小米MIUI 1048 | try { 1049 | Window window = mActivity.getWindow(); 1050 | Class clazz = mActivity.getWindow().getClass(); 1051 | Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams"); 1052 | Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE"); 1053 | int darkModeFlag = field.getInt(layoutParams); 1054 | Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class); 1055 | if (dark) { //状态栏亮色且黑色字体 1056 | extraFlagField.invoke(window, darkModeFlag, darkModeFlag); 1057 | } else { //清除黑色字体 1058 | extraFlagField.invoke(window, 0, darkModeFlag); 1059 | } 1060 | } catch (Exception e) { 1061 | e.printStackTrace(); 1062 | } 1063 | 1064 | // 魅族FlymeUI 1065 | try { 1066 | Window window = mActivity.getWindow(); 1067 | WindowManager.LayoutParams lp = window.getAttributes(); 1068 | Field darkFlag = WindowManager.LayoutParams.class.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON"); 1069 | Field meizuFlags = WindowManager.LayoutParams.class.getDeclaredField("meizuFlags"); 1070 | darkFlag.setAccessible(true); 1071 | meizuFlags.setAccessible(true); 1072 | int bit = darkFlag.getInt(null); 1073 | int value = meizuFlags.getInt(lp); 1074 | if (dark) { 1075 | value |= bit; 1076 | } else { 1077 | value &= ~bit; 1078 | } 1079 | meizuFlags.setInt(lp, value); 1080 | window.setAttributes(lp); 1081 | } catch (Exception e) { 1082 | e.printStackTrace(); 1083 | } 1084 | // android6.0+系统 1085 | // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 1086 | // if (dark) { 1087 | // mActivity.getWindow().getDecorView().setSystemUiVisibility( 1088 | // View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 1089 | // | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); 1090 | // } 1091 | // } 1092 | } 1093 | 1094 | 1095 | public static void initStatusBarFontDark(Activity mActivity, boolean dark) { 1096 | // 小米MIUI 1097 | try { 1098 | Window window = mActivity.getWindow(); 1099 | Class clazz = mActivity.getWindow().getClass(); 1100 | Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams"); 1101 | Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE"); 1102 | int darkModeFlag = field.getInt(layoutParams); 1103 | Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class); 1104 | if (dark) { //状态栏亮色且黑色字体 1105 | extraFlagField.invoke(window, darkModeFlag, darkModeFlag); 1106 | } else { //清除黑色字体 1107 | extraFlagField.invoke(window, 0, darkModeFlag); 1108 | } 1109 | } catch (Exception e) { 1110 | e.printStackTrace(); 1111 | } 1112 | 1113 | // 魅族FlymeUI 1114 | try { 1115 | Window window = mActivity.getWindow(); 1116 | WindowManager.LayoutParams lp = window.getAttributes(); 1117 | Field darkFlag = WindowManager.LayoutParams.class.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON"); 1118 | Field meizuFlags = WindowManager.LayoutParams.class.getDeclaredField("meizuFlags"); 1119 | darkFlag.setAccessible(true); 1120 | meizuFlags.setAccessible(true); 1121 | int bit = darkFlag.getInt(null); 1122 | int value = meizuFlags.getInt(lp); 1123 | if (dark) { 1124 | value |= bit; 1125 | } else { 1126 | value &= ~bit; 1127 | } 1128 | meizuFlags.setInt(lp, value); 1129 | window.setAttributes(lp); 1130 | } catch (Exception e) { 1131 | e.printStackTrace(); 1132 | } 1133 | } 1134 | 1135 | /** 1136 | * 设置应用全屏 1137 | * 1138 | * @return void 1139 | * @author lanhm 1140 | * @date 2014年11月18日 下午5:48:30 1141 | * @MethodName: setFullScreen 1142 | */ 1143 | public static void setFullScreen(Activity activity) { 1144 | activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); 1145 | } 1146 | 1147 | /** 1148 | * 退出全屏 1149 | * 1150 | * @return void 1151 | * @author lanhm 1152 | * @date 2014年11月18日 下午5:48:52 1153 | * @MethodName: quitFullScreen 1154 | */ 1155 | public static void quitFullScreen(Activity activity) { 1156 | final WindowManager.LayoutParams attrs = activity.getWindow().getAttributes(); 1157 | attrs.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN); 1158 | activity.getWindow().setAttributes(attrs); 1159 | activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); 1160 | } 1161 | 1162 | //隐藏虚拟按键,并且全屏 1163 | public static void hideBottomUIMenu(Activity activity) { 1164 | if (Build.VERSION.SDK_INT > 11 && Build.VERSION.SDK_INT < 19) { // lower api 1165 | View v = activity.getWindow().getDecorView(); 1166 | v.setSystemUiVisibility(View.GONE); 1167 | } else if (Build.VERSION.SDK_INT >= 19) { 1168 | //for new api versions. 1169 | View decorView = activity.getWindow().getDecorView(); 1170 | int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 1171 | | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_FULLSCREEN; 1172 | decorView.setSystemUiVisibility(uiOptions); 1173 | 1174 | } 1175 | } 1176 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/utils/SysUtils.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.utils 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.pm.ApplicationInfo 6 | import android.content.pm.PackageInfo 7 | import android.content.pm.PackageManager 8 | import android.os.Build 9 | import android.os.Environment 10 | import com.byl.mvvm.App 11 | import com.byl.mvvm.R 12 | import java.io.File 13 | 14 | 15 | object SysUtils { 16 | 17 | fun dp2Px(context: Context, dp: Float): Int { 18 | val scale: Float = context.resources.displayMetrics.density 19 | return (dp * scale + 0.5f).toInt() 20 | } 21 | 22 | fun px2Dp(context: Context, px: Float): Int { 23 | val scale: Float = context.resources.displayMetrics.density 24 | return (px / scale + 0.5f).toInt() 25 | } 26 | 27 | 28 | // 获取当前APP名称 29 | fun getAppName(context: Context): String? { 30 | val packageManager = context.packageManager 31 | val applicationInfo: ApplicationInfo 32 | applicationInfo = try { 33 | packageManager.getApplicationInfo(context.packageName, 0) 34 | } catch (e: java.lang.Exception) { 35 | return context.resources.getString(R.string.app_name) 36 | } 37 | return packageManager.getApplicationLabel(applicationInfo).toString() 38 | } 39 | 40 | fun getAppVersion(): String? { 41 | val context: Context = App.instance 42 | val manager: PackageManager = context.packageManager 43 | return try { 44 | val info: PackageInfo = manager.getPackageInfo(context.packageName, 0) 45 | info.versionName 46 | } catch (e: PackageManager.NameNotFoundException) { 47 | e.printStackTrace() 48 | "1.0.0" 49 | } 50 | } 51 | 52 | fun getAppVersionCode(): Int { 53 | val context: Context = App.instance 54 | val manager: PackageManager = context.packageManager 55 | return try { 56 | val info: PackageInfo = manager.getPackageInfo(context.packageName, 0) 57 | info.versionCode 58 | } catch (e: PackageManager.NameNotFoundException) { 59 | e.printStackTrace() 60 | 1 61 | } 62 | } 63 | 64 | /** 65 | * 获取手机型号 66 | * 67 | * @return 手机型号 68 | */ 69 | fun getSystemModel(): String? { 70 | return try { 71 | Build.MODEL 72 | } catch (e: Exception) { 73 | "" 74 | } 75 | } 76 | 77 | /** 78 | * 获取手机厂商 79 | * 80 | * @return 手机厂商 81 | */ 82 | fun getDeviceBrand(): String? { 83 | return try { 84 | Build.BRAND 85 | } catch (e: Exception) { 86 | "" 87 | } 88 | } 89 | 90 | fun initFiles() { 91 | var file = File(Environment.getExternalStorageDirectory(), "MVVM/data") 92 | if (!file.exists()) file.mkdirs() 93 | file = File(Environment.getExternalStorageDirectory(), "MVVM/images") 94 | if (!file.exists()) file.mkdirs() 95 | file = File(Environment.getExternalStorageDirectory(), "MVVM/download") 96 | if (!file.exists()) file.mkdirs() 97 | } 98 | 99 | fun getScreenWidth(activity: Activity): Int { 100 | var width = 0 101 | val windowManager = activity.windowManager 102 | val display = windowManager.defaultDisplay 103 | width = display.width 104 | return width 105 | } 106 | 107 | fun getScreenHeight(activity: Activity): Int { 108 | var height = 0 109 | val windowManager = activity.windowManager 110 | val display = windowManager.defaultDisplay 111 | height = display.height 112 | return height 113 | } 114 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/utils/ToastUtil.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.utils 2 | 3 | import android.content.Context 4 | import android.text.TextUtils 5 | import android.widget.Toast 6 | 7 | 8 | object ToastUtil { 9 | fun showToast(context: Context?, message: String?) { 10 | if (!TextUtils.isEmpty(message)) Toast.makeText(context, message, Toast.LENGTH_SHORT) 11 | .show() 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/byl/mvvm/widget/ViewClickDelay.kt: -------------------------------------------------------------------------------- 1 | package com.byl.mvvm.widget 2 | 3 | import android.view.View 4 | import com.byl.mvvm.widget.ViewClickDelay.SPACE_TIME 5 | import com.byl.mvvm.widget.ViewClickDelay.hash 6 | import com.byl.mvvm.widget.ViewClickDelay.lastClickTime 7 | 8 | object ViewClickDelay { 9 | var hash: Int = 0 10 | var lastClickTime: Long = 0 11 | var SPACE_TIME: Long = 1000 12 | } 13 | 14 | infix fun View.clicks(clickAction: () -> Unit) { 15 | this.setOnClickListener { 16 | if (this.hashCode() != hash) { 17 | hash = this.hashCode() 18 | lastClickTime = System.currentTimeMillis() 19 | clickAction() 20 | } else { 21 | val currentTime = System.currentTimeMillis() 22 | if (currentTime - lastClickTime > SPACE_TIME) { 23 | lastClickTime = System.currentTimeMillis() 24 | clickAction() 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 24 | 25 | 36 | 37 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_test_event.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 24 | 25 | 28 | 29 | 33 | 34 | 39 | 40 | 41 |