├── .gitignore ├── .idea ├── caches │ └── build_file_checksums.ser ├── codeStyles │ └── Project.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── kotlingank.jks ├── proguard-rules.pro ├── release │ ├── kotlinmvp_v1.1.0_release.apk │ └── output.json └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── jaygengi │ │ └── gank │ │ ├── Constants.kt │ │ ├── Extensions.kt │ │ ├── MyApplication.kt │ │ ├── api │ │ ├── ApiService.kt │ │ └── UriConstant.kt │ │ ├── base │ │ ├── BaseActivity.kt │ │ ├── BaseFragment.kt │ │ ├── BasePresenter.kt │ │ ├── IBaseView.kt │ │ └── IPresenter.kt │ │ ├── mvp │ │ ├── contract │ │ │ ├── CategoryContract.kt │ │ │ ├── GirlsContract.kt │ │ │ └── HomeContract.kt │ │ ├── model │ │ │ ├── CategoryModel.kt │ │ │ ├── GirlsModel.kt │ │ │ ├── Main.java │ │ │ ├── ToDayModel.kt │ │ │ └── bean │ │ │ │ ├── CategoryEntity.kt │ │ │ │ ├── GirlsEntity.kt │ │ │ │ ├── MySectionEntity.kt │ │ │ │ ├── TabEntity.kt │ │ │ │ └── ToDayEntity.kt │ │ └── presenter │ │ │ ├── CategoryPresenter.kt │ │ │ ├── GirlsPresenter.kt │ │ │ └── HomePresenter.kt │ │ ├── net │ │ ├── BaseResponse.kt │ │ ├── RetrofitManager.kt │ │ └── exception │ │ │ ├── ApiException.kt │ │ │ ├── ErrorStatus.kt │ │ │ └── ExceptionHandle.kt │ │ ├── scheduler │ │ ├── BaseScheduler.kt │ │ ├── ComputationMainScheduler.kt │ │ ├── IoMainScheduler.kt │ │ ├── NewThreadMainScheduler.kt │ │ ├── SchedulerUtils.kt │ │ ├── SingleMainScheduler.kt │ │ └── TrampolineMainScheduler.kt │ │ ├── ui │ │ ├── activity │ │ │ ├── MainActivity.kt │ │ │ ├── SearchActivity.kt │ │ │ ├── SplashActivity.kt │ │ │ └── WebViewActivity.kt │ │ ├── adapter │ │ │ ├── GirlsAdapter.kt │ │ │ ├── HomePageAdapter.kt │ │ │ ├── ToDayAndroidAdapter.kt │ │ │ └── ToDaySectionAdapter.kt │ │ └── fragment │ │ │ ├── GankTypeFragment.kt │ │ │ ├── HomeFragment.kt │ │ │ ├── MineFragment.kt │ │ │ └── home │ │ │ ├── CategoryFragment.kt │ │ │ └── WelfareFragment.kt │ │ ├── utils │ │ ├── AppUtils.kt │ │ ├── CleanLeakUtils.kt │ │ ├── DensityUtil.java │ │ ├── DisplayManager.kt │ │ ├── NetworkUtil.kt │ │ ├── Preference.kt │ │ ├── RxUtil.java │ │ ├── StatusBarUtil.kt │ │ ├── WatchHistoryUtils.kt │ │ └── img │ │ │ └── GlideImageLoader.java │ │ └── widget │ │ ├── SlidingTabLayout.java │ │ └── SlidingTabStrip.java │ └── res │ ├── anim │ ├── anim_in.xml │ ├── anim_out.xml │ ├── push_bottom_in.xml │ └── push_bottom_out.xml │ ├── drawable │ ├── ic_left.xml │ ├── line_h.xml │ └── shape_item.xml │ ├── layout │ ├── activity_main.xml │ ├── activity_search.xml │ ├── activity_splash.xml │ ├── activity_webview.xml │ ├── fragment_app.xml │ ├── fragment_gank_type.xml │ ├── fragment_home.xml │ ├── fragment_home_common_type.xml │ ├── fragment_mine.xml │ ├── fragment_welfare.xml │ ├── item_category.xml │ ├── item_category_info.xml │ ├── item_grils_info.xml │ ├── item_home_today.xml │ ├── item_home_today_img.xml │ ├── item_today_header.xml │ ├── layout_empty_view.xml │ ├── layout_error_view.xml │ ├── layout_loading_view.xml │ └── layout_network_view.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ ├── ic_action_clear.png │ ├── ic_action_search_black.png │ ├── ic_action_search_small.png │ ├── ic_action_search_white.png │ ├── ic_classification_normal.png │ ├── ic_classification_selected.png │ ├── ic_error.png │ ├── ic_home_normal.png │ ├── ic_home_selected.png │ ├── ic_mine_normal.png │ ├── ic_mine_selected.png │ ├── ic_no_data.png │ ├── ic_no_network.png │ └── img_jaygengi_avatar.jpg │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── transition-v21 │ └── arc_motion.xml │ ├── values-v19 │ └── styles.xml │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── ids.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── multiple-status-view ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── classic │ │ └── common │ │ └── MultipleStatusView.java │ └── res │ ├── layout │ ├── empty_view.xml │ ├── error_view.xml │ ├── loading_view.xml │ └── no_network_view.xml │ └── values │ ├── attrs.xml │ ├── ids.xml │ ├── strings.xml │ └── styles.xml ├── settings.gradle └── show ├── gank_type.png ├── gankapp.gif ├── home.png ├── img.png ├── mine.png └── type.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KotlinGankApp客户端 2 | 3 | 基于 **MVP** 结构,使用 Kotlin 语言开发和借助 [Gank.io](https://gank.io/) 提供的API开发的一个客户端 4 | 5 | Kotlin+MVP的框架思路来源于 [git-xuhao](https://github.com/git-xuhao/KotlinMvp)。 6 | 7 | [APK下载地址](https://github.com/JayGengi/KotlinGankApp/blob/master/app/release/kotlinmvp_v1.1.0_release.apk) 8 | 9 | ## 前言 10 | 11 | 作为Google主要力推的Kotlin开发语言,早已心痒,跃跃欲试。对于使用过程中,方法和函数的便捷使用和扩展插件的方便。值的大家尝试一波 12 | 13 | 14 | ### 界面截图 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' //扩展插件 4 | apply plugin: 'kotlin-kapt' //kapt3插件 5 | 6 | android { 7 | signingConfigs { 8 | // config { 9 | // keyAlias 'kotlingank' 10 | // keyPassword '123456' 11 | // storeFile file('../kotlingank.jks') 12 | // storePassword '123456' 13 | // } 14 | } 15 | compileSdkVersion 27 16 | buildToolsVersion "28.0.3" 17 | defaultConfig { 18 | applicationId "com.jaygengi.gank" 19 | minSdkVersion 18 20 | targetSdkVersion 27 21 | versionCode 1 22 | versionName "1.1.0" 23 | javaCompileOptions { 24 | annotationProcessorOptions { 25 | includeCompileClasspath true 26 | } 27 | } 28 | // signingConfig signingConfigs.config 29 | // 实现毛玻璃那种透明的效果需要添加的库 30 | renderscriptTargetApi 19 31 | renderscriptSupportModeEnabled true // Enable RS support 32 | 33 | ndk { 34 | //APP的build.gradle设置支持的SO库架构 35 | abiFilters 'armeabi', 'armeabi-v7a', 'x86' 36 | } 37 | multiDexEnabled true 38 | } 39 | buildTypes { 40 | debug { 41 | minifyEnabled false 42 | debuggable true 43 | // signingConfig signingConfigs.config 44 | } 45 | release { 46 | minifyEnabled false 47 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 48 | debuggable false 49 | // signingConfig signingConfigs.config 50 | zipAlignEnabled true 51 | } 52 | } 53 | sourceSets { 54 | main { 55 | jni.srcDirs = [] 56 | jniLibs.srcDirs = ['libs'] 57 | } 58 | } 59 | // 自定义输出配置 60 | android.applicationVariants.all { variant -> 61 | variant.outputs.all { 62 | outputFileName = "kotlinmvp_v${variant.versionName}_${variant.name}.apk" 63 | } 64 | } 65 | compileOptions { 66 | targetCompatibility JavaVersion.VERSION_1_8 67 | sourceCompatibility JavaVersion.VERSION_1_8 68 | } 69 | productFlavors { 70 | 71 | } 72 | 73 | dexOptions { 74 | jumboMode true 75 | } 76 | lintOptions { 77 | abortOnError false 78 | } 79 | } 80 | 81 | dependencies { 82 | implementation fileTree(include: ['*.jar'], dir: 'libs') 83 | //kotlin 支持库 84 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 85 | 86 | // Support库 87 | implementation 'com.android.support:support-v4:27.1.1' 88 | implementation 'com.android.support:appcompat-v7:27.1.1' 89 | implementation 'com.android.support:cardview-v7:27.1.1' 90 | implementation 'com.android.support:recyclerview-v7:27.1.1' 91 | implementation 'com.android.support:design:27.1.1' 92 | 93 | implementation 'com.android.support.constraint:constraint-layout:2.0.0-alpha2' 94 | 95 | implementation 'com.android.support:multidex:1.0.3' 96 | 97 | //qmui 98 | implementation 'com.qmuiteam:qmui:1.1.6' 99 | // 底部菜单 100 | implementation('com.flyco.tablayout:FlycoTabLayout_Lib:2.1.0@aar') { 101 | exclude group: 'com.android.support', module: 'support-v4' 102 | } 103 | 104 | // Retrofit 105 | implementation 'com.squareup.retrofit2:retrofit:2.4.0' 106 | implementation 'com.squareup.retrofit2:converter-gson:2.4.0' 107 | implementation 'com.squareup.retrofit2:converter-scalars:2.4.0' 108 | implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0' 109 | //rxjava2 110 | implementation 'io.reactivex.rxjava2:rxjava:2.2.2' 111 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' 112 | 113 | //Retrofit依赖OkHttp来进行网络请求 日志拦截器 114 | implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0' 115 | 116 | ////BaseQuickAdapter[很赞的适配器封装] 117 | implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.40' 118 | //smartRefreshLayout 下拉刷新 119 | implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.0.5.1' 120 | implementation 'com.scwang.smartrefresh:SmartRefreshHeader:1.0.3' 121 | 122 | implementation project(':multiple-status-view') 123 | //模糊透明 View 124 | implementation 'com.github.mmin18:realtimeblurview:1.1.0' 125 | 126 | //Logger 127 | implementation 'com.orhanobut:logger:2.1.1' 128 | //leakCanary 内存泄漏自检 129 | debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1' 130 | releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1' 131 | 132 | //banner 133 | implementation 'com.youth.banner:banner:1.4.10' 134 | implementation 'com.github.SherlockGougou:BigImageViewPager:v4_2.1.4' 135 | 136 | //glide 137 | implementation 'com.github.bumptech.glide:glide:4.8.0' 138 | annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0' 139 | implementation 'com.github.bumptech.glide:okhttp3-integration:4.8.0' 140 | //WebView 141 | implementation 'com.github.delight-im:Android-AdvancedWebView:v3.0.0' 142 | //今日头条屏幕适配 143 | // implementation 'me.jessyan:autosize:1.0.6' 144 | } 145 | -------------------------------------------------------------------------------- /app/kotlingank.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/app/kotlingank.jks -------------------------------------------------------------------------------- /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 | 23 | #Glide的混淆规则 24 | -keep public class * implements com.bumptech.glide.module.GlideModule 25 | -keep public class * extends com.bumptech.glide.AppGlideModule 26 | -keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { 27 | **[] $VALUES; 28 | public *; 29 | } 30 | 31 | # Platform calls Class.forName on types which do not exist on Android to determine platform. 32 | -dontnote retrofit2.Platform 33 | # Platform used when running on Java 8 VMs. Will not be used at runtime. 34 | -dontwarn retrofit2.Platform$Java8 35 | # Retain generic type information for use by reflection by converters and adapters. 36 | -keepattributes Signature 37 | # Retain declared checked exceptions for use by a Proxy instance. 38 | -keepattributes Exceptions 39 | -dontwarn org.xmlpull.v1.** 40 | -dontwarn okhttp3.** 41 | -keep class okhttp3.** { *; } 42 | -dontwarn okio.** 43 | -dontwarn javax.annotation.Nullable 44 | -dontwarn javax.annotation.ParametersAreNonnullByDefault 45 | -------------------------------------------------------------------------------- /app/release/kotlinmvp_v1.1.0_release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/app/release/kotlinmvp_v1.1.0_release.apk -------------------------------------------------------------------------------- /app/release/output.json: -------------------------------------------------------------------------------- 1 | [{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.1.0","enabled":true,"outputFile":"kotlinmvp_v1.1.0_release.apk","fullName":"release","baseName":"release"},"path":"kotlinmvp_v1.1.0_release.apk","properties":{}}] -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 32 | 33 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 54 | 55 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank 2 | 3 | // ┏┓   ┏┓ 4 | //┏┛┻━━━┛┻┓ 5 | //┃       ┃ 6 | //┃   ━   ┃ 7 | //┃ ┳┛ ┗┳ ┃ 8 | //┃       ┃ 9 | //┃   ┻   ┃ 10 | //┃       ┃ 11 | //┗━┓   ┏━┛ 12 | // ┃   ┃ 神兽保佑 13 | // ┃   ┃ 代码无BUG! 14 | // ┃   ┗━━━┓ 15 | // ┃       ┣┓ 16 | // ┃       ┏┛ 17 | // ┗┓┓┏━┳┓┏┛ 18 | // ┃┫┫ ┃┫┫ 19 | // ┗┻┛ ┗┻┛ 20 | /** 21 | * Created by xuhao on 2017/11/27. 22 | * desc: 常量 23 | */ 24 | class Constants private constructor() { 25 | 26 | companion object { 27 | 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank 2 | 3 | import android.content.Context 4 | import android.support.v4.app.Fragment 5 | import android.view.View 6 | import android.widget.Toast 7 | import com.jaygengi.gank.MyApplication 8 | 9 | /** 10 | * Created by xuhao on 2017/11/14. 11 | */ 12 | 13 | fun Fragment.showToast(content: String): Toast { 14 | val toast = Toast.makeText(this.activity?.applicationContext, content, Toast.LENGTH_SHORT) 15 | toast.show() 16 | return toast 17 | } 18 | 19 | fun Context.showToast(content: String): Toast { 20 | val toast = Toast.makeText(MyApplication.context, content, Toast.LENGTH_SHORT) 21 | toast.show() 22 | return toast 23 | } 24 | 25 | 26 | fun View.dip2px(dipValue: Float): Int { 27 | val scale = this.resources.displayMetrics.density 28 | return (dipValue * scale + 0.5f).toInt() 29 | } 30 | 31 | fun View.px2dip(pxValue: Float): Int { 32 | val scale = this.resources.displayMetrics.density 33 | return (pxValue / scale + 0.5f).toInt() 34 | } 35 | 36 | fun durationFormat(duration: Long?): String { 37 | val minute = duration!! / 60 38 | val second = duration % 60 39 | return if (minute <= 9) { 40 | if (second <= 9) { 41 | "0$minute' 0$second''" 42 | } else { 43 | "0$minute' $second''" 44 | } 45 | } else { 46 | if (second <= 9) { 47 | "$minute' 0$second''" 48 | } else { 49 | "$minute' $second''" 50 | } 51 | } 52 | } 53 | 54 | /** 55 | * 数据流量格式化 56 | */ 57 | fun Context.dataFormat(total: Long): String { 58 | var result: String 59 | var speedReal: Int = (total / (1024)).toInt() 60 | result = if (speedReal < 512) { 61 | speedReal.toString() + " KB" 62 | } else { 63 | val mSpeed = speedReal / 1024.0 64 | (Math.round(mSpeed * 100) / 100.0).toString() + " MB" 65 | } 66 | return result 67 | } 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/MyApplication.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import android.content.Context 6 | import android.os.Bundle 7 | import android.support.multidex.MultiDexApplication 8 | import android.util.Log 9 | import com.jaygengi.gank.utils.DisplayManager 10 | import com.orhanobut.logger.AndroidLogAdapter 11 | import com.orhanobut.logger.Logger 12 | import com.orhanobut.logger.PrettyFormatStrategy 13 | import com.scwang.smartrefresh.layout.SmartRefreshLayout 14 | import com.scwang.smartrefresh.layout.constant.SpinnerStyle 15 | import com.scwang.smartrefresh.layout.footer.ClassicsFooter 16 | import com.scwang.smartrefresh.layout.header.ClassicsHeader 17 | import com.squareup.leakcanary.LeakCanary 18 | import com.squareup.leakcanary.RefWatcher 19 | import kotlin.properties.Delegates 20 | 21 | 22 | public class MyApplication : MultiDexApplication(){ 23 | private var refWatcher: RefWatcher? = null 24 | 25 | companion object { 26 | 27 | private val TAG = "MyApplication" 28 | 29 | var context: Context by Delegates.notNull() 30 | private set 31 | 32 | fun getRefWatcher(context: Context): RefWatcher? { 33 | val myApplication = context.applicationContext as MyApplication 34 | return myApplication.refWatcher 35 | } 36 | 37 | } 38 | 39 | override fun onCreate() { 40 | super.onCreate() 41 | context = applicationContext 42 | // refWatcher = setupLeakCanary() 43 | initConfig() 44 | DisplayManager.init(this) 45 | registerActivityLifecycleCallbacks(mActivityLifecycleCallbacks) 46 | 47 | 48 | } 49 | 50 | private fun setupLeakCanary(): RefWatcher { 51 | return if (LeakCanary.isInAnalyzerProcess(this)) { 52 | RefWatcher.DISABLED 53 | } else LeakCanary.install(this) 54 | } 55 | 56 | /** 57 | * 初始化配置 58 | */ 59 | private fun initConfig() { 60 | 61 | val formatStrategy = PrettyFormatStrategy.newBuilder() 62 | .showThreadInfo(false) // 隐藏线程信息 默认:显示 63 | .methodCount(0) // 决定打印多少行(每一行代表一个方法)默认:2 64 | .methodOffset(7) // (Optional) Hides internal method calls up to offset. Default 5 65 | .tag("jaygengi") // (Optional) Global tag for every log. Default PRETTY_LOGGER 66 | .build() 67 | Logger.addLogAdapter(object : AndroidLogAdapter(formatStrategy) { 68 | override fun isLoggable(priority: Int, tag: String?): Boolean { 69 | return BuildConfig.DEBUG 70 | } 71 | }) 72 | } 73 | 74 | 75 | private val mActivityLifecycleCallbacks = object : Application.ActivityLifecycleCallbacks { 76 | override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { 77 | Log.d(TAG, "onCreated: " + activity.componentName.className) 78 | } 79 | 80 | override fun onActivityStarted(activity: Activity) { 81 | Log.d(TAG, "onStart: " + activity.componentName.className) 82 | } 83 | 84 | override fun onActivityResumed(activity: Activity) { 85 | 86 | } 87 | 88 | override fun onActivityPaused(activity: Activity) { 89 | 90 | } 91 | 92 | override fun onActivityStopped(activity: Activity) { 93 | 94 | } 95 | 96 | override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { 97 | 98 | } 99 | 100 | override fun onActivityDestroyed(activity: Activity) { 101 | Log.d(TAG, "onDestroy: " + activity.componentName.className) 102 | } 103 | } 104 | /** 105 | * 上拉加载,下拉刷新 106 | * static 代码段可以防止内存泄露 107 | */ 108 | init { 109 | //设置全局的Header构建器 110 | SmartRefreshLayout.setDefaultRefreshHeaderCreator { context, layout -> 111 | //全局设置主题颜色 112 | // layout.setPrimaryColorsId(R.color.common_color_line, R.color.qmui_config_color_black) 113 | //指定为经典Header,默认是 贝塞尔雷达Header 114 | ClassicsHeader(context).setSpinnerStyle(SpinnerStyle.Translate) 115 | } 116 | //设置全局的Footer构建器 117 | SmartRefreshLayout.setDefaultRefreshFooterCreator { context, layout -> 118 | // layout.setPrimaryColorsId(R.color.common_color_line, R.color.qmui_config_color_black) 119 | //指定为经典Footer,默认是 BallPulseFooter 120 | ClassicsFooter(context).setSpinnerStyle(SpinnerStyle.Translate) 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/api/ApiService.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.api 2 | 3 | import com.jaygengi.gank.mvp.model.bean.CategoryEntity 4 | import com.jaygengi.gank.mvp.model.bean.GirlsEntity 5 | import com.jaygengi.gank.mvp.model.bean.ToDayEntity 6 | import com.jaygengi.gank.net.BaseResponse 7 | import io.reactivex.Observable 8 | import retrofit2.http.GET 9 | import retrofit2.http.Path 10 | 11 | /** 12 | * @description: Api 接口 13 | * @author JayGengi 14 | * @date 2018/11/13 0013 下午 2:01 15 | * @email jaygengiii@gmail.com 16 | */ 17 | 18 | interface ApiService{ 19 | 20 | /** 21 | * 首页精选 22 | */ 23 | @GET(UrlConstant.BASE_URL+"data/福利/{limit}/{page}") 24 | fun getGirlsInfo(@Path("limit")limit:Int, 25 | @Path("page")page:Int): Observable 26 | 27 | /** 28 | * 获取福利数据信息 29 | */ 30 | @GET(UrlConstant.BASE_URL+"today") 31 | fun getToDayInfo(): Observable 32 | 33 | /** 34 | * 技术干货分类文章 35 | */ 36 | @GET(UrlConstant.BASE_URL+"search/query/listview/category/{key}/count/{limit}/page/{page} ") 37 | fun getCategoryInfo(@Path("key")key:String, 38 | @Path("limit")limit:Int, 39 | @Path("page")page:Int): Observable 40 | 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/api/UriConstant.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.api 2 | 3 | /** 4 | * @description: UrlConstant 5 | * @author JayGengi 6 | * @date 2018/11/13 0013 上午 10:28 7 | * @email jaygengiii@gmail.com 8 | */ 9 | object UrlConstant{ 10 | 11 | const val BASE_URL = "http://gank.io/api/" 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.base 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import android.util.Log 6 | import android.view.View 7 | import android.view.Window 8 | import android.view.WindowManager 9 | import android.widget.Toast 10 | import com.classic.common.MultipleStatusView 11 | import com.jaygengi.gank.MyApplication 12 | import com.qmuiteam.qmui.util.QMUIStatusBarHelper 13 | import io.reactivex.annotations.NonNull 14 | 15 | 16 | /** 17 | * @description: BaseActivity基类 18 | * @author JayGengi 19 | * @date 2018/10/29 0029 上午 11:57 20 | * @email jaygengiii@gmail.com 21 | */ 22 | abstract class BaseActivity : AppCompatActivity() { 23 | /** 24 | * 多种状态的 View 的切换 25 | */ 26 | protected var mLayoutStatusView: MultipleStatusView? = null 27 | 28 | override fun onCreate(savedInstanceState: Bundle?) { 29 | super.onCreate(savedInstanceState) 30 | QMUIStatusBarHelper.translucent(this) // 沉浸式状态栏 31 | QMUIStatusBarHelper.setStatusBarLightMode(this) 32 | setContentView(layoutId()) 33 | initData() 34 | initView() 35 | start() 36 | initListener() 37 | 38 | 39 | } 40 | 41 | private fun initListener() { 42 | mLayoutStatusView?.setOnClickListener(mRetryClickListener) 43 | } 44 | 45 | open val mRetryClickListener: View.OnClickListener = View.OnClickListener { 46 | start() 47 | } 48 | 49 | 50 | /** 51 | * 加载布局 52 | */ 53 | abstract fun layoutId(): Int 54 | 55 | /** 56 | * 初始化数据 57 | */ 58 | abstract fun initData() 59 | 60 | /** 61 | * 初始化 View 62 | */ 63 | abstract fun initView() 64 | 65 | /** 66 | * 开始请求 67 | */ 68 | abstract fun start() 69 | 70 | override fun onDestroy() { 71 | super.onDestroy() 72 | MyApplication.getRefWatcher(this)?.watch(this) 73 | } 74 | 75 | } 76 | 77 | 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/base/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.base 2 | 3 | import android.os.Bundle 4 | import android.support.annotation.LayoutRes 5 | import android.support.v4.app.Fragment 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import com.classic.common.MultipleStatusView 10 | import com.jaygengi.gank.MyApplication 11 | 12 | /** 13 | * @description: Fragment基类 14 | * @author JayGengi 15 | * @date 2018/11/13 0013 上午 10:35 16 | * @email jaygengiii@gmail.com 17 | */ 18 | 19 | abstract class BaseFragment: Fragment(){ 20 | /** 21 | * 当前页数 22 | */ 23 | var CURRENT_PAGE = 1 24 | /** 25 | * 每页容量- 每页有多少条记录 26 | */ 27 | val PAGE_CAPACITY = 10 28 | /** 29 | * 视图是否加载完毕 30 | */ 31 | private var isViewPrepare = false 32 | /** 33 | * 数据是否加载过了 34 | */ 35 | private var hasLoadData = false 36 | /** 37 | * 多种状态的 View 的切换 38 | */ 39 | protected var mLayoutStatusView: MultipleStatusView? = null 40 | 41 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 42 | return inflater.inflate(getLayoutId(),null) 43 | } 44 | 45 | 46 | 47 | override fun setUserVisibleHint(isVisibleToUser: Boolean) { 48 | super.setUserVisibleHint(isVisibleToUser) 49 | if (isVisibleToUser) { 50 | lazyLoadDataIfPrepared() 51 | } 52 | } 53 | 54 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 55 | super.onViewCreated(view, savedInstanceState) 56 | isViewPrepare = true 57 | initView() 58 | lazyLoadDataIfPrepared() 59 | //多种状态切换的view 重试点击事件 60 | mLayoutStatusView?.setOnClickListener(mRetryClickListener) 61 | } 62 | 63 | private fun lazyLoadDataIfPrepared() { 64 | if (userVisibleHint && isViewPrepare && !hasLoadData) { 65 | lazyLoad() 66 | hasLoadData = true 67 | } 68 | } 69 | 70 | open val mRetryClickListener: View.OnClickListener = View.OnClickListener { 71 | lazyLoad() 72 | } 73 | 74 | 75 | /** 76 | * 加载布局 77 | */ 78 | @LayoutRes 79 | abstract fun getLayoutId():Int 80 | 81 | /** 82 | * 初始化 ViewI 83 | */ 84 | abstract fun initView() 85 | 86 | /** 87 | * 懒加载 88 | */ 89 | abstract fun lazyLoad() 90 | 91 | override fun onDestroy() { 92 | super.onDestroy() 93 | activity?.let { MyApplication.getRefWatcher(it)?.watch(activity) } 94 | } 95 | 96 | 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/base/BasePresenter.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.base 2 | 3 | import io.reactivex.disposables.CompositeDisposable 4 | import io.reactivex.disposables.Disposable 5 | 6 | 7 | 8 | /** 9 | * Created by xuhao on 2017/11/16. 10 | * 11 | */ 12 | open class BasePresenter : IPresenter { 13 | 14 | var mRootView: T? = null 15 | private set 16 | 17 | private var compositeDisposable = CompositeDisposable() 18 | 19 | 20 | override fun attachView(mRootView: T) { 21 | this.mRootView = mRootView 22 | } 23 | 24 | override fun detachView() { 25 | mRootView = null 26 | 27 | //保证activity结束时取消所有正在执行的订阅 28 | if (!compositeDisposable.isDisposed) { 29 | compositeDisposable.clear() 30 | } 31 | 32 | } 33 | 34 | private val isViewAttached: Boolean 35 | get() = mRootView != null 36 | 37 | fun checkViewAttached() { 38 | if (!isViewAttached) throw MvpViewNotAttachedException() 39 | } 40 | 41 | fun addSubscription(disposable: Disposable) { 42 | compositeDisposable.add(disposable) 43 | } 44 | 45 | private class MvpViewNotAttachedException internal constructor() : RuntimeException("Please call IPresenter.attachView(IBaseView) before" + " requesting data to the IPresenter") 46 | 47 | 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/base/IBaseView.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.base 2 | 3 | /** 4 | * @description: View基类 5 | * @author JayGengi 6 | * @date 2018/11/13 0013 上午 10:35 7 | * @email jaygengiii@gmail.com 8 | */ 9 | interface IBaseView { 10 | 11 | fun showLoading() 12 | 13 | fun dismissLoading() 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/base/IPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.base 2 | 3 | 4 | 5 | /** 6 | * @description: Presenterj基类 7 | * @author JayGengi 8 | * @date 2018/11/13 0013 上午 10:35 9 | * @email jaygengiii@gmail.com 10 | */ 11 | 12 | 13 | interface IPresenter { 14 | 15 | fun attachView(mRootView: V) 16 | 17 | fun detachView() 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/mvp/contract/CategoryContract.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.mvp.contract 2 | 3 | import com.jaygengi.gank.base.IBaseView 4 | import com.jaygengi.gank.base.IPresenter 5 | import com.jaygengi.gank.mvp.model.bean.CategoryEntity 6 | 7 | /** 8 | * @description: 契约类 9 | * @author JayGengi 10 | * @date 2018/11/13 0013 上午 10:35 11 | * @email jaygengiii@gmail.com 12 | */ 13 | 14 | interface CategoryContract { 15 | 16 | interface View : IBaseView { 17 | 18 | /** 19 | * 获取最新一天的干货 20 | */ 21 | fun getCategoryInfo(todayInfo: CategoryEntity) 22 | /** 23 | * 显示错误信息 24 | */ 25 | fun showError(msg: String,errorCode:Int) 26 | 27 | 28 | } 29 | 30 | interface Presenter : IPresenter { 31 | 32 | /** 33 | * 获取最新一天的干货 34 | */ 35 | fun requestCategoryInfo(key:String,limit: Int,page: Int) 36 | 37 | 38 | } 39 | 40 | 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/mvp/contract/GirlsContract.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.mvp.contract 2 | 3 | import com.jaygengi.gank.base.IBaseView 4 | import com.jaygengi.gank.base.IPresenter 5 | import com.jaygengi.gank.mvp.model.bean.GirlsEntity 6 | import com.jaygengi.gank.mvp.model.bean.ToDayEntity 7 | import com.jaygengi.gank.net.BaseResponse 8 | 9 | /** 10 | * @description: 契约类 11 | * @author JayGengi 12 | * @date 2018/11/13 0013 上午 10:35 13 | * @email jaygengiii@gmail.com 14 | */ 15 | 16 | interface GirlsContract { 17 | 18 | interface View : IBaseView { 19 | 20 | /** 21 | * 显示福利轮播图 22 | */ 23 | fun showGirlInfo(dataInfo: GirlsEntity) 24 | 25 | /** 26 | * 显示错误信息 27 | */ 28 | fun showError(msg: String,errorCode:Int) 29 | 30 | } 31 | 32 | interface Presenter : IPresenter { 33 | 34 | /** 35 | * 获取福利轮播图 36 | */ 37 | fun requestGirlInfo(limit: Int,page: Int) 38 | 39 | } 40 | 41 | 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/mvp/contract/HomeContract.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.mvp.contract 2 | 3 | import com.jaygengi.gank.base.IBaseView 4 | import com.jaygengi.gank.base.IPresenter 5 | import com.jaygengi.gank.mvp.model.bean.GirlsEntity 6 | import com.jaygengi.gank.mvp.model.bean.ToDayEntity 7 | import com.jaygengi.gank.net.BaseResponse 8 | 9 | /** 10 | * @description: 契约类 11 | * @author JayGengi 12 | * @date 2018/11/13 0013 上午 10:35 13 | * @email jaygengiii@gmail.com 14 | */ 15 | 16 | interface HomeContract { 17 | 18 | interface View : IBaseView { 19 | /** 20 | * 显示福利轮播图 21 | */ 22 | fun showGirlInfo(dataInfo: GirlsEntity) 23 | /** 24 | * 获取最新一天的干货 25 | */ 26 | fun showToDayInfo(todayInfo: ToDayEntity) 27 | /** 28 | * 显示错误信息 29 | */ 30 | fun showError(msg: String,errorCode:Int) 31 | 32 | } 33 | 34 | interface Presenter : IPresenter { 35 | 36 | /** 37 | * 获取福利轮播图 38 | */ 39 | fun requestGirlInfo() 40 | /** 41 | * 获取最新一天的干货 42 | */ 43 | fun requestToDayInfo() 44 | } 45 | 46 | 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/mvp/model/CategoryModel.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.mvp.model 2 | 3 | import com.jaygengi.gank.mvp.model.bean.CategoryEntity 4 | import com.jaygengi.gank.net.RetrofitManager 5 | import com.jaygengi.gank.scheduler.SchedulerUtils 6 | import io.reactivex.Observable 7 | 8 | /** 9 | * @description: 获取最新一天的干货数据模型 10 | * @author JayGengi 11 | * @date 2018/11/14 0014 下午 4:33 12 | * @email jaygengiii@gmail.com 13 | */ 14 | class CategoryModel { 15 | 16 | 17 | /** 18 | * 获取福利数据信息 19 | */ 20 | fun getCategoryInfo(key:String,limit: Int,page: Int): Observable { 21 | return RetrofitManager.service.getCategoryInfo(key,limit,page) 22 | .compose(SchedulerUtils.ioToMain()) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/mvp/model/GirlsModel.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.mvp.model 2 | 3 | import com.jaygengi.gank.mvp.model.bean.GirlsEntity 4 | import com.jaygengi.gank.net.BaseResponse 5 | import com.jaygengi.gank.net.RetrofitManager 6 | import com.jaygengi.gank.scheduler.SchedulerUtils 7 | import io.reactivex.Observable 8 | 9 | /** 10 | * @description: 福利数据模型 11 | * @author JayGengi 12 | * @date 2018/11/13 0013 下午 3:35 13 | * @email jaygengiii@gmail.com 14 | */ 15 | class GirlsModel { 16 | 17 | 18 | /** 19 | * 获取福利数据信息 20 | */ 21 | fun getGirlsInfo(limit: Int,page: Int): Observable { 22 | return RetrofitManager.service.getGirlsInfo(limit,page) 23 | .compose(SchedulerUtils.ioToMain()) 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/mvp/model/Main.java: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.mvp.model; 2 | 3 | public class Main { 4 | } 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/mvp/model/ToDayModel.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.mvp.model 2 | 3 | import com.jaygengi.gank.mvp.model.bean.ToDayEntity 4 | import com.jaygengi.gank.net.RetrofitManager 5 | import com.jaygengi.gank.scheduler.SchedulerUtils 6 | import io.reactivex.Observable 7 | 8 | /** 9 | * @description: 获取最新一天的干货数据模型 10 | * @author JayGengi 11 | * @date 2018/11/14 0014 下午 4:33 12 | * @email jaygengiii@gmail.com 13 | */ 14 | class ToDayModel { 15 | 16 | 17 | /** 18 | * 获取福利数据信息 19 | */ 20 | fun getToDayInfo(): Observable { 21 | return RetrofitManager.service.getToDayInfo() 22 | .compose(SchedulerUtils.ioToMain()) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/mvp/model/bean/CategoryEntity.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.mvp.model.bean 2 | 3 | class CategoryEntity { 4 | 5 | 6 | /** 7 | * count : 10 8 | * error : false 9 | * results : [{"desc":"The Best DevOps Tools on OSX","ganhuo_id":"56cc6d22421aa95caa70793a","publishedAt":"2016-02-26T11:58:00.331000","readability":"","type":"iOS","url":"https://dzone.com/articles/the-best-devops-tools-on-osx","who":"Andrew Liu"},{"desc":"加载网络图片第三方库ssloadURLImage","ganhuo_id":"56cc6d23421aa95caa707951","publishedAt":"2015-10-21T02:57:40.909000","readability":"","type":"iOS","url":"https://github.com/shareJOBS/ssloadURLImage","who":"Andrew Liu"},{"desc":"强制修改 iOS 状态栏颜色","ganhuo_id":"56cc6d23421aa95caa70795b","publishedAt":"2015-10-22T02:06:07.739000","readability":"","type":"iOS","url":"http://www.jianshu.com/p/9d6b6f790493","who":"Andrew Liu"},{"desc":"Thoughts on Functional Programming in Swift","ganhuo_id":"56cc6d23421aa95caa7079a4","publishedAt":"2015-11-16T03:55:17.189000","readability":"","type":"iOS","url":"http://natashatherobot.com/functional-programming-in-swift/#","who":"CallMeWhy"},{"desc":"Swift Scripting By Example","ganhuo_id":"56cc6d23421aa95caa7079ba","publishedAt":"2015-11-20T03:54:49.808000","readability":"","type":"iOS","url":"http://swift.ayaka.me/posts/2015/11/5/swift-scripting-generating-acknowledgements-for-cocoapods-and-carthage-dependencies","who":"CallMeWhy"},{"desc":"通过 RAC 的 RACSignal 将 iOS 的任务转变为后台任务","ganhuo_id":"56cc6d23421aa95caa7079c7","publishedAt":"2015-05-18T03:52:44.016000","readability":"","type":"iOS","url":"http://spin.atomicobject.com/2015/05/14/ios-background-task-reactivecocoa/#.VVSask7KdL8.hackernews","who":"CallMeWhy"},{"desc":"iOS 富文本控件","ganhuo_id":"56cc6d23421aa95caa7079bf","publishedAt":"2015-11-18T05:17:57.241000","readability":"","type":"iOS","url":"https://github.com/ibireme/YYText","who":"mthli"},{"desc":"XCPlayground,是时候展现 Playground 的真正实力了。","ganhuo_id":"56cc6d23421aa95caa7079d3","publishedAt":"2016-03-03T12:12:56.684000","readability":"","type":"iOS","url":"http://nshipster.com/xcplayground/?utm_campaign=This%2BWeek%2Bin%2BSwift&utm_medium=web&utm_source=This_Week_in_Swift_39","who":"CallMeWhy"},{"desc":"AFNetworking 安全bug的回复","ganhuo_id":"56cc6d23421aa95caa7079d8","publishedAt":"2015-05-27T04:21:13.831000","readability":"","type":"iOS","url":"http://www.yming9.com/?p=579","who":"Andrew Liu"},{"desc":"PaintCode教程 译自Ranwenderlich:http://www.raywenderlich.com/100281/paintcode-for-designers-getting-started","ganhuo_id":"56cc6d23421aa95caa7079d9","publishedAt":"2016-03-31T11:44:55.091000","readability":"","type":"iOS","url":"http://www.jianshu.com/p/5e75408812df","who":"Andrew Liu"}] 10 | */ 11 | 12 | var count: Int = 0 13 | var isError: Boolean = false 14 | var results: List? = null 15 | 16 | class ResultsBean { 17 | /** 18 | * desc : The Best DevOps Tools on OSX 19 | * ganhuo_id : 56cc6d22421aa95caa70793a 20 | * publishedAt : 2016-02-26T11:58:00.331000 21 | * readability : 22 | * type : iOS 23 | * url : https://dzone.com/articles/the-best-devops-tools-on-osx 24 | * who : Andrew Liu 25 | */ 26 | 27 | var desc: String? = null 28 | var ganhuo_id: String? = null 29 | var publishedAt: String? = null 30 | var readability: String? = null 31 | var type: String? = null 32 | var url: String? = null 33 | var who: String? = null 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/mvp/model/bean/GirlsEntity.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.mvp.model.bean 2 | 3 | /** 4 | * @description: 福利 Entity 5 | * @author JayGengi 6 | * @date 2018/11/13 0013 下午 3:35 7 | * @email jaygengiii@gmail.com 8 | */ 9 | class GirlsEntity { 10 | /** 11 | * error : false 12 | * results : [{"_id":"5be14edb9d21223dd50660f8","createdAt":"2018-11-06T08:20:43.656Z","desc":"2018-11-06","publishedAt":"2018-11-06T00:00:00.0Z","source":"web","type":"福利","url":"https://ws1.sinaimg.cn/large/0065oQSqgy1fwyf0wr8hhj30ie0nhq6p.jpg","used":true,"who":"lijinshanmx"},{"_id":"5bcd71979d21220315c663fc","createdAt":"2018-10-22T06:43:35.440Z","desc":"2018-10-22","publishedAt":"2018-10-22T00:00:00.0Z","source":"web","type":"福利","url":"https://ws1.sinaimg.cn/large/0065oQSqgy1fwgzx8n1syj30sg15h7ew.jpg","used":true,"who":"lijinshanmx"},{"_id":"5bc434ac9d212279160c4c9e","createdAt":"2018-10-15T06:33:16.497Z","desc":"2018-10-15","publishedAt":"2018-10-15T00:00:00.0Z","source":"web","type":"福利","url":"https://ws1.sinaimg.cn/large/0065oQSqly1fw8wzdua6rj30sg0yc7gp.jpg","used":true,"who":"lijinshanmx"},{"_id":"5bbb0de09d21226111b86f1c","createdAt":"2018-10-08T07:57:20.978Z","desc":"2018-10-08","publishedAt":"2018-10-08T00:00:00.0Z","source":"web","type":"福利","url":"https://ws1.sinaimg.cn/large/0065oQSqly1fw0vdlg6xcj30j60mzdk7.jpg","used":true,"who":"lijinshanmx"},{"_id":"5ba206ec9d2122610aba3440","createdAt":"2018-09-19T08:21:00.295Z","desc":"2018-09-19","publishedAt":"2018-09-19T00:00:00.0Z","source":"web","type":"福利","url":"https://ws1.sinaimg.cn/large/0065oQSqly1fvexaq313uj30qo0wldr4.jpg","used":true,"who":"lijinshanmx"}] 13 | */ 14 | 15 | var isError: Boolean = false 16 | var results: List? = null 17 | 18 | class ResultsBean { 19 | /** 20 | * _id : 5be14edb9d21223dd50660f8 21 | * createdAt : 2018-11-06T08:20:43.656Z 22 | * desc : 2018-11-06 23 | * publishedAt : 2018-11-06T00:00:00.0Z 24 | * source : web 25 | * type : 福利 26 | * url : https://ws1.sinaimg.cn/large/0065oQSqgy1fwyf0wr8hhj30ie0nhq6p.jpg 27 | * used : true 28 | * who : lijinshanmx 29 | */ 30 | 31 | var _id: String? = null 32 | var createdAt: String? = null 33 | var desc: String? = null 34 | var publishedAt: String? = null 35 | var source: String? = null 36 | var type: String? = null 37 | var url: String? = null 38 | var isUsed: Boolean = false 39 | var who: String? = null 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/mvp/model/bean/MySectionEntity.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.mvp.model.bean 2 | 3 | import com.chad.library.adapter.base.entity.SectionEntity 4 | 5 | /** 6 | * @description: 分组布局 实体类必须继承SectionEntity 7 | * @author JayGengi 8 | * @date 2018/11/14 0014 下午 4:53 9 | * @email jaygengiii@gmail.com 10 | */ 11 | class MySectionEntity : SectionEntity { 12 | private val isMore: Boolean = false 13 | 14 | constructor(isHeader: Boolean, header: String) : super(isHeader, header) {} 15 | 16 | constructor(t: ToDayEntity) : super(t) {} 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/mvp/model/bean/TabEntity.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.mvp.model.bean 2 | 3 | import com.flyco.tablayout.listener.CustomTabEntity 4 | 5 | 6 | 7 | /** 8 | * @description: 首页tab entity 9 | * @author JayGengi 10 | * @date 2018/11/13 0013 上午 10:47 11 | * @email jaygengiii@gmail.com 12 | */ 13 | class TabEntity(var title: String, private var selectedIcon: Int, private var unSelectedIcon: Int) : CustomTabEntity { 14 | 15 | override fun getTabTitle(): String { 16 | return title 17 | } 18 | 19 | override fun getTabSelectedIcon(): Int { 20 | return selectedIcon 21 | } 22 | 23 | override fun getTabUnselectedIcon(): Int { 24 | return unSelectedIcon 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/mvp/model/bean/ToDayEntity.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.mvp.model.bean 2 | 3 | /** 4 | * @description: 获取最新一天的干货 5 | * @author JayGengi 6 | * @date 2018/11/14 0014 下午 4:33 7 | * @email jaygengiii@gmail.com 8 | */ 9 | class ToDayEntity { 10 | 11 | var isError: Boolean = false 12 | var results: ResultsBean? = null 13 | var category: List? = null 14 | 15 | class ResultsBean { 16 | var Android: List? = null 17 | var App: List? = null 18 | var iOS: List? = null 19 | var 休息视频: List? = null 20 | var 前端: List? = null 21 | var 拓展资源: List? = null 22 | var 瞎推荐: List? = null 23 | var 福利: List? = null 24 | class CategoryBean { 25 | /** 26 | * _id : 5bc49bb99d2122791c972ca9 27 | * createdAt : 2018-10-15T13:52:57.103Z 28 | * desc : 新版youtube视频效果 29 | * images : ["https://ww1.sinaimg.cn/large/0073sXn7gy1fwyf8dcpt0g308w0fse83","https://ww1.sinaimg.cn/large/0073sXn7gy1fwyf8gpdc4g308w0fsnpg"] 30 | * publishedAt : 2018-11-06T00:00:00.0Z 31 | * source : web 32 | * type : Android 33 | * url : https://github.com/moyokoo/YoutubeVideoSample 34 | * used : true 35 | * who : miaoyj 36 | */ 37 | var _id: String? = null 38 | var createdAt: String? = null 39 | var desc: String? = null 40 | var publishedAt: String? = null 41 | var source: String? = null 42 | var type: String? = null 43 | var url: String? = null 44 | var isUsed: Boolean = false 45 | var who: String? = null 46 | var images: List? = null 47 | } 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/mvp/presenter/CategoryPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.mvp.presenter 2 | 3 | import com.jaygengi.gank.base.BasePresenter 4 | import com.jaygengi.gank.mvp.contract.CategoryContract 5 | import com.jaygengi.gank.mvp.model.CategoryModel 6 | import com.jaygengi.gank.mvp.model.ToDayModel 7 | import com.jaygengi.gank.net.exception.ExceptionHandle 8 | 9 | 10 | /** 11 | * Created by xuhao on 2017/11/8. 12 | * 首页精选的 Presenter 13 | * (数据是 Banner 数据和一页数据组合而成的 HomeBean,查看接口然后在分析就明白了) 14 | */ 15 | 16 | class CategoryPresenter : BasePresenter(), CategoryContract.Presenter { 17 | 18 | 19 | private val categoryModel: CategoryModel by lazy { 20 | 21 | CategoryModel() 22 | } 23 | 24 | override fun requestCategoryInfo(key:String,limit: Int,page: Int) { 25 | checkViewAttached() 26 | mRootView?.showLoading() 27 | val disposable = categoryModel.getCategoryInfo(key,limit,page) 28 | .subscribe({ categoryList -> 29 | mRootView?.apply { 30 | dismissLoading() 31 | if(!categoryList.isError){ 32 | getCategoryInfo(categoryList) 33 | }else{ 34 | showError(ExceptionHandle.errorMsg, ExceptionHandle.errorCode) 35 | } 36 | } 37 | }, { t -> 38 | mRootView?.apply { 39 | //处理异常 40 | showError(ExceptionHandle.handleException(t), ExceptionHandle.errorCode) 41 | } 42 | 43 | }) 44 | 45 | addSubscription(disposable) 46 | } 47 | 48 | 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/mvp/presenter/GirlsPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.mvp.presenter 2 | 3 | import com.jaygengi.gank.base.BasePresenter 4 | import com.jaygengi.gank.mvp.contract.GirlsContract 5 | import com.jaygengi.gank.mvp.model.GirlsModel 6 | import com.jaygengi.gank.net.exception.ExceptionHandle 7 | 8 | 9 | /** 10 | * Created by xuhao on 2017/11/8. 11 | * 首页精选的 Presenter 12 | * (数据是 Banner 数据和一页数据组合而成的 HomeBean,查看接口然后在分析就明白了) 13 | */ 14 | 15 | class GirlsPresenter : BasePresenter(), GirlsContract.Presenter { 16 | 17 | 18 | 19 | private val girlsModel: GirlsModel by lazy { 20 | 21 | GirlsModel() 22 | } 23 | 24 | override fun requestGirlInfo(limit: Int,page: Int) { 25 | checkViewAttached() 26 | mRootView?.showLoading() 27 | val disposable = girlsModel.getGirlsInfo(limit,page) 28 | .subscribe({ girlsList -> 29 | mRootView?.apply { 30 | dismissLoading() 31 | if(!girlsList.isError){ 32 | showGirlInfo(girlsList) 33 | }else{ 34 | showError(ExceptionHandle.errorMsg,ExceptionHandle.errorCode) 35 | } 36 | } 37 | }, { t -> 38 | mRootView?.apply { 39 | //处理异常 40 | showError(ExceptionHandle.handleException(t),ExceptionHandle.errorCode) 41 | } 42 | 43 | }) 44 | 45 | addSubscription(disposable) 46 | } 47 | 48 | 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/mvp/presenter/HomePresenter.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.mvp.presenter 2 | 3 | import com.jaygengi.gank.base.BasePresenter 4 | import com.jaygengi.gank.mvp.contract.HomeContract 5 | import com.jaygengi.gank.mvp.model.GirlsModel 6 | import com.jaygengi.gank.mvp.model.ToDayModel 7 | import com.jaygengi.gank.net.exception.ExceptionHandle 8 | 9 | 10 | /** 11 | * @description: 最新一天的干货 12 | * @author JayGengi 13 | * @date 2018/11/16 0016 上午 11:29 14 | * @email jaygengiii@gmail.com 15 | */ 16 | 17 | class HomePresenter : BasePresenter(), HomeContract.Presenter { 18 | 19 | private val girlsModel: GirlsModel by lazy { 20 | 21 | GirlsModel() 22 | } 23 | private val todayModel: ToDayModel by lazy { 24 | 25 | ToDayModel() 26 | } 27 | override fun requestGirlInfo() { 28 | checkViewAttached() 29 | mRootView?.showLoading() 30 | val disposable = girlsModel.getGirlsInfo(1,1) 31 | .subscribe({ girlsList -> 32 | mRootView?.apply { 33 | dismissLoading() 34 | if(!girlsList.isError){ 35 | showGirlInfo(girlsList) 36 | }else{ 37 | showError(ExceptionHandle.errorMsg,ExceptionHandle.errorCode) 38 | } 39 | } 40 | }, { t -> 41 | mRootView?.apply { 42 | //处理异常 43 | showError(ExceptionHandle.handleException(t),ExceptionHandle.errorCode) 44 | } 45 | 46 | }) 47 | 48 | addSubscription(disposable) 49 | } 50 | override fun requestToDayInfo() { 51 | checkViewAttached() 52 | mRootView?.showLoading() 53 | val disposable = todayModel.getToDayInfo() 54 | .subscribe({ todayList -> 55 | mRootView?.apply { 56 | dismissLoading() 57 | if(!todayList.isError){ 58 | showToDayInfo(todayList) 59 | }else{ 60 | showError(ExceptionHandle.errorMsg,ExceptionHandle.errorCode) 61 | } 62 | } 63 | }, { t -> 64 | mRootView?.apply { 65 | //处理异常 66 | showError(ExceptionHandle.handleException(t),ExceptionHandle.errorCode) 67 | } 68 | 69 | }) 70 | 71 | addSubscription(disposable) 72 | } 73 | 74 | 75 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/net/BaseResponse.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.net 2 | 3 | /** 4 | * @description: 封装返回的数据 5 | * @author JayGengi 6 | * @date 2018/11/13 0013 下午 3:31 7 | * @email jaygengiii@gmail.com 8 | */ 9 | class BaseResponse(val error:Boolean, 10 | val results:T) -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/net/RetrofitManager.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.net 2 | 3 | import com.jaygengi.gank.MyApplication 4 | import com.jaygengi.gank.api.ApiService 5 | import com.jaygengi.gank.api.UrlConstant 6 | import com.jaygengi.gank.utils.NetworkUtil 7 | import com.jaygengi.gank.utils.Preference 8 | import okhttp3.* 9 | import okhttp3.logging.HttpLoggingInterceptor 10 | import retrofit2.Retrofit 11 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory 12 | import retrofit2.converter.gson.GsonConverterFactory 13 | import java.io.File 14 | import java.util.concurrent.TimeUnit 15 | 16 | /** 17 | * @description: RetrofitManager 18 | * @author JayGengi 19 | * @date 2018/11/13 0013 下午 2:03 20 | * @email jaygengiii@gmail.com 21 | */ 22 | 23 | object RetrofitManager{ 24 | 25 | val service: ApiService by lazy (LazyThreadSafetyMode.SYNCHRONIZED){ 26 | getRetrofit().create(ApiService::class.java) 27 | } 28 | 29 | private var token:String by Preference("token","") 30 | 31 | /** 32 | * 设置公共参数 33 | */ 34 | private fun addQueryParameterInterceptor(): Interceptor { 35 | return Interceptor { chain -> 36 | val originalRequest = chain.request() 37 | val request: Request 38 | val modifiedUrl = originalRequest.url().newBuilder() 39 | .build() 40 | request = originalRequest.newBuilder().url(modifiedUrl).build() 41 | chain.proceed(request) 42 | } 43 | } 44 | 45 | /** 46 | * 设置头 47 | */ 48 | private fun addHeaderInterceptor(): Interceptor { 49 | return Interceptor { chain -> 50 | val originalRequest = chain.request() 51 | val requestBuilder = originalRequest.newBuilder() 52 | // Provide your custom header here 53 | .header("token", token) 54 | .method(originalRequest.method(), originalRequest.body()) 55 | val request = requestBuilder.build() 56 | chain.proceed(request) 57 | } 58 | } 59 | 60 | /** 61 | * 设置缓存 62 | */ 63 | private fun addCacheInterceptor(): Interceptor { 64 | return Interceptor { chain -> 65 | var request = chain.request() 66 | if (!NetworkUtil.isNetworkAvailable(MyApplication.context)) { 67 | request = request.newBuilder() 68 | .cacheControl(CacheControl.FORCE_CACHE) 69 | .build() 70 | } 71 | val response = chain.proceed(request) 72 | if (NetworkUtil.isNetworkAvailable(MyApplication.context)) { 73 | val maxAge = 0 74 | // 有网络时 设置缓存超时时间0个小时 ,意思就是不读取缓存数据,只对get有用,post没有缓冲 75 | response.newBuilder() 76 | .header("Cache-Control", "public, max-age=" + maxAge) 77 | .removeHeader("Retrofit")// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效 78 | .build() 79 | } else { 80 | // 无网络时,设置超时为4周 只对get有用,post没有缓冲 81 | val maxStale = 60 * 60 * 24 * 28 82 | response.newBuilder() 83 | .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale) 84 | .removeHeader("nyn") 85 | .build() 86 | } 87 | response 88 | } 89 | } 90 | 91 | private fun getRetrofit(): Retrofit { 92 | // 获取retrofit的实例 93 | return Retrofit.Builder() 94 | .baseUrl(UrlConstant.BASE_URL) //自己配置 95 | .client(getOkHttpClient()) 96 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 97 | .addConverterFactory(GsonConverterFactory.create()) 98 | .build() 99 | 100 | } 101 | 102 | private fun getOkHttpClient(): OkHttpClient { 103 | //添加一个log拦截器,打印所有的log 104 | val httpLoggingInterceptor = HttpLoggingInterceptor() 105 | //可以设置请求过滤的水平,body,basic,headers 106 | httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY 107 | 108 | //设置 请求的缓存的大小跟位置 109 | val cacheFile = File(MyApplication.context.cacheDir, "cache") 110 | val cache = Cache(cacheFile, 1024 * 1024 * 50) //50Mb 缓存的大小 111 | 112 | return OkHttpClient.Builder() 113 | .addInterceptor(addQueryParameterInterceptor()) //参数添加 114 | .addInterceptor(addHeaderInterceptor()) // token过滤 115 | // .addInterceptor(addCacheInterceptor()) 116 | .addInterceptor(httpLoggingInterceptor) //日志,所有的请求响应度看到 117 | .cache(cache) //添加缓存 118 | .connectTimeout(60L, TimeUnit.SECONDS) 119 | .readTimeout(60L, TimeUnit.SECONDS) 120 | .writeTimeout(60L, TimeUnit.SECONDS) 121 | .build() 122 | } 123 | 124 | 125 | } 126 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/net/exception/ApiException.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.net.exception 2 | 3 | /** 4 | * Created by xuhao on 2017/12/5. 5 | * desc: 6 | */ 7 | class ApiException : RuntimeException { 8 | 9 | private var code: Int? = null 10 | 11 | 12 | constructor(throwable: Throwable, code: Int) : super(throwable) { 13 | this.code = code 14 | } 15 | 16 | constructor(message: String) : super(Throwable(message)) 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/net/exception/ErrorStatus.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.net.exception 2 | 3 | /** 4 | * Created by xuhao on 2017/12/5. 5 | * desc: 6 | */ 7 | object ErrorStatus { 8 | /** 9 | * 响应成功 10 | */ 11 | @JvmField 12 | val SUCCESS = 0 13 | 14 | /** 15 | * 未知错误 16 | */ 17 | @JvmField 18 | val UNKNOWN_ERROR = 1002 19 | 20 | /** 21 | * 服务器内部错误 22 | */ 23 | @JvmField 24 | val SERVER_ERROR = 1003 25 | 26 | /** 27 | * 网络连接超时 28 | */ 29 | @JvmField 30 | val NETWORK_ERROR = 1004 31 | 32 | /** 33 | * API解析异常(或者第三方数据结构更改)等其他异常 34 | */ 35 | @JvmField 36 | val API_ERROR = 1005 37 | 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/net/exception/ExceptionHandle.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.net.exception 2 | 3 | import com.google.gson.JsonParseException 4 | import com.orhanobut.logger.Logger 5 | 6 | import org.json.JSONException 7 | 8 | import java.net.ConnectException 9 | 10 | import java.net.SocketTimeoutException 11 | import java.net.UnknownHostException 12 | import java.text.ParseException 13 | 14 | /** 15 | * Created by xuhao on 2017/12/5. 16 | * desc: 异常处理类 17 | */ 18 | 19 | class ExceptionHandle { 20 | 21 | 22 | companion object { 23 | var errorCode = ErrorStatus.UNKNOWN_ERROR 24 | var errorMsg = "请求失败,请稍后重试" 25 | 26 | fun handleException(e: Throwable): String { 27 | e.printStackTrace() 28 | if (e is SocketTimeoutException) {//网络超时 29 | Logger.e("TAG", "网络连接异常: " + e.message) 30 | errorMsg = "网络连接异常" 31 | errorCode = ErrorStatus.NETWORK_ERROR 32 | } else if (e is ConnectException) { //均视为网络错误 33 | Logger.e("TAG", "网络连接异常: " + e.message) 34 | errorMsg = "网络连接异常" 35 | errorCode = ErrorStatus.NETWORK_ERROR 36 | } else if (e is JsonParseException 37 | || e is JSONException 38 | || e is ParseException) { //均视为解析错误 39 | Logger.e("TAG", "数据解析异常: " + e.message) 40 | errorMsg = "数据解析异常" 41 | errorCode = ErrorStatus.SERVER_ERROR 42 | } else if (e is ApiException) {//服务器返回的错误信息 43 | errorMsg = e.message.toString() 44 | errorCode = ErrorStatus.SERVER_ERROR 45 | } else if (e is UnknownHostException) { 46 | Logger.e("TAG", "网络连接异常: " + e.message) 47 | errorMsg = "网络连接异常" 48 | errorCode = ErrorStatus.NETWORK_ERROR 49 | } else if (e is IllegalArgumentException) { 50 | errorMsg = "参数错误" 51 | errorCode = ErrorStatus.SERVER_ERROR 52 | } else {//未知错误 53 | try { 54 | Logger.e("TAG", "错误: " + e.message) 55 | } catch (e1: Exception) { 56 | Logger.e("TAG", "未知错误Debug调试 ") 57 | } 58 | 59 | errorMsg = "未知错误,可能抛锚了吧~" 60 | errorCode = ErrorStatus.UNKNOWN_ERROR 61 | } 62 | return errorMsg 63 | } 64 | 65 | } 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/scheduler/BaseScheduler.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.scheduler 2 | 3 | import io.reactivex.* 4 | import org.reactivestreams.Publisher 5 | 6 | /** 7 | * Created by xuhao on 2017/11/17. 8 | * desc:RxJava2.x 5中基础相应类型 9 | */ 10 | 11 | 12 | 13 | abstract class BaseScheduler protected constructor(private val subscribeOnScheduler: Scheduler, 14 | private val observeOnScheduler: Scheduler) : ObservableTransformer, 15 | SingleTransformer, 16 | MaybeTransformer, 17 | CompletableTransformer, 18 | FlowableTransformer { 19 | 20 | override fun apply(upstream: Completable): CompletableSource { 21 | return upstream.subscribeOn(subscribeOnScheduler) 22 | .observeOn(observeOnScheduler) 23 | } 24 | 25 | override fun apply(upstream: Flowable): Publisher { 26 | return upstream.subscribeOn(subscribeOnScheduler) 27 | .observeOn(observeOnScheduler) 28 | } 29 | 30 | override fun apply(upstream: Maybe): MaybeSource { 31 | return upstream.subscribeOn(subscribeOnScheduler) 32 | .observeOn(observeOnScheduler) 33 | } 34 | 35 | override fun apply(upstream: Observable): ObservableSource { 36 | return upstream.subscribeOn(subscribeOnScheduler) 37 | .observeOn(observeOnScheduler) 38 | } 39 | 40 | override fun apply(upstream: Single): SingleSource { 41 | return upstream.subscribeOn(subscribeOnScheduler) 42 | .observeOn(observeOnScheduler) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/scheduler/ComputationMainScheduler.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.scheduler 2 | 3 | import com.jaygengi.gank.scheduler.BaseScheduler 4 | import io.reactivex.android.schedulers.AndroidSchedulers 5 | import io.reactivex.schedulers.Schedulers 6 | /** 7 | * Created by xuhao on 2017/11/17. 8 | * desc: 9 | */ 10 | 11 | 12 | class ComputationMainScheduler private constructor() : BaseScheduler(Schedulers.computation(), AndroidSchedulers.mainThread()) 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/scheduler/IoMainScheduler.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.scheduler 2 | 3 | import com.jaygengi.gank.scheduler.BaseScheduler 4 | import io.reactivex.android.schedulers.AndroidSchedulers 5 | import io.reactivex.schedulers.Schedulers 6 | 7 | /** 8 | * Created by xuhao on 2017/11/17. 9 | * desc: 10 | */ 11 | class IoMainScheduler : BaseScheduler(Schedulers.io(), AndroidSchedulers.mainThread()) 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/scheduler/NewThreadMainScheduler.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.scheduler 2 | 3 | import com.jaygengi.gank.scheduler.BaseScheduler 4 | import io.reactivex.android.schedulers.AndroidSchedulers 5 | import io.reactivex.schedulers.Schedulers 6 | 7 | /** 8 | * Created by xuhao on 2017/11/17. 9 | * desc: 10 | */ 11 | 12 | 13 | class NewThreadMainScheduler private constructor() : BaseScheduler(Schedulers.newThread(), AndroidSchedulers.mainThread()) 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/scheduler/SchedulerUtils.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.scheduler 2 | 3 | /** 4 | * Created by xuhao on 2017/11/17. 5 | * desc: 6 | */ 7 | 8 | object SchedulerUtils { 9 | 10 | fun ioToMain(): IoMainScheduler { 11 | return IoMainScheduler() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/scheduler/SingleMainScheduler.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.scheduler 2 | 3 | import com.jaygengi.gank.scheduler.BaseScheduler 4 | import io.reactivex.android.schedulers.AndroidSchedulers 5 | import io.reactivex.schedulers.Schedulers 6 | 7 | /** 8 | * Created by xuhao on 2017/11/17. 9 | * desc: 10 | */ 11 | 12 | 13 | class SingleMainScheduler private constructor() : BaseScheduler(Schedulers.single(), AndroidSchedulers.mainThread()) 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/scheduler/TrampolineMainScheduler.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.scheduler 2 | 3 | import com.jaygengi.gank.scheduler.BaseScheduler 4 | import io.reactivex.android.schedulers.AndroidSchedulers 5 | import io.reactivex.schedulers.Schedulers 6 | 7 | /** 8 | * Created by xuhao on 2017/11/17. 9 | * desc: 10 | */ 11 | 12 | 13 | class TrampolineMainScheduler private constructor() : BaseScheduler(Schedulers.trampoline(), AndroidSchedulers.mainThread()) 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/ui/activity/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.ui.activity 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import android.support.v4.app.FragmentTransaction 6 | import android.view.KeyEvent 7 | import com.flyco.tablayout.listener.CustomTabEntity 8 | import com.flyco.tablayout.listener.OnTabSelectListener 9 | import com.jaygengi.gank.mvp.model.bean.TabEntity 10 | import com.jaygengi.gank.R 11 | import com.jaygengi.gank.base.BaseActivity 12 | import com.jaygengi.gank.showToast 13 | import com.jaygengi.gank.ui.fragment.* 14 | import kotlinx.android.synthetic.main.activity_main.* 15 | import java.util.* 16 | 17 | 18 | /** 19 | * @description: 首页 20 | * @author JayGengi 21 | * @date 2018/10/29 0029 上午 11:57 22 | * @email jaygengiii@gmail.com 23 | */ 24 | 25 | 26 | class MainActivity : BaseActivity() { 27 | 28 | 29 | private val mTitles = arrayOf("首页", "分类", "个人中心") 30 | 31 | // 未被选中的图标 32 | private val mIconUnSelectIds = intArrayOf(R.mipmap.ic_home_normal, R.mipmap.ic_classification_normal, R.mipmap.ic_mine_normal) 33 | // 被选中的图标 34 | private val mIconSelectIds = intArrayOf(R.mipmap.ic_home_selected, R.mipmap.ic_classification_selected, R.mipmap.ic_mine_selected) 35 | 36 | private val mTabEntities = ArrayList() 37 | 38 | private var mHomeFragment: HomeFragment? = null 39 | private var mHotFragment: GankTypeFragment? = null 40 | private var mMineFragment: MineFragment? = null 41 | 42 | //默认为0 43 | private var mIndex = 0 44 | 45 | 46 | override fun onCreate(savedInstanceState: Bundle?) { 47 | if (savedInstanceState != null) { 48 | mIndex = savedInstanceState.getInt("currTabIndex") 49 | } 50 | super.onCreate(savedInstanceState) 51 | initTab() 52 | tab_layout.currentTab = mIndex 53 | switchFragment(mIndex) 54 | 55 | } 56 | 57 | override fun layoutId(): Int { 58 | return R.layout.activity_main 59 | } 60 | 61 | 62 | //初始化底部菜单 63 | private fun initTab() { 64 | (0 until mTitles.size) 65 | .mapTo(mTabEntities) { TabEntity(mTitles[it], mIconSelectIds[it], mIconUnSelectIds[it]) } 66 | //为Tab赋值 67 | tab_layout.setTabData(mTabEntities) 68 | tab_layout.setOnTabSelectListener(object : OnTabSelectListener { 69 | override fun onTabSelect(position: Int) { 70 | //切换Fragment 71 | switchFragment(position) 72 | } 73 | 74 | override fun onTabReselect(position: Int) { 75 | 76 | } 77 | }) 78 | } 79 | 80 | /** 81 | * 切换Fragment 82 | * @param position 下标 83 | */ 84 | private fun switchFragment(position: Int) { 85 | val transaction = supportFragmentManager.beginTransaction() 86 | hideFragments(transaction) 87 | when (position) { 88 | 0 // 首页 89 | -> mHomeFragment?.let { 90 | transaction.show(it) 91 | } ?: HomeFragment.getInstance(mTitles[position]).let { 92 | mHomeFragment = it 93 | transaction.add(R.id.fl_container, it, "home") 94 | } 95 | 1 //热门 96 | -> mHotFragment?.let { 97 | transaction.show(it) 98 | } ?: GankTypeFragment.getInstance(mTitles[position]).let { 99 | mHotFragment = it 100 | transaction.add(R.id.fl_container, it, "hot") } 101 | 2 //我的 102 | -> mMineFragment?.let { 103 | transaction.show(it) 104 | } ?: MineFragment.getInstance(mTitles[position]).let { 105 | mMineFragment = it 106 | transaction.add(R.id.fl_container, it, "mine") } 107 | 108 | else -> { 109 | 110 | } 111 | } 112 | 113 | mIndex = position 114 | tab_layout.currentTab = mIndex 115 | transaction.commitAllowingStateLoss() 116 | } 117 | 118 | 119 | /** 120 | * 隐藏所有的Fragment 121 | * @param transaction transaction 122 | */ 123 | private fun hideFragments(transaction: FragmentTransaction) { 124 | mHomeFragment?.let { transaction.hide(it) } 125 | mHotFragment?.let { transaction.hide(it) } 126 | mMineFragment?.let { transaction.hide(it) } 127 | } 128 | 129 | 130 | @SuppressLint("MissingSuperCall") 131 | override fun onSaveInstanceState(outState: Bundle) { 132 | // showToast("onSaveInstanceState->"+mIndex) 133 | // super.onSaveInstanceState(outState) 134 | //记录fragment的位置,防止崩溃 activity被系统回收时,fragment错乱 135 | if (tab_layout != null) { 136 | outState.putInt("currTabIndex", mIndex) 137 | } 138 | } 139 | 140 | override fun initView() { 141 | 142 | } 143 | 144 | override fun initData() { 145 | 146 | } 147 | 148 | override fun start() { 149 | 150 | } 151 | 152 | private var mExitTime: Long = 0 153 | 154 | override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { 155 | if (keyCode == KeyEvent.KEYCODE_BACK) { 156 | if (System.currentTimeMillis().minus(mExitTime) <= 2000) { 157 | finish() 158 | } else { 159 | mExitTime = System.currentTimeMillis() 160 | showToast("再按一次退出程序") 161 | } 162 | return true 163 | } 164 | return super.onKeyDown(keyCode, event) 165 | } 166 | 167 | 168 | } 169 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/ui/activity/SearchActivity.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.ui.activity 2 | 3 | import android.annotation.SuppressLint 4 | import com.jaygengi.gank.R 5 | import com.jaygengi.gank.base.BaseActivity 6 | import kotlinx.android.synthetic.main.activity_search.* 7 | 8 | 9 | /** 10 | * @description: 首頁搜索頁 11 | * @author JayGengi 12 | * @date 2018/10/29 0029 上午 11:57 13 | * @email jaygengiii@gmail.com 14 | */ 15 | 16 | 17 | class SearchActivity : BaseActivity() { 18 | 19 | override fun layoutId(): Int { 20 | return R.layout.activity_search 21 | } 22 | 23 | @SuppressLint("ResourceAsColor") 24 | override fun initView() { 25 | topbar.setTitle("Gank Search").setTextColor(R.color.color_black) 26 | topbar.addLeftImageButton(R.drawable.ic_left,R.id.left).setOnClickListener { 27 | finish() 28 | } 29 | } 30 | 31 | override fun start() { 32 | } 33 | 34 | override fun initData() { 35 | } 36 | 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/ui/activity/SplashActivity.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.ui.activity 2 | 3 | import android.Manifest 4 | import android.annotation.SuppressLint 5 | import android.content.Intent 6 | import android.view.animation.AlphaAnimation 7 | import android.view.animation.Animation 8 | import android.view.animation.Animation.AnimationListener 9 | import com.jaygengi.gank.MyApplication 10 | import com.jaygengi.gank.R 11 | import com.jaygengi.gank.base.BaseActivity 12 | import com.jaygengi.gank.utils.AppUtils 13 | import kotlinx.android.synthetic.main.activity_splash.* 14 | 15 | 16 | /** 17 | * @description: 启动页 18 | * @author JayGengi 19 | * @date 2018/10/29 0029 上午 11:56 20 | * @email jaygengiii@gmail.com 21 | */ 22 | 23 | class SplashActivity : BaseActivity() { 24 | 25 | 26 | 27 | private var alphaAnimation:AlphaAnimation?=null 28 | 29 | 30 | override fun layoutId(): Int = R.layout.activity_splash 31 | 32 | override fun initData() { 33 | 34 | } 35 | 36 | @SuppressLint("SetTextI18n") 37 | override fun initView() { 38 | 39 | //渐变展示启动屏 40 | alphaAnimation= AlphaAnimation(0.3f, 1.0f) 41 | alphaAnimation?.duration = 2000 42 | alphaAnimation?.setAnimationListener(object : AnimationListener { 43 | override fun onAnimationEnd(arg0: Animation) { 44 | redirectTo() 45 | } 46 | override fun onAnimationRepeat(animation: Animation) {} 47 | 48 | override fun onAnimationStart(animation: Animation) {} 49 | 50 | }) 51 | tv_version_name.text = "v${AppUtils.getVerName(MyApplication.context)}" 52 | if (alphaAnimation != null) { 53 | iv_web_icon.startAnimation(alphaAnimation) 54 | } 55 | } 56 | override fun start() { 57 | 58 | } 59 | 60 | fun redirectTo() { 61 | val intent = Intent(this, MainActivity::class.java) 62 | startActivity(intent) 63 | finish() 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/ui/activity/WebViewActivity.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.ui.activity 2 | 3 | import android.annotation.SuppressLint 4 | import android.graphics.Bitmap 5 | import android.view.View 6 | import com.jaygengi.gank.R 7 | import com.jaygengi.gank.base.BaseActivity 8 | import im.delight.android.webview.AdvancedWebView 9 | import kotlinx.android.synthetic.main.activity_webview.* 10 | 11 | /** 12 | * @description: WebViewActivity 13 | * @author JayGengi 14 | * @date 2018/11/16 0016 下午 1:02 15 | * @email jaygengiii@gmail.com 16 | */ 17 | class WebViewActivity : BaseActivity(), AdvancedWebView.Listener{ 18 | private var url:String? = "" 19 | private var title:String? = "" 20 | 21 | private var mWebView: AdvancedWebView? = null 22 | override fun layoutId(): Int { 23 | return R.layout.activity_webview 24 | } 25 | 26 | override fun initData() { 27 | } 28 | 29 | override fun start() { 30 | } 31 | 32 | 33 | @SuppressLint("ResourceAsColor") 34 | override fun initView() { 35 | url = intent.getStringExtra("url") 36 | title = intent.getStringExtra("title") 37 | topbar.setTitle(title).setTextColor(R.color.color_black) 38 | topbar.addLeftImageButton(R.drawable.ic_left,R.id.left).setOnClickListener { 39 | finish() 40 | } 41 | mWebView = findViewById(R.id.webview) as AdvancedWebView 42 | mWebView!!.setListener(this, this) 43 | setSettings() 44 | mWebView!!.loadUrl(url) 45 | } 46 | private fun setSettings(){ 47 | mWebView!!.settings.setSupportZoom(true) 48 | } 49 | override fun onDestroy() { 50 | mWebView!!.onDestroy() 51 | super.onDestroy() 52 | } 53 | 54 | override fun onPageFinished(url: String?) { 55 | } 56 | 57 | override fun onPageError(errorCode: Int, description: String?, failingUrl: String?) { 58 | } 59 | 60 | override fun onDownloadRequested(url: String?, suggestedFilename: String?, mimeType: String?, contentLength: Long, contentDisposition: String?, userAgent: String?) { 61 | } 62 | 63 | override fun onExternalPageRequest(url: String?) { 64 | } 65 | 66 | override fun onPageStarted(url: String?, favicon: Bitmap?) { 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/ui/adapter/GirlsAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.ui.adapter 2 | 3 | import com.bumptech.glide.Glide 4 | import com.chad.library.adapter.base.BaseQuickAdapter 5 | import com.chad.library.adapter.base.BaseViewHolder 6 | import com.jaygengi.gank.R 7 | import com.jaygengi.gank.mvp.model.bean.GirlsEntity 8 | 9 | /** 10 | * @description: 美女福利适配器 11 | * @author JayGengi 12 | * @date 2018/11/14 0014 下午 5:01 13 | * @email jaygengiii@gmail.com 14 | */ 15 | class GirlsAdapter(data: List?) 16 | : BaseQuickAdapter 17 | (R.layout.item_grils_info, data) { 18 | 19 | override fun convert(helper: BaseViewHolder, item: GirlsEntity.ResultsBean) { 20 | //Glide 加载图片简单用法 21 | Glide.with(mContext).load(item.url).into(helper.getView(R.id.rental_image)) 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/ui/adapter/HomePageAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.ui.adapter 2 | 3 | import android.support.v4.app.Fragment 4 | import android.support.v4.app.FragmentManager 5 | import android.support.v4.app.FragmentPagerAdapter 6 | 7 | /** 8 | * @description: PageAdapter 9 | * @author JayGengi 10 | * @date 2018/11/15 0015 下午 4:48 11 | * @email jaygengiii@gmail.com 12 | */ 13 | class HomePageAdapter(fm: FragmentManager?) : FragmentPagerAdapter(fm) { 14 | 15 | private val fragments = ArrayList() 16 | private val titles = ArrayList() 17 | 18 | fun setData(fragments: List, titles: List) { 19 | this.fragments.clear() 20 | this.fragments.addAll(fragments) 21 | this.titles.clear() 22 | this.titles.addAll(titles) 23 | } 24 | 25 | override fun getItem(position: Int): Fragment { 26 | return fragments[position] 27 | } 28 | 29 | override fun getCount() = fragments.size 30 | 31 | override fun getPageTitle(position: Int) = titles[position] 32 | 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/ui/adapter/ToDayAndroidAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.ui.adapter 2 | 3 | import android.annotation.SuppressLint 4 | import com.chad.library.adapter.base.BaseQuickAdapter 5 | import com.chad.library.adapter.base.BaseViewHolder 6 | import com.jaygengi.gank.R 7 | import com.jaygengi.gank.mvp.model.bean.CategoryEntity 8 | import java.text.ParseException 9 | import java.text.SimpleDateFormat 10 | import java.util.* 11 | 12 | /** 13 | * @description: 获取最新一天的干货适配器 14 | * @author JayGengi 15 | * @date 2018/11/14 0014 下午 5:01 16 | * @email jaygengiii@gmail.com 17 | */ 18 | class ToDayAndroidAdapter(data: List?) 19 | : BaseQuickAdapter 20 | (R.layout.item_category, data) { 21 | 22 | @SuppressLint("SimpleDateFormat") 23 | override fun convert(helper: BaseViewHolder, item: CategoryEntity.ResultsBean) { 24 | 25 | val sf = SimpleDateFormat("yyyy-MM-dd") 26 | try { 27 | val date = sf.parse(item.publishedAt) 28 | val calendar = Calendar.getInstance() 29 | calendar.time = date 30 | val time = calendar.get(Calendar.YEAR).toString()+"-"+(calendar.get(Calendar.MONTH) + 1)+"-"+Calendar.DAY_OF_MONTH 31 | helper.setText(R.id.content, item.desc) 32 | .setText(R.id.auther, "auther: "+item.who) 33 | .setText(R.id.time,time) 34 | } catch (e: ParseException) { 35 | e.printStackTrace() 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/ui/fragment/GankTypeFragment.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.ui.fragment 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import android.support.v4.app.Fragment 6 | import com.jaygengi.gank.R 7 | import com.jaygengi.gank.base.BaseFragment 8 | import com.jaygengi.gank.ui.adapter.HomePageAdapter 9 | import com.jaygengi.gank.ui.fragment.home.* 10 | import kotlinx.android.synthetic.main.fragment_gank_type.* 11 | 12 | /** 13 | * @description: GankType 14 | * @author JayGengi 15 | * @date 2018/10/29 0029 下午 1:55 16 | * @email jaygengiii@gmail.com 17 | */ 18 | class GankTypeFragment : BaseFragment(){ 19 | 20 | private var mTitle: String? = null 21 | private val fragments = ArrayList() 22 | 23 | private val titles = ArrayList() 24 | companion object { 25 | fun getInstance(title: String): GankTypeFragment { 26 | val fragment = GankTypeFragment() 27 | val bundle = Bundle() 28 | fragment.arguments = bundle 29 | fragment.mTitle = title 30 | return fragment 31 | } 32 | } 33 | 34 | override fun getLayoutId(): Int = R.layout.fragment_gank_type 35 | 36 | override fun lazyLoad() { 37 | } 38 | 39 | @SuppressLint("ResourceAsColor", "SetTextI18n") 40 | override fun initView() { 41 | title.text ="GankType" 42 | fragments.add(WelfareFragment()) 43 | fragments.add(CategoryFragment.newInstance("Android")) 44 | fragments.add(CategoryFragment.newInstance("App")) 45 | fragments.add(CategoryFragment.newInstance("iOS")) 46 | fragments.add(CategoryFragment.newInstance("休息视频")) 47 | fragments.add(CategoryFragment.newInstance("前端")) 48 | fragments.add(CategoryFragment.newInstance("拓展资源")) 49 | fragments.add(CategoryFragment.newInstance("瞎推荐")) 50 | titles.add("福利") 51 | titles.add("Android") 52 | titles.add("App") 53 | titles.add("iOS") 54 | titles.add("休息视频") 55 | titles.add("前端") 56 | titles.add("拓展资源") 57 | titles.add("瞎推荐") 58 | viewpager.adapter = HomePageAdapter(childFragmentManager).apply { 59 | setData(fragments,titles) 60 | } 61 | viewpager.offscreenPageLimit = 4 62 | 63 | sliding_tabs.setViewPager(viewpager) 64 | } 65 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/ui/fragment/HomeFragment.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.ui.fragment 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Intent 5 | import android.os.Build 6 | import android.os.Bundle 7 | import android.support.v4.app.ActivityOptionsCompat 8 | import android.support.v4.content.ContextCompat 9 | import android.support.v7.widget.LinearLayoutManager 10 | import android.view.View 11 | import com.bumptech.glide.Glide 12 | import com.jaygengi.gank.R 13 | import com.jaygengi.gank.base.BaseFragment 14 | import com.jaygengi.gank.mvp.contract.HomeContract 15 | import com.jaygengi.gank.mvp.model.bean.GirlsEntity 16 | import com.jaygengi.gank.mvp.model.bean.ToDayEntity 17 | import com.jaygengi.gank.mvp.presenter.HomePresenter 18 | import com.jaygengi.gank.net.exception.ErrorStatus 19 | import com.jaygengi.gank.ui.activity.SearchActivity 20 | import com.jaygengi.gank.ui.adapter.ToDaySectionAdapter 21 | import kotlinx.android.synthetic.main.fragment_home.* 22 | import android.support.v7.widget.RecyclerView 23 | 24 | 25 | 26 | /** 27 | * @description: 首页 28 | * @author JayGengi 29 | * @date 2018/11/7 0007 下午 5:00 30 | * @email jaygengiii@gmail.com 31 | */ 32 | 33 | class HomeFragment : BaseFragment(), HomeContract.View { 34 | 35 | 36 | private val mPresenter by lazy { HomePresenter() } 37 | 38 | private var mCategoryList = ArrayList() 39 | 40 | private val mAdapter by lazy { activity?.let { ToDaySectionAdapter(context!!, mCategoryList) } } 41 | 42 | private var mTitle: String? = null 43 | 44 | 45 | private val categoryList = ArrayList() 46 | override fun getLayoutId(): Int = R.layout.fragment_home 47 | 48 | override fun lazyLoad() { 49 | loadData() 50 | } 51 | @SuppressLint("ResourceAsColor") 52 | override fun initView() { 53 | 54 | collapsing_topbar_layout.setCollapsedTitleTextColor(R.color.color_black) 55 | topbar.addRightImageButton(R.mipmap.ic_action_search_black,R.id.right).setOnClickListener { 56 | openSearchActivity(topbar.findViewById(R.id.right)) 57 | } 58 | 59 | mPresenter.attachView(this) 60 | mLayoutStatusView = multipleStatusView 61 | recyclerView.apply { 62 | setHasFixedSize(true) 63 | // isNestedScrollingEnabled = false 64 | layoutManager = LinearLayoutManager(context) 65 | adapter = mAdapter 66 | } 67 | 68 | 69 | recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { 70 | 71 | override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) { 72 | 73 | super.onScrolled(recyclerView, dx, dy) 74 | 75 | val l = recyclerView!!.layoutManager as LinearLayoutManager 76 | 77 | val adapterNowPos = l.findFirstVisibleItemPosition() 78 | 79 | collapsing_topbar_layout.title = categoryList[adapterNowPos] 80 | 81 | } 82 | 83 | }) 84 | 85 | } 86 | private fun loadData(){ 87 | mPresenter.requestGirlInfo() 88 | //获取分类信息 89 | mPresenter.requestToDayInfo() 90 | } 91 | override fun showGirlInfo(dataInfo: GirlsEntity) { 92 | Glide.with(this).load(dataInfo.results!![0].url).into(head_img) 93 | } 94 | override fun showToDayInfo(todayInfo: ToDayEntity) { 95 | mAdapter?.run { 96 | 97 | 98 | if (todayInfo.category != null && todayInfo.results!= null) { 99 | categoryList.clear() 100 | categoryList.addAll(todayInfo.category!!) 101 | setNewData(todayInfo.category) 102 | setToDayTypeInfo(todayInfo.results) 103 | } else { 104 | multipleStatusView?.showEmpty() 105 | } 106 | } 107 | } 108 | 109 | private fun openSearchActivity(view :View) { 110 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 111 | val options = activity?.let { ActivityOptionsCompat.makeSceneTransitionAnimation(it, view, "JayGengi") } 112 | startActivity(Intent(activity, SearchActivity::class.java), options?.toBundle()) 113 | } else { 114 | startActivity(Intent(activity, SearchActivity::class.java)) 115 | } 116 | } 117 | 118 | override fun showError(msg: String, errorCode: Int) { 119 | if (errorCode == ErrorStatus.NETWORK_ERROR) { 120 | multipleStatusView?.showNoNetwork() 121 | 122 | } else { 123 | multipleStatusView?.showError() 124 | } 125 | } 126 | /** 127 | * 显示 Loading (下拉刷新的时候不需要显示 Loading) 128 | */ 129 | override fun showLoading() { 130 | mLayoutStatusView?.showLoading() 131 | } 132 | /** 133 | * 隐藏 Loading 134 | */ 135 | override fun dismissLoading() { 136 | multipleStatusView?.showContent() 137 | // if(mRefreshLayout!=null && mRefreshLayout.isLoading){ 138 | // mRefreshLayout.finishRefresh() 139 | // } 140 | } 141 | 142 | override fun onDestroy() { 143 | super.onDestroy() 144 | mPresenter.detachView() 145 | } 146 | companion object { 147 | fun getInstance(title: String): HomeFragment { 148 | val fragment = HomeFragment() 149 | val bundle = Bundle() 150 | fragment.arguments = bundle 151 | fragment.mTitle = title 152 | return fragment 153 | } 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/ui/fragment/MineFragment.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.ui.fragment 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.view.View 6 | import com.jaygengi.gank.R 7 | import com.jaygengi.gank.base.BaseFragment 8 | import com.jaygengi.gank.ui.activity.WebViewActivity 9 | import kotlinx.android.synthetic.main.fragment_mine.* 10 | 11 | /** 12 | * Created by xuhao on 2017/11/9. 13 | * 我的 14 | */ 15 | class MineFragment : BaseFragment(), View.OnClickListener { 16 | 17 | 18 | private var mTitle: String? = null 19 | companion object { 20 | fun getInstance(title: String): MineFragment { 21 | val fragment = MineFragment() 22 | val bundle = Bundle() 23 | fragment.arguments = bundle 24 | fragment.mTitle = title 25 | return fragment 26 | } 27 | } 28 | override fun getLayoutId(): Int= R.layout.fragment_mine 29 | 30 | override fun initView() { 31 | github_url.setOnClickListener(this) 32 | gank_url.setOnClickListener(this) 33 | demo_url.setOnClickListener(this) 34 | qmui_url.setOnClickListener(this) 35 | retrofit_url.setOnClickListener(this) 36 | adapter_url.setOnClickListener(this) 37 | smartRefreshLayout_url.setOnClickListener(this) 38 | multiple_status_view_url.setOnClickListener(this) 39 | 40 | } 41 | 42 | override fun lazyLoad() { 43 | 44 | } 45 | 46 | override fun onClick(v: View?) { 47 | when(v!!.id) { 48 | R.id.github_url -> { 49 | val intent = Intent(context, WebViewActivity::class.java) 50 | intent.putExtra("title","JayGengi") 51 | intent.putExtra("url", "https://github.com/JayGengi/KotlinGankApp") 52 | startActivity(intent) 53 | } 54 | R.id.gank_url -> { 55 | val intent = Intent(context, WebViewActivity::class.java) 56 | intent.putExtra("title","Gank.io") 57 | intent.putExtra("url", "https://gank.io") 58 | startActivity(intent) 59 | } 60 | R.id.demo_url -> { 61 | val intent = Intent(context, WebViewActivity::class.java) 62 | intent.putExtra("title","KotlinMvp") 63 | intent.putExtra("url", "https://github.com/git-xuhao/KotlinMvp") 64 | startActivity(intent) 65 | } 66 | R.id.qmui_url -> { 67 | val intent = Intent(context, WebViewActivity::class.java) 68 | intent.putExtra("title","提高 Android UI 开发效率的 UI 库") 69 | intent.putExtra("url", "https://github.com/QMUI/QMUI_Android") 70 | startActivity(intent) 71 | } 72 | R.id.retrofit_url -> { 73 | val intent = Intent(context, WebViewActivity::class.java) 74 | intent.putExtra("title","retrofit") 75 | intent.putExtra("url", "https://github.com/square/retrofit") 76 | startActivity(intent) 77 | } 78 | R.id.multiple_status_view_url -> { 79 | val intent = Intent(context, WebViewActivity::class.java) 80 | intent.putExtra("title","MultipleStatusView") 81 | intent.putExtra("url", "https://github.com/qyxxjd/MultipleStatusView") 82 | startActivity(intent) 83 | } 84 | R.id.smartRefreshLayout_url -> { 85 | val intent = Intent(context, WebViewActivity::class.java) 86 | intent.putExtra("title","SmartRefreshLayout") 87 | intent.putExtra("url", "https://github.com/scwang90/SmartRefreshLayout") 88 | startActivity(intent) 89 | } 90 | 91 | 92 | 93 | } 94 | } 95 | 96 | 97 | 98 | 99 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/ui/fragment/home/CategoryFragment.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.ui.fragment.home 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import android.os.Bundle 6 | import android.support.v7.widget.LinearLayoutManager 7 | import com.jaygengi.gank.R 8 | import com.jaygengi.gank.base.BaseFragment 9 | import com.jaygengi.gank.mvp.contract.CategoryContract 10 | import com.jaygengi.gank.mvp.model.bean.CategoryEntity 11 | import com.jaygengi.gank.mvp.presenter.CategoryPresenter 12 | import com.jaygengi.gank.net.exception.ErrorStatus 13 | import com.jaygengi.gank.showToast 14 | import com.jaygengi.gank.ui.activity.WebViewActivity 15 | import com.jaygengi.gank.ui.adapter.ToDayAndroidAdapter 16 | import kotlinx.android.synthetic.main.fragment_home_common_type.* 17 | 18 | /** 19 | * @description: android 20 | * @author JayGengi 21 | * @date 2018/11/7 0007 下午 5:00 22 | * @email jaygengiii@gmail.com 23 | */ 24 | 25 | class CategoryFragment : BaseFragment(), CategoryContract.View { 26 | private var isFirstLoad = false 27 | //category 后面可接受参数 all | Android | iOS | 休息视频 | 福利 | 拓展资源 | 前端 | 瞎推荐 | App 28 | lateinit var key: String 29 | 30 | private val mPresenter by lazy { CategoryPresenter() } 31 | 32 | private var androidList = ArrayList() 33 | 34 | private val mAdapter by lazy { activity?.let { ToDayAndroidAdapter( androidList) } } 35 | 36 | 37 | 38 | override fun getLayoutId(): Int = R.layout.fragment_home_common_type 39 | 40 | override fun lazyLoad() { 41 | isFirstLoad = true 42 | key = arguments!!.getString("key") 43 | loadData() 44 | } 45 | override fun initView() { 46 | mPresenter.attachView(this) 47 | mLayoutStatusView = multipleStatusView 48 | mRefreshLayout.setOnRefreshListener { 49 | CURRENT_PAGE =1 50 | loadData() 51 | } 52 | mRefreshLayout.setOnLoadMoreListener { 53 | CURRENT_PAGE ++ 54 | loadData() 55 | } 56 | recycler.apply { 57 | setHasFixedSize(true) 58 | isNestedScrollingEnabled = false 59 | layoutManager = LinearLayoutManager(context) 60 | adapter = mAdapter 61 | } 62 | mAdapter!!.setOnItemClickListener { adapter, _, position -> 63 | val item :CategoryEntity.ResultsBean = adapter.getItem(position) as CategoryEntity.ResultsBean 64 | val intent = Intent(context, WebViewActivity::class.java) 65 | intent.putExtra("title", item.desc) 66 | intent.putExtra("url", item.url) 67 | startActivity(intent) 68 | } 69 | 70 | 71 | } 72 | private fun loadData(){ 73 | 74 | mPresenter.requestCategoryInfo(key,PAGE_CAPACITY,CURRENT_PAGE) 75 | } 76 | override fun getCategoryInfo(todayInfo: CategoryEntity) { 77 | multipleStatusView?.showContent() 78 | mAdapter?.run { 79 | if (todayInfo.results != null && todayInfo.results!!.isNotEmpty()) { 80 | if (CURRENT_PAGE == 1) { 81 | androidList.clear() 82 | } 83 | androidList.addAll(todayInfo.results!!) 84 | setNewData(androidList) 85 | } else { 86 | multipleStatusView?.showEmpty() 87 | } 88 | } 89 | } 90 | override fun showError(msg: String, errorCode: Int) { 91 | showToast(msg) 92 | if (errorCode == ErrorStatus.NETWORK_ERROR) { 93 | multipleStatusView?.showNoNetwork() 94 | 95 | } else { 96 | multipleStatusView?.showError() 97 | } 98 | } 99 | 100 | /** 101 | * 显示 Loading (下拉刷新的时候不需要显示 Loading) 102 | */ 103 | override fun showLoading() { 104 | if(isFirstLoad) { 105 | isFirstLoad = false 106 | mLayoutStatusView?.showLoading() 107 | } 108 | } 109 | /** 110 | * 隐藏 Loading 111 | */ 112 | override fun dismissLoading() { 113 | if(mRefreshLayout!=null && mRefreshLayout.isRefreshing){ 114 | mRefreshLayout.finishRefresh() 115 | } 116 | if(mRefreshLayout!=null && mRefreshLayout.isLoading){ 117 | mRefreshLayout.finishLoadMore() 118 | } 119 | } 120 | 121 | override fun onDestroy() { 122 | super.onDestroy() 123 | mPresenter.detachView() 124 | } 125 | 126 | companion object { 127 | fun newInstance(key: String): CategoryFragment { 128 | val args = Bundle() 129 | args.putString("key", key) 130 | val fragment = CategoryFragment() 131 | fragment.arguments = args 132 | return fragment 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/ui/fragment/home/WelfareFragment.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.ui.fragment.home 2 | 3 | import android.support.v7.widget.GridLayoutManager 4 | import cc.shinichi.library.ImagePreview 5 | import com.jaygengi.gank.R 6 | import com.jaygengi.gank.base.BaseFragment 7 | import com.jaygengi.gank.mvp.contract.GirlsContract 8 | import com.jaygengi.gank.mvp.model.bean.GirlsEntity 9 | import com.jaygengi.gank.mvp.presenter.GirlsPresenter 10 | import com.jaygengi.gank.net.exception.ErrorStatus 11 | import com.jaygengi.gank.ui.adapter.GirlsAdapter 12 | import kotlinx.android.synthetic.main.fragment_home_common_type.* 13 | import cc.shinichi.library.bean.ImageInfo 14 | 15 | 16 | 17 | 18 | 19 | /** 20 | * @description: 福利 21 | * @author JayGengi 22 | * @date 2018/11/15 0015 下午 4:59 23 | * @email jaygengiii@gmail.com 24 | */ 25 | 26 | class WelfareFragment : BaseFragment(), GirlsContract.View { 27 | private var isRefresh = false 28 | private val imageInfoList: ArrayList = ArrayList() 29 | 30 | private var imageInfo = ImageInfo() 31 | private val mPresenter by lazy { GirlsPresenter() } 32 | private var grilsList = ArrayList() 33 | private val mAdapter by lazy { activity?.let { GirlsAdapter( grilsList) } } 34 | override fun getLayoutId(): Int = R.layout.fragment_welfare 35 | 36 | 37 | override fun initView() { 38 | mPresenter.attachView(this) 39 | mLayoutStatusView = multipleStatusView 40 | mRefreshLayout.setOnRefreshListener { 41 | CURRENT_PAGE =1 42 | loadData() 43 | } 44 | mRefreshLayout.setOnLoadMoreListener { 45 | CURRENT_PAGE++ 46 | loadData() 47 | } 48 | recycler.apply { 49 | setHasFixedSize(true) 50 | isNestedScrollingEnabled = false 51 | layoutManager = GridLayoutManager(context,2) 52 | adapter = mAdapter 53 | } 54 | mAdapter!!.setOnItemClickListener { adapter, _, position -> 55 | ImagePreview 56 | .getInstance() 57 | .setContext(context!!) 58 | .setIndex(position)// 从第一张图片开始,索引从0开始哦 59 | .setFolderName("Gank")// 保存的文件夹名称,SD卡根目录 60 | .setImageInfoList(imageInfoList) 61 | .start() 62 | } 63 | } 64 | 65 | override fun lazyLoad() { 66 | isRefresh = true 67 | loadData() 68 | } 69 | private fun loadData(){ 70 | 71 | mPresenter.requestGirlInfo(PAGE_CAPACITY,CURRENT_PAGE) 72 | } 73 | override fun showGirlInfo(dataInfo: GirlsEntity) { 74 | multipleStatusView?.showContent() 75 | mAdapter?.run { 76 | if (dataInfo.results != null && dataInfo.results!!.isNotEmpty()) { 77 | if (CURRENT_PAGE == 1) { 78 | grilsList.clear() 79 | imageInfoList.clear() 80 | } 81 | grilsList.addAll(dataInfo.results!!) 82 | for(img : GirlsEntity.ResultsBean in grilsList){ 83 | imageInfo = ImageInfo() 84 | imageInfo.originUrl = img.url 85 | imageInfo.thumbnailUrl = img.url 86 | imageInfoList.add(imageInfo) 87 | } 88 | setNewData(grilsList) 89 | } else { 90 | multipleStatusView?.showEmpty() 91 | } 92 | } 93 | } 94 | 95 | override fun showError(msg: String, errorCode: Int) { 96 | if (errorCode == ErrorStatus.NETWORK_ERROR) { 97 | multipleStatusView?.showNoNetwork() 98 | 99 | } else { 100 | multipleStatusView?.showError() 101 | } 102 | } 103 | 104 | /** 105 | * 显示 Loading (下拉刷新的时候不需要显示 Loading) 106 | */ 107 | override fun showLoading() { 108 | if(isRefresh) { 109 | isRefresh = false 110 | mLayoutStatusView?.showLoading() 111 | } 112 | } 113 | /** 114 | * 隐藏 Loading 115 | */ 116 | override fun dismissLoading() { 117 | if(mRefreshLayout!=null && mRefreshLayout.isRefreshing){ 118 | mRefreshLayout.finishRefresh() 119 | } 120 | if(mRefreshLayout!=null && mRefreshLayout.isLoading){ 121 | mRefreshLayout.finishLoadMore() 122 | } 123 | } 124 | 125 | override fun onDestroy() { 126 | super.onDestroy() 127 | mPresenter.detachView() 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/utils/AppUtils.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.utils 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.ActivityManager 5 | import android.content.Context 6 | import android.content.pm.PackageManager 7 | import android.os.Build 8 | import java.security.MessageDigest 9 | 10 | /** 11 | * Created by xuhao on 2017/12/6. 12 | * desc: APP 相关的工具类 13 | */ 14 | 15 | class AppUtils private constructor() { 16 | 17 | 18 | init { 19 | throw Error("Do not need instantiate!") 20 | } 21 | 22 | companion object { 23 | 24 | private val DEBUG = true 25 | private val TAG = "AppUtils" 26 | 27 | 28 | /** 29 | * 得到软件版本号 30 | * 31 | * @param context 上下文 32 | * @return 当前版本Code 33 | */ 34 | fun getVerCode(context: Context): Int { 35 | var verCode = -1 36 | try { 37 | val packageName = context.packageName 38 | verCode = context.packageManager 39 | .getPackageInfo(packageName, 0).versionCode 40 | } catch (e: PackageManager.NameNotFoundException) { 41 | e.printStackTrace() 42 | } 43 | 44 | return verCode 45 | } 46 | 47 | 48 | /** 49 | * 获取应用运行的最大内存 50 | * 51 | * @return 最大内存 52 | */ 53 | val maxMemory: Long 54 | get() = Runtime.getRuntime().maxMemory() / 1024 55 | 56 | 57 | /** 58 | * 得到软件显示版本信息 59 | * 60 | * @param context 上下文 61 | * @return 当前版本信息 62 | */ 63 | fun getVerName(context: Context): String { 64 | var verName = "" 65 | try { 66 | val packageName = context.packageName 67 | verName = context.packageManager 68 | .getPackageInfo(packageName, 0).versionName 69 | } catch (e: PackageManager.NameNotFoundException) { 70 | e.printStackTrace() 71 | } 72 | 73 | return verName 74 | } 75 | 76 | 77 | @SuppressLint("PackageManagerGetSignatures") 78 | /** 79 | * 获取应用签名 80 | * 81 | * @param context 上下文 82 | * @param pkgName 包名 83 | * @return 返回应用的签名 84 | */ 85 | fun getSign(context: Context, pkgName: String): String? { 86 | return try { 87 | @SuppressLint("PackageManagerGetSignatures") val pis = context.packageManager 88 | .getPackageInfo(pkgName, 89 | PackageManager.GET_SIGNATURES) 90 | hexDigest(pis.signatures[0].toByteArray()) 91 | } catch (e: PackageManager.NameNotFoundException) { 92 | e.printStackTrace() 93 | null 94 | } 95 | 96 | } 97 | 98 | /** 99 | * 将签名字符串转换成需要的32位签名 100 | * 101 | * @param paramArrayOfByte 签名byte数组 102 | * @return 32位签名字符串 103 | */ 104 | private fun hexDigest(paramArrayOfByte: ByteArray): String { 105 | val hexDigits = charArrayOf(48.toChar(), 49.toChar(), 50.toChar(), 51.toChar(), 52.toChar(), 53.toChar(), 54.toChar(), 55.toChar(), 56.toChar(), 57.toChar(), 97.toChar(), 98.toChar(), 99.toChar(), 100.toChar(), 101.toChar(), 102.toChar()) 106 | try { 107 | val localMessageDigest = MessageDigest.getInstance("MD5") 108 | localMessageDigest.update(paramArrayOfByte) 109 | val arrayOfByte = localMessageDigest.digest() 110 | val arrayOfChar = CharArray(32) 111 | var i = 0 112 | var j = 0 113 | while (true) { 114 | if (i >= 16) { 115 | return String(arrayOfChar) 116 | } 117 | val k = arrayOfByte[i].toInt() 118 | arrayOfChar[j] = hexDigits[0xF and k.ushr(4)] 119 | arrayOfChar[++j] = hexDigits[k and 0xF] 120 | i++ 121 | j++ 122 | } 123 | } catch (e: Exception) { 124 | e.printStackTrace() 125 | } 126 | 127 | return "" 128 | } 129 | 130 | 131 | /** 132 | * 获取设备的可用内存大小 133 | * 134 | * @param context 应用上下文对象context 135 | * @return 当前内存大小 136 | */ 137 | fun getDeviceUsableMemory(context: Context): Int { 138 | val am = context.getSystemService( 139 | Context.ACTIVITY_SERVICE) as ActivityManager 140 | val mi = ActivityManager.MemoryInfo() 141 | am.getMemoryInfo(mi) 142 | // 返回当前系统的可用内存 143 | return (mi.availMem / (1024 * 1024)).toInt() 144 | } 145 | 146 | 147 | fun getMobileModel(): String { 148 | var model: String? = Build.MODEL 149 | model = model?.trim { it <= ' ' } ?: "" 150 | return model 151 | } 152 | 153 | /** 154 | * 获取手机系统SDK版本 155 | * 156 | * @return 如API 17 则返回 17 157 | */ 158 | val sdkVersion: Int 159 | get() = android.os.Build.VERSION.SDK_INT 160 | } 161 | 162 | 163 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/utils/CleanLeakUtils.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.utils 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import android.view.inputmethod.InputMethodManager 6 | 7 | import java.lang.reflect.Field 8 | 9 | /** 10 | * Created by xuhao on 2017/12/13. 11 | * desc: 12 | */ 13 | 14 | object CleanLeakUtils { 15 | 16 | fun fixInputMethodManagerLeak(destContext: Context?) { 17 | if (destContext == null) { 18 | return 19 | } 20 | val inputMethodManager = destContext.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 21 | 22 | val viewArray = arrayOf("mCurRootView", "mServedView", "mNextServedView") 23 | var filed: Field 24 | var filedObject: Any? 25 | 26 | for (view in viewArray) { 27 | try { 28 | filed = inputMethodManager.javaClass.getDeclaredField(view) 29 | if (!filed.isAccessible) { 30 | filed.isAccessible = true 31 | } 32 | filedObject = filed.get(inputMethodManager) 33 | if (filedObject != null && filedObject is View) { 34 | val fileView = filedObject as View? 35 | if (fileView!!.context === destContext) { // 被InputMethodManager持有引用的context是想要目标销毁的 36 | filed.set(inputMethodManager, null) // 置空,破坏掉path to gc节点 37 | } else { 38 | break// 不是想要目标销毁的,即为又进了另一层界面了,不要处理,避免影响原逻辑,也就不用继续for循环了 39 | } 40 | } 41 | } catch (t: Throwable) { 42 | t.printStackTrace() 43 | } 44 | 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/utils/DensityUtil.java: -------------------------------------------------------------------------------- 1 | 2 | package com.jaygengi.gank.utils; 3 | 4 | import android.content.Context; 5 | 6 | public class DensityUtil { 7 | 8 | public static int getStatusBarHeight(Context context) { 9 | int result = 0; 10 | int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); 11 | if (resourceId > 0) { 12 | result = context.getResources().getDimensionPixelSize(resourceId); 13 | } 14 | return result; 15 | } 16 | 17 | public static int dip2px(Context context, float dpValue) { 18 | final float scale = context.getResources().getDisplayMetrics().density; 19 | return (int) (dpValue * scale + 0.5f); 20 | } 21 | 22 | public static int dip2px(Context context, double dpValue) { 23 | float density = context.getResources().getDisplayMetrics().density; 24 | return (int) (dpValue * (double) density + 0.5D); 25 | } 26 | 27 | public static int px2dip(Context context, float pxValue) { 28 | final float scale = context.getResources().getDisplayMetrics().density; 29 | return (int) (pxValue / scale + 0.5f); 30 | } 31 | 32 | public static int px2sp(Context context, float pxValue) { 33 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 34 | return (int) (pxValue / fontScale + 0.5f); 35 | } 36 | 37 | public static int sp2px(Context context, float spValue) { 38 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 39 | return (int) (spValue * fontScale + 0.5f); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/utils/DisplayManager.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.utils 2 | 3 | import android.content.Context 4 | import android.util.DisplayMetrics 5 | 6 | /** 7 | * Created by xuhao on 2017/11/27. 8 | * desc: 9 | */ 10 | 11 | object DisplayManager { 12 | init { 13 | 14 | } 15 | 16 | private var displayMetrics: DisplayMetrics? = null 17 | 18 | private var screenWidth: Int? = null 19 | 20 | private var screenHeight: Int? = null 21 | 22 | private var screenDpi: Int? = null 23 | 24 | fun init(context: Context) { 25 | displayMetrics = context.resources.displayMetrics 26 | screenWidth = displayMetrics?.widthPixels 27 | screenHeight = displayMetrics?.heightPixels 28 | screenDpi = displayMetrics?.densityDpi 29 | } 30 | 31 | 32 | //UI图的大小 33 | private const val STANDARD_WIDTH = 1080 34 | private const val STANDARD_HEIGHT = 1920 35 | 36 | 37 | fun getScreenWidth(): Int? { 38 | return screenWidth 39 | } 40 | 41 | fun getScreenHeight(): Int? { 42 | return screenHeight 43 | } 44 | 45 | 46 | /** 47 | * 传入UI图中问题的高度,单位像素 48 | * @param size 49 | * @return 50 | */ 51 | fun getPaintSize(size: Int): Int? { 52 | return getRealHeight(size) 53 | } 54 | 55 | /** 56 | * 输入UI图的尺寸,输出实际的px 57 | * 58 | * @param px ui图中的大小 59 | * @return 60 | */ 61 | fun getRealWidth(px: Int): Int? { 62 | //ui图的宽度 63 | return getRealWidth(px, STANDARD_WIDTH.toFloat()) 64 | } 65 | 66 | /** 67 | * 输入UI图的尺寸,输出实际的px,第二个参数是父布局 68 | * 69 | * @param px ui图中的大小 70 | * @param parentWidth 父view在ui图中的高度 71 | * @return 72 | */ 73 | fun getRealWidth(px: Int, parentWidth: Float): Int? { 74 | return (px / parentWidth * getScreenWidth()!!).toInt() 75 | } 76 | 77 | /** 78 | * 输入UI图的尺寸,输出实际的px 79 | * 80 | * @param px ui图中的大小 81 | * @return 82 | */ 83 | fun getRealHeight(px: Int): Int? { 84 | //ui图的宽度 85 | return getRealHeight(px, STANDARD_HEIGHT.toFloat()) 86 | } 87 | 88 | /** 89 | * 输入UI图的尺寸,输出实际的px,第二个参数是父布局 90 | * 91 | * @param px ui图中的大小 92 | * @param parentHeight 父view在ui图中的高度 93 | * @return 94 | */ 95 | fun getRealHeight(px: Int, parentHeight: Float): Int? { 96 | return (px / parentHeight * getScreenHeight()!!).toInt() 97 | } 98 | 99 | /** 100 | * dip转px 101 | * @param dipValue 102 | * @return int 103 | */ 104 | fun dip2px(dipValue: Float): Int? { 105 | val scale = displayMetrics?.density 106 | return (dipValue * scale!! + 0.5f).toInt() 107 | } 108 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/utils/NetworkUtil.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.utils 2 | 3 | import android.content.Context 4 | import android.net.ConnectivityManager 5 | import android.net.NetworkInfo 6 | import android.telephony.TelephonyManager 7 | import java.io.IOException 8 | import java.net.HttpURLConnection 9 | import java.net.NetworkInterface 10 | import java.net.SocketException 11 | import java.net.URL 12 | 13 | 14 | /** 15 | * Created by xuhao on 2017/11/13. 16 | */ 17 | class NetworkUtil{ 18 | 19 | companion object { 20 | 21 | var NET_CNNT_BAIDU_OK = 1 // NetworkAvailable 22 | var NET_CNNT_BAIDU_TIMEOUT = 2 // no NetworkAvailable 23 | var NET_NOT_PREPARE = 3 // Net no ready 24 | var NET_ERROR = 4 //net error 25 | private val TIMEOUT = 3000 // TIMEOUT 26 | /** 27 | * check NetworkAvailable 28 | * 29 | * @param context 30 | * @return 31 | */ 32 | @JvmStatic 33 | fun isNetworkAvailable(context: Context): Boolean { 34 | val manager = context.applicationContext.getSystemService( 35 | Context.CONNECTIVITY_SERVICE) as ConnectivityManager 36 | val info = manager.activeNetworkInfo 37 | return !(null == info || !info.isAvailable) 38 | } 39 | 40 | /** 41 | * 得到ip地址 42 | * 43 | * @return 44 | */ 45 | @JvmStatic 46 | fun getLocalIpAddress(): String { 47 | var ret = "" 48 | try { 49 | val en = NetworkInterface.getNetworkInterfaces() 50 | while (en.hasMoreElements()) { 51 | val enumIpAddress = en.nextElement().inetAddresses 52 | while (enumIpAddress.hasMoreElements()) { 53 | val netAddress = enumIpAddress.nextElement() 54 | if (!netAddress.isLoopbackAddress) { 55 | ret = netAddress.hostAddress.toString() 56 | } 57 | } 58 | } 59 | } catch (ex: SocketException) { 60 | ex.printStackTrace() 61 | } 62 | 63 | return ret 64 | } 65 | 66 | 67 | /** 68 | * ping "http://www.baidu.com" 69 | * 70 | * @return 71 | */ 72 | @JvmStatic 73 | private fun pingNetWork(): Boolean { 74 | var result = false 75 | var httpUrl: HttpURLConnection? = null 76 | try { 77 | httpUrl = URL("http://www.baidu.com") 78 | .openConnection() as HttpURLConnection 79 | httpUrl.connectTimeout = TIMEOUT 80 | httpUrl.connect() 81 | result = true 82 | } catch (e: IOException) { 83 | } finally { 84 | if (null != httpUrl) { 85 | httpUrl.disconnect() 86 | } 87 | } 88 | return result 89 | } 90 | 91 | /** 92 | * check is3G 93 | * 94 | * @param context 95 | * @return boolean 96 | */ 97 | @JvmStatic 98 | fun is3G(context: Context): Boolean { 99 | val connectivityManager = context 100 | .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 101 | val activeNetInfo = connectivityManager.activeNetworkInfo 102 | return activeNetInfo != null && activeNetInfo.type == ConnectivityManager.TYPE_MOBILE 103 | } 104 | 105 | /** 106 | * isWifi 107 | * 108 | * @param context 109 | * @return boolean 110 | */ 111 | @JvmStatic 112 | fun isWifi(context: Context): Boolean { 113 | val connectivityManager = context 114 | .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 115 | val activeNetInfo = connectivityManager.activeNetworkInfo 116 | return activeNetInfo != null && activeNetInfo.type == ConnectivityManager.TYPE_WIFI 117 | } 118 | 119 | /** 120 | * is2G 121 | * 122 | * @param context 123 | * @return boolean 124 | */ 125 | @JvmStatic 126 | fun is2G(context: Context): Boolean { 127 | val connectivityManager = context 128 | .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 129 | val activeNetInfo = connectivityManager.activeNetworkInfo 130 | return activeNetInfo != null && (activeNetInfo.subtype == TelephonyManager.NETWORK_TYPE_EDGE 131 | || activeNetInfo.subtype == TelephonyManager.NETWORK_TYPE_GPRS || activeNetInfo 132 | .subtype == TelephonyManager.NETWORK_TYPE_CDMA) 133 | } 134 | 135 | /** 136 | * is wifi on 137 | */ 138 | @JvmStatic 139 | fun isWifiEnabled(context: Context): Boolean { 140 | val mgrConn = context 141 | .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 142 | val mgrTel = context 143 | .getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager 144 | return mgrConn.activeNetworkInfo != null && mgrConn 145 | .activeNetworkInfo.state == NetworkInfo.State.CONNECTED || mgrTel 146 | .networkType == TelephonyManager.NETWORK_TYPE_UMTS 147 | } 148 | 149 | } 150 | 151 | 152 | 153 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/utils/Preference.kt: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.utils 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.content.SharedPreferences 6 | import com.jaygengi.gank.MyApplication 7 | import java.io.* 8 | import kotlin.reflect.KProperty 9 | 10 | /** 11 | * Created by xuhao on 2017/12/11. 12 | * desc:kotlin委托属性+SharedPreference实例 13 | */ 14 | class Preference(val name:String, private val default:T) { 15 | 16 | 17 | companion object { 18 | private const val file_name = "kotlin_mvp_file" 19 | 20 | private val prefs: SharedPreferences by lazy { 21 | MyApplication.context.getSharedPreferences(file_name, Context.MODE_PRIVATE) 22 | } 23 | /** 24 | * 删除全部数据 25 | */ 26 | fun clearPreference(){ 27 | prefs.edit().clear().apply() 28 | } 29 | 30 | /** 31 | * 根据key删除存储数据 32 | */ 33 | fun clearPreference(key : String){ 34 | prefs.edit().remove(key).apply() 35 | } 36 | } 37 | 38 | 39 | 40 | 41 | operator fun getValue(thisRef: Any?, property: KProperty<*>): T { 42 | return getSharedPreferences(name, default) 43 | } 44 | 45 | operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { 46 | putSharedPreferences(name, value) 47 | } 48 | 49 | @SuppressLint("CommitPrefEdits") 50 | private fun putSharedPreferences(name: String, value: T) = with(prefs.edit()) { 51 | when (value) { 52 | is Long -> putLong(name, value) 53 | is String -> putString(name, value) 54 | is Int -> putInt(name, value) 55 | is Boolean -> putBoolean(name, value) 56 | is Float -> putFloat(name, value) 57 | else -> putString(name,serialize(value)) 58 | }.apply() 59 | } 60 | 61 | @Suppress("UNCHECKED_CAST") 62 | private fun getSharedPreferences(name: String, default: T): T = with(prefs) { 63 | val res: Any = when (default) { 64 | is Long -> getLong(name, default) 65 | is String -> getString(name, default) 66 | is Int -> getInt(name, default) 67 | is Boolean -> getBoolean(name, default) 68 | is Float -> getFloat(name, default) 69 | else -> deSerialization(getString(name,serialize(default))) 70 | } 71 | return res as T 72 | } 73 | 74 | 75 | 76 | /** 77 | * 序列化对象 78 | 79 | * @param person 80 | * * 81 | * @return 82 | * * 83 | * @throws IOException 84 | */ 85 | @Throws(IOException::class) 86 | private fun serialize(obj: A): String { 87 | val byteArrayOutputStream = ByteArrayOutputStream() 88 | val objectOutputStream = ObjectOutputStream( 89 | byteArrayOutputStream) 90 | objectOutputStream.writeObject(obj) 91 | var serStr = byteArrayOutputStream.toString("ISO-8859-1") 92 | serStr = java.net.URLEncoder.encode(serStr, "UTF-8") 93 | objectOutputStream.close() 94 | byteArrayOutputStream.close() 95 | return serStr 96 | } 97 | 98 | /** 99 | * 反序列化对象 100 | 101 | * @param str 102 | * * 103 | * @return 104 | * * 105 | * @throws IOException 106 | * * 107 | * @throws ClassNotFoundException 108 | */ 109 | @Suppress("UNCHECKED_CAST") 110 | @Throws(IOException::class, ClassNotFoundException::class) 111 | private fun deSerialization(str: String): A { 112 | val redStr = java.net.URLDecoder.decode(str, "UTF-8") 113 | val byteArrayInputStream = ByteArrayInputStream( 114 | redStr.toByteArray(charset("ISO-8859-1"))) 115 | val objectInputStream = ObjectInputStream( 116 | byteArrayInputStream) 117 | val obj = objectInputStream.readObject() as A 118 | objectInputStream.close() 119 | byteArrayInputStream.close() 120 | return obj 121 | } 122 | 123 | 124 | /** 125 | * 查询某个key是否已经存在 126 | * 127 | * @param key 128 | * @return 129 | */ 130 | fun contains(key: String): Boolean { 131 | return prefs.contains(key) 132 | } 133 | 134 | /** 135 | * 返回所有的键值对 136 | * 137 | * @param context 138 | * @return 139 | */ 140 | fun getAll(): Map { 141 | return prefs.all 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/utils/RxUtil.java: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.utils; 2 | 3 | import io.reactivex.FlowableTransformer; 4 | import io.reactivex.ObservableTransformer; 5 | import io.reactivex.android.schedulers.AndroidSchedulers; 6 | import io.reactivex.schedulers.Schedulers; 7 | 8 | /** 9 | * 描述:RxJava工具类 10 | * 11 | * @author JayGengi 12 | */ 13 | public class RxUtil { 14 | 15 | /** 16 | * 统一线程处理 17 | * 18 | * @param 19 | * @return 20 | */ 21 | public static FlowableTransformer rxSchedulerHelper() { 22 | 23 | //compose简化线程 24 | return observable -> observable.subscribeOn(Schedulers.io()) 25 | .observeOn(AndroidSchedulers.mainThread()); 26 | } 27 | 28 | /** 29 | * 统一线程处理 30 | * 31 | * @param 32 | * @return 33 | */ 34 | public static ObservableTransformer rxObservableSchedulerHelper() { 35 | 36 | //compose简化线程 37 | return observable -> observable.subscribeOn(Schedulers.io()) 38 | .observeOn(AndroidSchedulers.mainThread()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/utils/img/GlideImageLoader.java: -------------------------------------------------------------------------------- 1 | package com.jaygengi.gank.utils.img; 2 | 3 | import android.content.Context; 4 | import android.widget.ImageView; 5 | 6 | import com.bumptech.glide.Glide; 7 | import com.youth.banner.loader.ImageLoader; 8 | 9 | /** 10 | * @description: Image加载器 11 | * @author JayGengi 12 | * @date 2018/11/13 0013 下午 5:18 13 | * @email jaygengiii@gmail.com 14 | */ 15 | 16 | public class GlideImageLoader extends ImageLoader { 17 | 18 | @Override 19 | public void displayImage(Context context, Object path, ImageView imageView) { 20 | /** 21 | 注意: 22 | 1.图片加载器由自己选择,这里不限制,只是提供几种使用方法 23 | 2.返回的图片路径为Object类型,由于不能确定你到底使用的那种图片加载器, 24 | 传输的到的是什么格式,那么这种就使用Object接收和返回,你只需要强转成你传输的类型就行, 25 | 切记不要胡乱强转! 26 | */ 27 | 28 | //Glide 加载图片简单用法 29 | Glide.with(context).load(path).into(imageView); 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaygengi/gank/widget/SlidingTabStrip.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jaygengi.gank.widget; 18 | 19 | import android.content.Context; 20 | import android.graphics.Canvas; 21 | import android.graphics.Color; 22 | import android.graphics.Paint; 23 | import android.util.AttributeSet; 24 | import android.util.TypedValue; 25 | import android.view.View; 26 | import android.widget.LinearLayout; 27 | 28 | import com.jaygengi.gank.R; 29 | import com.jaygengi.gank.utils.DensityUtil; 30 | 31 | 32 | class SlidingTabStrip extends LinearLayout { 33 | 34 | private static final int DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 2; 35 | private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26; 36 | private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 8; 37 | private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFF33B5E5; 38 | 39 | private static final int DEFAULT_DIVIDER_THICKNESS_DIPS = 1; 40 | private static final byte DEFAULT_DIVIDER_COLOR_ALPHA = 0x20; 41 | private static final float DEFAULT_DIVIDER_HEIGHT = 0.5f; 42 | 43 | private final int mBottomBorderThickness; 44 | private final Paint mBottomBorderPaint; 45 | 46 | private final int mSelectedIndicatorThickness; 47 | private final Paint mSelectedIndicatorPaint; 48 | 49 | private final int mDefaultBottomBorderColor; 50 | 51 | private final Paint mDividerPaint; 52 | private final float mDividerHeight; 53 | 54 | private int mSelectedPosition; 55 | private float mSelectionOffset; 56 | 57 | private SlidingTabLayout.TabColorizer mCustomTabColorizer; 58 | private final SimpleTabColorizer mDefaultTabColorizer; 59 | 60 | private Context context; 61 | 62 | private int stripLength; 63 | 64 | SlidingTabStrip(Context context) { 65 | this(context, null); 66 | } 67 | 68 | SlidingTabStrip(Context context, AttributeSet attrs) { 69 | super(context, attrs); 70 | this.context = context; 71 | 72 | setWillNotDraw(false); 73 | 74 | stripLength = DensityUtil.dip2px(context, 20f); 75 | 76 | final float density = getResources().getDisplayMetrics().density; 77 | 78 | TypedValue outValue = new TypedValue(); 79 | //TODO 80 | context.getTheme().resolveAttribute(R.attr.colorPrimary, outValue, true); 81 | final int themeForegroundColor = outValue.data; 82 | 83 | mDefaultBottomBorderColor = setColorAlpha(themeForegroundColor, 84 | DEFAULT_BOTTOM_BORDER_COLOR_ALPHA); 85 | 86 | mDefaultTabColorizer = new SimpleTabColorizer(); 87 | mDefaultTabColorizer.setIndicatorColors(DEFAULT_SELECTED_INDICATOR_COLOR); 88 | mDefaultTabColorizer.setDividerColors(setColorAlpha(themeForegroundColor, 89 | DEFAULT_DIVIDER_COLOR_ALPHA)); 90 | 91 | mBottomBorderThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density); 92 | mBottomBorderPaint = new Paint(); 93 | mBottomBorderPaint.setColor(mDefaultBottomBorderColor); 94 | 95 | mSelectedIndicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density); 96 | mSelectedIndicatorPaint = new Paint(); 97 | mSelectedIndicatorPaint.setStrokeWidth(DensityUtil.dip2px(context, 2)); 98 | mSelectedIndicatorPaint.setStrokeCap(Paint.Cap.ROUND); 99 | 100 | mDividerHeight = DEFAULT_DIVIDER_HEIGHT; 101 | mDividerPaint = new Paint(); 102 | mDividerPaint.setStrokeWidth((int) (DEFAULT_DIVIDER_THICKNESS_DIPS * density)); 103 | this.setBackgroundColor(Color.parseColor("#00000000")); 104 | } 105 | 106 | void setCustomTabColorizer(SlidingTabLayout.TabColorizer customTabColorizer) { 107 | mCustomTabColorizer = customTabColorizer; 108 | invalidate(); 109 | } 110 | 111 | void setSelectedIndicatorColors(int... colors) { 112 | // Make sure that the custom colorizer is removed 113 | mCustomTabColorizer = null; 114 | mDefaultTabColorizer.setIndicatorColors(colors); 115 | invalidate(); 116 | } 117 | 118 | void setDividerColors(int... colors) { 119 | // Make sure that the custom colorizer is removed 120 | mCustomTabColorizer = null; 121 | mDefaultTabColorizer.setDividerColors(colors); 122 | invalidate(); 123 | } 124 | 125 | void setStripLength(int length){ 126 | this.stripLength = length; 127 | invalidate(); 128 | } 129 | 130 | void onViewPagerPageChanged(int position, float positionOffset) { 131 | mSelectedPosition = position; 132 | mSelectionOffset = positionOffset; 133 | invalidate(); 134 | } 135 | 136 | @Override 137 | protected void onDraw(Canvas canvas) { 138 | final int height = getHeight(); 139 | final int childCount = getChildCount(); 140 | final int dividerHeightPx = (int) (Math.min(Math.max(0f, mDividerHeight), 1f) * height); 141 | final SlidingTabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null 142 | ? mCustomTabColorizer 143 | : mDefaultTabColorizer; 144 | 145 | // Thick colored underline below the current selection 146 | if (childCount > 0) { 147 | 148 | // int length = DensityUtil.dip2px(context, 20); 149 | int length = stripLength; 150 | View selectedTitle = getChildAt(mSelectedPosition); 151 | int left = selectedTitle.getLeft() + selectedTitle.getPaddingLeft(); 152 | int right = selectedTitle.getRight() - selectedTitle.getPaddingRight(); 153 | 154 | int middle = (left + right) / 2; 155 | int start = middle - length / 2; 156 | int end = middle + length / 2; 157 | 158 | 159 | 160 | int color = tabColorizer.getIndicatorColor(mSelectedPosition); 161 | 162 | if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) { 163 | int nextColor = tabColorizer.getIndicatorColor(mSelectedPosition + 1); 164 | if (color != nextColor) { 165 | color = blendColors(nextColor, color, mSelectionOffset); 166 | } 167 | 168 | View nextTitle = getChildAt(mSelectedPosition + 1); 169 | 170 | int nextLeft = nextTitle.getLeft() + nextTitle.getPaddingLeft(); 171 | int nextRight = nextTitle.getRight() - nextTitle.getPaddingRight(); 172 | int nextMiddle = (nextLeft + nextRight) / 2; 173 | int nextStart = nextMiddle - length / 2; 174 | int nextEnd = nextMiddle +length / 2; 175 | 176 | // Draw the selection partway between the tabs 177 | // View nextTitle = getChildAt(mSelectedPosition + 1); 178 | // left = (int) (mSelectionOffset * (nextTitle.getLeft() + length) + 179 | // (1.0f - mSelectionOffset) * (left + length)); 180 | // right = (int) (mSelectionOffset * (nextTitle.getRight() + length) + 181 | // (1.0f - mSelectionOffset) * (right + length)); 182 | 183 | // start = (int) (mSelectionOffset * nextStart + (1.0f - mSelectionOffset) * start); 184 | // end = (int) (mSelectionOffset * nextEnd + (1.0f - mSelectionOffset) * end); 185 | if (mSelectionOffset <= 0.5f) { 186 | // start = start; 187 | end = (int) ((0.5f - mSelectionOffset) / 0.5f * end + mSelectionOffset / 0.5f * nextEnd); 188 | } else { 189 | start = (int) ((1 - mSelectionOffset) / 0.5f * start + (mSelectionOffset - 0.5f) / 0.5f * nextStart); 190 | end = nextEnd; 191 | } 192 | 193 | } 194 | 195 | // mSelectedIndicatorPaint.setColor(color); 196 | mSelectedIndicatorPaint.setColor(getResources().getColor(R.color.md_blue_500)); 197 | 198 | // canvas.drawRect(start, height - mSelectedIndicatorThickness, end, 199 | // height, mSelectedIndicatorPaint); 200 | // canvas.drawRect(start, height - Tools.dip2px(context, 2), end, 201 | // height, mSelectedIndicatorPaint); 202 | canvas.drawLine(start, height - DensityUtil.dip2px(context, 1), end, height - DensityUtil.dip2px(context, 1), mSelectedIndicatorPaint); 203 | 204 | } 205 | 206 | // Thin underline along the entire bottom edge 207 | // canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint); 208 | 209 | // Vertical separators between the titles 210 | // int separatorTop = (height - dividerHeightPx) / 2; 211 | // for (int i = 0; i < childCount - 1; i++) { 212 | // View child = getChildAt(i); 213 | // mDividerPaint.setColor(tabColorizer.getDividerColor(i)); 214 | // canvas.drawLine(child.getRight(), separatorTop, child.getRight(), 215 | // separatorTop + dividerHeightPx, mDividerPaint); 216 | // } 217 | 218 | } 219 | 220 | /** 221 | * Set the alpha value of the {@code color} to be the given {@code alpha} value. 222 | */ 223 | private static int setColorAlpha(int color, byte alpha) { 224 | return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color)); 225 | } 226 | 227 | /** 228 | * Blend {@code color1} and {@code color2} using the given ratio. 229 | * 230 | * @param ratio of which to blend. 1.0 will return {@code color1}, 0.5 will give an even blend, 231 | * 0.0 will return {@code color2}. 232 | */ 233 | private static int blendColors(int color1, int color2, float ratio) { 234 | final float inverseRation = 1f - ratio; 235 | float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation); 236 | float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation); 237 | float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation); 238 | return Color.rgb((int) r, (int) g, (int) b); 239 | } 240 | 241 | private static class SimpleTabColorizer implements SlidingTabLayout.TabColorizer { 242 | private int[] mIndicatorColors; 243 | private int[] mDividerColors; 244 | 245 | @Override 246 | public final int getIndicatorColor(int position) { 247 | return mIndicatorColors[position % mIndicatorColors.length]; 248 | } 249 | 250 | @Override 251 | public final int getDividerColor(int position) { 252 | return mDividerColors[position % mDividerColors.length]; 253 | } 254 | 255 | void setIndicatorColors(int... colors) { 256 | mIndicatorColors = colors; 257 | } 258 | 259 | void setDividerColors(int... colors) { 260 | mDividerColors = colors; 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /app/src/main/res/anim/anim_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/anim/anim_out.xml: -------------------------------------------------------------------------------- 1 | 4 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/anim/push_bottom_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/anim/push_bottom_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_left.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/line_h.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 12 | 13 | 18 | 19 | 20 | 26 | 27 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 16 | 17 | 24 | 25 | 34 | 35 | 36 | 37 | 47 | 48 | 57 | 58 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_webview.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_app.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_gank_type.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | 20 | 21 | 22 | 27 | 28 | 32 | 33 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 12 | 13 | 24 | 25 | 35 | 36 | 46 | 47 | 48 | 49 | 50 | 59 | 65 | 66 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_home_common_type.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 18 | 19 | 20 | 25 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_welfare.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 18 | 23 | 24 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_category.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 21 | 28 | 29 | 30 | 31 | 36 | 37 | 43 | 50 | 57 | 58 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_category_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 24 | 25 | 30 | 38 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_grils_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 13 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_home_today.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 17 | 18 | 26 | 27 | 35 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_home_today_img.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_today_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 19 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_empty_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_error_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_loading_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_network_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_action_clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/app/src/main/res/mipmap-xhdpi/ic_action_clear.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_action_search_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/app/src/main/res/mipmap-xhdpi/ic_action_search_black.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_action_search_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/app/src/main/res/mipmap-xhdpi/ic_action_search_small.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_action_search_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/app/src/main/res/mipmap-xhdpi/ic_action_search_white.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_classification_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/app/src/main/res/mipmap-xhdpi/ic_classification_normal.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_classification_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/app/src/main/res/mipmap-xhdpi/ic_classification_selected.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/app/src/main/res/mipmap-xhdpi/ic_error.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_home_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/app/src/main/res/mipmap-xhdpi/ic_home_normal.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_home_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/app/src/main/res/mipmap-xhdpi/ic_home_selected.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_mine_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/app/src/main/res/mipmap-xhdpi/ic_mine_normal.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_mine_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/app/src/main/res/mipmap-xhdpi/ic_mine_selected.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_no_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/app/src/main/res/mipmap-xhdpi/ic_no_data.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_no_network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/app/src/main/res/mipmap-xhdpi/ic_no_network.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/img_jaygengi_avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/app/src/main/res/mipmap-xhdpi/img_jaygengi_avatar.jpg -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/transition-v21/arc_motion.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values-v19/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | #549cf8 5 | #EF5362 6 | #e1e1e1 7 | #9a9a9a 8 | 9 | #5000 10 | 11 | 12 | #EF5362 13 | #FE6D4B 14 | #FFCF47 15 | #9FD661 16 | #3FD0AD 17 | #2BBDF3 18 | #00b0ff 19 | #AC8FEF 20 | #EE85C1 21 | #2196F3 22 | #ff4081 23 | #dd2c00 24 | #ee447AA4 25 | #616161 26 | 27 | 28 | 29 | #CCFFFFFF 30 | 31 | #EEEEEE 32 | #E0E0E0 33 | 34 | #FFFFFF 35 | #000000 36 | 37 | #87CEEB 38 | 39 | 40 | #000000 41 | 42 | #ddd 43 | #aaa 44 | 45 | @color/color_lighter_gray 46 | 47 | #00000000 48 | 49 | #7FFFD4 50 | 51 | #5000 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 16dp 3 | 51dp 4 | 5 | 6 | 10dp 7 | 15dp 8 | 10dp 9 | 5dp 10 | 11 | 10sp 12 | 12sp 13 | 14sp 14 | 16sp 15 | 18sp 16 | 17 | 0.5dp 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Gank 3 | 4 | 5 | JayGengi 6 | 查看个人主页 > 7 | 8 | 9 | 10 | 加载中… 11 | 请检查网络后点击重新加载 12 | 数据获取失败(ㄒoㄒ)~~ 13 | 暂时木有内容呀~~ 14 | 干货集中营 15 | 每日分享妹子图 和 技术干货 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 14 | 17 | 27 | 31 | 32 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | ext.kotlin_version = '1.2.71' 4 | repositories { 5 | google() 6 | mavenCentral() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.2.1' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | mavenCentral() 22 | maven { 23 | url "https://jitpack.io" 24 | } 25 | } 26 | 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Oct 16 11:31:23 CST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /multiple-status-view/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.iml -------------------------------------------------------------------------------- /multiple-status-view/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | 5 | compileSdkVersion 27 6 | buildToolsVersion "28.0.3" 7 | 8 | defaultConfig { 9 | minSdkVersion 18 10 | targetSdkVersion 27 11 | versionCode 1 12 | versionName "1.1.0" 13 | } 14 | 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { } -------------------------------------------------------------------------------- /multiple-status-view/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\AndroidStudio\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /multiple-status-view/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /multiple-status-view/src/main/java/com/classic/common/MultipleStatusView.java: -------------------------------------------------------------------------------- 1 | package com.classic.common; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.util.AttributeSet; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.RelativeLayout; 10 | 11 | import java.util.ArrayList; 12 | 13 | /** 14 | * 类描述: 一个方便在多种状态切换的view 15 | * 16 | * 创建时间: 2016/1/15 10:20. 17 | */ 18 | @SuppressWarnings("unused") 19 | public class MultipleStatusView extends RelativeLayout { 20 | private static final String TAG = "MultipleStatusView"; 21 | 22 | private static final RelativeLayout.LayoutParams DEFAULT_LAYOUT_PARAMS = 23 | new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, 24 | RelativeLayout.LayoutParams.MATCH_PARENT); 25 | 26 | public static final int STATUS_CONTENT = 0x00; 27 | public static final int STATUS_LOADING = 0x01; 28 | public static final int STATUS_EMPTY = 0x02; 29 | public static final int STATUS_ERROR = 0x03; 30 | public static final int STATUS_NO_NETWORK = 0x04; 31 | 32 | private static final int NULL_RESOURCE_ID = -1; 33 | 34 | private View mEmptyView; 35 | private View mErrorView; 36 | private View mLoadingView; 37 | private View mNoNetworkView; 38 | private View mContentView; 39 | private int mEmptyViewResId; 40 | private int mErrorViewResId; 41 | private int mLoadingViewResId; 42 | private int mNoNetworkViewResId; 43 | private int mContentViewResId; 44 | 45 | private int mViewStatus; 46 | private LayoutInflater mInflater; 47 | private OnClickListener mOnRetryClickListener; 48 | 49 | private final ArrayList mOtherIds = new ArrayList<>(); 50 | 51 | public MultipleStatusView(Context context) { 52 | this(context, null); 53 | } 54 | 55 | public MultipleStatusView(Context context, AttributeSet attrs) { 56 | this(context, attrs, 0); 57 | } 58 | 59 | public MultipleStatusView(Context context, AttributeSet attrs, int defStyleAttr) { 60 | super(context, attrs, defStyleAttr); 61 | final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MultipleStatusView, defStyleAttr, 0); 62 | mEmptyViewResId = a.getResourceId(R.styleable.MultipleStatusView_emptyView, R.layout.empty_view); 63 | mErrorViewResId = a.getResourceId(R.styleable.MultipleStatusView_errorView, R.layout.error_view); 64 | mLoadingViewResId = a.getResourceId(R.styleable.MultipleStatusView_loadingView, R.layout.loading_view); 65 | mNoNetworkViewResId = a.getResourceId(R.styleable.MultipleStatusView_noNetworkView, R.layout.no_network_view); 66 | mContentViewResId = a.getResourceId(R.styleable.MultipleStatusView_contentView, NULL_RESOURCE_ID); 67 | a.recycle(); 68 | mInflater = LayoutInflater.from(getContext()); 69 | } 70 | 71 | @Override protected void onFinishInflate() { 72 | super.onFinishInflate(); 73 | showContent(); 74 | } 75 | 76 | @Override protected void onDetachedFromWindow() { 77 | super.onDetachedFromWindow(); 78 | clear(mEmptyView, mLoadingView, mErrorView, mNoNetworkView); 79 | if (null != mOtherIds) { 80 | mOtherIds.clear(); 81 | } 82 | if (null != mOnRetryClickListener) { 83 | mOnRetryClickListener = null; 84 | } 85 | mInflater = null; 86 | } 87 | 88 | /** 89 | * 获取当前状态 90 | */ 91 | public int getViewStatus() { 92 | return mViewStatus; 93 | } 94 | 95 | /** 96 | * 设置重试点击事件 97 | * 98 | * @param onRetryClickListener 重试点击事件 99 | */ 100 | public void setOnRetryClickListener(OnClickListener onRetryClickListener) { 101 | this.mOnRetryClickListener = onRetryClickListener; 102 | } 103 | 104 | /** 105 | * 显示空视图 106 | */ 107 | public final void showEmpty() { 108 | showEmpty(mEmptyViewResId, DEFAULT_LAYOUT_PARAMS); 109 | } 110 | 111 | /** 112 | * 显示空视图 113 | * @param layoutId 自定义布局文件 114 | * @param layoutParams 布局参数 115 | */ 116 | public final void showEmpty(int layoutId, ViewGroup.LayoutParams layoutParams) { 117 | showEmpty(inflateView(layoutId), layoutParams); 118 | } 119 | 120 | /** 121 | * 显示空视图 122 | * @param view 自定义视图 123 | * @param layoutParams 布局参数 124 | */ 125 | public final void showEmpty(View view, ViewGroup.LayoutParams layoutParams) { 126 | checkNull(view, "Empty view is null!"); 127 | mViewStatus = STATUS_EMPTY; 128 | if (null == mEmptyView) { 129 | mEmptyView = view; 130 | View emptyRetryView = mEmptyView.findViewById(R.id.empty_retry_view); 131 | if (null != mOnRetryClickListener && null != emptyRetryView) { 132 | emptyRetryView.setOnClickListener(mOnRetryClickListener); 133 | } 134 | mOtherIds.add(mEmptyView.getId()); 135 | addView(mEmptyView, 0, layoutParams); 136 | } 137 | showViewById(mEmptyView.getId()); 138 | } 139 | 140 | /** 141 | * 显示错误视图 142 | */ 143 | public final void showError() { 144 | showError(mErrorViewResId, DEFAULT_LAYOUT_PARAMS); 145 | } 146 | 147 | /** 148 | * 显示错误视图 149 | * @param layoutId 自定义布局文件 150 | * @param layoutParams 布局参数 151 | */ 152 | public final void showError(int layoutId, ViewGroup.LayoutParams layoutParams) { 153 | showError(inflateView(layoutId), layoutParams); 154 | } 155 | 156 | /** 157 | * 显示错误视图 158 | * @param view 自定义视图 159 | * @param layoutParams 布局参数 160 | */ 161 | public final void showError(View view, ViewGroup.LayoutParams layoutParams) { 162 | checkNull(view, "Error view is null!"); 163 | mViewStatus = STATUS_ERROR; 164 | if (null == mErrorView) { 165 | mErrorView = view; 166 | View errorRetryView = mErrorView.findViewById(R.id.error_retry_view); 167 | if (null != mOnRetryClickListener && null != errorRetryView) { 168 | errorRetryView.setOnClickListener(mOnRetryClickListener); 169 | } 170 | mOtherIds.add(mErrorView.getId()); 171 | addView(mErrorView, 0, layoutParams); 172 | } 173 | showViewById(mErrorView.getId()); 174 | } 175 | 176 | /** 177 | * 显示加载中视图 178 | */ 179 | public final void showLoading() { 180 | showLoading(mLoadingViewResId, DEFAULT_LAYOUT_PARAMS); 181 | } 182 | 183 | /** 184 | * 显示加载中视图 185 | * @param layoutId 自定义布局文件 186 | * @param layoutParams 布局参数 187 | */ 188 | public final void showLoading(int layoutId, ViewGroup.LayoutParams layoutParams) { 189 | showLoading(inflateView(layoutId), layoutParams); 190 | } 191 | 192 | /** 193 | * 显示加载中视图 194 | * @param view 自定义视图 195 | * @param layoutParams 布局参数 196 | */ 197 | public final void showLoading(View view, ViewGroup.LayoutParams layoutParams) { 198 | checkNull(view, "Loading view is null!"); 199 | mViewStatus = STATUS_LOADING; 200 | if (null == mLoadingView) { 201 | mLoadingView = view; 202 | mOtherIds.add(mLoadingView.getId()); 203 | addView(mLoadingView, 0, layoutParams); 204 | } 205 | showViewById(mLoadingView.getId()); 206 | } 207 | 208 | /** 209 | * 显示无网络视图 210 | */ 211 | public final void showNoNetwork() { 212 | showNoNetwork(mNoNetworkViewResId, DEFAULT_LAYOUT_PARAMS); 213 | } 214 | 215 | /** 216 | * 显示无网络视图 217 | * @param layoutId 自定义布局文件 218 | * @param layoutParams 布局参数 219 | */ 220 | public final void showNoNetwork(int layoutId, ViewGroup.LayoutParams layoutParams) { 221 | showNoNetwork(inflateView(layoutId), layoutParams); 222 | } 223 | 224 | /** 225 | * 显示无网络视图 226 | * @param view 自定义视图 227 | * @param layoutParams 布局参数 228 | */ 229 | public final void showNoNetwork(View view, ViewGroup.LayoutParams layoutParams) { 230 | checkNull(view, "No network view is null!"); 231 | mViewStatus = STATUS_NO_NETWORK; 232 | if (null == mNoNetworkView) { 233 | mNoNetworkView = view; 234 | View noNetworkRetryView = mNoNetworkView.findViewById(R.id.no_network_retry_view); 235 | if (null != mOnRetryClickListener && null != noNetworkRetryView) { 236 | noNetworkRetryView.setOnClickListener(mOnRetryClickListener); 237 | } 238 | mOtherIds.add(mNoNetworkView.getId()); 239 | addView(mNoNetworkView, 0, layoutParams); 240 | } 241 | showViewById(mNoNetworkView.getId()); 242 | } 243 | 244 | /** 245 | * 显示内容视图 246 | */ 247 | public final void showContent() { 248 | mViewStatus = STATUS_CONTENT; 249 | if (null == mContentView && mContentViewResId != NULL_RESOURCE_ID) { 250 | mContentView = mInflater.inflate(mContentViewResId, null); 251 | addView(mContentView, 0, DEFAULT_LAYOUT_PARAMS); 252 | } 253 | showContentView(); 254 | } 255 | 256 | private void showContentView() { 257 | final int childCount = getChildCount(); 258 | for (int i = 0; i < childCount; i++) { 259 | View view = getChildAt(i); 260 | view.setVisibility(mOtherIds.contains(view.getId()) ? View.GONE : View.VISIBLE); 261 | } 262 | } 263 | 264 | private View inflateView(int layoutId) { 265 | return mInflater.inflate(layoutId, null); 266 | } 267 | 268 | private void showViewById(int viewId) { 269 | final int childCount = getChildCount(); 270 | for (int i = 0; i < childCount; i++) { 271 | View view = getChildAt(i); 272 | view.setVisibility(view.getId() == viewId ? View.VISIBLE : View.GONE); 273 | } 274 | } 275 | 276 | private void checkNull(Object object, String hint) { 277 | if (null == object) { 278 | throw new NullPointerException(hint); 279 | } 280 | } 281 | 282 | private void clear(View... views) { 283 | if (null == views) { 284 | return; 285 | } 286 | try { 287 | for (View view : views) { 288 | if (null != view) { 289 | removeView(view); 290 | } 291 | } 292 | } catch (Exception e) { 293 | e.printStackTrace(); 294 | } 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /multiple-status-view/src/main/res/layout/empty_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /multiple-status-view/src/main/res/layout/error_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /multiple-status-view/src/main/res/layout/loading_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 13 | -------------------------------------------------------------------------------- /multiple-status-view/src/main/res/layout/no_network_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /multiple-status-view/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /multiple-status-view/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /multiple-status-view/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | multiple-status-view 3 | 4 | 暂无数据 5 | 加载失败 6 | 网络异常 7 | 8 | -------------------------------------------------------------------------------- /multiple-status-view/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':multiple-status-view' 2 | 3 | -------------------------------------------------------------------------------- /show/gank_type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/show/gank_type.png -------------------------------------------------------------------------------- /show/gankapp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/show/gankapp.gif -------------------------------------------------------------------------------- /show/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/show/home.png -------------------------------------------------------------------------------- /show/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/show/img.png -------------------------------------------------------------------------------- /show/mine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/show/mine.png -------------------------------------------------------------------------------- /show/type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayGengi/KotlinGankApp/8dd5d503160f7fbe4d1dd3313b3dab4e78446b6c/show/type.png --------------------------------------------------------------------------------