├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── dev │ └── debug │ │ ├── MVVMSmart-dev-debug_v1.0_t20200502-18.apk │ │ └── output.json ├── proguard-rules.pro ├── src │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── wzq │ │ │ └── mvvmsmart │ │ │ └── ExampleInstrumentedTest.java │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── wzq │ │ │ │ └── sample │ │ │ │ ├── app │ │ │ │ └── App.kt │ │ │ │ ├── base │ │ │ │ ├── BaseActivity.kt │ │ │ │ ├── BaseFragment.kt │ │ │ │ └── BaseViewModel.kt │ │ │ │ ├── bean │ │ │ │ ├── DemoBean.java │ │ │ │ ├── FormEntity.kt │ │ │ │ └── User.java │ │ │ │ ├── data │ │ │ │ └── source │ │ │ │ │ └── http │ │ │ │ │ ├── HttpDataSourceImpl.kt │ │ │ │ │ ├── service │ │ │ │ │ └── DemoApiService.kt │ │ │ │ │ └── service2 │ │ │ │ │ ├── DemoApiService.kt │ │ │ │ │ └── MRequest.java │ │ │ │ ├── http2 │ │ │ │ ├── BaseConfig.java │ │ │ │ ├── Interceptor │ │ │ │ │ ├── HttpCommonInterceptor.java │ │ │ │ │ └── TokenInterceptor.java │ │ │ │ ├── OkHttpClientHelper.java │ │ │ │ ├── base │ │ │ │ │ ├── BaseObserver.java │ │ │ │ │ ├── BaseRequest.java │ │ │ │ │ └── BaseResponse.java │ │ │ │ ├── cookie │ │ │ │ │ ├── CookieJarImpl.java │ │ │ │ │ ├── CookieStore.java │ │ │ │ │ ├── CookieUtils.java │ │ │ │ │ └── MemoryCookieStore.java │ │ │ │ ├── listener │ │ │ │ │ ├── OnServerResponseListener.java │ │ │ │ │ └── OnServerResponseListener2.java │ │ │ │ ├── net_utils │ │ │ │ │ ├── BaseCommonUtils.java │ │ │ │ │ ├── GsonUtil.java │ │ │ │ │ ├── MetaDataUtil.java │ │ │ │ │ ├── MmkvUtils.java │ │ │ │ │ ├── RetrofitUtil.java │ │ │ │ │ ├── Utils.java │ │ │ │ │ └── gsontypeadapter │ │ │ │ │ │ ├── DoubleTypeAdapter.java │ │ │ │ │ │ ├── FloatTypeAdapter.java │ │ │ │ │ │ ├── IntegerTypeAdapter.java │ │ │ │ │ │ ├── LongTypeAdapter.java │ │ │ │ │ │ └── NumberUtils.java │ │ │ │ ├── observer │ │ │ │ │ └── HttpDisposableObserver.java │ │ │ │ ├── oldhelper │ │ │ │ │ ├── BaseParamsLinkedHashMap.java │ │ │ │ │ └── ParamsLinkedHashMap.java │ │ │ │ └── token │ │ │ │ │ ├── TokenBean.java │ │ │ │ │ ├── TokenService.java │ │ │ │ │ └── TokenUtils.java │ │ │ │ ├── ui │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── MainFragment.kt │ │ │ │ ├── MainViewModel.kt │ │ │ │ ├── bottom_tab │ │ │ │ │ ├── activity │ │ │ │ │ │ └── TabBarActivity.kt │ │ │ │ │ └── fragment │ │ │ │ │ │ ├── TabBar1Fragment.kt │ │ │ │ │ │ ├── TabBar2Fragment.kt │ │ │ │ │ │ ├── TabBar3Fragment.kt │ │ │ │ │ │ └── TabBar4Fragment.kt │ │ │ │ ├── form_livedata │ │ │ │ │ ├── FormFragment.kt │ │ │ │ │ └── FormViewModel.kt │ │ │ │ ├── recycler_multi │ │ │ │ │ ├── MultiRecycleViewFragment.kt │ │ │ │ │ ├── MultiRecycleViewModel.kt │ │ │ │ │ └── MyMultiAdapter.kt │ │ │ │ ├── recycler_single_network │ │ │ │ │ ├── MAdapter.kt │ │ │ │ │ ├── NetWorkFragment.kt │ │ │ │ │ ├── NetWorkModel.kt │ │ │ │ │ ├── NetWorkViewModel.kt │ │ │ │ │ └── detail │ │ │ │ │ │ ├── DetailFragment.kt │ │ │ │ │ │ └── DetailViewModel.kt │ │ │ │ ├── room_db │ │ │ │ │ ├── RoomSampleFragment.kt │ │ │ │ │ ├── RoomSampleViewModel.kt │ │ │ │ │ ├── Word.kt │ │ │ │ │ ├── WordDao.kt │ │ │ │ │ ├── WordDatabase.kt │ │ │ │ │ └── WordRepository.java │ │ │ │ ├── testnet │ │ │ │ │ ├── TestNetFragment.kt │ │ │ │ │ └── TestNetViewModel.kt │ │ │ │ └── viewpager_frgment │ │ │ │ │ ├── BaseFragmentPagerAdapter.kt │ │ │ │ │ ├── BasePagerFragment.kt │ │ │ │ │ └── ViewPagerGroupFragment.kt │ │ │ │ └── utils │ │ │ │ ├── HttpsUtils.java │ │ │ │ ├── RetrofitClient.kt │ │ │ │ └── TestUtils.kt │ │ └── res │ │ │ ├── drawable-xxhdpi │ │ │ ├── back.png │ │ │ ├── huanzhe.png │ │ │ ├── toolbar_more.png │ │ │ ├── wode_select.png │ │ │ ├── xiaoxi_select.png │ │ │ └── yingyong.png │ │ │ ├── layout │ │ │ ├── activity_demo.xml │ │ │ ├── activity_tab_bar.xml │ │ │ ├── fragment_base_pager_temp.xml │ │ │ ├── fragment_detail.xml │ │ │ ├── fragment_form_temp.xml │ │ │ ├── fragment_home.xml │ │ │ ├── fragment_multi_rv.xml │ │ │ ├── fragment_network.xml │ │ │ ├── fragment_room1.xml │ │ │ ├── fragment_tab_bar_1.xml │ │ │ ├── fragment_tab_bar_2.xml │ │ │ ├── fragment_tab_bar_3.xml │ │ │ ├── fragment_tab_bar_fore.xml │ │ │ ├── fragment_test_net.xml │ │ │ ├── item_multiple1.xml │ │ │ ├── item_multiple2.xml │ │ │ ├── item_single.xml │ │ │ └── layout_toolbar.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── navigation │ │ │ └── nav_home.xml │ │ │ ├── values-zh-rCN │ │ │ └── strings.xml │ │ │ └── values │ │ │ ├── attrs.xml │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ └── test │ │ └── java │ │ └── com │ │ └── wzq │ │ └── mvvmsmart │ │ └── ExampleUnitTest.java └── test.json ├── build.gradle ├── config.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── mvvmsmart ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── wzq │ │ └── mvvmsmart │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ │ └── com │ │ │ └── wzq │ │ │ └── mvvmsmart │ │ │ ├── base │ │ │ ├── AppManagerMVVM.kt │ │ │ ├── BaseActivityMVVM.kt │ │ │ ├── BaseApplicationMVVM.kt │ │ │ ├── BaseFragmentMVVM.kt │ │ │ ├── BaseModelMVVM.kt │ │ │ ├── BaseViewModelMVVM.kt │ │ │ ├── IBaseViewMVVM.kt │ │ │ ├── IBaseViewModelMVVM.kt │ │ │ ├── IModelMVVM.kt │ │ │ └── ViewModelFactory.kt │ │ │ ├── binding │ │ │ └── viewadapter │ │ │ │ ├── image │ │ │ │ └── ViewAdapter.kt │ │ │ │ ├── recyclerview │ │ │ │ ├── DividerLine.kt │ │ │ │ ├── LayoutManagers.kt │ │ │ │ └── LineManagers.kt │ │ │ │ ├── viewgroup │ │ │ │ └── IBindingItemViewModel.kt │ │ │ │ └── webview │ │ │ │ └── ViewAdapter.kt │ │ │ ├── event │ │ │ ├── SingleLiveEvent.kt │ │ │ ├── SnackbarMessage.kt │ │ │ └── StateLiveData.kt │ │ │ ├── http │ │ │ ├── BaseResponse.kt │ │ │ ├── DownLoadManager.kt │ │ │ ├── ExceptionHandle.java │ │ │ ├── NetworkUtil.kt │ │ │ ├── ResponseThrowable.java │ │ │ ├── cookie │ │ │ │ ├── CookieJarImpl.java │ │ │ │ └── store │ │ │ │ │ ├── CookieStore.java │ │ │ │ │ ├── MemoryCookieStore.java │ │ │ │ │ ├── PersistentCookieStore.java │ │ │ │ │ └── SerializableHttpCookie.java │ │ │ ├── download │ │ │ │ ├── DownLoadStateBean.kt │ │ │ │ ├── DownLoadSubscriber.kt │ │ │ │ ├── ProgressCallBack.kt │ │ │ │ └── ProgressResponseBody.kt │ │ │ └── interceptor │ │ │ │ ├── BaseInterceptor.kt │ │ │ │ ├── CacheInterceptor.kt │ │ │ │ ├── ProgressInterceptor.kt │ │ │ │ └── logging │ │ │ │ ├── I.kt │ │ │ │ ├── Level.kt │ │ │ │ ├── Logger.kt │ │ │ │ ├── LoggingInterceptor.kt │ │ │ │ └── Printer.kt │ │ │ ├── utils │ │ │ ├── CloseUtils.kt │ │ │ ├── GlideLoadUtils.java │ │ │ ├── KLog.kt │ │ │ ├── RegexUtils.kt │ │ │ ├── RxUtils.java │ │ │ ├── SDCardUtils.kt │ │ │ ├── Tasks.java │ │ │ ├── ToastUtils.java │ │ │ ├── Utils.kt │ │ │ └── constant │ │ │ │ ├── MemoryConstants.kt │ │ │ │ ├── RegexConstants.kt │ │ │ │ └── TimeConstants.kt │ │ │ └── widget │ │ │ └── EmptyViewHelper.java │ └── res │ │ ├── layout │ │ └── empty_layout.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher_mvvmsmart.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher_mvvmsmart.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher_mvvmsmart.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher_mvvmsmart.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher_mvvmsmart.png │ │ ├── values-zh-rCN │ │ └── strings.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── wzq │ └── mvvmsmart │ └── ExampleUnitTest.java ├── settings.gradle ├── todo └── wzq.jks /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea 5 | /.idea/caches 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | .cxx 16 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/dev/debug/MVVMSmart-dev-debug_v1.0_t20200502-18.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzqjava/MVVMSmart-kotlin/95bf5e2ab434147b9f6c58e966e3bf1b85297b0a/app/dev/debug/MVVMSmart-dev-debug_v1.0_t20200502-18.apk -------------------------------------------------------------------------------- /app/dev/debug/output.json: -------------------------------------------------------------------------------- 1 | [{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0","enabled":true,"outputFile":"MVVMSmart-dev-debug_v1.0_t20200502-18.apk","fullName":"devDebug","baseName":"dev-debug","dirName":""},"path":"MVVMSmart-dev-debug_v1.0_t20200502-18.apk","properties":{}}] -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/wzq/mvvmsmart/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.wzq.mvvmsmart; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.wzq.mvvmsmart", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 22 | 23 | 24 | 27 | 28 | 29 | 32 | 33 | 34 | 38 | 41 | 42 | 45 | 46 | 49 | 50 | 51 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/app/App.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.app 2 | 3 | import cat.ereza.customactivityoncrash.config.CaocConfig 4 | import com.jeremyliao.liveeventbus.LiveEventBus 5 | import com.scwang.smartrefresh.layout.SmartRefreshLayout 6 | import com.scwang.smartrefresh.layout.footer.ClassicsFooter 7 | import com.scwang.smartrefresh.layout.header.ClassicsHeader 8 | import com.tencent.mmkv.MMKV 9 | import com.wzq.mvvmsmart.base.BaseApplicationMVVM 10 | import com.wzq.mvvmsmart.utils.KLog 11 | import com.wzq.sample.BuildConfig 12 | import com.wzq.sample.R 13 | import com.wzq.sample.ui.MainActivity 14 | 15 | class App : BaseApplicationMVVM() { 16 | override fun onCreate() { 17 | super.onCreate() 18 | //是否开启打印日志 19 | KLog.init(BuildConfig.DEBUG) 20 | //初始化全局异常崩溃 21 | initCrash() 22 | MMKV.initialize(this) 23 | LiveEventBus 24 | .config() 25 | .supportBroadcast(this) // 配置支持跨进程、跨APP通信,传入Context,需要在application onCreate中配置 26 | .lifecycleObserverAlwaysActive(true) // 整个生命周期(从onCreate到onDestroy)都可以实时收到消息 27 | } 28 | 29 | private fun initCrash() { 30 | CaocConfig.Builder.create() 31 | .backgroundMode(CaocConfig.BACKGROUND_MODE_SILENT) //背景模式,开启沉浸式 32 | .enabled(true) //是否启动全局异常捕获 33 | .showErrorDetails(true) //是否显示错误详细信息 34 | .showRestartButton(true) //是否显示重启按钮 35 | .trackActivities(true) //是否跟踪Activity 36 | .minTimeBetweenCrashesMs(2000) //崩溃的间隔时间(毫秒) 37 | .errorDrawable(R.mipmap.ic_launcher) //错误图标 38 | .restartActivity(MainActivity::class.java) //重新启动后的activity 39 | // .errorActivity(YourCustomErrorActivity.class) //崩溃后的错误activity 40 | // .eventListener(new YourCustomEventListener()) //崩溃后的错误监听 41 | .apply() 42 | } 43 | 44 | companion object { 45 | init { 46 | ClassicsFooter.REFRESH_FOOTER_LOADING = "加载中..." 47 | //设置全局的Header构建器 48 | SmartRefreshLayout.setDefaultRefreshHeaderCreator { context, layout -> 49 | ClassicsHeader(context) //.setTimeFormat(new DynamicTimeFormat("更新于 %s"));//指定为经典Header,默认是 贝塞尔雷达Header 50 | } 51 | //设置全局的Footer构建器 52 | SmartRefreshLayout.setDefaultRefreshFooterCreator { context, layout -> //指定为经典Footer,默认是 BallPulseFooter 53 | ClassicsFooter(context).setDrawableSize(20f) 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.base 2 | 3 | import androidx.databinding.ViewDataBinding 4 | import com.wzq.mvvmsmart.base.BaseActivityMVVM 5 | import com.wzq.mvvmsmart.base.BaseViewModelMVVM 6 | 7 | /** 8 | * 9 | * 作者:王志强 10 | * 11 | * 12 | * 13 | * 创建时间:2019/12/25 14 | * 15 | * 16 | * 17 | * 文件描述: 18 | * 19 | * 20 | */ 21 | abstract class BaseActivity : BaseActivityMVVM() { 22 | val tag = javaClass.simpleName 23 | 24 | 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/base/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.base 2 | 3 | import androidx.databinding.ViewDataBinding 4 | import com.wzq.mvvmsmart.base.BaseFragmentMVVM 5 | 6 | abstract class BaseFragment : BaseFragmentMVVM() { 7 | val baseFragmentTAG = javaClass.simpleName 8 | 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/base/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.base 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.MutableLiveData 5 | import com.wzq.mvvmsmart.base.BaseViewModelMVVM 6 | import com.wzq.mvvmsmart.event.StateLiveData 7 | 8 | /** 9 | * 10 | * 作者:王志强 11 | * 12 | * 13 | * 14 | * 创建时间:2019/12/25 15 | * 16 | * 17 | * 18 | * 文件描述: 19 | * 20 | * 21 | */ 22 | open class BaseViewModel(application: Application) : BaseViewModelMVVM(application) { 23 | var stateLiveData: StateLiveData = StateLiveData() 24 | fun getStateLiveData(): MutableLiveData { 25 | return stateLiveData 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/bean/FormEntity.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.bean 2 | 3 | import java.io.Serializable 4 | 5 | class FormEntity( 6 | var id: String, 7 | var name: String, 8 | var bir: String, 9 | var marry: Boolean 10 | 11 | ) : Serializable 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/data/source/http/HttpDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.data.source.http 2 | 3 | import com.wzq.mvvmsmart.http.BaseResponse 4 | import com.wzq.sample.bean.DemoBean 5 | import com.wzq.sample.bean.DemoBean.ItemsEntity 6 | import com.wzq.sample.data.source.http.service.DemoApiService 7 | import io.reactivex.Observable 8 | import java.util.* 9 | import java.util.concurrent.TimeUnit 10 | 11 | class HttpDataSourceImpl private constructor(private val apiService: DemoApiService) { 12 | fun login(): Observable { 13 | // TODO: wzq 2019/12/18 延迟时间 14 | return Observable.just(Any()).delay(1, TimeUnit.SECONDS) //延迟3秒 15 | } 16 | 17 | fun loadMore(): Observable { 18 | return Observable.create { observableEmitter -> 19 | val entity = DemoBean() 20 | val students: MutableList = ArrayList() 21 | //模拟一部分假数据 22 | for (i in 0..9) { 23 | val student = ItemsEntity() 24 | student.id = -1 25 | student.name = "模拟条目" 26 | students.add(student) 27 | } 28 | entity.items = students 29 | observableEmitter.onNext(entity) 30 | }.delay(2, TimeUnit.SECONDS) //延迟3秒 31 | } 32 | 33 | fun demoGet(pageNum: Int): Observable> { 34 | return apiService.demoGet(pageNum) 35 | } 36 | 37 | fun demoPost(catalog: String?): Observable> { 38 | return apiService.demoPost(catalog) 39 | } 40 | 41 | companion object { 42 | @Volatile 43 | private var INSTANCE: HttpDataSourceImpl? = null 44 | fun getInstance(apiService: DemoApiService): HttpDataSourceImpl? { 45 | if (INSTANCE == null) { 46 | synchronized(HttpDataSourceImpl::class.java) { 47 | if (INSTANCE == null) { 48 | INSTANCE = HttpDataSourceImpl(apiService) 49 | } 50 | } 51 | } 52 | return INSTANCE 53 | } 54 | 55 | fun destroyInstance() { 56 | INSTANCE = null 57 | } 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/data/source/http/service/DemoApiService.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.data.source.http.service 2 | 3 | import com.wzq.mvvmsmart.http.BaseResponse 4 | import com.wzq.sample.bean.DemoBean 5 | import io.reactivex.Observable 6 | import retrofit2.http.* 7 | 8 | /** 9 | * created 王志强 2020.04.30 10 | */ 11 | interface DemoApiService { 12 | @GET("action/apiv2/banner") 13 | fun demoGet(@Query("catalog") pageNum: Int): Observable> 14 | 15 | @FormUrlEncoded 16 | @POST("action/apiv2/banner") 17 | fun demoPost(@Field("catalog") catalog: String?): Observable> 18 | 19 | @get:GET("getJsonFile") 20 | val jsonFile: Observable> 21 | 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/data/source/http/service2/DemoApiService.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2.service 2 | 3 | import com.wzq.sample.bean.User 4 | import com.wzq.sample.http2.base.BaseResponse 5 | import io.reactivex.Observable 6 | import okhttp3.RequestBody 7 | import retrofit2.http.Body 8 | import retrofit2.http.POST 9 | 10 | interface DemoApiService { 11 | // 获取用户个人信息 12 | @POST("data/center/summary") 13 | fun getPersonalSummary(@Body requestBody: RequestBody?): Observable> 14 | 15 | // 获取用户个人信息2 16 | @POST("center/summary") 17 | fun getPersonalSummary2(@Body requestBody: RequestBody?): Observable> 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/data/source/http/service2/MRequest.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.data.source.http.service2; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | 6 | import com.wzq.sample.bean.User; 7 | import com.wzq.sample.http2.listener.OnServerResponseListener; 8 | import com.wzq.sample.http2.base.BaseResponse; 9 | import com.wzq.sample.http2.base.BaseRequest; 10 | import com.wzq.sample.http2.service.DemoApiService; 11 | 12 | import io.reactivex.Observable; 13 | import okhttp3.MediaType; 14 | import okhttp3.RequestBody; 15 | 16 | /** 17 | * author :王志强 18 | * date : 2019/11/12 11:10 19 | */ 20 | public class MRequest extends BaseRequest { 21 | private DemoApiService service; 22 | 23 | public static MRequest getInstance() { 24 | return Holder.INSTANCE; 25 | } 26 | 27 | private static class Holder { 28 | @SuppressLint("StaticFieldLeak") 29 | static MRequest INSTANCE = new MRequest(); 30 | } 31 | 32 | private MRequest() { 33 | super(); 34 | this.service = retrofit.create(DemoApiService.class); 35 | } 36 | // 获取个人信息 37 | public void getPersonalSummary(Context context, int what, String jsonParams, OnServerResponseListener listener) { 38 | this.mContext = context; 39 | RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), jsonParams); 40 | Observable> observable = service.getPersonalSummary(requestBody); 41 | doRequest(observable, what, listener); 42 | } 43 | 44 | // 获取个人信息2 45 | public void getPersonalSummary2(Context context, int what, String jsonParams, OnServerResponseListener listener) { 46 | this.mContext = context; 47 | RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), jsonParams); 48 | Observable> observable = service.getPersonalSummary2(requestBody); 49 | doRequest(observable, what, listener); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/BaseConfig.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2; 2 | 3 | /** 4 | * created 王志强 2020.04.30 5 | **/ 6 | public class BaseConfig { 7 | public static final int GLIDE_MEMORYCACHE_MAXSIZE = 1024 * 1024 * 20; // 20mb 8 | public static int IL_ERROR_RES = -1; 9 | public static final long TIMESCREENON = 90000; 10 | public static final int OKHTTP_CONNECTTIMEOUT = 15; 11 | public static final int OKHTTP_READTIMEOUT = 15; 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/Interceptor/TokenInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2.Interceptor; 2 | 3 | 4 | import com.wzq.mvvmsmart.utils.KLog; 5 | import com.wzq.sample.http2.token.TokenUtils; 6 | import com.wzq.sample.http2.net_utils.MmkvUtils; 7 | 8 | import java.io.IOException; 9 | 10 | import okhttp3.Interceptor; 11 | import okhttp3.Request; 12 | import okhttp3.Response; 13 | 14 | /** 15 | * created 王志强 2020.04.30 16 | * 拦截token,刷新token 17 | */ 18 | public class TokenInterceptor implements Interceptor { 19 | // private static final String TAG = TokenInterceptor.class.getSimpleName(); 20 | 21 | @Override 22 | public Response intercept(Chain chain) throws IOException { 23 | Request.Builder request = chain.request().newBuilder(); 24 | Response proceed = chain.proceed(request.build()); 25 | okhttp3.MediaType mediaType = proceed.body().contentType(); 26 | //如果token过期 再去重新请求token 然后设置token的请求头 重新发起请求 用户无感 27 | String content = proceed.body().string(); 28 | 29 | if (!"".equals(MmkvUtils.getStringValue("refreshToken"))) { 30 | //根据和服务端的约定判断token过期 31 | if (TokenUtils.isTokenExpired(content)) { 32 | KLog.INSTANCE.e("自动刷新Token,然后重新请求数据"); 33 | //同步请求方式,获取最新的Token 34 | String newToken = TokenUtils.getNewToken(); 35 | KLog.INSTANCE.e("newToken:" + newToken); 36 | if (newToken.equals("")) {//token刷新失败 37 | return proceed.newBuilder() 38 | .body(okhttp3.ResponseBody.create(mediaType, content)) 39 | .build(); 40 | } 41 | Request newRequest = chain.request().newBuilder() 42 | .removeHeader("Authorization") 43 | .addHeader("Authorization", "Bearer " + MmkvUtils.getStringValue("accessToken")) 44 | .build(); 45 | //重新请求 46 | return chain.proceed(newRequest); 47 | } 48 | } 49 | return proceed.newBuilder() 50 | .body(okhttp3.ResponseBody.create(mediaType, content)) 51 | .build(); 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/OkHttpClientHelper.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2; 2 | 3 | import com.wzq.sample.http2.Interceptor.HttpCommonInterceptor; 4 | import com.wzq.sample.http2.Interceptor.TokenInterceptor; 5 | import com.wzq.sample.http2.cookie.CookieJarImpl; 6 | import com.wzq.sample.http2.cookie.MemoryCookieStore; 7 | 8 | import java.util.concurrent.TimeUnit; 9 | 10 | import okhttp3.OkHttpClient; 11 | 12 | /** 13 | * created 王志强 2020.04.30 14 | **/ 15 | public class OkHttpClientHelper { 16 | 17 | private OkHttpClient okHttpClient; 18 | private MemoryCookieStore mStore = new MemoryCookieStore(); 19 | 20 | private static class OkHttpClientHelperHolder { 21 | private static OkHttpClientHelper instance = new OkHttpClientHelper(); 22 | } 23 | 24 | public static OkHttpClientHelper getInstance() { 25 | return OkHttpClientHelperHolder.instance; 26 | } 27 | 28 | /** 29 | * 设置带缓存的OkHttpClient 30 | */ 31 | public OkHttpClient okHttpsCacheClient() { 32 | if (okHttpClient == null) { 33 | okHttpClient = okHttpsBuilder().build(); 34 | } 35 | return okHttpClient; 36 | } 37 | 38 | private OkHttpClient.Builder okHttpsBuilder() { 39 | OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder(); 40 | okHttpClientBuilder.cookieJar(new CookieJarImpl(mStore)); 41 | okHttpClientBuilder.connectTimeout(BaseConfig.OKHTTP_CONNECTTIMEOUT, TimeUnit.SECONDS); 42 | okHttpClientBuilder.readTimeout(BaseConfig.OKHTTP_READTIMEOUT, TimeUnit.SECONDS); 43 | okHttpClientBuilder.writeTimeout(BaseConfig.OKHTTP_READTIMEOUT, TimeUnit.SECONDS); 44 | okHttpClientBuilder.addInterceptor(new HttpCommonInterceptor()); 45 | okHttpClientBuilder.addInterceptor(new TokenInterceptor()); 46 | return okHttpClientBuilder; 47 | } 48 | 49 | public MemoryCookieStore getMemoryCookieStore() { 50 | return mStore; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/base/BaseRequest.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2.base; 2 | 3 | import android.content.Context; 4 | 5 | import com.wzq.sample.http2.listener.OnServerResponseListener; 6 | import com.wzq.sample.http2.observer.HttpDisposableObserver; 7 | import com.wzq.sample.http2.net_utils.RetrofitUtil; 8 | 9 | import io.reactivex.Observable; 10 | import retrofit2.Retrofit; 11 | 12 | /** 13 | * created 王志强 2020.04.30 14 | */ 15 | public class BaseRequest { 16 | 17 | protected Context mContext; 18 | private RetrofitUtil retrofitTool; 19 | protected Retrofit retrofit; 20 | 21 | public BaseRequest() { 22 | this.retrofitTool = RetrofitUtil.getInstance(); 23 | this.retrofit = retrofitTool.getRetrofit(); 24 | } 25 | 26 | protected void doRequest(Observable observable, int what, OnServerResponseListener listener) { 27 | retrofitTool.toSubscribe(observable, new HttpDisposableObserver(mContext, what, listener)); 28 | } 29 | 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/base/BaseResponse.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2.base; 2 | 3 | /** 4 | * created 王志强 2020.04.30 5 | * 根据服务器返回决定字段(不同公司字段有差异,任意修改即可) 6 | */ 7 | public class BaseResponse { 8 | private int currentTime; 9 | private int totalRows; 10 | private String message; 11 | private int code; 12 | private T data; 13 | 14 | public int getCurrentTime() { 15 | return currentTime; 16 | } 17 | 18 | public void setCurrentTime(int currentTime) { 19 | this.currentTime = currentTime; 20 | } 21 | 22 | public int getTotalRows() { 23 | return totalRows; 24 | } 25 | 26 | public void setTotalRows(int totalRows) { 27 | this.totalRows = totalRows; 28 | } 29 | 30 | public String getMessage() { 31 | return message; 32 | } 33 | 34 | public void setMessage(String message) { 35 | this.message = message; 36 | } 37 | 38 | 39 | public T getData() { 40 | return data; 41 | } 42 | 43 | public void setData(T data) { 44 | this.data = data; 45 | } 46 | 47 | public int getCode() { 48 | return code; 49 | } 50 | 51 | public void setCode(int code) { 52 | this.code = code; 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return "BaseResponse{" + 58 | "currentTime=" + currentTime + 59 | ", totalRows=" + totalRows + 60 | ", message='" + message + '\'' + 61 | ", code=" + code + 62 | ", data=" + data + 63 | '}'; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/cookie/CookieJarImpl.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2.cookie; 2 | 3 | 4 | import java.util.List; 5 | 6 | import okhttp3.Cookie; 7 | import okhttp3.CookieJar; 8 | import okhttp3.HttpUrl; 9 | 10 | /** 11 | * created 王志强 2020.04.30 12 | */ 13 | public class CookieJarImpl implements CookieJar { 14 | private CookieStore cookieStore; 15 | 16 | public CookieJarImpl(CookieStore cookieStore) { 17 | if (cookieStore == null) throw new IllegalArgumentException("cookieStore can not be null."); 18 | this.cookieStore = cookieStore; 19 | } 20 | 21 | @Override 22 | public synchronized void saveFromResponse(HttpUrl url, List cookies) { 23 | cookieStore.add(url, cookies); 24 | } 25 | 26 | @Override 27 | public synchronized List loadForRequest(HttpUrl url) { 28 | return cookieStore.get(url); 29 | } 30 | 31 | public CookieStore getCookieStore() { 32 | return cookieStore; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/cookie/CookieStore.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2.cookie; 2 | 3 | import java.util.List; 4 | 5 | import okhttp3.Cookie; 6 | import okhttp3.HttpUrl; 7 | 8 | /** 9 | * created 王志强 2020.04.30 10 | */ 11 | public interface CookieStore { 12 | 13 | void add(HttpUrl uri, List cookie); 14 | 15 | List get(HttpUrl uri); 16 | 17 | List getCookies(); 18 | 19 | boolean remove(HttpUrl uri, Cookie cookie); 20 | 21 | boolean removeAll(); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/cookie/CookieUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.wzq.sample.http2.cookie; 5 | 6 | import android.content.Context; 7 | import android.text.TextUtils; 8 | import android.webkit.CookieManager; 9 | import android.webkit.CookieSyncManager; 10 | 11 | import com.wzq.sample.http2.net_utils.MetaDataUtil; 12 | 13 | 14 | /** 15 | * @author created 王志强 2020.04.30 16 | */ 17 | public class CookieUtils { 18 | private final String KCookieDel = "deleteMe"; 19 | public static final String KJsessionidCookie = "JSESSIONID_COOKIE"; 20 | private Context mContext; 21 | private String mCookies = ""; 22 | private static CookieUtils mCookieUtils = null; 23 | 24 | public CookieUtils(Context context) { 25 | this.mContext = context; 26 | } 27 | 28 | public static synchronized CookieUtils getInstance(Context context) { 29 | if (mCookieUtils == null) { 30 | mCookieUtils = new CookieUtils(context); 31 | } 32 | return mCookieUtils; 33 | } 34 | 35 | /** 36 | * 保存cookie 37 | * * @author 38 | */ 39 | public void saveCookie(String cookie) { 40 | if (TextUtils.isEmpty(cookie) || KCookieDel.equals(cookie)) { 41 | return; 42 | } 43 | String cookieStr = KJsessionidCookie + "=" + cookie; 44 | if (cookieStr.equals(mCookies)) { 45 | return; 46 | } 47 | mCookies = cookieStr; 48 | } 49 | 50 | /** 51 | * 同步一下webview cookie 52 | * 53 | * @author 54 | */ 55 | public void synCookies(String url) { 56 | if (TextUtils.isEmpty(mCookies) || KCookieDel.equals(mCookies)) { 57 | return; 58 | } 59 | CookieSyncManager.createInstance(mContext); 60 | CookieManager cookieManager = CookieManager.getInstance(); 61 | cookieManager.setAcceptCookie(true); 62 | // cookieManager.removeSessionCookie();//移除 63 | // cookieManager.removeAllCookie(); 64 | try { 65 | Thread.sleep(300); 66 | } catch (InterruptedException e) { 67 | e.printStackTrace(); 68 | } 69 | cookieManager.setCookie(MetaDataUtil.getBaseUrl(), mCookies); 70 | CookieSyncManager.getInstance().sync(); 71 | // if (Build.VERSION.SDK_INT < 21) { 72 | // CookieSyncManager.getInstance().sync(); 73 | // } else { 74 | // CookieManager.getInstance().flush(); 75 | // } 76 | } 77 | 78 | public String getCookie() { 79 | return mCookies; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/cookie/MemoryCookieStore.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2.cookie; 2 | import java.util.ArrayList; 3 | import java.util.HashMap; 4 | import java.util.Iterator; 5 | import java.util.List; 6 | import java.util.Set; 7 | 8 | import okhttp3.Cookie; 9 | import okhttp3.HttpUrl; 10 | 11 | /** 12 | * created 王志强 2020.04.30 13 | */ 14 | public class MemoryCookieStore implements CookieStore { 15 | private final HashMap> allCookies = new HashMap<>(); 16 | 17 | @Override 18 | public void add(HttpUrl url, List cookies) { 19 | List oldCookies = allCookies.get(url.host()); 20 | 21 | if (oldCookies != null) { 22 | Iterator itNew = cookies.iterator(); 23 | Iterator itOld = oldCookies.iterator(); 24 | while (itNew.hasNext()) { 25 | String va = itNew.next().name(); 26 | while (va != null && itOld.hasNext()) { 27 | String v = itOld.next().name(); 28 | if (v != null && va.equals(v)) { 29 | itOld.remove(); 30 | } 31 | } 32 | } 33 | oldCookies.addAll(cookies); 34 | } else { 35 | allCookies.put(url.host(), cookies); 36 | } 37 | 38 | 39 | } 40 | 41 | @Override 42 | public List get(HttpUrl uri) { 43 | List newCookies = new ArrayList<>(); 44 | if (allCookies.get(uri.host()) != null) { 45 | for (Cookie cookie : allCookies.get(uri.host())) {//深拷贝 防止多线程并发时报数组越界和空指针问题 46 | Cookie parse = Cookie.parse(uri, cookie.toString()); 47 | newCookies.add(parse); 48 | } 49 | } else { 50 | allCookies.put(uri.host(), newCookies); 51 | } 52 | /*List cookies = allCookies.get(uri.host()); 53 | if (cookies == null) 54 | { 55 | cookies = new ArrayList<>(); 56 | allCookies.put(uri.host(), cookies); 57 | }*/ 58 | return newCookies; 59 | 60 | } 61 | 62 | @Override 63 | public boolean removeAll() { 64 | allCookies.clear(); 65 | return true; 66 | } 67 | 68 | @Override 69 | public List getCookies() { 70 | List cookies = new ArrayList<>(); 71 | Set httpUrls = allCookies.keySet(); 72 | for (String url : httpUrls) { 73 | cookies.addAll(allCookies.get(url)); 74 | } 75 | return cookies; 76 | } 77 | 78 | 79 | @Override 80 | public boolean remove(HttpUrl uri, Cookie cookie) { 81 | List cookies = allCookies.get(uri.host()); 82 | if (cookie != null) { 83 | return cookies.remove(cookie); 84 | } 85 | return false; 86 | } 87 | 88 | 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/listener/OnServerResponseListener.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2.listener; 2 | 3 | 4 | import com.wzq.sample.http2.base.BaseResponse; 5 | 6 | /** 7 | * created 王志强 2020.04.30 8 | */ 9 | public interface OnServerResponseListener { 10 | /** 11 | * 服务器成功返回 12 | * @param what 请求标记位 13 | * @param isQualified 返回的数据是否合格 14 | * @param baseResponse 网络返回Response 15 | */ 16 | void success(int what, boolean isQualified, BaseResponse baseResponse); 17 | 18 | /** 19 | * 返回错误 20 | * 21 | * @param what 22 | * @param throwable 错误类型有可能是移动端也有可能是后端 23 | */ 24 | void error(int what, Throwable throwable); 25 | 26 | /** 27 | * 重连 28 | */ 29 | void reTry(int what); 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/listener/OnServerResponseListener2.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2.listener; 2 | 3 | /** 4 | * created 王志强 2020.04.15 5 | * 增加两个回调,方便扩展统一加载loading 6 | */ 7 | public interface OnServerResponseListener2 extends OnServerResponseListener { 8 | 9 | /** 10 | * 开始加载 11 | */ 12 | void onStart(); 13 | 14 | /** 15 | * 加载结束 16 | */ 17 | void onComplete(); 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/net_utils/GsonUtil.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2.net_utils; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.google.gson.reflect.TypeToken; 6 | import com.wzq.sample.http2.net_utils.gsontypeadapter.DoubleTypeAdapter; 7 | import com.wzq.sample.http2.net_utils.gsontypeadapter.FloatTypeAdapter; 8 | import com.wzq.sample.http2.net_utils.gsontypeadapter.IntegerTypeAdapter; 9 | import com.wzq.sample.http2.net_utils.gsontypeadapter.LongTypeAdapter; 10 | 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | /** 15 | * created 王志强 2020.04.30 16 | */ 17 | public class GsonUtil { 18 | 19 | private static Gson gson = null; 20 | 21 | static { 22 | if (gson == null) { 23 | gson = new GsonBuilder() 24 | .registerTypeAdapter(Integer.class, new IntegerTypeAdapter()) 25 | .registerTypeAdapter(int.class, new IntegerTypeAdapter()) 26 | .registerTypeAdapter(Double.class, new DoubleTypeAdapter()) 27 | .registerTypeAdapter(double.class, new DoubleTypeAdapter()) 28 | .registerTypeAdapter(Long.class, new LongTypeAdapter()) 29 | .registerTypeAdapter(long.class, new LongTypeAdapter()) 30 | .registerTypeAdapter(Float.class, new FloatTypeAdapter()) 31 | .registerTypeAdapter(float.class, new FloatTypeAdapter()) 32 | .create(); 33 | } 34 | } 35 | 36 | public static Gson getGson() { 37 | return gson; 38 | } 39 | 40 | private GsonUtil() { 41 | } 42 | 43 | 44 | /** 45 | * 转成json 46 | * 47 | * @param object 48 | * @return 和GsonString方法相同, 历史遗留问题, 暂不用修改 49 | */ 50 | public static String bean2String(Object object) { 51 | String gsonString = null; 52 | if (gson != null) { 53 | gsonString = gson.toJson(object); 54 | } 55 | return gsonString; 56 | } 57 | 58 | /** 59 | * 转成bean 60 | * 61 | * @param gsonString 62 | * @param cls 63 | * @return 64 | */ 65 | public static T gson2Bean(String gsonString, Class cls) { 66 | T t = null; 67 | if (gson != null) { 68 | t = gson.fromJson(gsonString, cls); 69 | } 70 | return t; 71 | } 72 | 73 | /** 74 | * 转成list 75 | * 76 | * @param gsonString 77 | * @param cls 78 | * @return 79 | */ 80 | public static List gson2List(String gsonString, Class cls) { 81 | List list = null; 82 | if (gson != null) { 83 | list = gson.fromJson(gsonString, new TypeToken>() { 84 | }.getType()); 85 | } 86 | return list; 87 | } 88 | 89 | /** 90 | * 转成list中有map的 91 | * 92 | * @param gsonString 93 | * @return 94 | */ 95 | public static List> gson2ListMaps(String gsonString) { 96 | List> list = null; 97 | if (gson != null) { 98 | list = gson.fromJson(gsonString, 99 | new TypeToken>>() { 100 | }.getType()); 101 | } 102 | return list; 103 | } 104 | 105 | /** 106 | * 转成map的 107 | * 108 | * @param gsonString 109 | * @return 110 | */ 111 | public static Map gson2Maps(String gsonString) { 112 | Map map = null; 113 | if (gson != null) { 114 | map = gson.fromJson(gsonString, new TypeToken>() { 115 | }.getType()); 116 | } 117 | return map; 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/net_utils/MmkvUtils.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2.net_utils; 2 | 3 | import com.tencent.mmkv.MMKV; 4 | import com.wzq.mvvmsmart.utils.KLog; 5 | 6 | /** 7 | * created 王志强 2020.04.30 8 | */ 9 | public class MmkvUtils { 10 | private static final String TAG = MmkvUtils.class.getSimpleName(); 11 | 12 | public static void putIntValue(String name, int value) { 13 | MMKV.defaultMMKV() 14 | .encode(name, value); 15 | } 16 | 17 | public static int getIntValue(String name) { 18 | return MMKV.defaultMMKV() 19 | .decodeInt(name); 20 | } 21 | 22 | public static void putStringValue(String name, String value) { 23 | KLog.INSTANCE.e("MMkV===="+name+":"+value); 24 | MMKV.defaultMMKV().encode(name, value); 25 | } 26 | 27 | 28 | public static String getStringValue(String name) { 29 | return MMKV.defaultMMKV().decodeString(name, ""); 30 | } 31 | 32 | public static void putBooleanValue(String name, boolean value) { 33 | MMKV.defaultMMKV().encode(name, value); 34 | } 35 | 36 | public static boolean getBooleanValue(String name) { 37 | return MMKV.defaultMMKV().decodeBool(name, false); 38 | } 39 | 40 | /** 41 | * 存储数据 42 | * 43 | * @param key 44 | * @param value 45 | */ 46 | public static void set(String key, Object value) { 47 | MMKV kv = MMKV.defaultMMKV(); 48 | boolean result = false; 49 | if (value instanceof Integer) { 50 | result = kv.encode(key, (Integer) value); 51 | } else if (value instanceof Long) { 52 | result = kv.encode(key, (Long) value); 53 | } else if (value instanceof Float) { 54 | result = kv.encode(key, (Float) value); 55 | } else if (value instanceof Double) { 56 | result = kv.encode(key, (Double) value); 57 | } else if (value instanceof Boolean) { 58 | result = kv.encode(key, (Boolean) value); 59 | } else if (value instanceof String) { 60 | result = kv.encode(key, (String) value); 61 | } else if (value instanceof byte[]) { 62 | result = kv.encode(key, (byte[]) value); 63 | } 64 | } 65 | 66 | /** 67 | * 获取数据 68 | * 69 | * @param key 70 | * @param defValue 71 | * @param 72 | * @return 73 | */ 74 | public static T get(String key, T defValue) { 75 | MMKV kv = MMKV.defaultMMKV(); 76 | Object result = null; 77 | if (defValue instanceof Integer) { 78 | result = kv.decodeInt(key, (Integer) defValue); 79 | } else if (defValue instanceof Long) { 80 | result = kv.decodeLong(key, (Long) defValue); 81 | } else if (defValue instanceof Float) { 82 | result = kv.decodeFloat(key, (Float) defValue); 83 | } else if (defValue instanceof Double) { 84 | result = kv.decodeDouble(key, (Double) defValue); 85 | } else if (defValue instanceof Boolean) { 86 | result = kv.decodeBool(key, (Boolean) defValue); 87 | } else if (defValue instanceof String) { 88 | result = kv.decodeString(key, (String) defValue); 89 | } else if (defValue instanceof byte[]) { 90 | result = kv.decodeBytes(key); 91 | } 92 | return (T) result; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/net_utils/RetrofitUtil.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2.net_utils; 2 | 3 | 4 | import com.wzq.sample.http2.OkHttpClientHelper; 5 | import com.wzq.sample.http2.base.BaseResponse; 6 | 7 | import io.reactivex.Observable; 8 | import io.reactivex.android.schedulers.AndroidSchedulers; 9 | import io.reactivex.observers.DisposableObserver; 10 | import io.reactivex.schedulers.Schedulers; 11 | import okhttp3.OkHttpClient; 12 | import retrofit2.Retrofit; 13 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; 14 | import retrofit2.converter.gson.GsonConverterFactory; 15 | 16 | /** 17 | * created 王志强 2020.04.30 18 | **/ 19 | public class RetrofitUtil { 20 | 21 | private Retrofit retrofit; 22 | 23 | private RetrofitUtil() { 24 | OkHttpClient okHttpClient = OkHttpClientHelper.getInstance().okHttpsCacheClient(); 25 | retrofit = new Retrofit.Builder() 26 | .client(okHttpClient) 27 | .addConverterFactory(GsonConverterFactory.create(GsonUtil.getGson())) 28 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 29 | .baseUrl(MetaDataUtil.getBaseUrl()) 30 | .build(); 31 | } 32 | 33 | public Retrofit getRetrofit(){ 34 | return retrofit; 35 | } 36 | 37 | public void toSubscribe(Observable o, DisposableObserver> s) { 38 | o.subscribeOn(Schedulers.io()) 39 | .unsubscribeOn(Schedulers.io()) 40 | .observeOn(AndroidSchedulers.mainThread()) 41 | .subscribe(s); 42 | } 43 | 44 | public static RetrofitUtil getInstance() { 45 | return Holder.instance; 46 | } 47 | 48 | private static class Holder { 49 | private static final RetrofitUtil instance = new RetrofitUtil(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/net_utils/gsontypeadapter/DoubleTypeAdapter.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2.net_utils.gsontypeadapter; 2 | 3 | import android.util.Log; 4 | 5 | import com.google.gson.TypeAdapter; 6 | import com.google.gson.stream.JsonReader; 7 | import com.google.gson.stream.JsonToken; 8 | import com.google.gson.stream.JsonWriter; 9 | 10 | import java.io.IOException; 11 | 12 | /** 13 | * created 王志强 2020.04.30 14 | * 部分服务器框架会把数字数据返回null, 并且服务器处理不好,客户端可以处理 15 | */ 16 | public class DoubleTypeAdapter extends TypeAdapter { 17 | @Override 18 | public void write(JsonWriter out, Double value) throws IOException { 19 | try { 20 | if (value == null){ 21 | value = 0D; 22 | } 23 | out.value(value); 24 | } catch (Exception e) { 25 | e.printStackTrace(); 26 | } 27 | } 28 | 29 | @Override 30 | public Double read(JsonReader in) throws IOException { 31 | try { 32 | if (in.peek() == JsonToken.NULL) { 33 | in.nextNull(); 34 | Log.e("TypeAdapter", "null is not a number"); 35 | return 0D; 36 | } 37 | if (in.peek() == JsonToken.BOOLEAN) { 38 | boolean b = in.nextBoolean(); 39 | Log.e("TypeAdapter", b + " is not a number"); 40 | return 0D; 41 | } 42 | if (in.peek() == JsonToken.STRING) { 43 | String str = in.nextString(); 44 | if (com.wzq.sample.http2.utils.gsontypeadapter.NumberUtils.isFloatOrDouble(str)){ 45 | return Double.parseDouble(str); 46 | } else { 47 | Log.e("TypeAdapter", str + " is not a number"); 48 | return 0D; 49 | } 50 | } else { 51 | Double value = in.nextDouble(); 52 | return value == null ? 0D : value; 53 | } 54 | }catch(NumberFormatException e){ 55 | Log.e("TypeAdapter", e.getMessage(), e); 56 | } catch (Exception e) { 57 | Log.e("TypeAdapter", e.getMessage(), e); 58 | } 59 | return 0D; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/net_utils/gsontypeadapter/FloatTypeAdapter.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2.net_utils.gsontypeadapter; 2 | 3 | import android.util.Log; 4 | 5 | import com.google.gson.TypeAdapter; 6 | import com.google.gson.stream.JsonReader; 7 | import com.google.gson.stream.JsonToken; 8 | import com.google.gson.stream.JsonWriter; 9 | 10 | import java.io.IOException; 11 | 12 | /** 13 | * created 王志强 2020.04.30 14 | */ 15 | public class FloatTypeAdapter extends TypeAdapter { 16 | @Override 17 | public void write(JsonWriter out, Float value) throws IOException { 18 | try { 19 | if (value == null){ 20 | value = 0F; 21 | } 22 | out.value(value.toString()); 23 | } catch (Exception e) { 24 | e.printStackTrace(); 25 | } 26 | } 27 | 28 | @Override 29 | public Float read(JsonReader in) throws IOException { 30 | try { 31 | Float value; 32 | if (in.peek() == JsonToken.NULL) { 33 | in.nextNull(); 34 | Log.e("TypeAdapter", "null is not a number"); 35 | return 0F; 36 | } 37 | if (in.peek() == JsonToken.BOOLEAN) { 38 | boolean b = in.nextBoolean(); 39 | Log.e("TypeAdapter", b + " is not a number"); 40 | return 0F; 41 | } 42 | if (in.peek() == JsonToken.STRING) { 43 | String str = in.nextString(); 44 | if (com.wzq.sample.http2.utils.gsontypeadapter.NumberUtils.isFloatOrDouble(str)){ 45 | return Float.parseFloat(str); 46 | } else { 47 | Log.e("TypeAdapter", str + " is not a number"); 48 | return 0F; 49 | } 50 | } else { 51 | String str = in.nextString(); 52 | value = Float.valueOf(str); 53 | } 54 | return value; 55 | } catch (Exception e) { 56 | Log.e("TypeAdapter", "Not a number", e); 57 | } 58 | return 0F; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/net_utils/gsontypeadapter/IntegerTypeAdapter.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2.net_utils.gsontypeadapter; 2 | 3 | import android.util.Log; 4 | 5 | import com.google.gson.TypeAdapter; 6 | import com.google.gson.stream.JsonReader; 7 | import com.google.gson.stream.JsonToken; 8 | import com.google.gson.stream.JsonWriter; 9 | 10 | import java.io.IOException; 11 | 12 | /** 13 | * created 王志强 2020.04.30 14 | */ 15 | public class IntegerTypeAdapter extends TypeAdapter { 16 | 17 | @Override 18 | public void write(JsonWriter out, Integer value)throws IOException { 19 | try { 20 | if (value == null){ 21 | value = 0; 22 | } 23 | out.value(value); 24 | } catch (Exception e) { 25 | e.printStackTrace(); 26 | } 27 | } 28 | 29 | @Override 30 | public Integer read(JsonReader in)throws IOException { 31 | try { 32 | Integer value; 33 | if (in.peek() == JsonToken.NULL) { 34 | in.nextNull(); 35 | Log.e("TypeAdapter", "null is not a number"); 36 | return 0; 37 | } 38 | if (in.peek() == JsonToken.BOOLEAN) { 39 | boolean b = in.nextBoolean(); 40 | Log.e("TypeAdapter", b + " is not a number"); 41 | return 0; 42 | } 43 | if (in.peek() == JsonToken.STRING) { 44 | String str = in.nextString(); 45 | if (com.wzq.sample.http2.utils.gsontypeadapter.NumberUtils.isIntOrLong(str)){ 46 | return Integer.parseInt(str); 47 | } else { 48 | Log.e("TypeAdapter", str + " is not a int number"); 49 | return 0; 50 | } 51 | } else { 52 | value = in.nextInt(); 53 | return value; 54 | } 55 | } catch (Exception e) { 56 | Log.e("TypeAdapter", "Not a number", e); 57 | } 58 | return 0; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/net_utils/gsontypeadapter/LongTypeAdapter.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2.net_utils.gsontypeadapter; 2 | 3 | import android.util.Log; 4 | 5 | import com.google.gson.TypeAdapter; 6 | import com.google.gson.stream.JsonReader; 7 | import com.google.gson.stream.JsonToken; 8 | import com.google.gson.stream.JsonWriter; 9 | 10 | import java.io.IOException; 11 | 12 | /** 13 | * created 王志强 2020.04.30 14 | */ 15 | public class LongTypeAdapter extends TypeAdapter { 16 | 17 | 18 | @Override 19 | public void write(JsonWriter out, Long value) throws IOException { 20 | try { 21 | if (value == null){ 22 | value = 0L; 23 | } 24 | out.value(value); 25 | } catch (Exception e) { 26 | e.printStackTrace(); 27 | } 28 | } 29 | 30 | @Override 31 | public Long read(JsonReader in) throws IOException { 32 | try { 33 | Long value; 34 | if (in.peek() == JsonToken.NULL) { 35 | in.nextNull(); 36 | Log.e("TypeAdapter", "null is not a number"); 37 | return 0L; 38 | } 39 | if (in.peek() == JsonToken.BOOLEAN) { 40 | boolean b = in.nextBoolean(); 41 | Log.e("TypeAdapter", b + " is not a number"); 42 | return 0L; 43 | } 44 | if (in.peek() == JsonToken.STRING) { 45 | String str = in.nextString(); 46 | if (com.wzq.sample.http2.utils.gsontypeadapter.NumberUtils.isIntOrLong(str)){ 47 | return Long.parseLong(str); 48 | } else { 49 | Log.e("TypeAdapter", str + " is not a int number"); 50 | return 0L; 51 | } 52 | } else { 53 | value = in.nextLong(); 54 | return value; 55 | } 56 | } catch (Exception e) { 57 | Log.e("TypeAdapter", "Not a number", e); 58 | } 59 | return 0L; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/net_utils/gsontypeadapter/NumberUtils.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2.utils.gsontypeadapter; 2 | 3 | import android.text.TextUtils; 4 | 5 | import java.util.regex.Pattern; 6 | 7 | /** 8 | * created 王志强 2020.04.30 9 | */ 10 | public class NumberUtils { 11 | public static boolean isIntOrLong(String str){ 12 | return TextUtils.isDigitsOnly(str); 13 | } 14 | 15 | public static boolean isFloatOrDouble(String str){ 16 | Pattern pattern = Pattern.compile("^[-\\+]?[.\\d]*$"); 17 | return pattern.matcher(str).matches(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/oldhelper/BaseParamsLinkedHashMap.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2.oldhelper; 2 | 3 | import java.io.Serializable; 4 | import java.util.LinkedHashMap; 5 | 6 | /** 7 | * created 王志强 2020.04.30 8 | * 多态使用 9 | * 多个参数封装到LinkedHashMap 10 | */ 11 | public class BaseParamsLinkedHashMap extends LinkedHashMap implements Serializable { 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/oldhelper/ParamsLinkedHashMap.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2.oldhelper; 2 | 3 | import java.util.Iterator; 4 | import java.util.Map; 5 | 6 | /** 7 | * created 王志强 2020.04.30 8 | * Get参数 9 | */ 10 | public class ParamsLinkedHashMap extends BaseParamsLinkedHashMap { 11 | /** 12 | * 添加参数 13 | */ 14 | public void addParams(String key, Object object) { 15 | this.put(key, object); 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | int i = 0; 21 | String s = ""; 22 | Iterator iter = entrySet().iterator(); 23 | while (iter.hasNext()) { 24 | if (i != 0) { 25 | s = s + "&"; 26 | } 27 | Map.Entry entry = (Map.Entry) iter.next(); 28 | String key = (String) entry.getKey(); 29 | String val = (String) entry.getValue(); 30 | s = s + key + "=" + val; 31 | i++; 32 | } 33 | 34 | return s; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/token/TokenBean.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2.token; 2 | 3 | /** 4 | * created 王志强 2020.04.30 5 | * 和服务器约定的TokenBena(不同公司字段略有差异,任意修改即可) 6 | */ 7 | public class TokenBean { 8 | 9 | /** 10 | * accessToken : ccc859e6-45ce-4df9-9500-e6de1e4bb6d2 11 | * expired : null 12 | * accessTokenExpiration : null 13 | * accessTokenExpiresIn : 3599 14 | * scope : null 15 | * tokenType : bearer 16 | * refreshTokenExpiration : null 17 | * refreshToken : 8c247745-4366-4cf6-a6ff-9039248448f4 18 | * expirationTime : 1576756867 19 | */ 20 | 21 | private String accessToken; 22 | private String expired; 23 | private String accessTokenExpiration; 24 | private int accessTokenExpiresIn; 25 | private String scope; 26 | private String tokenType; 27 | private String refreshTokenExpiration; 28 | private String refreshToken; 29 | private int expirationTime; 30 | 31 | public String getAccessToken() { 32 | return accessToken; 33 | } 34 | 35 | public void setAccessToken(String accessToken) { 36 | this.accessToken = accessToken; 37 | } 38 | 39 | public String getExpired() { 40 | return expired; 41 | } 42 | 43 | public void setExpired(String expired) { 44 | this.expired = expired; 45 | } 46 | 47 | public String getAccessTokenExpiration() { 48 | return accessTokenExpiration; 49 | } 50 | 51 | public void setAccessTokenExpiration(String accessTokenExpiration) { 52 | this.accessTokenExpiration = accessTokenExpiration; 53 | } 54 | 55 | public int getAccessTokenExpiresIn() { 56 | return accessTokenExpiresIn; 57 | } 58 | 59 | public void setAccessTokenExpiresIn(int accessTokenExpiresIn) { 60 | this.accessTokenExpiresIn = accessTokenExpiresIn; 61 | } 62 | 63 | public String getScope() { 64 | return scope; 65 | } 66 | 67 | public void setScope(String scope) { 68 | this.scope = scope; 69 | } 70 | 71 | public String getTokenType() { 72 | return tokenType; 73 | } 74 | 75 | public void setTokenType(String tokenType) { 76 | this.tokenType = tokenType; 77 | } 78 | 79 | public String getRefreshTokenExpiration() { 80 | return refreshTokenExpiration; 81 | } 82 | 83 | public void setRefreshTokenExpiration(String refreshTokenExpiration) { 84 | this.refreshTokenExpiration = refreshTokenExpiration; 85 | } 86 | 87 | public String getRefreshToken() { 88 | return refreshToken; 89 | } 90 | 91 | public void setRefreshToken(String refreshToken) { 92 | this.refreshToken = refreshToken; 93 | } 94 | 95 | public int getExpirationTime() { 96 | return expirationTime; 97 | } 98 | 99 | public void setExpirationTime(int expirationTime) { 100 | this.expirationTime = expirationTime; 101 | } 102 | 103 | @Override 104 | public String toString() { 105 | return "TokenBean{" + 106 | "accessToken='" + accessToken + '\'' + 107 | ", expired='" + expired + '\'' + 108 | ", accessTokenExpiration='" + accessTokenExpiration + '\'' + 109 | ", accessTokenExpiresIn=" + accessTokenExpiresIn + 110 | ", scope='" + scope + '\'' + 111 | ", tokenType='" + tokenType + '\'' + 112 | ", refreshTokenExpiration='" + refreshTokenExpiration + '\'' + 113 | ", refreshToken='" + refreshToken + '\'' + 114 | ", expirationTime=" + expirationTime + 115 | '}'; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/http2/token/TokenService.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.http2.token; 2 | 3 | 4 | import com.wzq.sample.http2.base.BaseResponse; 5 | 6 | import retrofit2.Call; 7 | import retrofit2.http.GET; 8 | import retrofit2.http.Query; 9 | 10 | /** 11 | * created 王志强 2020.04.30 12 | */ 13 | public interface TokenService { 14 | /** 15 | * 刷新token接口 16 | * @param refreshToken 17 | * @return 18 | */ 19 | @GET("chinese/student/refreshToken") 20 | Call> refreshToken(@Query(value = "refreshToken", encoded = true) String refreshToken); 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.pm.ActivityInfo 5 | import android.os.Bundle 6 | import com.wzq.sample.BR 7 | import com.wzq.sample.R 8 | import com.wzq.sample.base.BaseActivity 9 | import com.wzq.sample.base.BaseViewModel 10 | import com.wzq.sample.databinding.ActivityDemoBinding 11 | 12 | class MainActivity : BaseActivity() { 13 | 14 | 15 | @SuppressLint("SourceLockedOrientationActivity") 16 | override fun initParam() { 17 | super.initParam() 18 | requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT 19 | } 20 | 21 | 22 | 23 | override fun initContentView(savedInstanceState: Bundle?): Int { 24 | return R.layout.activity_demo 25 | } 26 | 27 | override fun initVariableId(): Int { 28 | return BR.viewModel 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui 2 | 3 | import android.app.Application 4 | import com.wzq.mvvmsmart.event.SingleLiveEvent 5 | import com.wzq.sample.base.BaseViewModel 6 | 7 | class MainViewModel(application: Application) : BaseViewModel(application) { 8 | //使用Observable 9 | var requestCameraPermissions = SingleLiveEvent() 10 | 11 | //使用LiveData 12 | var loadUrlEvent = SingleLiveEvent() 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/bottom_tab/activity/TabBarActivity.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.bottom_tab.activity 2 | 3 | import android.os.Bundle 4 | import androidx.core.content.ContextCompat 5 | import androidx.fragment.app.Fragment 6 | import com.wzq.sample.BR 7 | import com.wzq.sample.R 8 | import com.wzq.sample.base.BaseActivity 9 | import com.wzq.sample.base.BaseViewModel 10 | import com.wzq.sample.databinding.ActivityTabBarBinding 11 | import com.wzq.sample.ui.bottom_tab.fragment.TabBar1Fragment 12 | import com.wzq.sample.ui.bottom_tab.fragment.TabBar2Fragment 13 | import com.wzq.sample.ui.bottom_tab.fragment.TabBar3Fragment 14 | import com.wzq.sample.ui.bottom_tab.fragment.TabBar4Fragment 15 | import me.majiajie.pagerbottomtabstrip.listener.OnTabItemSelectedListener 16 | import java.util.* 17 | 18 | /** 19 | * 底部tab按钮的例子 20 | * 所有例子仅做参考,理解如何使用才最重要。 21 | */ 22 | class TabBarActivity : BaseActivity() { 23 | private var mFragments: MutableList? = null 24 | override fun initContentView(savedInstanceState: Bundle?): Int { 25 | return R.layout.activity_tab_bar 26 | } 27 | 28 | override fun initVariableId(): Int { 29 | return BR.viewModel 30 | } 31 | 32 | override fun initData() { 33 | //初始化Fragment 34 | initFragment() 35 | //初始化底部Button 36 | initBottomTab() 37 | } 38 | 39 | private fun initFragment() { 40 | mFragments = ArrayList() 41 | (mFragments as ArrayList).add(TabBar1Fragment()) 42 | (mFragments as ArrayList).add(TabBar2Fragment()) 43 | (mFragments as ArrayList).add(TabBar3Fragment()) 44 | (mFragments as ArrayList).add(TabBar4Fragment()) 45 | //默认选中第一个 46 | commitAllowingStateLoss(0) 47 | } 48 | 49 | private fun initBottomTab() { 50 | val navigationController = binding.pagerBottomTab.material() 51 | .addItem(R.drawable.yingyong, "应用") 52 | .addItem(R.drawable.huanzhe, "工作") 53 | .addItem(R.drawable.xiaoxi_select, "消息") 54 | .addItem(R.drawable.wode_select, "我的") 55 | .setDefaultColor(ContextCompat.getColor(this, R.color.textColorVice)) 56 | .build() 57 | //底部按钮的点击事件监听 58 | navigationController.addTabItemSelectedListener(object : OnTabItemSelectedListener { 59 | override fun onSelected(index: Int, old: Int) { 60 | // FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); 61 | // transaction.replace(R.id.frameLayout, mFragments.get(index)); 62 | // transaction.commitAllowingStateLoss(); 63 | commitAllowingStateLoss(index) 64 | } 65 | 66 | override fun onRepeat(index: Int) {} 67 | }) 68 | } 69 | 70 | private fun commitAllowingStateLoss(position: Int) { 71 | hideAllFragment() 72 | val transaction = supportFragmentManager.beginTransaction() 73 | var currentFragment = supportFragmentManager.findFragmentByTag(position.toString() + "") 74 | if (currentFragment != null) { 75 | transaction.show(currentFragment) 76 | } else { 77 | currentFragment = mFragments!![position] 78 | transaction.add(R.id.frameLayout, currentFragment, position.toString() + "") 79 | } 80 | transaction.commitAllowingStateLoss() 81 | } 82 | 83 | //隐藏所有Fragment 84 | private fun hideAllFragment() { 85 | val transaction = supportFragmentManager.beginTransaction() 86 | for (i in mFragments!!.indices) { 87 | val currentFragment = supportFragmentManager.findFragmentByTag(i.toString() + "") 88 | if (currentFragment != null) { 89 | transaction.hide(currentFragment) 90 | } 91 | } 92 | transaction.commitAllowingStateLoss() 93 | } 94 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/bottom_tab/fragment/TabBar1Fragment.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.bottom_tab.fragment 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.wzq.sample.BR 8 | import com.wzq.sample.R 9 | import com.wzq.sample.base.BaseFragment 10 | import com.wzq.sample.base.BaseViewModel 11 | import com.wzq.sample.databinding.FragmentTabBar1Binding 12 | 13 | class TabBar1Fragment : BaseFragment() { 14 | override fun initContentView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): Int { 15 | return R.layout.fragment_tab_bar_1 16 | } 17 | 18 | override fun initVariableId(): Int { 19 | return BR.viewModel 20 | } 21 | 22 | /** 23 | * ViewPager中的Fragment使用navigation,需要创建View的时候,从之前的父类中remove掉 24 | * 不remove掉的话,从其他页面返回含有ViewPager页面的话会报错"The specified child already has a parent" 25 | */ 26 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 27 | if (lastView != null) { 28 | val parent = lastView?.parent as ViewGroup? 29 | parent?.removeView(lastView) 30 | } 31 | super.onCreateView(inflater, container, savedInstanceState) 32 | return lastView 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/bottom_tab/fragment/TabBar2Fragment.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.bottom_tab.fragment 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.wzq.sample.BR 8 | import com.wzq.sample.R 9 | import com.wzq.sample.base.BaseFragment 10 | import com.wzq.sample.base.BaseViewModel 11 | import com.wzq.sample.databinding.FragmentTabBar2Binding 12 | 13 | class TabBar2Fragment : BaseFragment() { 14 | override fun initContentView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): Int { 15 | return R.layout.fragment_tab_bar_2 16 | } 17 | 18 | override fun initVariableId(): Int { 19 | return BR.viewModel 20 | } 21 | 22 | /** 23 | * ViewPager中的Fragment使用navigation,需要创建View的时候,从之前的父类中remove掉 24 | * 不remove掉的话,从其他页面返回含有ViewPager页面的话会报错"The specified child already has a parent" 25 | */ 26 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 27 | if (lastView != null) { 28 | val parent = lastView?.parent as ViewGroup? 29 | parent?.removeView(lastView) 30 | } 31 | super.onCreateView(inflater, container, savedInstanceState) 32 | return lastView 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/bottom_tab/fragment/TabBar3Fragment.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.bottom_tab.fragment 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.wzq.sample.BR 8 | import com.wzq.sample.R 9 | import com.wzq.sample.base.BaseFragment 10 | import com.wzq.sample.base.BaseViewModel 11 | import com.wzq.sample.databinding.FragmentTabBar3Binding 12 | 13 | class TabBar3Fragment : BaseFragment() { 14 | override fun initContentView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): Int { 15 | return R.layout.fragment_tab_bar_3 16 | } 17 | 18 | override fun initVariableId(): Int { 19 | return BR.viewModel 20 | } 21 | 22 | /** 23 | * ViewPager中的Fragment使用navigation,需要创建View的时候,从之前的父类中remove掉 24 | * 不remove掉的话,从其他页面返回含有ViewPager页面的话会报错"The specified child already has a parent" 25 | */ 26 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 27 | if (lastView != null) { 28 | val parent = lastView?.parent as ViewGroup? 29 | parent?.removeView(lastView) 30 | } 31 | super.onCreateView(inflater, container, savedInstanceState) 32 | return lastView 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/bottom_tab/fragment/TabBar4Fragment.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.bottom_tab.fragment 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.wzq.sample.BR 8 | import com.wzq.sample.R 9 | import com.wzq.sample.base.BaseFragment 10 | import com.wzq.sample.base.BaseViewModel 11 | import com.wzq.sample.databinding.FragmentTabBarForeBinding 12 | 13 | class TabBar4Fragment : BaseFragment() { 14 | override fun initContentView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): Int { 15 | return R.layout.fragment_tab_bar_fore 16 | } 17 | 18 | override fun initVariableId(): Int { 19 | return BR.viewModel 20 | } 21 | 22 | /** 23 | * ViewPager中的Fragment使用navigation,需要创建View的时候,从之前的父类中remove掉 24 | * 不remove掉的话,从其他页面返回含有ViewPager页面的话会报错"The specified child already has a parent" 25 | */ 26 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 27 | if (lastView != null) { 28 | val parent = lastView?.parent as ViewGroup? 29 | parent?.removeView(lastView) 30 | } 31 | super.onCreateView(inflater, container, savedInstanceState) 32 | return lastView 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/form_livedata/FormFragment.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.form_livedata 2 | 3 | import android.app.DatePickerDialog 4 | import android.app.DatePickerDialog.OnDateSetListener 5 | import android.os.Bundle 6 | import android.view.LayoutInflater 7 | import android.view.ViewGroup 8 | import android.widget.Toast 9 | import androidx.appcompat.app.AppCompatActivity 10 | import androidx.navigation.fragment.NavHostFragment 11 | import com.google.gson.Gson 12 | import com.wzq.mvvmsmart.utils.KLog 13 | import com.wzq.mvvmsmart.utils.ToastUtils 14 | import com.wzq.sample.BR 15 | import com.wzq.sample.R 16 | import com.wzq.sample.base.BaseFragment 17 | import com.wzq.sample.bean.FormEntity 18 | import com.wzq.sample.databinding.FragmentFormTempBinding 19 | import java.util.* 20 | 21 | /** 22 | * Created by 王志强 on 2019/11/30. 23 | * 表单提交/编辑界面 24 | */ 25 | class FormFragment : BaseFragment() { 26 | private lateinit var entity: FormEntity 27 | override fun initParam() { 28 | //获取列表传入的实体 29 | val mBundle = arguments 30 | if (mBundle != null) { 31 | entity = mBundle.getSerializable("entity") as FormEntity 32 | } 33 | } 34 | 35 | override fun initContentView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): Int { 36 | return R.layout.fragment_form_temp 37 | } 38 | 39 | override fun initVariableId(): Int { 40 | return BR.viewModel 41 | } 42 | 43 | override fun initData() { 44 | binding.presenter = Presenter() 45 | binding.switchId.isChecked = entity.marry 46 | viewModel.entityLiveData.value = entity 47 | //通过binding拿到toolbar控件, 设置给Activity 48 | (activity as AppCompatActivity?)?.setSupportActionBar(binding.title.toolbar) 49 | } 50 | 51 | /** 52 | * 初始化Toolbar 53 | */ 54 | override fun initToolbar() { 55 | //ID不为空是修改 56 | binding.title.tvTitle.text = "表单编辑" 57 | binding.title.ivRight.setOnClickListener { ToastUtils.showShort("点击了更多") } 58 | binding.title.ivBack.setOnClickListener{ NavHostFragment.findNavController(this@FormFragment).navigateUp() } 59 | } 60 | 61 | /** 62 | * 封装布局中的点击事件儿; 63 | */ 64 | inner class Presenter { 65 | fun commitClick() { 66 | Toast.makeText(activity, "触发提交按钮", Toast.LENGTH_SHORT).show() 67 | val submitJson = Gson().toJson(viewModel.entityLiveData.value) 68 | // MaterialDialogUtils.Companion.showBasicDialog(context, "提交的json实体数据:\r\n$submitJson").show() 69 | } 70 | 71 | fun showDateDialog() { 72 | val calendar = Calendar.getInstance() 73 | val year = calendar[Calendar.YEAR] 74 | val month = calendar[Calendar.MONTH] 75 | val day = calendar[Calendar.DAY_OF_MONTH] 76 | val datePickerDialog = context?.let { 77 | DatePickerDialog(it, OnDateSetListener { view, year, month, dayOfMonth -> 78 | val value = viewModel.entityLiveData.value 79 | //设置数据到实体中,自动刷新界面 80 | value?.bir = year.toString() + "年" + (month + 1) + "月" + dayOfMonth + "日" 81 | viewModel.entityLiveData.setValue(value) 82 | }, year, month, day) 83 | } 84 | datePickerDialog?.setMessage("生日选择") 85 | datePickerDialog?.show() 86 | } 87 | } 88 | 89 | override fun initViewObservable() { 90 | super.initViewObservable() 91 | binding.switchId.setOnCheckedChangeListener { buttonView, isChecked -> //是否已婚Switch点状态改变回调 92 | KLog.e("婚姻状态::$isChecked") 93 | val value = viewModel.entityLiveData.value 94 | value?.marry= isChecked 95 | viewModel.entityLiveData.setValue(value) 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/form_livedata/FormViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.form_livedata 2 | 3 | import android.app.Application 4 | import com.wzq.mvvmsmart.event.SingleLiveEvent 5 | import com.wzq.sample.base.BaseViewModel 6 | import com.wzq.sample.bean.FormEntity 7 | 8 | class FormViewModel(application: Application) : BaseViewModel(application) { 9 | var entity: FormEntity? = null 10 | var entityLiveData = SingleLiveEvent() 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/recycler_multi/MultiRecycleViewFragment.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.recycler_multi 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import androidx.lifecycle.Observer 7 | import androidx.recyclerview.widget.LinearLayoutManager 8 | import com.wzq.mvvmsmart.utils.ToastUtils 9 | import com.wzq.sample.BR 10 | import com.wzq.sample.R 11 | import com.wzq.sample.base.BaseFragment 12 | import com.wzq.sample.bean.DemoBean.ItemsEntity 13 | import com.wzq.sample.databinding.FragmentMultiRvBinding 14 | import java.util.* 15 | 16 | /** 17 | * Create Date:2019/01/25 18 | * Description:RecycleView多布局实现 19 | */ 20 | class MultiRecycleViewFragment : BaseFragment() { 21 | private lateinit var mAdapter: MyMultiAdapter 22 | override fun initContentView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): Int { 23 | return R.layout.fragment_multi_rv 24 | } 25 | 26 | override fun initVariableId(): Int { 27 | return BR.viewModel 28 | } 29 | 30 | override fun initData() { 31 | super.initData() 32 | viewModel.getData() 33 | initRecyclerView() 34 | } 35 | 36 | override fun initViewObservable() { 37 | super.initViewObservable() 38 | viewModel.itemsEntityLiveData.observe(this, Observer { itemsEntities: ArrayList? -> mAdapter!!.setNewData(itemsEntities) }) 39 | } 40 | 41 | private fun initRecyclerView() { 42 | val datas = ArrayList() 43 | mAdapter = MyMultiAdapter(datas) 44 | binding.adapter = mAdapter 45 | binding.layoutManager = LinearLayoutManager(activity) 46 | mAdapter.setOnItemClickListener { adapter, view, position -> 47 | val item = adapter.getItem(position) as ItemsEntity? 48 | ToastUtils.showShort(item?.name + "---" + position) 49 | } 50 | mAdapter.setOnItemChildClickListener { adapter, view, position -> 51 | val item = adapter.getItem(position) as ItemsEntity? 52 | when (view.id) { 53 | R.id.btn1 -> ToastUtils.showShort("btn1---$position") 54 | R.id.btn2 -> ToastUtils.showShort("btn2---$position") 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/recycler_multi/MultiRecycleViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.recycler_multi 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.MutableLiveData 5 | import com.wzq.sample.base.BaseViewModel 6 | import com.wzq.sample.bean.DemoBean.ItemsEntity 7 | import com.wzq.sample.utils.TestUtils 8 | import java.util.* 9 | 10 | /** 11 | * 王志强 12 | * Create Date:2019/01/25 13 | * Description: 14 | */ 15 | class MultiRecycleViewModel(application: Application) : BaseViewModel(application) { 16 | //给RecyclerView添加ObservableList 17 | var itemsEntityLiveData: MutableLiveData> = MutableLiveData() 18 | fun getData() { 19 | val datas = ArrayList() 20 | for (i in 0..49) { 21 | val itemBean = ItemsEntity(i, "MVVMSmart", TestUtils.getGirlImgUrl()) 22 | if (i % 2 == 0) { 23 | itemBean.itemType = 0 24 | } else { 25 | itemBean.itemType = 1 26 | } 27 | datas.add(itemBean) 28 | } 29 | itemsEntityLiveData.value = datas 30 | 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/recycler_multi/MyMultiAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.recycler_multi 2 | 3 | import android.view.View 4 | import android.widget.ImageView 5 | import com.chad.library.adapter.base.BaseMultiItemQuickAdapter 6 | import com.chad.library.adapter.base.BaseViewHolder 7 | import com.wzq.mvvmsmart.utils.GlideLoadUtils 8 | import com.wzq.sample.R 9 | import com.wzq.sample.bean.DemoBean.ItemsEntity 10 | 11 | /** 12 | * author : 王志强 13 | * date : 2020/01/09 11:34 14 | */ 15 | class MyMultiAdapter(data: List?) : BaseMultiItemQuickAdapter(data) { 16 | 17 | override fun convert(helper: BaseViewHolder, item: ItemsEntity) { 18 | when (helper.itemViewType) { 19 | 0 -> { 20 | GlideLoadUtils.loadRoundCornerImg(helper.getView(R.id.iv1) as ImageView, item.img, 21 | R.mipmap.ic_launcher, 3) 22 | helper.setText(R.id.tv_name, item.name) 23 | helper.addOnClickListener(R.id.btn1) 24 | } 25 | 1 -> { 26 | GlideLoadUtils.loadRoundCornerImg(helper.getView(R.id.iv1) as ImageView, item.img, 27 | R.mipmap.ic_launcher, 3) 28 | GlideLoadUtils.loadRoundCornerImg(helper.getView(R.id.iv2) as ImageView, item.img, 29 | R.mipmap.ic_launcher, 3) 30 | helper.setText(R.id.tv_name, item.name) 31 | helper.addOnClickListener(R.id.btn2) 32 | } 33 | } 34 | } 35 | 36 | 37 | /** 38 | * Same as QuickAdapter#QuickAdapter(Context,int) but with 39 | * some initialization data. 40 | */ 41 | init { 42 | addItemType(0, R.layout.item_multiple1) 43 | addItemType(1, R.layout.item_multiple2) 44 | } 45 | 46 | 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/recycler_single_network/MAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.recycler_single_network 2 | 3 | import com.chad.library.adapter.base.BaseQuickAdapter 4 | import com.chad.library.adapter.base.BaseViewHolder 5 | import com.wzq.mvvmsmart.utils.GlideLoadUtils 6 | import com.wzq.mvvmsmart.utils.KLog 7 | import com.wzq.sample.R 8 | import com.wzq.sample.bean.DemoBean.ItemsEntity 9 | 10 | /** 11 | * author : 王志强 12 | * date : 2020/01/09 11:34 13 | */ 14 | class MAdapter(layoutResId: Int, data: List<*>?) : BaseQuickAdapter(layoutResId, data as MutableList?) { 15 | override fun convert(helper: BaseViewHolder, item: ItemsEntity) { 16 | helper.setText(R.id.iv2, item.name) 17 | KLog.e(item.img) 18 | GlideLoadUtils.loadRoundCornerImg(helper.getView(R.id.iv1), item.img, R.mipmap.ic_launcher, 10) 19 | helper.addOnClickListener(R.id.btn2) 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/recycler_single_network/NetWorkModel.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.recycler_single_network 2 | 3 | import com.wzq.mvvmsmart.base.BaseModelMVVM 4 | import com.wzq.mvvmsmart.http.BaseResponse 5 | import com.wzq.sample.bean.DemoBean 6 | import com.wzq.sample.data.source.http.service.DemoApiService 7 | import com.wzq.sample.utils.RetrofitClient 8 | import io.reactivex.Completable 9 | import io.reactivex.Observable 10 | 11 | /** 12 | * 作者:王志强 13 | * 创建时间:2020/4/13 14 | * 文件描述: 15 | */ 16 | class NetWorkModel : BaseModelMVVM() { 17 | fun demoGet(pageNum: Int): Observable> { 18 | val instance: RetrofitClient = RetrofitClient.instance 19 | val demoApiService = instance.create(DemoApiService::class.java) 20 | return demoApiService.demoGet(pageNum) 21 | } 22 | 23 | fun loadMore(): Completable? { 24 | return null 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/recycler_single_network/detail/DetailFragment.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.recycler_single_network.detail 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import com.wzq.mvvmsmart.base.BaseFragmentMVVM 7 | import com.wzq.sample.BR 8 | import com.wzq.sample.R 9 | import com.wzq.sample.bean.DemoBean.ItemsEntity 10 | import com.wzq.sample.databinding.FragmentDetailBinding 11 | 12 | /** 13 | * 详情界面 14 | */ 15 | class DetailFragment : BaseFragmentMVVM() { 16 | private var entity: ItemsEntity? = null 17 | override fun initParam() { 18 | //获取列表传入的实体 19 | val mBundle = arguments 20 | if (mBundle != null) { 21 | entity = mBundle.getParcelable("entity") 22 | } 23 | } 24 | 25 | override fun initContentView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): Int { 26 | return R.layout.fragment_detail 27 | } 28 | 29 | override fun initVariableId(): Int { 30 | return BR.viewModel 31 | } 32 | 33 | override fun initData() { 34 | viewModel.setDemoEntity(entity) 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/recycler_single_network/detail/DetailViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.recycler_single_network.detail 2 | 3 | import android.app.Application 4 | import androidx.databinding.ObservableField 5 | import com.wzq.mvvmsmart.base.BaseViewModelMVVM 6 | import com.wzq.sample.bean.DemoBean.ItemsEntity 7 | 8 | class DetailViewModel(application: Application) : BaseViewModelMVVM(application) { 9 | var entity: ObservableField? = ObservableField() 10 | fun setDemoEntity(entity: ItemsEntity?) { 11 | this.entity!!.set(entity) 12 | } 13 | 14 | override fun onDestroy() { 15 | super.onDestroy() 16 | entity = null 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/room_db/RoomSampleFragment.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.room_db 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import androidx.lifecycle.Observer 7 | import com.wzq.mvvmsmart.utils.ToastUtils 8 | import com.wzq.sample.BR 9 | import com.wzq.sample.R 10 | import com.wzq.sample.base.BaseFragment 11 | import com.wzq.sample.databinding.FragmentRoom1Binding 12 | 13 | /** 14 | * Create Date:2020/01/01 15 | * 实现Room数据的基本操作 16 | * 王志强 17 | */ 18 | class RoomSampleFragment : BaseFragment() { 19 | override fun initContentView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): Int { 20 | return R.layout.fragment_room1 21 | } 22 | 23 | override fun initVariableId(): Int { 24 | return BR.viewModel 25 | } 26 | 27 | override fun initViewObservable() { 28 | super.initViewObservable() 29 | viewModel.allWordsLive.observe(this, Observer { words -> 30 | val text = StringBuilder() 31 | for (i in words.indices) { 32 | val word = words[i] 33 | text.append(word.id).append(":").append(word.word).append("=").append(word.chineseMeaning).append("\n") 34 | } 35 | binding.textView.text = text.toString() 36 | }) 37 | binding.buttonInsert.setOnClickListener { 38 | val word1 = Word("Hello", "你好!") 39 | viewModel.insertWords(word1) 40 | } 41 | binding.buttonClear.setOnClickListener { viewModel.deleteAllWords() } 42 | binding.buttonUpdate.setOnClickListener { 43 | ToastUtils.showShort("更新 id=1 的数据...") 44 | val word = Word("update", "更新!") 45 | word.id = 1 46 | viewModel.updateWords(word) 47 | } 48 | binding.buttonDelete.setOnClickListener { 49 | ToastUtils.showShort("删除 id=1 的数据...") 50 | val word = Word("update", "更新!") 51 | word.id = 1 52 | viewModel.deleteWords(word) 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/room_db/RoomSampleViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.room_db 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.LiveData 5 | import com.wzq.sample.base.BaseViewModel 6 | 7 | /** 8 | * Create Date:2020/01/01 9 | * 实现Room数据的基本操作 10 | * 王志强 11 | */ 12 | class RoomSampleViewModel(application: Application) : BaseViewModel(application) { 13 | private val wordRepository: WordRepository = WordRepository(application) 14 | val allWordsLive: LiveData> 15 | get() = wordRepository.listLiveData 16 | 17 | fun insertWords(vararg words: Word?) { 18 | wordRepository.insertWords(*words) 19 | } 20 | 21 | fun updateWords(vararg words: Word?) { 22 | wordRepository.updateWords(*words) 23 | } 24 | 25 | fun deleteWords(vararg words: Word?) { 26 | wordRepository.deleteWords(*words) 27 | } 28 | 29 | fun deleteAllWords() { 30 | wordRepository.deleteAllWords() 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/room_db/Word.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.room_db 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | 7 | @Entity 8 | class Word(@field:ColumnInfo(name = "english_word") var word: String, @field:ColumnInfo(name = "chinese_meaning") var chineseMeaning: String) { 9 | @PrimaryKey(autoGenerate = true) 10 | var id = 0 11 | 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/room_db/WordDao.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.room_db 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.* 5 | 6 | @Dao // Database access object 7 | interface WordDao { 8 | @Insert 9 | fun insertWords(vararg words: Word?) 10 | 11 | @Update 12 | fun updateWords(vararg words: Word?) 13 | 14 | @Delete 15 | fun deleteWords(vararg words: Word?) 16 | 17 | @Query("DELETE FROM WORD") 18 | fun deleteAllWords() 19 | 20 | //List getAllWords(); 21 | @get:Query("SELECT * FROM WORD ORDER BY ID DESC") 22 | val allWordsLive: LiveData> 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/room_db/WordDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.room_db 2 | 3 | import android.content.Context 4 | import androidx.room.Database 5 | import androidx.room.Room 6 | import androidx.room.RoomDatabase 7 | 8 | //singleton 9 | @Database(entities = [Word::class], version = 1, exportSchema = false) 10 | abstract class WordDatabase : RoomDatabase() { 11 | abstract val wordDao: WordDao? 12 | 13 | companion object { 14 | private var INSTANCE: WordDatabase? = null 15 | 16 | @Synchronized 17 | fun getDatabase(context: Context): WordDatabase? { 18 | if (INSTANCE == null) { 19 | INSTANCE = Room.databaseBuilder(context.applicationContext, WordDatabase::class.java, "word_database") 20 | .build() 21 | } 22 | return INSTANCE 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/room_db/WordRepository.java: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.room_db; 2 | 3 | import android.content.Context; 4 | import android.os.AsyncTask; 5 | 6 | import androidx.lifecycle.LiveData; 7 | 8 | import java.util.List; 9 | 10 | class WordRepository { 11 | private LiveData> listLiveData; 12 | private WordDao wordDao; 13 | 14 | 15 | LiveData> getListLiveData() { 16 | return listLiveData; 17 | } 18 | 19 | WordRepository(Context context) { 20 | WordDatabase wordDatabase = WordDatabase.Companion.getDatabase(context.getApplicationContext()); 21 | wordDao = wordDatabase.getWordDao(); 22 | listLiveData = wordDao.getAllWordsLive(); 23 | } 24 | 25 | void insertWords(Word... words) { 26 | new InsertAsyncTask(wordDao).execute(words); 27 | } 28 | 29 | void updateWords(Word... words) { 30 | new UpdateAsyncTask(wordDao).execute(words); 31 | } 32 | 33 | void deleteWords(Word... words) { 34 | new DeleteAsyncTask(wordDao).execute(words); 35 | } 36 | 37 | void deleteAllWords(Word... words) { 38 | new DeleteAllAsyncTask(wordDao).execute(); 39 | } 40 | 41 | 42 | static class InsertAsyncTask extends AsyncTask { 43 | private WordDao wordDao; 44 | 45 | InsertAsyncTask(WordDao wordDao) { 46 | this.wordDao = wordDao; 47 | } 48 | 49 | @Override 50 | protected Void doInBackground(Word... words) { 51 | wordDao.insertWords(words); 52 | return null; 53 | } 54 | 55 | } 56 | 57 | static class UpdateAsyncTask extends AsyncTask { 58 | private WordDao wordDao; 59 | 60 | UpdateAsyncTask(WordDao wordDao) { 61 | this.wordDao = wordDao; 62 | } 63 | 64 | @Override 65 | protected Void doInBackground(Word... words) { 66 | wordDao.updateWords(words); 67 | return null; 68 | } 69 | 70 | } 71 | 72 | static class DeleteAsyncTask extends AsyncTask { 73 | private WordDao wordDao; 74 | 75 | DeleteAsyncTask(WordDao wordDao) { 76 | this.wordDao = wordDao; 77 | } 78 | 79 | @Override 80 | protected Void doInBackground(Word... words) { 81 | wordDao.deleteWords(words); 82 | return null; 83 | } 84 | 85 | } 86 | 87 | static class DeleteAllAsyncTask extends AsyncTask { 88 | private WordDao wordDao; 89 | 90 | DeleteAllAsyncTask(WordDao wordDao) { 91 | this.wordDao = wordDao; 92 | } 93 | 94 | @Override 95 | protected Void doInBackground(Void... voids) { 96 | wordDao.deleteAllWords(); 97 | return null; 98 | } 99 | 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/testnet/TestNetFragment.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.testnet 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.lifecycle.Observer 8 | import com.wzq.sample.BR 9 | import com.wzq.sample.R 10 | import com.wzq.sample.base.BaseFragment 11 | import com.wzq.sample.databinding.FragmentTestNetBinding 12 | import com.wzq.sample.http2.net_utils.MmkvUtils 13 | 14 | /** 15 | * Create Date:2019/01/25 16 | * Description:RecycleView多布局实现 17 | */ 18 | class TestNetFragment : BaseFragment() { 19 | override fun initContentView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): Int { 20 | return R.layout.fragment_test_net 21 | } 22 | 23 | override fun initVariableId(): Int { 24 | return BR.viewModel 25 | } 26 | 27 | 28 | override fun initViewObservable() { 29 | super.initViewObservable() 30 | binding.button.setOnClickListener { v: View? -> 31 | // 有效期为一个月,一个月后需要更新token,暂时写死,可以用一个月,(本次添加时间:2020.04.30) 32 | MmkvUtils.putStringValue("accessToken", "Bearer 6766c5f7-154e-40e0-bb05-7d66b91a11e8") // dev 1473203的账号 33 | viewModel.getPersonalSummary() // 请求网络数据; 34 | // viewModel.getPersonalSummary2() // 请求网络数据2; 35 | viewModel.userLiveData.observe(this, Observer { s -> binding.tvJson.text = "result: ${s.nickname + s.goldNum}" }) 36 | } 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/testnet/TestNetViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.testnet 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.MutableLiveData 5 | import com.wzq.mvvmsmart.utils.KLog 6 | import com.wzq.mvvmsmart.utils.ToastUtils 7 | import com.wzq.sample.base.BaseViewModel 8 | import com.wzq.sample.bean.User 9 | import com.wzq.sample.data.source.http.service2.MRequest 10 | import com.wzq.sample.http2.listener.OnServerResponseListener 11 | import com.wzq.sample.http2.base.BaseResponse 12 | import com.wzq.sample.http2.net_utils.GsonUtil 13 | import java.util.* 14 | 15 | /** 16 | * 王志强 17 | * Create Date:2019/01/25 18 | * Description:业务类 19 | */ 20 | class TestNetViewModel(application: Application) : BaseViewModel(application) { 21 | //给RecyclerView添加ObservableList 22 | var userLiveData = MutableLiveData() 23 | 24 | /** 25 | 线程调度,compose操作符是直接对当前Observable进行操作(可简单理解为不停地.方法名().方法名()链式操作当前Observable) 26 | 网络错误的异常转换, 这里可以换成自己的ExceptionHandle 27 | 请求与ViewModel周期同步 28 | 获取个人信息 29 | */ 30 | 31 | fun getPersonalSummary() { 32 | val param: Map = HashMap() 33 | MRequest.getInstance().getPersonalSummary(null, 0, GsonUtil.bean2String(param), object : OnServerResponseListener { 34 | override fun success(what: Int, isQualified: Boolean, baseResponse: BaseResponse) { 35 | KLog.e("=====success========="); 36 | when (what) { 37 | 0 -> { 38 | KLog.e(baseResponse.data) 39 | val user: User = baseResponse.data as User 40 | userLiveData.value = user 41 | // KLog.e(user.toString()) 42 | } 43 | else -> { 44 | } 45 | } 46 | } 47 | 48 | override fun error(what: Int, throwable: Throwable?) { 49 | KLog.e("=====error=========throwable:" + throwable?.message) 50 | ToastUtils.showLong("开发者:接口失效,请自由替换开源接口") 51 | } 52 | 53 | override fun reTry(what: Int) { 54 | KLog.e("=====reTry========="); 55 | } 56 | }) 57 | } 58 | 59 | // 获取个人信息2 60 | fun getPersonalSummary2() { 61 | val param: Map = HashMap() 62 | MRequest.getInstance().getPersonalSummary2(null, 0, GsonUtil.bean2String(param), object : OnServerResponseListener { 63 | override fun success(what: Int, isQualified: Boolean, baseResponse: BaseResponse) { 64 | KLog.e("=====success========="); 65 | when (what) { 66 | 0 -> { 67 | KLog.e(baseResponse.data) 68 | val user: User = baseResponse.data as User 69 | userLiveData.value = user 70 | // KLog.e(user.toString()) 71 | } 72 | else -> { 73 | } 74 | } 75 | } 76 | 77 | override fun error(what: Int, throwable: Throwable?) { 78 | KLog.e("=====error=========throwable:" + throwable?.message); 79 | } 80 | 81 | override fun reTry(what: Int) { 82 | KLog.e("=====reTry========="); 83 | } 84 | }) 85 | } 86 | 87 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/viewpager_frgment/BaseFragmentPagerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.viewpager_frgment 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.fragment.app.FragmentManager 5 | import androidx.fragment.app.FragmentPagerAdapter 6 | 7 | /** 8 | * FragmentPager适配器 9 | */ 10 | class BaseFragmentPagerAdapter //使用构造方法来将数据传进去 11 | (fm: FragmentManager?, //ViewPager要填充的fragment列表 12 | private val list: List?, //tab中的title文字列表 13 | private val title: List?) : FragmentPagerAdapter(fm!!) { 14 | 15 | override fun getItem(position: Int): Fragment { //获得position中的fragment来填充 16 | return list!![position] 17 | } 18 | 19 | override fun getCount(): Int { //返回FragmentPager的个数 20 | return list!!.size 21 | } 22 | 23 | //FragmentPager的标题,如果重写这个方法就显示不出tab的标题内容 24 | override fun getPageTitle(position: Int): CharSequence? { 25 | return title!![position] 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/viewpager_frgment/BasePagerFragment.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.viewpager_frgment 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import androidx.fragment.app.Fragment 7 | import com.google.android.material.tabs.TabLayout.TabLayoutOnPageChangeListener 8 | import com.wzq.sample.BR 9 | import com.wzq.sample.R 10 | import com.wzq.sample.base.BaseFragment 11 | import com.wzq.sample.base.BaseViewModel 12 | import com.wzq.sample.databinding.FragmentBasePagerTempBinding 13 | 14 | /** 15 | * 抽取的二级BasePagerFragment 16 | */ 17 | abstract class BasePagerFragment : BaseFragment() { 18 | private var mFragments: List? = null 19 | private var titlePager: List? = null 20 | protected abstract fun pagerFragment(): List? 21 | protected abstract fun pagerTitleString(): List? 22 | override fun initContentView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): Int { 23 | return R.layout.fragment_base_pager_temp 24 | } 25 | 26 | override fun initVariableId(): Int { 27 | return BR.viewModel 28 | } 29 | 30 | override fun initData() { 31 | mFragments = pagerFragment() 32 | titlePager = pagerTitleString() 33 | //设置Adapter 34 | val pagerAdapter = BaseFragmentPagerAdapter(childFragmentManager, mFragments, titlePager) 35 | binding.viewPager.adapter = pagerAdapter 36 | binding.tabs.setupWithViewPager(binding.viewPager) 37 | binding.viewPager.addOnPageChangeListener(TabLayoutOnPageChangeListener(binding.tabs)) 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/ui/viewpager_frgment/ViewPagerGroupFragment.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.ui.viewpager_frgment 2 | 3 | import androidx.fragment.app.Fragment 4 | import com.wzq.sample.ui.bottom_tab.fragment.TabBar1Fragment 5 | import com.wzq.sample.ui.bottom_tab.fragment.TabBar2Fragment 6 | import com.wzq.sample.ui.bottom_tab.fragment.TabBar3Fragment 7 | import com.wzq.sample.ui.bottom_tab.fragment.TabBar4Fragment 8 | import java.util.* 9 | 10 | /** 11 | * Description:ViewPager+Fragment的实现(绑定命令) 12 | * 使用这个Fragment,内部封装好了ViewPager+Fragment; 13 | */ 14 | class ViewPagerGroupFragment : BasePagerFragment() { 15 | override fun pagerFragment(): List? { 16 | val list: MutableList = ArrayList() 17 | list.add(TabBar1Fragment()) 18 | list.add(TabBar2Fragment()) 19 | list.add(TabBar3Fragment()) 20 | list.add(TabBar4Fragment()) 21 | return list 22 | } 23 | 24 | override fun pagerTitleString(): List? { 25 | val list: MutableList = ArrayList() 26 | list.add("page1") 27 | list.add("page2") 28 | list.add("page3") 29 | list.add("page4") 30 | return list 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wzq/sample/utils/TestUtils.kt: -------------------------------------------------------------------------------- 1 | package com.wzq.sample.utils 2 | 3 | /** 4 | * 作者:王志强 5 | * 创建时间:2019/12/23 6 | * 文件描述: 7 | */ 8 | object TestUtils { 9 | fun getGirlImgUrl(): String { 10 | val index = (Math.random() * girlImgs.size).toInt() 11 | return girlImgs[index] 12 | } 13 | 14 | var girlImgs = arrayOf( // --------------------sex girl------------------------ 15 | "http://ww1.sinaimg.cn/large/0065oQSqly1g2pquqlp0nj30n00yiq8u.jpg", 16 | "https://ww1.sinaimg.cn/large/0065oQSqly1g2hekfwnd7j30sg0x4djy.jpg", 17 | "https://pixabay.com/get/55e0d0404a57ad14f6da8c7dda79367a1d39d8e65b526c48702873d2954ecc5ebf_640.jpg", 18 | "https://pixabay.com/get/54e6d2444d55ab14f6da8c7dda79367a1d39d8e65b526c48702873d2954ecc5ebf_640.jpg", 19 | "https://pixabay.com/get/52e9dc444f54b108f5d08460962933761339dded504c704c72277dd69444c35f_640.jpg", 20 | "https://pixabay.com/get/54e2d5454856a414f6da8c7dda79367a1d39d8e65b526c48702873d2954ecc5ebf_640.jpg", 21 | "https://pixabay.com/get/52e6dd454955aa14f6da8c7dda79367a1d39d8e65b526c48702873d2954ecc5ebf_640.jpg", 22 | "https://pixabay.com/get/54e8d54b4a55af14f6da8c7dda79367a1d39d8e65b526c48702873d2954ecc5ebf_640.jpg", 23 | "https://pixabay.com/get/52e6dc4b4e55ac14f6da8c7dda79367a1d39d8e65b526c48702873d2954ecc5ebf_640.jpg", 24 | "https://pixabay.com/get/52e6dc4b4e54ab14f6da8c7dda79367a1d39d8e65b526c48702873d2954ecc5ebf_640.jpg", 25 | "https://pixabay.com/get/54e7d5474854ae14f6da8c7dda79367a1d39d8e65b526c48702873d2954ecc5ebf_640.jpg", //----------------------not sex girl---------- 26 | "http://f.expoon.com/sub/news/2016/01/21/887844_230x162_0.jpg", 27 | "http://f.expoon.com/sub/news/2016/01/21/580828_230x162_0.jpg", 28 | "http://f.expoon.com/sub/news/2016/01/21/745921_230x162_0.jpg", 29 | "http://f.expoon.com/sub/news/2016/01/21/158040_230x162_0.jpg", 30 | "http://f.expoon.com/sub/news/2016/01/21/158040_230x162_0.jpg", 31 | "http://f.expoon.com/sub/news/2016/01/21/865222_230x162_0.jpg", 32 | "http://f.expoon.com/sub/news/2016/01/20/370858_230x162_0.jpg", 33 | "http://f.expoon.com/sub/news/2016/01/20/868385_230x162_0.jpg", 34 | "http://f.expoon.com/sub/news/2016/01/20/768695_230x162_0.jpg" 35 | ) 36 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzqjava/MVVMSmart-kotlin/95bf5e2ab434147b9f6c58e966e3bf1b85297b0a/app/src/main/res/drawable-xxhdpi/back.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/huanzhe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzqjava/MVVMSmart-kotlin/95bf5e2ab434147b9f6c58e966e3bf1b85297b0a/app/src/main/res/drawable-xxhdpi/huanzhe.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/toolbar_more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzqjava/MVVMSmart-kotlin/95bf5e2ab434147b9f6c58e966e3bf1b85297b0a/app/src/main/res/drawable-xxhdpi/toolbar_more.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/wode_select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzqjava/MVVMSmart-kotlin/95bf5e2ab434147b9f6c58e966e3bf1b85297b0a/app/src/main/res/drawable-xxhdpi/wode_select.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/xiaoxi_select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzqjava/MVVMSmart-kotlin/95bf5e2ab434147b9f6c58e966e3bf1b85297b0a/app/src/main/res/drawable-xxhdpi/xiaoxi_select.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/yingyong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzqjava/MVVMSmart-kotlin/95bf5e2ab434147b9f6c58e966e3bf1b85297b0a/app/src/main/res/drawable-xxhdpi/yingyong.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 20 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_tab_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 17 | 18 | 23 | 24 | 28 | 29 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_base_pager_temp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 18 | 19 | 28 | 29 | 33 | 34 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_form_temp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 12 | 13 | 14 | 15 | 17 | 18 | 22 | 23 | 26 | 27 | 33 | 34 | 40 | 41 | 47 | 48 | 49 | 55 | 56 | 62 | 63 | 64 | 70 | 71 | 72 | 78 | 79 | 85 | 86 | 87 | 92 | 93 | 94 | 95 | 96 |