Repository: malonecoder/Awesome-Kotlin-WanAndroid Branch: master Commit: 2bf7cfa3624f Files: 192 Total size: 419.2 KB Directory structure: gitextract_0t5_wqvy/ ├── .gitignore ├── .idea/ │ ├── codeStyles/ │ │ ├── Project.xml │ │ └── codeStyleConfig.xml │ ├── compiler.xml │ ├── encodings.xml │ ├── gradle.xml │ ├── inspectionProfiles/ │ │ └── Project_Default.xml │ ├── misc.xml │ ├── runConfigurations.xml │ └── vcs.xml ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── lxm/ │ │ └── wanandroid/ │ │ └── ExampleInstrumentedTest.kt │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── lxm/ │ │ │ └── wanandroid/ │ │ │ ├── MainActivity.kt │ │ │ ├── repository/ │ │ │ │ ├── model/ │ │ │ │ │ ├── ArticleResponse.kt │ │ │ │ │ ├── Banner.kt │ │ │ │ │ ├── HttpResponse.kt │ │ │ │ │ ├── Listing.kt │ │ │ │ │ ├── LoginBean.kt │ │ │ │ │ ├── Navigation.kt │ │ │ │ │ ├── Resource.kt │ │ │ │ │ ├── TreeBean.kt │ │ │ │ │ └── WelfareResponse.kt │ │ │ │ └── remote/ │ │ │ │ ├── LoginResposity.kt │ │ │ │ └── httpClient/ │ │ │ │ ├── API.kt │ │ │ │ └── RetrofitClient.kt │ │ │ ├── ui/ │ │ │ │ ├── ArticleFragment.kt │ │ │ │ ├── CollectActivity.kt │ │ │ │ ├── LoginActivity.kt │ │ │ │ ├── NavigationFragment.kt │ │ │ │ ├── ProjectFragment.kt │ │ │ │ ├── TagArticleActivity.kt │ │ │ │ ├── TreeFragment.kt │ │ │ │ ├── ViewBigImageActivity.kt │ │ │ │ ├── WelfareFragment.kt │ │ │ │ ├── adapter/ │ │ │ │ │ ├── ArticleAdapter.kt │ │ │ │ │ ├── NaviAdapter.kt │ │ │ │ │ ├── TreeAdapter.kt │ │ │ │ │ └── WelfareAdapter.kt │ │ │ │ └── base/ │ │ │ │ ├── BaseRecyclerAdapter.kt │ │ │ │ ├── BaseRecyclerViewHoder.kt │ │ │ │ ├── OnItemClickListener.kt │ │ │ │ └── OnItemLongClickListener.kt │ │ │ ├── utils/ │ │ │ │ ├── CookieUtils.kt │ │ │ │ ├── GlideUtil.kt │ │ │ │ └── webview/ │ │ │ │ ├── WebViewActivity.java │ │ │ │ └── config/ │ │ │ │ ├── FullscreenHolder.java │ │ │ │ ├── IWebPageView.java │ │ │ │ ├── ImageClickInterface.java │ │ │ │ ├── MyWebChromeClient.java │ │ │ │ └── MyWebViewClient.java │ │ │ └── viewmodel/ │ │ │ ├── ArticleViewModel.kt │ │ │ ├── CategoryViewModel.kt │ │ │ ├── CollectViewModel.kt │ │ │ ├── LoginViewModel.kt │ │ │ ├── NaviViewModelView.kt │ │ │ ├── ProjectViewModel.kt │ │ │ ├── TreeViewModel.kt │ │ │ └── WelfareModelView.kt │ │ └── res/ │ │ ├── drawable/ │ │ │ ├── ic_launcher_background.xml │ │ │ ├── process_bg.xml │ │ │ ├── selector_bg_tree_tag.xml │ │ │ └── selector_collect_check.xml │ │ ├── drawable-v24/ │ │ │ └── ic_launcher_foreground.xml │ │ ├── layout/ │ │ │ ├── activity_collection.xml │ │ │ ├── activity_login.xml │ │ │ ├── activity_main.xml │ │ │ ├── activity_show_image.xml │ │ │ ├── activity_web_view.xml │ │ │ ├── article_banner.xml │ │ │ ├── article_fragment.xml │ │ │ ├── category_activity.xml │ │ │ ├── drawer_header_layout.xml │ │ │ ├── item_article.xml │ │ │ ├── item_banner.xml │ │ │ ├── item_image_view_pager.xml │ │ │ ├── item_tree.xml │ │ │ ├── item_tree_tag.xml │ │ │ ├── item_view.xml │ │ │ ├── item_view_welfare.xml │ │ │ ├── main_content.xml │ │ │ └── tree_fragment.xml │ │ ├── menu/ │ │ │ ├── main.xml │ │ │ ├── navigation.xml │ │ │ └── webview_menu.xml │ │ ├── mipmap-anydpi-v26/ │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ └── values/ │ │ ├── attrs_circle_view.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test/ │ └── java/ │ └── com/ │ └── lxm/ │ └── wanandroid/ │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── module_library/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── lxm/ │ │ └── module_library/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── lxm/ │ │ │ └── module_library/ │ │ │ ├── anim/ │ │ │ │ ├── AnimManager.kt │ │ │ │ ├── AnimUtils.kt │ │ │ │ └── ToolbarAnimManager.kt │ │ │ ├── base/ │ │ │ │ ├── BaseActivity.kt │ │ │ │ ├── BaseFragment.kt │ │ │ │ ├── BaseViewModel.kt │ │ │ │ └── NoViewModel.java │ │ │ ├── global/ │ │ │ │ ├── AppManager.java │ │ │ │ └── GlobalApplication.java │ │ │ ├── helper/ │ │ │ │ ├── RetrofitCreateHelper.java │ │ │ │ ├── RxHelper.java │ │ │ │ └── okhttp/ │ │ │ │ ├── TrustManager.java │ │ │ │ ├── cache/ │ │ │ │ │ ├── CacheInterceptor.java │ │ │ │ │ └── HttpCache.java │ │ │ │ └── cookies/ │ │ │ │ ├── CookieManger.java │ │ │ │ ├── OkHttpCookies.java │ │ │ │ └── PersistentCookieStore.java │ │ │ ├── materialLogin/ │ │ │ │ ├── DefaultLoginView.java │ │ │ │ ├── DefaultRegisterView.java │ │ │ │ ├── MaterialLoginView.java │ │ │ │ └── RegisterView.java │ │ │ ├── statusbar/ │ │ │ │ ├── StatusBarUtil.java │ │ │ │ └── StatusBarView.java │ │ │ ├── utils/ │ │ │ │ ├── AppUtils.kt │ │ │ │ ├── BaseTools.java │ │ │ │ ├── BitmapUtils.kt │ │ │ │ ├── CheckNetwork.java │ │ │ │ ├── ClassUtil.kt │ │ │ │ ├── DateUtils.kt │ │ │ │ ├── DialogUtils.kt │ │ │ │ ├── DisplayUtils.kt │ │ │ │ ├── FileUtils.kt │ │ │ │ ├── HtmlUtils.kt │ │ │ │ ├── HttpUtils.kt │ │ │ │ ├── ImageUtils.kt │ │ │ │ ├── JsonUtils.kt │ │ │ │ ├── MD5Utils.kt │ │ │ │ ├── MyDividerItemDecoration.java │ │ │ │ ├── NetworkConnectionUtils.kt │ │ │ │ ├── PreferencesUtil.kt │ │ │ │ ├── RefreshHelper.kt │ │ │ │ ├── ResourcesUtils.kt │ │ │ │ ├── ScreenAdapterUtils.kt │ │ │ │ ├── ScreenUtils.kt │ │ │ │ ├── SnackbarUtils.kt │ │ │ │ ├── StatusBarUtils.kt │ │ │ │ ├── StringUtils.kt │ │ │ │ ├── TimestampUtils.kt │ │ │ │ ├── ToastUtil.java │ │ │ │ ├── ToastUtils.kt │ │ │ │ ├── UnicodeUtils.kt │ │ │ │ └── WifiAutoConnectManager.kt │ │ │ └── xrecycleview/ │ │ │ ├── BaseRefreshHeader.java │ │ │ ├── LoadingMoreFooter.java │ │ │ ├── WrapAdapter.java │ │ │ ├── XRecyclerView.java │ │ │ └── YunRefreshHeader.java │ │ └── res/ │ │ ├── drawable/ │ │ │ ├── app_loading_anim.xml │ │ │ ├── common_progress_cirle.xml │ │ │ ├── header_loading_anim.xml │ │ │ ├── ic_add.xml │ │ │ ├── ic_clear.xml │ │ │ ├── login_back.xml │ │ │ └── shape_line.xml │ │ ├── layout/ │ │ │ ├── activity_base.xml │ │ │ ├── custom_login_view.xml │ │ │ ├── custom_register_view.xml │ │ │ ├── default_login_view.xml │ │ │ ├── default_register_view.xml │ │ │ ├── fragment_base.xml │ │ │ ├── layout_loading_error.xml │ │ │ ├── layout_loading_view.xml │ │ │ ├── login_layout.xml │ │ │ ├── login_view.xml │ │ │ ├── refresh_footer.xml │ │ │ ├── refresh_header.xml │ │ │ └── register_layout.xml │ │ ├── values/ │ │ │ ├── attrs.xml │ │ │ ├── colors.xml │ │ │ ├── dimen.xml │ │ │ ├── drawables.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── values-w600dp/ │ │ └── dimen.xml │ └── test/ │ └── java/ │ └── com/ │ └── lxm/ │ └── module_library/ │ └── ExampleUnitTest.java └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.iml .gradle /local.properties /.idea/caches /.idea/libraries /.idea/modules.xml /.idea/workspace.xml /.idea/navEditor.xml /.idea/assetWizardSettings.xml .DS_Store /build /captures .externalNativeBuild ================================================ FILE: .idea/codeStyles/Project.xml ================================================
xmlns:android ^$
xmlns:.* ^$ BY_NAME
.*:id http://schemas.android.com/apk/res/android
.*:name http://schemas.android.com/apk/res/android
name ^$
style ^$
.* ^$ BY_NAME
.* http://schemas.android.com/apk/res/android ANDROID_ATTRIBUTE_ORDER
.* .* BY_NAME
================================================ FILE: .idea/codeStyles/codeStyleConfig.xml ================================================ ================================================ FILE: .idea/compiler.xml ================================================ ================================================ FILE: .idea/encodings.xml ================================================ ================================================ FILE: .idea/gradle.xml ================================================ ================================================ FILE: .idea/inspectionProfiles/Project_Default.xml ================================================ ================================================ FILE: .idea/misc.xml ================================================ ================================================ FILE: .idea/runConfigurations.xml ================================================ ================================================ FILE: .idea/vcs.xml ================================================ ================================================ FILE: README.md ================================================ # Awesome-Kotlin-WanAndroid ## 简介 项目主要用来入门`kotlin`语言和google推荐的`MVVM`架构,使用了一些`Jetpack`组件来配合MVVM使用,采用了很多流行的开源库,`Okhttp`,`Retrofit`,`Rxjava`,`Glide`,`Livedatabus`等,代码逻辑简单易懂,仅适用入门 ## 项目功能列表截图 ## 功能TODO List - [x] 首页 - [x] 知识体系 - [x] 导航 - [x] 项目 - [x] 福利 - [x] 登录/登出 - [x] 我的收藏 - [ ] 欢迎页 - [ ] 热门搜索 - [ ] 待办清单功能 - [ ] 网络层切换协程 - [ ] 代码整体优化 ## 使用的开源库 * [Retrofit](https://github.com/square/retrofit) * [Okhttp](https://github.com/square/okhttp) * [Rxjava](https://github.com/ReactiveX/RxJava) * [Glide](https://github.com/bumptech/glide) * [LiveDataBus](https://github.com/JeremyLiao/LiveEventBus) * [glide-transformations](https://github.com/wasabeef/glide-transformations) * [toastcompat](https://github.com/PureWriter/ToastCompat) * [circleimageview](https://github.com/hdodenhof/CircleImageView) * [PhotoView](https://github.com/chrisbanes/PhotoView) * [flowlayout](https://github.com/hongyangAndroid/FlowLayout) * [MZBannerView](https://github.com/pinguo-zhouwei/MZBannerView) * [MaterialLogin](https://github.com/shem8/MaterialLogin) * [AgentWeb](https://github.com/Justson/AgentWeb) ## 非常感谢 * [CloudReader](https://github.com/youlookwhat/CloudReader) * [WanAndroid Open API](https://www.wanandroid.com/blog/show/2) * [Gan Open API](https://gank.io/) * [界面参考](https://flutterchina.club/app/gm.html) * [ICON素材](https://www.iconfont.cn/) ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 28 defaultConfig { applicationId "com.lxm.wanandroid" minSdkVersion 21 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility = '1.8' targetCompatibility = '1.8' } dataBinding { enabled = true } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' implementation 'com.android.support:design:28.0.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' implementation project(':module_library') implementation 'com.google.android.gms:play-services-plus:15.0.1' implementation 'com.flyco.tablayout:FlycoTabLayout_Lib:2.1.2@aar' implementation 'android.arch.lifecycle:extensions:1.1.1' implementation 'com.android.support:design:28.0.0-rc01' implementation 'com.just.agentweb:agentweb:4.0.2' // Navigation def nav_version = "1.0.0-alpha06" implementation "android.arch.navigation:navigation-fragment:$nav_version" implementation "android.arch.navigation:navigation-ui:$nav_version" androidTestImplementation "android.arch.navigation:navigation-testing:$nav_version" implementation "org.jetbrains.kotlin:kotlin-reflect:1.2.60" //Room def room_version = "1.1.1" implementation "android.arch.persistence.room:runtime:$room_version" kapt "android.arch.persistence.room:compiler:$room_version" implementation "android.arch.persistence.room:rxjava2:$room_version" implementation "android.arch.persistence.room:guava:$room_version" testImplementation "android.arch.persistence.room:testing:$room_version" //首页banner implementation 'com.github.pinguo-zhouwei:MZBannerView:v2.0.2' //体系flowlayout implementation 'com.hyman:flowlayout-lib:1.1.2' //大图查看 implementation 'com.github.chrisbanes:PhotoView:1.2' //圆形头像 implementation 'de.hdodenhof:circleimageview:3.0.0' //livedataBus implementation 'com.jeremyliao:live-event-bus:1.4.4' } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: app/src/androidTest/java/com/lxm/wanandroid/ExampleInstrumentedTest.kt ================================================ package com.lxm.wanandroid import android.support.test.InstrumentationRegistry import android.support.test.runner.AndroidJUnit4 import org.junit.Test import org.junit.runner.RunWith import org.junit.Assert.* /** * Instrumented test, which will execute on an Android device. * * See [testing documentation](http://d.android.com/tools/testing). */ @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { @Test fun useAppContext() { // Context of the app under test. val appContext = InstrumentationRegistry.getTargetContext() assertEquals("com.lxm.wanandroid", appContext.packageName) } } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/com/lxm/wanandroid/MainActivity.kt ================================================ package com.lxm.wanandroid import android.arch.lifecycle.Observer import android.arch.lifecycle.ViewModel import android.arch.lifecycle.ViewModelProvider import android.arch.lifecycle.ViewModelProviders import android.content.Intent import android.os.Bundle import android.support.v4.app.Fragment import android.support.v4.app.FragmentManager import android.support.v4.app.FragmentPagerAdapter import android.support.v7.app.AppCompatActivity import android.view.Gravity import android.view.KeyEvent import android.view.Menu import android.view.MenuItem import android.widget.TextView import com.jeremyliao.liveeventbus.LiveEventBus import com.lxm.module_library.helper.okhttp.cookies.CookieManger import com.lxm.module_library.statusbar.StatusBarUtil import com.lxm.module_library.utils.PreferencesUtil import com.lxm.module_library.utils.ToastUtils import com.lxm.wanandroid.repository.remote.LoginRepository import com.lxm.wanandroid.ui.* import com.lxm.wanandroid.viewmodel.LoginViewModel import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.main_content.* const val PHOTO_URL = "https://cdn.duitang.com/uploads/blog/201404/22/20140422142715_8GtUk.thumb.600_0.jpeg" class MainActivity : AppCompatActivity() { private val mTitles by lazy { mutableListOf("主页", "知识体系", "导航", "项目", "福利") } private val mFragments: MutableList by lazy { mutableListOf() } private val mAdapter: MyPagerAdapter by lazy { MyPagerAdapter(supportFragmentManager) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) StatusBarUtil.setColor(this, resources.getColor(R.color.colorTheme), 0) setSupportActionBar(toolbar) supportActionBar?.setDisplayHomeAsUpEnabled(false) iv_photo.setOnClickListener { drawerLayout.openDrawer(Gravity.START) } var loginViewModel = ViewModelProviders.of(this@MainActivity, object : ViewModelProvider.Factory { override fun create(modelClass: Class): T { return LoginViewModel(LoginRepository()) as T } })[LoginViewModel::class.java] var textView = navigationView.getHeaderView(0).findViewById(R.id.tvUserName) textView.text = "萧兮易水寒" val menuItem = navigationView.menu.findItem(R.id.accout) as MenuItem var isLogin: Boolean by PreferencesUtil("login", false) if (isLogin) { menuItem.title = "注销" menuItem.setIcon(R.drawable.ic_logout_menu) } else { menuItem.title = "登陆" menuItem.setIcon(R.drawable.ic_login_menu) } LiveEventBus.get() .with(LoginActivity.LOGIN_SUCCESS, String::class.java) .observe(this@MainActivity, Observer { menuItem.title = "注销" menuItem.setIcon(R.drawable.ic_logout_menu) }) navigationView.setNavigationItemSelectedListener { when (it.itemId) { R.id.colletion -> { if (isLogin) { val intent = Intent(this@MainActivity, CollectActivity::class.java) startActivity(intent) } else { val intent = Intent(this@MainActivity, LoginActivity::class.java) startActivity(intent) } } R.id.about -> { } R.id.accout -> { if (isLogin) { loginViewModel.logout() loginViewModel.logoutStatus.observe(this@MainActivity, Observer { if (it?.errorCode == 0) { //退出成功 清空cookie isLogin = false CookieManger.clearAllCookies() menuItem.title = "登陆" menuItem.setIcon(R.drawable.ic_login_menu) ToastUtils.showToast("注销成功") } }) } else { val intent = Intent(this@MainActivity, LoginActivity::class.java) startActivity(intent) } } else -> { } } drawerLayout.closeDrawer(Gravity.START) true } mFragments.run { add(ArticleFragment.getInstance()) add(TreeFragment.getInstance()) add(NavigationFragment.getInstance()) add(ProjectFragment.getInstance()) add(WelfareFragment.getInstance()) } mAdapter.setFragments(mFragments) mAdapter.setTitles(mTitles) view_pager.adapter = mAdapter view_pager.offscreenPageLimit = 5 tablayout.setViewPager(view_pager) } override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.main, menu) return true } override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.action_search -> { true } else -> super.onOptionsItemSelected(item) } } override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { if (keyCode == KeyEvent.KEYCODE_BACK) { if (drawerLayout.isDrawerOpen(Gravity.START)) { drawerLayout.closeDrawer(Gravity.START) return true } } return super.onKeyDown(keyCode, event) } } class MyPagerAdapter(fragmentManager: FragmentManager) : FragmentPagerAdapter(fragmentManager) { var fragmentList: MutableList = mutableListOf() var titleList: MutableList = mutableListOf() override fun getCount(): Int { return fragmentList.size } override fun getItem(p0: Int): Fragment { return fragmentList[p0] } override fun getPageTitle(position: Int): CharSequence? { return titleList[position] } fun setFragments(fragment: MutableList) { fragmentList = fragment; } fun setTitles(mTitles: MutableList) { titleList = mTitles } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/repository/model/ArticleResponse.kt ================================================ package com.lxm.wanandroid.repository.model /** * curPage : 1 * datas : [{"apkLink":"","author":"红橙Darren","chapterId":245,"chapterName":"集合相关","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":3326,"link":"https://www.jianshu.com/p/9edd74769f21","niceDate":"2018-08-27","origin":"","projectLink":"","publishTime":1535372956000,"superChapterId":245,"superChapterName":"Java深入","tags":[],"title":"数据结构算法 - 栈和队列","type":0,"userId":-1,"visible":1,"zan":0}] * offset : 0 * over : false * pageCount : 80 * size : 20 * total : 1596 */ data class ArticleResponseBody(var curPage: Int = 0, var offset: Int = 0, var isOver: Boolean = false, var pageCount: Int = 0, var size: Int = 0, var total: Int = 0, var datas: List? = null) /** * apkLink : * author : 红橙Darren * chapterId : 245 * chapterName : 集合相关 * collect : false * courseId : 13 * desc : * envelopePic : * fresh : false * id : 3326 * link : https://www.jianshu.com/p/9edd74769f21 * niceDate : 2018-08-27 * origin : * projectLink : * publishTime : 1535372956000 * superChapterId : 245 * superChapterName : Java深入 * tags : [] * title : 数据结构算法 - 栈和队列 * type : 0 * userId : -1 * visible : 1 * zan : 0 */ data class ArticleBean(var apkLink: String? = null, var author: String? = null, var chapterId: Int = 0, var chapterName: String? = null, var collect: Boolean = false, var courseId: Int = 0, var desc: String? = null, var envelopePic: String? = null, var fresh: Boolean = false, var id: Int = 0, var link: String? = null, var niceDate: String? = null, var origin: String? = null, var originId: Int? = null, var projectLink: String? = null, var publishTime: Long = 0, var superChapterId: Int = 0, var superChapterName: String? = null, var title: String? = null, var type: Int = 0, var userId: Int = 0, var visible: Int = 0, var zan: Int = 0, var tags: List? = null) data class Project(var name:String?,var url:String?) ================================================ FILE: app/src/main/java/com/lxm/wanandroid/repository/model/Banner.kt ================================================ package com.lxm.wanandroid.repository.model data class Banner( val desc: String, val id: Int, val imagePath: String, val isVisible: Int, val order: Int, val title: String, val type: Int, val url: String ) ================================================ FILE: app/src/main/java/com/lxm/wanandroid/repository/model/HttpResponse.kt ================================================ package com.lxm.wanandroid.repository.model /** * data : {"curPage":1,"datas":[{"apkLink":"","author":"红橙Darren","chapterId":245,"chapterName":"集合相关","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":3326,"link":"https://www.jianshu.com/p/9edd74769f21","niceDate":"2018-08-27","origin":"","projectLink":"","publishTime":1535372956000,"superChapterId":245,"superChapterName":"Java深入","tags":[],"title":"数据结构算法 - 栈和队列","type":0,"userId":-1,"visible":1,"zan":0}],"offset":0,"over":false,"pageCount":80,"size":20,"total":1596} * errorCode : 0 * errorMsg : */ data class HttpResponse( var data: T? = null, var errorCode: Int = 0, var errorMsg: String? = null ) ================================================ FILE: app/src/main/java/com/lxm/wanandroid/repository/model/Listing.kt ================================================ package com.lxm.wanandroid.repository.model import android.arch.lifecycle.MutableLiveData data class Listing( val pagedList: MutableLiveData, val loadStatus: MutableLiveData> ) ================================================ FILE: app/src/main/java/com/lxm/wanandroid/repository/model/LoginBean.kt ================================================ package com.lxm.wanandroid.repository.model data class LoginBean( val admin: Boolean, val chapterTops: List, val collectIds: List, val email: String, val icon: String, val id: Int, val nickname: String, val password: String, val token: String, val type: Int, val username: String ) ================================================ FILE: app/src/main/java/com/lxm/wanandroid/repository/model/Navigation.kt ================================================ package com.lxm.wanandroid.repository.model data class Navigation( val articles: List, val cid: Int, val name: String ) ================================================ FILE: app/src/main/java/com/lxm/wanandroid/repository/model/Resource.kt ================================================ package com.lxm.wanandroid.repository.model /** * Created by juan on 2018/05/23. */ data class Resource(val status: Status, val data: T?, val message: String?) { companion object { fun loading(msg: String? = null, data: T? = null): Resource { return Resource(Status.LOADING, data, msg) } fun success(data: T? = null): Resource { return Resource(Status.SUCCESS, data, null) } fun error(msg: String? = null, data: T? = null): Resource { return Resource(Status.ERROR, data, msg) } } } enum class Status { SUCCESS, ERROR, LOADING } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/repository/model/TreeBean.kt ================================================ package com.lxm.wanandroid.repository.model import java.io.Serializable data class TreeBean( val children: List, val courseId: Int, val id: Int, val name: String, val order: Int, val parentChapterId: Int, val userControlSetTop: Boolean, val visible: Int ):Serializable ================================================ FILE: app/src/main/java/com/lxm/wanandroid/repository/model/WelfareResponse.kt ================================================ package com.lxm.wanandroid.repository.model data class WelfareResponse( val error: Boolean, val results: List ) data class Welfare( val _id: String, val createdAt: String, val desc: String, val publishedAt: String, val source: String, val type: String, val url: String, val used: Boolean, val who: String ) ================================================ FILE: app/src/main/java/com/lxm/wanandroid/repository/remote/LoginResposity.kt ================================================ package com.lxm.wanandroid.repository.remote import android.arch.lifecycle.MutableLiveData import com.lxm.module_library.helper.RxHelper import com.lxm.wanandroid.repository.model.HttpResponse import com.lxm.wanandroid.repository.model.LoginBean import com.lxm.wanandroid.repository.remote.httpClient.RetrofitClient import io.reactivex.functions.Consumer class LoginRepository { val login = MutableLiveData>() val register = MutableLiveData>() val logout = MutableLiveData>() fun login(account: String, password: String) { RetrofitClient.getInstance(RetrofitClient.WAN_BASE_URL).login(account, password) .compose(RxHelper.rxSchedulerHelper()) .subscribe(Consumer { login.value = it }, Consumer { login.value = HttpResponse(null, 500, it.message!!) }) } fun register(account: String, password: String, rPassword: String) { RetrofitClient.getInstance(RetrofitClient.WAN_BASE_URL).register(account, password, rPassword) .compose(RxHelper.rxSchedulerHelper()) .subscribe(Consumer { register.value = it }, Consumer { register.value = HttpResponse(null, 500, it.message!!) }) } fun logout() { RetrofitClient.getInstance(RetrofitClient.WAN_BASE_URL).logout() .compose(RxHelper.rxSchedulerHelper()) .subscribe(Consumer { logout.value = it }, Consumer { logout.value = HttpResponse(null, 500, it.message!!) }) } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/repository/remote/httpClient/API.kt ================================================ package com.lxm.wanandroid.repository.remote.httpClient import com.lxm.wanandroid.repository.model.* import io.reactivex.Observable import retrofit2.http.* interface API{ /* * * 获取首页新闻列表 * * */ @GET("/article/list/{page}/json") fun getArticleList(@Path(value = "page")page:Int):Observable>> /* * * 获取首页Banner * * */ @GET("/banner/json") fun getHomeBanner():Observable>> /* * * 获取知识体系 * * */ @GET("/tree/json") fun getTrees():Observable>> /* * * 获取知识体系目录 * /article/list/0/json?cid=60 * */ @GET("/article/list/{page}/json") fun getCategory(@Path(value = "page")page:Int,@Query(value = "cid")cid:Int):Observable>> /* * * 获取知识体系目录 * /navi/json * */ @GET("/navi/json") fun getNavigation():Observable>> /* * * 获取Gan福利 * /navi/json * */ @GET("/api/data/{type}/{page_size}/{page}") fun getWelfare(@Path("type") id: String, @Path("page_size") page_size: Int,@Path("page") page: Int): Observable /* * * 获取项目列表 * * */ @GET("/article/listproject/{page}/json") fun getProjectList(@Path(value = "page")page:Int):Observable>> /** * 登录 */ @FormUrlEncoded @POST("/user/login") fun login(@Field(value = "username")username: String, @Field(value = "password")password: String): Observable> /** * 注销 * https://www.wanandroid.com/user/logout/json */ @GET("/user/logout/json") fun logout():Observable> /** * 注册 */ @FormUrlEncoded @POST("/user/register") fun register(@Field(value = "username")account: String, @Field(value = "password")password: String, @Field(value = "repassword")rPassword: String): Observable> /** * 我的收藏列表 * lg/collect/list/0/json */ @GET("/lg/collect/list/{page}/json") fun getCollectList(@Path(value = "page")page:Int):Observable>> /** * 收藏 * https://www.wanandroid.com/lg/collect/1165/json */ @POST("/lg/collect/{id}/json") fun collect(@Path(value = "id")id:Int):Observable> /** * 取消收藏 * https://www.wanandroid.com/lg/uncollect_originId/2333/json */ @POST("/lg/uncollect_originId/{id}/json") fun unCollect(@Path(value = "id")id:Int):Observable> } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/repository/remote/httpClient/RetrofitClient.kt ================================================ package com.lxm.wanandroid.repository.remote.httpClient import com.lxm.module_library.helper.RetrofitCreateHelper object RetrofitClient { const val WAN_BASE_URL = "https://www.wanandroid.com" const val GAN_BASE_URL = "https://gank.io" private var api: API? = null fun getInstance(type: String): API { api = RetrofitCreateHelper.createApi(API::class.java, type) return api!! } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/ui/ArticleFragment.kt ================================================ package com.lxm.wanandroid.ui import android.arch.lifecycle.Observer import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.widget.ImageView import com.lxm.module_library.base.BaseFragment import com.lxm.module_library.utils.RefreshHelper import com.lxm.module_library.xrecycleview.XRecyclerView import com.lxm.wanandroid.R import com.lxm.wanandroid.repository.model.ArticleBean import com.lxm.wanandroid.repository.model.Banner import com.lxm.wanandroid.repository.model.Status import com.lxm.wanandroid.ui.adapter.ArticleAdapter import com.lxm.wanandroid.ui.base.OnItemClickListener import com.lxm.wanandroid.utils.GlideUtil import com.lxm.wanandroid.utils.webview.WebViewActivity import com.lxm.wanandroid.viewmodel.ArticleViewModel import com.zhouwei.mzbanner.holder.MZViewHolder import kotlinx.android.synthetic.main.article_banner.* import kotlinx.android.synthetic.main.article_fragment.* class ArticleFragment : BaseFragment() { private lateinit var headerView: View private var bannerList : List? = null private val mAdapter: ArticleAdapter by lazy { ArticleAdapter() } override fun getLayoutID(): Int { return R.layout.article_fragment } companion object { fun getInstance(): ArticleFragment { return ArticleFragment() } } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) showContentView() initView() } private fun initView() { swipeLayout.setOnRefreshListener { viewModel.mPage = 0 loadData() } swipeLayout.isRefreshing = true RefreshHelper.init(recyclerView, false) headerView = layoutInflater.inflate(R.layout.article_banner, null) recyclerView.addHeaderView(headerView) recyclerView.adapter = mAdapter recyclerView.setLoadingListener(object : XRecyclerView.LoadingListener { override fun onLoadMore() { viewModel.mPage = viewModel.mPage + 1; getHomeList() } override fun onRefresh() { } }) viewModel.loadStatus.observe(this, Observer { when (it?.status) { Status.ERROR -> showError() Status.SUCCESS -> showContentView() } }) viewModel.pagedList.observe(this, Observer { swipeLayout.isRefreshing = false if (it == null) { return@Observer } if(viewModel.mPage ==0){ mAdapter.setData(it.data?.datas!!) return@Observer } mAdapter.addDataAll(it.data?.datas!!) if(it?.data?.datas?.size!! < it?.data?.size!!){ recyclerView.noMoreLoading() }else{ recyclerView.refreshComplete() } }) mAdapter.setOnItemClickListener(object : OnItemClickListener { override fun onClick(t: ArticleBean, position: Int) { WebViewActivity.loadUrl(activity, t.link, t.title) } }) } private fun getBanners() { viewModel.getBanners().observe(this@ArticleFragment, Observer { bannerList = it?.data banner.setBannerPageClickListener { view, i -> WebViewActivity.loadUrl(activity, it?.data?.get(i)?.url,null) } banner.setPages( it?.data as List? ) { BannerViewHolder() } banner.start() }) } class BannerViewHolder : MZViewHolder { private var mImageView: ImageView? = null override fun createView(context: Context): View { val view = LayoutInflater.from(context).inflate(R.layout.item_banner, null) mImageView = view.findViewById(R.id.banner_image) as ImageView return view } override fun onBind(context: Context, position: Int, data: Banner?) { data?.let { GlideUtil.displayCircleCorner(mImageView!!, it.imagePath) } } } override fun onRetry() { loadData() } override fun loadData() { getHomeList() getBanners() } private fun getHomeList() { this.viewModel.getHomeList() } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/ui/CollectActivity.kt ================================================ package com.lxm.wanandroid.ui import android.arch.lifecycle.Observer import android.os.Bundle import com.lxm.module_library.base.BaseActivity import com.lxm.module_library.utils.RefreshHelper import com.lxm.module_library.xrecycleview.XRecyclerView import com.lxm.wanandroid.R import com.lxm.wanandroid.repository.model.ArticleBean import com.lxm.wanandroid.repository.model.Status import com.lxm.wanandroid.ui.adapter.ArticleAdapter import com.lxm.wanandroid.ui.base.OnItemClickListener import com.lxm.wanandroid.utils.webview.WebViewActivity import com.lxm.wanandroid.viewmodel.CollectViewModel import kotlinx.android.synthetic.main.article_fragment.* class CollectActivity : BaseActivity() { private val mAdapter: ArticleAdapter by lazy { ArticleAdapter() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.article_fragment) setTitle("我的收藏") initView() getCollection() } private fun initView() { swipeLayout.setOnRefreshListener { swipeLayout.isRefreshing = true viewModel.mPage = 0 getCollection() } RefreshHelper.init(recyclerView, false) mAdapter.isCollectionList = true recyclerView.adapter = mAdapter recyclerView.setLoadingListener(object : XRecyclerView.LoadingListener { override fun onLoadMore() { viewModel.mPage = viewModel.mPage + 1; getCollection() } override fun onRefresh() { } }) showLoading() viewModel.loadStatus.observe(this, Observer { when (it?.status) { // Status.ERROR -> showError() Status.SUCCESS -> showContentView() } }) this.viewModel.collectionList.observe(this, Observer { swipeLayout.isRefreshing = false if (it == null) { return@Observer } if(viewModel.mPage ==0){ mAdapter.setData(it.data?.datas!!) return@Observer } mAdapter.addDataAll(it.data?.datas!!) if(it?.data?.datas?.size!! < it?.data?.size!!){ recyclerView.noMoreLoading() }else{ recyclerView.refreshComplete() } }) mAdapter.setOnItemClickListener(object : OnItemClickListener { override fun onClick(t: ArticleBean, position: Int) { WebViewActivity.loadUrl(this@CollectActivity, t.link, t.title) } }) } private fun getCollection() { viewModel.getCollect() } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/ui/LoginActivity.kt ================================================ package com.lxm.wanandroid.ui import android.arch.lifecycle.Observer import android.arch.lifecycle.ViewModel import android.arch.lifecycle.ViewModelProvider import android.arch.lifecycle.ViewModelProviders import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.text.TextUtils import android.view.View import android.widget.Toast import com.jeremyliao.liveeventbus.LiveEventBus import com.lxm.module_library.materialLogin.DefaultLoginView import com.lxm.module_library.materialLogin.DefaultRegisterView import com.lxm.module_library.materialLogin.MaterialLoginView import com.lxm.module_library.statusbar.StatusBarUtil import com.lxm.module_library.utils.PreferencesUtil import com.lxm.module_library.utils.ToastUtils import com.lxm.wanandroid.R import com.lxm.wanandroid.repository.remote.LoginRepository import com.lxm.wanandroid.viewmodel.LoginViewModel import kotlinx.android.synthetic.main.main_content.* class LoginActivity : AppCompatActivity() { companion object{ const val LOGIN_SUCCESS = "loginSuccess" } private lateinit var model: LoginViewModel private lateinit var login: MaterialLoginView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) initToolbar() initView() initData() } private fun initData() { model = ViewModelProviders.of(this@LoginActivity, object : ViewModelProvider.Factory { override fun create(modelClass: Class): T { return LoginViewModel(LoginRepository()) as T } })[LoginViewModel::class.java] model.apply { loginStatus.observe(this@LoginActivity, Observer { if (it?.errorCode == 0) { //存储账号和cookie等信息 ToastUtils.showToast("登录成功", Toast.LENGTH_LONG) var isLogin: Boolean by PreferencesUtil("login", false) var userName: String by PreferencesUtil("userName", "Android") var nikeName: String by PreferencesUtil("nikeName", "易水寒") isLogin = true userName = it.data?.username!! nikeName = it.data?.nickname!! LiveEventBus.get() .with(LOGIN_SUCCESS) .post("登录成功") finish() } else { ToastUtils.showToast(it?.errorMsg!!, Toast.LENGTH_LONG) } }) registerStauts.observe(this@LoginActivity, Observer { if (it?.errorCode == 0) { login.animateLogin() ToastUtils.showToast("注册成功,请登录", Toast.LENGTH_LONG) } else { ToastUtils.showToast(it?.errorMsg!!, Toast.LENGTH_LONG) } }) } } private fun initView() { login = findViewById(R.id.login) as MaterialLoginView (login.loginView as DefaultLoginView).setListener { loginUser, loginPass -> if (!TextUtils.isEmpty(loginUser.editText?.text) && !TextUtils.isEmpty(loginPass.editText?.text)) { model.login(loginUser.editText?.text.toString(), loginPass.editText?.text.toString()) } else { ToastUtils.showToast("用户名和密码不能为空", Toast.LENGTH_SHORT) } } (login.registerView as DefaultRegisterView).setListener { registerUser, registerPass, registerPassRep -> if (!TextUtils.isEmpty(registerUser.editText?.text) && !TextUtils.isEmpty(registerPass.editText?.text) && !TextUtils.isEmpty( registerPassRep.editText?.text)) { model.register( registerUser.editText?.text.toString(), registerPass.editText?.text.toString(), registerPassRep.editText?.text.toString() ) } else { ToastUtils.showToast("用户名和密码不能为空", Toast.LENGTH_SHORT) } } } private fun initToolbar() { StatusBarUtil.setColor(this, resources.getColor(R.color.colorTheme), 0) toolbar.setNavigationOnClickListener { finish() } } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/ui/NavigationFragment.kt ================================================ package com.lxm.wanandroid.ui import android.arch.lifecycle.Observer import android.os.Bundle import android.support.v7.widget.LinearLayoutManager import android.view.View import com.lxm.module_library.base.BaseFragment import com.lxm.module_library.xrecycleview.XRecyclerView import com.lxm.wanandroid.R import com.lxm.wanandroid.repository.model.ArticleBean import com.lxm.wanandroid.repository.model.Status import com.lxm.wanandroid.ui.adapter.NaviAdapter import com.lxm.wanandroid.utils.webview.WebViewActivity import com.lxm.wanandroid.viewmodel.NaviViewModelView import com.zhy.view.flowlayout.FlowLayout import kotlinx.android.synthetic.main.article_fragment.* class NavigationFragment : BaseFragment() { private val mAdapter: NaviAdapter by lazy { NaviAdapter(itemClickListener) } private val itemClickListener: NaviAdapter.OnItemNavigationClickListener = object : NaviAdapter.OnItemNavigationClickListener { override fun itemClick(view: View, position: Int, parent: FlowLayout, articles: List) { var article = articles[position] WebViewActivity.loadUrl(activity, article.link, article.title) } } override fun getLayoutID(): Int { return R.layout.tree_fragment } companion object { fun getInstance(): NavigationFragment { return NavigationFragment() } } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) showContentView() initView() } private fun initView() { swipeLayout.setOnRefreshListener { loadData() } swipeLayout.isRefreshing = true val layoutManager = LinearLayoutManager(activity) recyclerView.layoutManager = layoutManager recyclerView.setPullRefreshEnabled(false) recyclerView.setLoadingMoreEnabled(false) recyclerView.clearHeader() recyclerView.adapter = mAdapter recyclerView.setLoadingListener(object : XRecyclerView.LoadingListener { override fun onLoadMore() { getTreeList() } override fun onRefresh() { } }) viewModel.loadStatus.observe(this, Observer { when (it?.status) { Status.ERROR -> showError() } }) } override fun onRetry() { loadData() } override fun loadData() { getTreeList() } private fun getTreeList() { this.viewModel.getVavigations().observe(this@NavigationFragment, Observer { swipeLayout.isRefreshing = false recyclerView.refreshComplete() it?.let { list -> mAdapter.setData(it) } mAdapter.notifyDataSetChanged() }) } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/ui/ProjectFragment.kt ================================================ package com.lxm.wanandroid.ui import android.arch.lifecycle.Observer import android.os.Bundle import com.lxm.module_library.base.BaseFragment import com.lxm.module_library.utils.RefreshHelper import com.lxm.module_library.xrecycleview.XRecyclerView import com.lxm.wanandroid.R import com.lxm.wanandroid.repository.model.ArticleBean import com.lxm.wanandroid.repository.model.Banner import com.lxm.wanandroid.repository.model.Status import com.lxm.wanandroid.ui.adapter.ArticleAdapter import com.lxm.wanandroid.ui.base.OnItemClickListener import com.lxm.wanandroid.utils.webview.WebViewActivity import com.lxm.wanandroid.viewmodel.ArticleViewModel import com.lxm.wanandroid.viewmodel.ProjectViewModel import kotlinx.android.synthetic.main.article_fragment.* class ProjectFragment : BaseFragment() { private val mAdapter: ArticleAdapter by lazy { ArticleAdapter() } override fun getLayoutID(): Int { return R.layout.article_fragment } companion object { fun getInstance(): ProjectFragment { return ProjectFragment() } } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) showContentView() initView() } private fun initView() { swipeLayout.setOnRefreshListener { viewModel.mPage = 0 loadData() } swipeLayout.isRefreshing = true RefreshHelper.init(recyclerView, false) recyclerView.adapter = mAdapter recyclerView.setLoadingListener(object : XRecyclerView.LoadingListener { override fun onLoadMore() { viewModel.mPage = viewModel.mPage + 1; getProjectList() } override fun onRefresh() { } }) viewModel.loadStatus.observe(this, Observer { when (it?.status) { Status.ERROR -> showError() Status.SUCCESS -> showContentView() } }) this.viewModel.pagedList.observe(this, Observer { swipeLayout.isRefreshing = false if (it == null) { return@Observer } if(viewModel.mPage ==0){ mAdapter.setData(it.data?.datas!!) return@Observer } mAdapter.addDataAll(it.data?.datas!!) if(it?.data?.datas?.size!! < it?.data?.size!!){ recyclerView.noMoreLoading() }else{ recyclerView.refreshComplete() } }) mAdapter.setOnItemClickListener(object : OnItemClickListener { override fun onClick(t: ArticleBean, position: Int) { WebViewActivity.loadUrl(activity, t.link, t.title) } }) } override fun onRetry() { loadData() } override fun loadData() { getProjectList() } private fun getProjectList() { this.viewModel.getProjects() } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/ui/TagArticleActivity.kt ================================================ package com.lxm.wanandroid.ui import android.arch.lifecycle.Observer import android.os.Bundle import com.lxm.module_library.base.BaseActivity import com.lxm.module_library.utils.RefreshHelper import com.lxm.module_library.xrecycleview.XRecyclerView import com.lxm.wanandroid.R import com.lxm.wanandroid.repository.model.ArticleBean import com.lxm.wanandroid.repository.model.Status import com.lxm.wanandroid.repository.model.TreeBean import com.lxm.wanandroid.ui.adapter.ArticleAdapter import com.lxm.wanandroid.ui.base.OnItemClickListener import com.lxm.wanandroid.utils.webview.WebViewActivity import com.lxm.wanandroid.viewmodel.CategoryViewModel import kotlinx.android.synthetic.main.article_fragment.* class TagArticleActivity: BaseActivity() { lateinit var treeBean: TreeBean private val mAdapter: ArticleAdapter by lazy { ArticleAdapter() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.category_activity) initView() treeBean = intent.getSerializableExtra("TagBean") as TreeBean setTitle(treeBean.name) getCategory(treeBean.id) } private fun initView() { swipeLayout.setOnRefreshListener { swipeLayout.isRefreshing = true viewModel.mPage = 0 getCategory(treeBean.id) } RefreshHelper.init(recyclerView, false) recyclerView.adapter = mAdapter recyclerView.setLoadingListener(object : XRecyclerView.LoadingListener { override fun onLoadMore() { viewModel.mPage = viewModel.mPage + 1; getCategory(treeBean.id) } override fun onRefresh() { } }) showLoading() viewModel.loadStatus.observe(this, Observer { when (it?.status) { // Status.ERROR -> showError() Status.SUCCESS -> showContentView() } }) this.viewModel.pagedList.observe(this, Observer { swipeLayout.isRefreshing = false if (it == null) { return@Observer } if(viewModel.mPage ==0){ mAdapter.setData(it.data?.datas!!) return@Observer } mAdapter.addDataAll(it.data?.datas!!) if(it?.data?.datas?.size!! < it?.data?.size!!){ recyclerView.noMoreLoading() }else{ recyclerView.refreshComplete() } }) mAdapter.setOnItemClickListener(object : OnItemClickListener { override fun onClick(t: ArticleBean, position: Int) { WebViewActivity.loadUrl(this@TagArticleActivity, t.link, t.title) } }) } private fun getCategory(id: Int) { viewModel.getCategory(id) } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/ui/TreeFragment.kt ================================================ package com.lxm.wanandroid.ui import android.arch.lifecycle.Observer import android.content.Intent import android.os.Bundle import android.support.v7.widget.LinearLayoutManager import android.view.View import com.lxm.module_library.base.BaseFragment import com.lxm.module_library.xrecycleview.XRecyclerView import com.lxm.wanandroid.R import com.lxm.wanandroid.repository.model.Status import com.lxm.wanandroid.repository.model.TreeBean import com.lxm.wanandroid.ui.adapter.TreeAdapter import com.lxm.wanandroid.viewmodel.TreeViewModel import com.zhy.view.flowlayout.FlowLayout import kotlinx.android.synthetic.main.article_fragment.* class TreeFragment : BaseFragment() { private val mAdapter: TreeAdapter by lazy { TreeAdapter(itemClickListener) } private val itemClickListener: TreeAdapter.OnItemNavigationClickListener = object : TreeAdapter.OnItemNavigationClickListener { override fun itemClick(view: View, position: Int, parent: FlowLayout, children: List) { val intent = Intent(parent.context, TagArticleActivity::class.java) intent.putExtra("TagBean", children[position]) parent.context.startActivity(intent) } } override fun getLayoutID(): Int { return R.layout.tree_fragment } companion object { fun getInstance(): TreeFragment { return TreeFragment() } } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) showContentView() initView() } private fun initView() { swipeLayout.setOnRefreshListener { loadData() } swipeLayout.isRefreshing = true val layoutManager = LinearLayoutManager(activity) recyclerView.layoutManager = layoutManager recyclerView.setPullRefreshEnabled(false) recyclerView.setLoadingMoreEnabled(false) recyclerView.clearHeader() recyclerView.adapter = mAdapter recyclerView.setLoadingListener(object : XRecyclerView.LoadingListener { override fun onLoadMore() { getTreeList() } override fun onRefresh() { } }) viewModel.loadStatus.observe(this, Observer { when (it?.status) { Status.ERROR -> showError() } }) } override fun onRetry() { loadData() } override fun loadData() { getTreeList() } private fun getTreeList() { this.viewModel.getTrees().observe(this@TreeFragment, Observer { swipeLayout.isRefreshing = false recyclerView.refreshComplete() it?.let { list -> mAdapter.setData(list) } mAdapter.notifyDataSetChanged() }) } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/ui/ViewBigImageActivity.kt ================================================ package com.lxm.wanandroid.ui import android.content.Context import android.content.Intent import android.graphics.drawable.Drawable import android.os.Bundle import android.support.v4.app.FragmentActivity import android.support.v4.view.PagerAdapter import android.support.v4.view.ViewPager.OnPageChangeListener import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.ProgressBar import com.bumptech.glide.Glide import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.target.Target import com.lxm.wanandroid.R import kotlinx.android.synthetic.main.activity_show_image.* import uk.co.senab.photoview.PhotoView import uk.co.senab.photoview.PhotoViewAttacher import java.util.ArrayList class ViewBigImageActivity : FragmentActivity(), OnPageChangeListener, PhotoViewAttacher.OnPhotoTapListener { override fun onPhotoTap(p0: View?, p1: Float, p2: Float) { finish() } /** * 图片集合 */ private var imageList: MutableList? = null /** * 接收穿过来当前选择的图片的position */ internal var position: Int = 0 /** * 当前页数 */ private var page: Int = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_show_image) getIntentData() } private fun getIntentData() { val bundle = intent.extras if (bundle != null) { position = bundle.getInt("code") imageList = bundle.getStringArrayList("imageList") } val adapter = ViewPagerAdapter() viewpager?.adapter = adapter viewpager?.currentItem = position page = position viewpager?.addOnPageChangeListener(this) viewpager?.isEnabled = false viewpager_text?.text = (position + 1).toString() + " / " + imageList!!.size } /** * ViewPager的适配器 */ private inner class ViewPagerAdapter internal constructor() : PagerAdapter() { private val inflater: LayoutInflater = layoutInflater override fun instantiateItem(container: ViewGroup, position: Int): Any { val view = inflater.inflate(R.layout.item_image_view_pager, container, false) val zoomImageView = view.findViewById(R.id.zoom_image_view) val progressBar = view.findViewById(R.id.loading) // 保存网络图片的路径 val adapterImageEntity = getItem(position) as String val imageUrl: String imageUrl = adapterImageEntity progressBar.visibility = View.VISIBLE progressBar.isClickable = false Glide.with(this@ViewBigImageActivity).load(imageUrl) .transition(DrawableTransitionOptions.withCrossFade(700)) .listener(object : RequestListener { override fun onLoadFailed( e: GlideException?, model: Any, target: Target, isFirstResource: Boolean ): Boolean { return false } override fun onResourceReady( resource: Drawable, model: Any, target: Target, dataSource: DataSource, isFirstResource: Boolean ): Boolean { progressBar.visibility = View.GONE /**这里应该是加载成功后图片的高,最大为屏幕的高 */ val height = resource.intrinsicHeight val wHeight = windowManager.defaultDisplay.height if (height < wHeight) { zoomImageView.scaleType = ImageView.ScaleType.FIT_CENTER } else { zoomImageView.scaleType = ImageView.ScaleType.CENTER_CROP } return false } }).into(zoomImageView) zoomImageView.setOnPhotoTapListener(this@ViewBigImageActivity) container.addView(view, 0) return view } override fun getCount(): Int { return if (imageList == null || imageList?.size == 0) { 0 } else imageList!!.size } override fun isViewFromObject(arg0: View, arg1: Any): Boolean { return arg0 === arg1 } override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { val view = `object` as View container.removeView(view) } private fun getItem(position: Int): Any { return imageList!![position] } } /** * 下面是对Viewpager的监听 */ override fun onPageScrollStateChanged(arg0: Int) {} override fun onPageScrolled(arg0: Int, arg1: Float, arg2: Int) {} /** * 本方法主要监听viewpager滑动的时候的操作 * 每当页数发生改变时重新设定一遍当前的页数和总页数 */ override fun onPageSelected(arg0: Int) { viewpager_text?.text = (arg0 + 1).toString() + " / " + imageList!!.size page = arg0 } override fun onDestroy() { imageList?.clear() super.onDestroy() } companion object { /** * @param position 大图的话是第几张图片 从0开始 * @param imageList 图片集合 */ fun startImageList( context: Context, position: Int, imageList: ArrayList ) { val bundle = Bundle() bundle.putInt("code", position) bundle.putStringArrayList("imageList", imageList) val intent = Intent(context, ViewBigImageActivity::class.java) intent.putExtras(bundle) context.startActivity(intent) } } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/ui/WelfareFragment.kt ================================================ package com.lxm.wanandroid.ui import android.arch.lifecycle.Observer import android.os.Bundle import android.support.v7.widget.StaggeredGridLayoutManager import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.RelativeLayout import com.lxm.module_library.base.BaseFragment import com.lxm.module_library.xrecycleview.XRecyclerView import com.lxm.wanandroid.R import com.lxm.wanandroid.repository.model.Status import com.lxm.wanandroid.repository.model.Welfare import com.lxm.wanandroid.ui.adapter.WelfareAdapter import com.lxm.wanandroid.ui.base.OnItemClickListener import com.lxm.wanandroid.viewmodel.WelfareModelView import kotlinx.android.synthetic.main.article_fragment.* class WelfareFragment : BaseFragment() { var imageList: ArrayList = ArrayList() override fun getLayoutID() = R.layout.article_fragment private val mAdapter: WelfareAdapter by lazy { WelfareAdapter() } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val ll = inflater.inflate(com.lxm.module_library.R.layout.fragment_base, null) contentView = LayoutInflater.from(activity).inflate(R.layout.article_fragment, null, false) val params = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) contentView.layoutParams = params val mContainer = ll.findViewById(com.lxm.module_library.R.id.container) mContainer.addView(contentView) return ll } companion object { fun getInstance(): WelfareFragment { return WelfareFragment() } } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) showContentView() initView() } private fun initView() { swipeLayout.setOnRefreshListener { viewModel.mPage = 1 loadData() } swipeLayout.isRefreshing = true recyclerView.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL) recyclerView.setPullRefreshEnabled(false) recyclerView.clearHeader() recyclerView.adapter = mAdapter recyclerView.setLoadingListener(object : XRecyclerView.LoadingListener { override fun onLoadMore() { viewModel.mPage = viewModel.mPage+1 getWelfare() } override fun onRefresh() { } }) viewModel.loadStatus.observe(this, Observer { when (it?.status) { Status.ERROR -> showError() } }) mAdapter.setOnItemClickListener(object : OnItemClickListener { override fun onClick(t: Welfare, position: Int) { activity?.let { ViewBigImageActivity.startImageList(it, position, imageList) } } }) } override fun onRetry() { loadData() } override fun loadData() { getWelfare() } private fun getWelfare() { this.viewModel.getWelfare().observe(this@WelfareFragment, Observer { if (viewModel.mPage == 1) { mAdapter.setData(it?.results) imageList.clear() for (item in it?.results!!) { imageList.add(item.url) } } else { mAdapter.addDataAll(it?.results!!) for (item in it?.results!!) { imageList.add(item.url) } } swipeLayout.isRefreshing = false recyclerView.refreshComplete() mAdapter.notifyDataSetChanged() }) } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/ui/adapter/ArticleAdapter.kt ================================================ package com.lxm.wanandroid.ui.adapter import android.content.Intent import android.graphics.Paint import android.view.View import android.widget.CheckBox import android.widget.ImageView import android.widget.TextView import com.lxm.module_library.utils.PreferencesUtil import com.lxm.module_library.utils.ToastUtils import com.lxm.wanandroid.R import com.lxm.wanandroid.repository.model.ArticleBean import com.lxm.wanandroid.repository.model.HttpResponse import com.lxm.wanandroid.repository.remote.httpClient.RetrofitClient import com.lxm.wanandroid.ui.LoginActivity import com.lxm.wanandroid.ui.base.BaseRecyclerAdapter import com.lxm.wanandroid.ui.base.BaseRecyclerViewHolder import com.lxm.wanandroid.utils.webview.WebViewActivity import com.lxm.wanandroid.viewmodel.CollectViewModel class ArticleAdapter : BaseRecyclerAdapter() { var isCollectionList: Boolean = false private var collectionViewModel: CollectViewModel = CollectViewModel() override fun getItemLayout(): Int { return R.layout.item_view } override fun onBindViewHoder(holder: BaseRecyclerViewHolder, position: Int) { var articleBean: ArticleBean = mutableList?.get(position) with(holder){ setValue(R.id.tv_title, articleBean.title) setValue(R.id.tv_time, articleBean.niceDate) setValue(R.id.tv_author, articleBean.author) setValue(R.id.iv_image, articleBean.envelopePic) } var tagTextView = holder.getView(R.id.tv_tag_name) as TextView tagTextView.text = articleBean.chapterName if (articleBean.tags != null && articleBean.tags?.size!! > 0) { tagTextView.paint.flags = Paint.UNDERLINE_TEXT_FLAG; //下划线 tagTextView.paint.isAntiAlias = true;//抗锯齿 tagTextView.setOnClickListener { WebViewActivity.loadUrl( tagTextView.context, RetrofitClient.WAN_BASE_URL + articleBean.tags?.get(0)?.url, articleBean.chapterName ) } } val isNewImageView: ImageView = holder.getView(R.id.iv_new) as ImageView if (articleBean.fresh) { isNewImageView.visibility = View.VISIBLE } else { isNewImageView.visibility = View.GONE } val isCollectImage: CheckBox = holder.getView(R.id.iv_collect) as CheckBox isCollectImage.isChecked = articleBean.collect if(isCollectionList){ isCollectImage.isChecked = true articleBean.id = articleBean.originId!! } isCollectImage.setOnClickListener { var isLogin: Boolean by PreferencesUtil("login", false) if(isLogin){ if (isCollectImage.isChecked) { collectionViewModel.collect(articleBean.id, object : CollectionObserver> { override fun onChanged(t: HttpResponse?) { if (t?.errorCode == 0) { ToastUtils.showToast("收藏成功") } else { ToastUtils.showToast("收藏失败") } } }) } else { collectionViewModel.unCollect(articleBean.id, object : CollectionObserver> { override fun onChanged(t: HttpResponse?) { if (t?.errorCode == 0) { ToastUtils.showToast("取消收藏成功") } else { ToastUtils.showToast("取消收藏失败") } } }) } }else{ val intent = Intent(context, LoginActivity::class.java) context.startActivity(intent) } } } interface CollectionObserver { fun onChanged(t: T?) } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/ui/adapter/NaviAdapter.kt ================================================ package com.lxm.wanandroid.ui.adapter import android.graphics.Color import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import com.lxm.wanandroid.R import com.lxm.wanandroid.repository.model.ArticleBean import com.lxm.wanandroid.repository.model.Navigation import com.lxm.wanandroid.repository.model.TreeBean import com.lxm.wanandroid.ui.base.BaseRecyclerAdapter import com.lxm.wanandroid.ui.base.BaseRecyclerViewHolder import com.zhy.view.flowlayout.FlowLayout import com.zhy.view.flowlayout.TagAdapter import com.zhy.view.flowlayout.TagFlowLayout import java.util.* class NaviAdapter(var itemClickListener: OnItemNavigationClickListener) : BaseRecyclerAdapter() { override fun getItemLayout(): Int { return R.layout.item_tree } override fun onBindViewHoder(holder: BaseRecyclerViewHolder, position: Int) { val navigation = mutableList[position] val viewHolder: ViewHolder = holder as ViewHolder viewHolder.titleView?.text = navigation.name showTag(navigation, viewHolder.flowLayout) } private fun showTag( navigation: Navigation, flowLayout: TagFlowLayout? ) { flowLayout?.adapter = object : TagAdapter(navigation.articles) { override fun getView( parent: FlowLayout, position: Int, articleBean: ArticleBean ): View { val textView = View.inflate(parent.context, R.layout.item_tree_tag, null) as TextView textView.text = articleBean.title // textView.textColors = Color.rgb(Random().nextInt(255), Random().nextInt(255), Random().nextInt(255)) return textView } } flowLayout?.setOnTagClickListener { view, position, parent -> itemClickListener?.itemClick(view, position, parent,navigation.articles) true } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseRecyclerViewHolder { val item = LayoutInflater.from(parent.context).inflate(R.layout.item_tree, parent, false) return ViewHolder(item) } class ViewHolder(itemView: View) : BaseRecyclerViewHolder(itemView) { var titleView: TextView? = null var flowLayout: TagFlowLayout? = null init { titleView = itemView.findViewById(R.id.tv_tree_title) flowLayout = itemView.findViewById(R.id.fl_tree) } companion object } private fun getBackGround(): Drawable { val drawable = GradientDrawable() drawable.cornerRadius = 16f drawable.setColor(Color.rgb(Random().nextInt(255), Random().nextInt(255), Random().nextInt(255))) return drawable } interface OnItemNavigationClickListener { /** *单击事件 */ fun itemClick(view: View, position: Int, parent: FlowLayout, articles: List) } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/ui/adapter/TreeAdapter.kt ================================================ package com.lxm.wanandroid.ui.adapter import android.graphics.Color import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import com.lxm.wanandroid.R import com.lxm.wanandroid.repository.model.TreeBean import com.lxm.wanandroid.ui.base.BaseRecyclerAdapter import com.lxm.wanandroid.ui.base.BaseRecyclerViewHolder import com.zhy.view.flowlayout.FlowLayout import com.zhy.view.flowlayout.TagAdapter import com.zhy.view.flowlayout.TagFlowLayout import java.util.* class TreeAdapter(var itemClickListener: OnItemNavigationClickListener) : BaseRecyclerAdapter() { override fun getItemLayout(): Int { return R.layout.item_tree } override fun onBindViewHoder(holder: BaseRecyclerViewHolder, position: Int) { val treeBean = mutableList[position] val viewHolder: ViewHolder = holder as ViewHolder viewHolder.titleView?.text = treeBean.name showTag(treeBean, viewHolder.flowLayout) } private fun showTag( treeBean: TreeBean, flowLayout: TagFlowLayout? ) { flowLayout?.adapter = object : TagAdapter(treeBean.children) { override fun getView( parent: FlowLayout, position: Int, o: TreeBean ): View { val textView = View.inflate(parent.context, R.layout.item_tree_tag, null) as TextView textView.text = o.name textView.setBackgroundDrawable(getBackGround()) return textView } } flowLayout?.setOnTagClickListener { view, position, parent -> itemClickListener?.itemClick(view, position, parent,treeBean.children) true } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseRecyclerViewHolder { val item = LayoutInflater.from(parent.context).inflate(R.layout.item_tree, parent, false) return ViewHolder(item) } class ViewHolder(itemView: View) : BaseRecyclerViewHolder(itemView) { var titleView: TextView? = null var flowLayout: TagFlowLayout? = null init { titleView = itemView.findViewById(R.id.tv_tree_title) flowLayout = itemView.findViewById(R.id.fl_tree) } companion object } private fun getBackGround(): Drawable { val drawable = GradientDrawable() drawable.cornerRadius = 16f drawable.setColor(Color.rgb(Random().nextInt(255), Random().nextInt(255), Random().nextInt(255))) return drawable } interface OnItemNavigationClickListener { /** *单击事件 */ fun itemClick(view: View, position: Int, parent: FlowLayout, children: List) } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/ui/adapter/WelfareAdapter.kt ================================================ package com.lxm.wanandroid.ui.adapter import android.widget.ImageView import com.lxm.module_library.utils.DisplayUtils import com.lxm.wanandroid.R import com.lxm.wanandroid.repository.model.Welfare import com.lxm.wanandroid.ui.base.BaseRecyclerAdapter import com.lxm.wanandroid.ui.base.BaseRecyclerViewHolder import com.lxm.wanandroid.utils.GlideUtil class WelfareAdapter : BaseRecyclerAdapter() { override fun getItemLayout(): Int { return R.layout.item_view_welfare } override fun onBindViewHoder(holder: BaseRecyclerViewHolder, position: Int) { var welfare: Welfare = mutableList?.get(position) val imageView: ImageView = holder.getView(R.id.iv_welfare) as ImageView GlideUtil.displayCircleCorner(imageView,welfare.url) } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/ui/base/BaseRecyclerAdapter.kt ================================================ package com.lxm.wanandroid.ui.base import android.content.Context import android.support.annotation.IntRange import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.ViewGroup abstract class BaseRecyclerAdapter : RecyclerView.Adapter() { lateinit var context: Context var layoutRes: Int = 0 var mutableList: MutableList = mutableListOf() var listener: OnItemClickListener? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseRecyclerViewHolder { context = parent.context return BaseRecyclerViewHolder( LayoutInflater.from(context).inflate( getItemLayout(), null ) ) } fun setOnItemClickListener(listener: OnItemClickListener) { this.listener = listener } override fun onBindViewHolder(holder: BaseRecyclerViewHolder, position: Int) { onBindViewHoder(holder, position) holder.itemView.setOnClickListener { listener?.onClick(mutableList.get(position),position) } } override fun getItemCount(): Int { return mutableList.size } fun setData(items: List?) { this.mutableList = items as MutableList notifyDataSetChanged() } fun addData(@IntRange(from = 0) position: Int, data: T) { mutableList.add(position, data) notifyItemInserted(position) compatibilityDataSizeChanged(1) } fun addData(data: T) { mutableList.add(data) notifyItemInserted(mutableList?.size) } fun addDataAll(data: List) { mutableList.addAll(data) notifyDataSetChanged() } fun addAdd(data: T) { mutableList.add(data) notifyItemInserted(mutableList?.size) } fun remove(position: Int) { mutableList.removeAt(position) notifyItemRemoved(position) notifyItemRangeChanged(position, mutableList!!.size - position) } private fun compatibilityDataSizeChanged(size: Int) { val dataSize = if (mutableList == null) 0 else mutableList!!.size if (dataSize == size) { notifyDataSetChanged() } } /** * 需要重写的方法 * @param holder * @param position */ abstract fun onBindViewHoder(holder: BaseRecyclerViewHolder, position: Int) abstract fun getItemLayout(): Int } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/ui/base/BaseRecyclerViewHoder.kt ================================================ package com.lxm.wanandroid.ui.base import android.support.v7.widget.RecyclerView import android.text.TextUtils import android.view.View import android.widget.ImageView import android.widget.TextView import com.bumptech.glide.Glide import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions open class BaseRecyclerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { private val map = mutableMapOf() fun getView(id: Int): View? { var viewId = map[id] if (viewId == null) { viewId = itemView.findViewById(id) map[id] = viewId } return viewId } fun setValue(id: Int, string: String?) { val view = getView(id) when (view) { is TextView -> view.text = string is ImageView -> showPic(view, string) } } private fun showPic(view: ImageView, string: String?) { if (TextUtils.isEmpty(string)) { view.visibility = View.GONE } else { view.visibility = View.VISIBLE Glide.with(view.context) .load(string) .transition(DrawableTransitionOptions.withCrossFade(500)) .into(view) } } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/ui/base/OnItemClickListener.kt ================================================ package com.lxm.wanandroid.ui.base interface OnItemClickListener { fun onClick(t: T, position: Int) } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/ui/base/OnItemLongClickListener.kt ================================================ package com.lxm.wanandroid.ui.base interface OnItemLongClickListener { fun onLongClick(t: T, position: Int) } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/utils/CookieUtils.kt ================================================ package com.lxm.wanandroid.utils import android.content.Context import android.util.Log import android.webkit.CookieManager import android.webkit.CookieSyncManager import com.google.android.gms.common.util.SharedPreferencesUtils import okhttp3.Cookie object CookieUtils { /** * clear Cookie * * @param context */ fun clearCookie(context: Context) { CookieSyncManager.createInstance(context) CookieSyncManager.getInstance().startSync() CookieManager.getInstance().removeSessionCookie() } /** * Sync Cookie */ fun syncCookie(context: Context, url: String,cookies:List) { try { CookieSyncManager.createInstance(context) val cookieManager = CookieManager.getInstance() cookies.forEach { val name = it.name() val value = it.value() val cookieStr = "$name=$value" cookieManager.setCookie(url,cookieStr) } cookieManager.flush() } catch (e: Exception) { } } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/utils/GlideUtil.kt ================================================ package com.lxm.wanandroid.utils import android.content.Context import android.graphics.Bitmap import android.widget.ImageView import com.bumptech.glide.Glide import com.bumptech.glide.load.Transformation import com.bumptech.glide.load.resource.bitmap.CircleCrop import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions import jp.wasabeef.glide.transformations.BlurTransformation import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.load.resource.bitmap.RoundedCorners object GlideUtil { private fun displayGaussian(context: Context, url: String, imageView: ImageView) { // "23":模糊度;"4":图片缩放4倍后再进行模糊 Glide.with(context) .load(url) .transition(DrawableTransitionOptions.withCrossFade()) .transition(DrawableTransitionOptions.withCrossFade(500)) .transform(BlurTransformation(50, 8) as Transformation) .into(imageView) } /** * 加载圆角图,暂时用到显示头像 */ fun displayCircle(imageView: ImageView, imageUrl: String) { Glide.with(imageView.context) .load(imageUrl) .transition(DrawableTransitionOptions.withCrossFade(500)) .transform(CircleCrop()) // .apply(bitmapTransform(new CircleCrop())) // .transform(new GlideCircleTransform()) // .transform(new RoundedCorners(20)) // .transform(new CenterCrop(), new RoundedCorners(20)) .into(imageView) } fun displayImage(imageView: ImageView, imageUrl: String) { Glide.with(imageView.context) .load(imageUrl) .into(imageView) } fun displayCircleCorner(imageView: ImageView, imageUrl: String){ //设置图片圆角角度 val roundedCorners = RoundedCorners(6) //通过RequestOptions扩展功能,override:采样率,因为ImageView就这么大,可以压缩图片,降低内存消耗 val options = RequestOptions.bitmapTransform(roundedCorners) Glide.with(imageView.context).load(imageUrl).apply(options).into(imageView) } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/utils/webview/WebViewActivity.java ================================================ package com.lxm.wanandroid.utils.webview; import android.annotation.SuppressLint; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.support.v4.content.ContextCompat; import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.*; import android.webkit.WebSettings; import android.webkit.WebView; import android.widget.FrameLayout; import android.widget.ProgressBar; import android.widget.TextView; import com.lxm.module_library.statusbar.StatusBarUtil; import com.lxm.module_library.utils.BaseTools; import com.lxm.module_library.utils.CheckNetwork; import com.lxm.module_library.utils.ToastUtil; import com.lxm.wanandroid.R; import com.lxm.wanandroid.utils.webview.config.*; /** * 网页可以处理: * 点击相应控件:拨打电话、发送短信、发送邮件、上传图片、播放视频 * 进度条、返回网页上一层、显示网页标题 * Thanks to: https://github.com/youlookwhat/WebViewStudy * contact me: http://www.jianshu.com/users/e43c6e979831/latest_articles */ public class WebViewActivity extends AppCompatActivity implements IWebPageView { // 进度条 private ProgressBar mProgressBar; private WebView webView; // 全屏时视频加载view private FrameLayout videoFullView; private Toolbar mTitleToolBar; // 加载视频相关 private MyWebChromeClient mWebChromeClient; // title private String mTitle; // 网页链接 private String mUrl; // 可滚动的title 使用简单 没有渐变效果,文字两旁有阴影 private TextView tvGunTitle; private boolean isTitleFix; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_web_view); getIntentData(); initTitle(); initWebView(); webView.loadUrl(mUrl); getDataFromBrowser(getIntent()); } private void getIntentData() { if (getIntent() != null) { mTitle = getIntent().getStringExtra("mTitle"); mUrl = getIntent().getStringExtra("mUrl"); isTitleFix = getIntent().getBooleanExtra("isTitleFix", false); } } private void initTitle() { StatusBarUtil.setColor(this, getResources().getColor(R.color.colorTheme), 0); mProgressBar = findViewById(R.id.pb_progress); webView = findViewById(R.id.webview_detail); videoFullView = findViewById(R.id.video_fullView); mTitleToolBar = findViewById(R.id.title_tool_bar); tvGunTitle = findViewById(R.id.tv_gun_title); initToolBar(); } private void initToolBar() { setSupportActionBar(mTitleToolBar); ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { //去除默认Title显示 actionBar.setDisplayShowTitleEnabled(false); } mTitleToolBar.setOverflowIcon(ContextCompat.getDrawable(this, R.drawable.actionbar_more)); tvGunTitle.postDelayed(() -> tvGunTitle.setSelected(true), 1900); tvGunTitle.setText(mTitle); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.webview_menu, menu); return true; } @Override public void setTitle(String mTitle) { if (!isTitleFix) { tvGunTitle.setText(mTitle); this.mTitle = mTitle; } } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: // 返回键 handleFinish(); break; case R.id.actionbar_share: // 分享到 break; case R.id.actionbar_cope: // 复制链接 BaseTools.copy(webView.getUrl()); ToastUtil.showToast("复制成功"); break; case R.id.actionbar_open: // 打开链接 BaseTools.openLink(WebViewActivity.this, webView.getUrl()); break; case R.id.actionbar_webview_refresh: // 刷新页面 if (webView != null) { webView.reload(); } break; default: break; } return super.onOptionsItemSelected(item); } @SuppressLint("SetJavaScriptEnabled") private void initWebView() { mProgressBar.setVisibility(View.VISIBLE); WebSettings ws = webView.getSettings(); // 网页内容的宽度是否可大于WebView控件的宽度 ws.setLoadWithOverviewMode(false); // 保存表单数据 ws.setSaveFormData(true); // 是否应该支持使用其屏幕缩放控件和手势缩放 ws.setSupportZoom(true); ws.setBuiltInZoomControls(true); ws.setDisplayZoomControls(false); // 启动应用缓存 ws.setAppCacheEnabled(true); // 设置缓存模式 ws.setCacheMode(WebSettings.LOAD_DEFAULT); // setDefaultZoom api19被弃用 // 设置此属性,可任意比例缩放。 ws.setUseWideViewPort(true); // 不缩放 webView.setInitialScale(100); // 告诉WebView启用JavaScript执行。默认的是false。 ws.setJavaScriptEnabled(true); // 页面加载好以后,再放开图片 ws.setBlockNetworkImage(false); // 使用localStorage则必须打开 ws.setDomStorageEnabled(true); // 排版适应屏幕 ws.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS); // WebView是否新窗口打开(加了后可能打不开网页) // ws.setSupportMultipleWindows(true); // webview从5.0开始默认不允许混合模式,https中不能加载http资源,需要设置开启。 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { ws.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); } /** 设置字体默认缩放大小(改变网页字体大小,setTextSize api14被弃用)*/ ws.setTextZoom(100); mWebChromeClient = new MyWebChromeClient(this); webView.setWebChromeClient(mWebChromeClient); // 与js交互 webView.addJavascriptInterface(new ImageClickInterface(this), "injectedObject"); webView.setWebViewClient(new MyWebViewClient(this)); webView.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { return handleLongImage(); } }); } @Override public void hindProgressBar() { mProgressBar.setVisibility(View.GONE); } @Override public void showWebView() { webView.setVisibility(View.VISIBLE); } @Override public void hindWebView() { webView.setVisibility(View.INVISIBLE); } @Override public void fullViewAddView(View view) { FrameLayout decor = (FrameLayout) getWindow().getDecorView(); videoFullView = new FullscreenHolder(WebViewActivity.this); videoFullView.addView(view); decor.addView(videoFullView); } @Override public void showVideoFullView() { videoFullView.setVisibility(View.VISIBLE); } @Override public void hindVideoFullView() { videoFullView.setVisibility(View.GONE); } @Override public void startProgress(int newProgress) { mProgressBar.setVisibility(View.VISIBLE); mProgressBar.setProgress(newProgress); if (newProgress == 100) { mProgressBar.setVisibility(View.GONE); } } @Override public void addImageClickListener() { // loadImageClickJS(); // loadTextClickJS(); } private void loadImageClickJS() { // 这段js函数的功能就是,遍历所有的img节点,并添加onclick函数,函数的功能是在图片点击的时候调用本地java接口并传递url过去 webView.loadUrl("javascript:(function(){" + "var objs = document.getElementsByTagName(\"img\");" + "for(var i=0;i= Build.VERSION_CODES.LOLLIPOP) { finishAfterTransition(); } else { finish(); } } @Override protected void onPause() { super.onPause(); webView.onPause(); } @Override protected void onResume() { super.onResume(); webView.onResume(); // 支付宝网页版在打开文章详情之后,无法点击按钮下一步 webView.resumeTimers(); // 设置为横屏 if (getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } } @Override protected void onDestroy() { if (videoFullView != null) { videoFullView.clearAnimation(); videoFullView.removeAllViews(); } if (webView != null) { webView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null); webView.clearHistory(); ViewGroup parent = (ViewGroup) webView.getParent(); if (parent != null) { parent.removeView(webView); } webView.removeAllViews(); webView.stopLoading(); webView.setWebChromeClient(null); webView.setWebViewClient(null); webView.destroy(); webView = null; mProgressBar.clearAnimation(); tvGunTitle.clearAnimation(); tvGunTitle.clearFocus(); } super.onDestroy(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (newConfig.fontScale != 1) { getResources(); } } /** * 禁止改变字体大小 */ @Override public Resources getResources() { Resources res = super.getResources(); Configuration config = new Configuration(); config.setToDefaults(); res.updateConfiguration(config, res.getDisplayMetrics()); return res; } /** * 打开网页: * * @param mContext 上下文 * @param mUrl 要加载的网页url * @param mTitle title */ public static void loadUrl(Context mContext, String mUrl, String mTitle) { loadUrl(mContext, mUrl, mTitle, false); } /** * 打开网页: * * @param mContext 上下文 * @param mUrl 要加载的网页url * @param mTitle title * @param isTitleFixed title是否固定 */ public static void loadUrl(Context mContext, String mUrl, String mTitle, boolean isTitleFixed) { if (CheckNetwork.isNetworkConnected(mContext)) { Intent intent = new Intent(mContext, WebViewActivity.class); intent.putExtra("mUrl", mUrl); intent.putExtra("isTitleFix", isTitleFixed); intent.putExtra("mTitle", mTitle == null ? "" : mTitle); mContext.startActivity(intent); } else { ToastUtil.showToastLong("当前网络不可用,请检查你的网络设置"); } } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/utils/webview/config/FullscreenHolder.java ================================================ package com.lxm.wanandroid.utils.webview.config; import android.content.Context; import android.view.MotionEvent; import android.widget.FrameLayout; /** * Created by jingbin on 2016/11/17. */ public class FullscreenHolder extends FrameLayout { public FullscreenHolder(Context ctx) { super(ctx); setBackgroundColor(ctx.getResources().getColor(android.R.color.black)); } @Override public boolean onTouchEvent(MotionEvent event) { return true; } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/utils/webview/config/IWebPageView.java ================================================ package com.lxm.wanandroid.utils.webview.config; import android.view.View; /** * Created by jingbin on 2016/11/17. */ public interface IWebPageView { // 隐藏进度条 void hindProgressBar(); // 显示webview void showWebView(); // 隐藏webview void hindWebView(); /** * 进度条变化时调用 */ void startProgress(int newProgress); /** * 添加js监听 */ void addImageClickListener(); /** * 播放网络视频全屏调用 */ void fullViewAddView(View view); void showVideoFullView(); void hindVideoFullView(); /** * 得到网页标题 */ void setTitle(String title); } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/utils/webview/config/ImageClickInterface.java ================================================ package com.lxm.wanandroid.utils.webview.config; import android.content.Context; import android.content.Intent; import android.text.TextUtils; import android.util.Log; import android.webkit.JavascriptInterface; import android.widget.Toast; import com.lxm.wanandroid.ui.ViewBigImageActivity; /** * Created by jingbin on 2016/11/17. * js通信接口 */ public class ImageClickInterface { private Context context; public ImageClickInterface(Context context) { this.context = context; } @JavascriptInterface public void imageClick(String imgUrl, String hasLink) { Toast.makeText(context, "----点击了图片", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(context, ViewBigImageActivity.class); context.startActivity(intent); Log.e("----点击了图片 url: ", "" + imgUrl); } @JavascriptInterface public void textClick(String type, String item_pk) { if (!TextUtils.isEmpty(type) && !TextUtils.isEmpty(item_pk)) { Log.e("----点击了文字", ""); } } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/utils/webview/config/MyWebChromeClient.java ================================================ package com.lxm.wanandroid.utils.webview.config; import android.content.Intent; import android.content.pm.ActivityInfo; import android.net.Uri; import android.view.LayoutInflater; import android.view.View; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebView; import com.lxm.wanandroid.utils.webview.WebViewActivity; import static android.app.Activity.RESULT_OK; /** * Created by jingbin on 2016/11/17. * - 播放网络视频配置 * - 上传图片(兼容) * 点击空白区域的左边,因是公司图片,自己编辑过,所以显示不全,见谅 */ public class MyWebChromeClient extends WebChromeClient { private ValueCallback mUploadMessage; private ValueCallback mUploadMessageForAndroid5; public static int FILECHOOSER_RESULTCODE = 1; public static int FILECHOOSER_RESULTCODE_FOR_ANDROID_5 = 2; private View mXProgressVideo; private WebViewActivity mActivity; private IWebPageView mIWebPageView; private View mXCustomView; private CustomViewCallback mXCustomViewCallback; public MyWebChromeClient(IWebPageView mIWebPageView) { this.mIWebPageView = mIWebPageView; this.mActivity = (WebViewActivity) mIWebPageView; } // 播放网络视频时全屏会被调用的方法 @Override public void onShowCustomView(View view, CustomViewCallback callback) { mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); mIWebPageView.hindWebView(); // 如果一个视图已经存在,那么立刻终止并新建一个 if (mXCustomView != null) { callback.onCustomViewHidden(); return; } mActivity.fullViewAddView(view); mXCustomView = view; mXCustomViewCallback = callback; mIWebPageView.showVideoFullView(); } // 视频播放退出全屏会被调用的 @Override public void onHideCustomView() { if (mXCustomView == null)// 不是全屏播放状态 return; mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); mXCustomView.setVisibility(View.GONE); if (mActivity.getVideoFullView() != null) { mActivity.getVideoFullView().removeView(mXCustomView); } mXCustomView = null; mIWebPageView.hindVideoFullView(); mXCustomViewCallback.onCustomViewHidden(); mIWebPageView.showWebView(); } // 视频加载时进程loading @Override public View getVideoLoadingProgressView() { if (mXProgressVideo == null) { LayoutInflater inflater = LayoutInflater.from(mActivity); // mXProgressVideo = inflater.inflate(R.layout.video_loading_progress, null); } return mXProgressVideo; } @Override public void onProgressChanged(WebView view, int newProgress) { super.onProgressChanged(view, newProgress); mIWebPageView.startProgress(newProgress); } /** * 判断是否是全屏 */ public boolean inCustomView() { return (mXCustomView != null); } @Override public void onReceivedTitle(WebView view, String title) { super.onReceivedTitle(view, title); mIWebPageView.setTitle(title); // // 设置title // mActivity.setTitle(title); // this.title = title; } // private String title = ""; // public String getTitle() { // return title + " "; // } //扩展浏览器上传文件 //3.0++版本 public void openFileChooser(ValueCallback uploadMsg, String acceptType) { openFileChooserImpl(uploadMsg); } //3.0--版本 public void openFileChooser(ValueCallback uploadMsg) { openFileChooserImpl(uploadMsg); } public void openFileChooser(ValueCallback uploadMsg, String acceptType, String capture) { openFileChooserImpl(uploadMsg); } // For Android > 5.0 @Override public boolean onShowFileChooser(WebView webView, ValueCallback uploadMsg, FileChooserParams fileChooserParams) { openFileChooserImplForAndroid5(uploadMsg); return true; } private void openFileChooserImpl(ValueCallback uploadMsg) { mUploadMessage = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("image/*"); mActivity.startActivityForResult(Intent.createChooser(i, "文件选择"), FILECHOOSER_RESULTCODE); } private void openFileChooserImplForAndroid5(ValueCallback uploadMsg) { mUploadMessageForAndroid5 = uploadMsg; Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT); contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE); contentSelectionIntent.setType("image/*"); Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER); chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent); chooserIntent.putExtra(Intent.EXTRA_TITLE, "图片选择"); mActivity.startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE_FOR_ANDROID_5); } /** * 5.0以下 上传图片成功后的回调 */ public void mUploadMessage(Intent intent, int resultCode) { if (null == mUploadMessage) return; Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData(); mUploadMessage.onReceiveValue(result); mUploadMessage = null; } /** * 5.0以上 上传图片成功后的回调 */ public void mUploadMessageForAndroid5(Intent intent, int resultCode) { if (null == mUploadMessageForAndroid5) return; Uri result = (intent == null || resultCode != RESULT_OK) ? null : intent.getData(); if (result != null) { mUploadMessageForAndroid5.onReceiveValue(new Uri[]{result}); } else { mUploadMessageForAndroid5.onReceiveValue(new Uri[]{}); } mUploadMessageForAndroid5 = null; } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/utils/webview/config/MyWebViewClient.java ================================================ package com.lxm.wanandroid.utils.webview.config; import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.text.TextUtils; import android.webkit.WebView; import android.webkit.WebViewClient; import com.lxm.module_library.utils.BaseTools; import com.lxm.module_library.utils.CheckNetwork; import com.lxm.wanandroid.utils.webview.WebViewActivity; /** * Created by jingbin on 2016/11/17. * 监听网页链接: * - 优酷视频直接跳到自带浏览器 * - 根据标识:打电话、发短信、发邮件 * - 进度条的显示 * - 添加javascript监听 */ public class MyWebViewClient extends WebViewClient { private IWebPageView mIWebPageView; private WebViewActivity mActivity; public MyWebViewClient(IWebPageView mIWebPageView) { this.mIWebPageView = mIWebPageView; mActivity = (WebViewActivity) mIWebPageView; } @SuppressWarnings("deprecation") @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // DebugUtil.error("----url:"+url); if (TextUtils.isEmpty(url)) { return false; } if (url.startsWith("http:") || url.startsWith("https:")) { // 可能有提示下载Apk文件 if (url.contains(".apk")) { handleOtherwise(mActivity, url); return true; } return false; } handleOtherwise(mActivity, url); return true; } @Override public void onPageFinished(WebView view, String url) { if (!CheckNetwork.isNetworkConnected(mActivity)) { mIWebPageView.hindProgressBar(); } // html加载完成之后,添加监听图片的点击js函数 mIWebPageView.addImageClickListener(); super.onPageFinished(view, url); } // 视频全屏播放按返回页面被放大的问题 @Override public void onScaleChanged(WebView view, float oldScale, float newScale) { super.onScaleChanged(view, oldScale, newScale); if (newScale - oldScale > 7) { view.setInitialScale((int) (oldScale / newScale * 100)); //异常放大,缩回去。 } } /** * 网页里可能唤起其他的app */ private void handleOtherwise(Activity activity, String url) { String appPackageName = ""; // 支付宝支付 if (url.contains("alipays")) { appPackageName = "com.eg.android.AlipayGphone"; // 微信支付 } else if (url.contains("weixin://wap/pay")) { appPackageName = "com.tencent.mm"; // 京东产品详情 } else if (url.contains("openapp.jdmobile")) { appPackageName = "com.jingdong.app.mall"; } else { startActivity(url); } if (BaseTools.isApplicationAvilible(activity, appPackageName)) { startActivity(url); } } private void startActivity(String url) { try { Intent intent1 = new Intent(); intent1.setAction("android.intent.action.VIEW"); Uri uri = Uri.parse(url); intent1.setData(uri); intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mActivity.startActivity(intent1); } catch (Exception e) { e.printStackTrace(); } } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/viewmodel/ArticleViewModel.kt ================================================ package com.lxm.wanandroid.viewmodel import android.arch.lifecycle.MutableLiveData import com.lxm.module_library.base.BaseViewModel import com.lxm.module_library.helper.RxHelper import com.lxm.wanandroid.repository.model.* import com.lxm.wanandroid.repository.remote.httpClient.RetrofitClient import io.reactivex.functions.Consumer class ArticleViewModel : BaseViewModel() { var mPage = 0 var banner = MutableLiveData>>() val pagedList = MutableLiveData>>() val loadStatus by lazy { MutableLiveData>() } fun getHomeList(): Listing>> { loadStatus.postValue(Resource.loading()) val subscribe = RetrofitClient.getInstance(RetrofitClient.WAN_BASE_URL).getArticleList(mPage) .compose(RxHelper.rxSchedulerHelper()) .subscribe(Consumer>> { if(it.data != null){ loadStatus.postValue(Resource.success()) pagedList.value = it }else{ loadStatus.postValue(Resource.error()) } }, Consumer { if (mPage > 0) { mPage-- } loadStatus.postValue(Resource.error()) }) addDisposable(subscribe) return Listing(pagedList,loadStatus) } fun getBanners(): MutableLiveData>> { val subscribe = RetrofitClient.getInstance(RetrofitClient.WAN_BASE_URL).getHomeBanner() .compose(RxHelper.rxSchedulerHelper()) .subscribe(Consumer>>{ banner.postValue(it) }, Consumer { banner.postValue(null) }) addDisposable(subscribe) return banner } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/viewmodel/CategoryViewModel.kt ================================================ package com.lxm.wanandroid.viewmodel import android.arch.lifecycle.MutableLiveData import com.lxm.module_library.base.BaseViewModel import com.lxm.module_library.helper.RxHelper import com.lxm.wanandroid.repository.model.* import com.lxm.wanandroid.repository.remote.httpClient.RetrofitClient import io.reactivex.functions.Consumer class CategoryViewModel: BaseViewModel() { var mPage = 0 val pagedList = MutableLiveData>>() val loadStatus by lazy { MutableLiveData>() } fun getCategory(id: Int): Listing>> { loadStatus.postValue(Resource.loading()) val subscribe = RetrofitClient.getInstance(RetrofitClient.WAN_BASE_URL).getCategory(mPage,id) .compose(RxHelper.rxSchedulerHelper()) .subscribe(Consumer>> { if(it.data != null){ loadStatus.postValue(Resource.success()) pagedList.value = it }else{ loadStatus.postValue(Resource.error()) } }, Consumer { if (mPage > 0) { mPage-- } loadStatus.postValue(Resource.error()) }) addDisposable(subscribe) return Listing(pagedList,loadStatus) } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/viewmodel/CollectViewModel.kt ================================================ package com.lxm.wanandroid.viewmodel import android.arch.lifecycle.MutableLiveData import com.lxm.module_library.base.BaseViewModel import com.lxm.module_library.helper.RxHelper import com.lxm.wanandroid.repository.model.* import com.lxm.wanandroid.repository.remote.httpClient.RetrofitClient import com.lxm.wanandroid.ui.adapter.ArticleAdapter import io.reactivex.functions.Consumer class CollectViewModel : BaseViewModel() { var mPage = 0 val collectionList = MutableLiveData>>() val loadStatus by lazy { MutableLiveData>() } fun getCollect(): Listing>> { loadStatus.postValue(Resource.loading()) val subscribe = RetrofitClient.getInstance(RetrofitClient.WAN_BASE_URL).getCollectList(mPage) .compose(RxHelper.rxSchedulerHelper()) .subscribe(Consumer>> { if(it.data != null){ loadStatus.postValue(Resource.success()) collectionList.value = it }else{ loadStatus.postValue(Resource.error()) } }, Consumer { if (mPage > 0) { mPage-- } loadStatus.postValue(Resource.error()) }) addDisposable(subscribe) return Listing(collectionList,loadStatus) } fun collect(id:Int,collectionObserver: ArticleAdapter.CollectionObserver>) { val subscribe = RetrofitClient.getInstance(RetrofitClient.WAN_BASE_URL).collect(id) .compose(RxHelper.rxSchedulerHelper()) .subscribe(Consumer> { collectionObserver.onChanged(it) }, Consumer { collectionObserver.onChanged(null) }) addDisposable(subscribe) } fun unCollect(id:Int,collectionObserver: ArticleAdapter.CollectionObserver>) { val subscribe = RetrofitClient.getInstance(RetrofitClient.WAN_BASE_URL).unCollect(id) .compose(RxHelper.rxSchedulerHelper()) .subscribe(Consumer> { collectionObserver.onChanged(it) }, Consumer { collectionObserver.onChanged(null) }) addDisposable(subscribe) } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/viewmodel/LoginViewModel.kt ================================================ package com.lxm.wanandroid.viewmodel import android.arch.lifecycle.Transformations import com.lxm.module_library.base.BaseViewModel import com.lxm.wanandroid.repository.remote.LoginRepository class LoginViewModel(val loginRepository: LoginRepository) : BaseViewModel() { var loginStatus = Transformations.map(loginRepository.login){it} var registerStauts = Transformations.map(loginRepository.register){it} var logoutStatus = Transformations.map(loginRepository.logout){it} fun login(username:String,password:String) { loginRepository.login(username,password) } fun logout() { loginRepository.logout() } fun register(username:String,password:String,repassword:String) { loginRepository.register(username,password,repassword) } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/viewmodel/NaviViewModelView.kt ================================================ package com.lxm.wanandroid.viewmodel import android.arch.lifecycle.MutableLiveData import com.lxm.module_library.base.BaseViewModel import com.lxm.module_library.helper.RxHelper import com.lxm.wanandroid.repository.model.Navigation import com.lxm.wanandroid.repository.model.Resource import com.lxm.wanandroid.repository.remote.httpClient.RetrofitClient class NaviViewModelView : BaseViewModel() { private val naviList = MutableLiveData>() val loadStatus by lazy { MutableLiveData>() } fun getVavigations(): MutableLiveData> { RetrofitClient.getInstance(RetrofitClient.WAN_BASE_URL).getNavigation() .compose(RxHelper.rxSchedulerHelper()) .subscribe({ naviList.postValue(it.data) }, { loadStatus.postValue(Resource.error()) }) return naviList } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/viewmodel/ProjectViewModel.kt ================================================ package com.lxm.wanandroid.viewmodel import android.arch.lifecycle.MutableLiveData import com.lxm.module_library.base.BaseViewModel import com.lxm.module_library.helper.RxHelper import com.lxm.wanandroid.repository.model.* import com.lxm.wanandroid.repository.remote.httpClient.RetrofitClient import io.reactivex.functions.Consumer class ProjectViewModel : BaseViewModel() { var mPage = 0 val pagedList = MutableLiveData>>() val loadStatus by lazy { MutableLiveData>() } fun getProjects(): Listing>> { loadStatus.postValue(Resource.loading()) val subscribe = RetrofitClient.getInstance(RetrofitClient.WAN_BASE_URL).getProjectList(mPage) .compose(RxHelper.rxSchedulerHelper()) .subscribe(Consumer>> { if(it.data != null){ loadStatus.postValue(Resource.success()) pagedList.value = it }else{ loadStatus.postValue(Resource.error()) } }, Consumer { if (mPage > 0) { mPage-- } loadStatus.postValue(Resource.error()) }) addDisposable(subscribe) return Listing(pagedList,loadStatus) } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/viewmodel/TreeViewModel.kt ================================================ package com.lxm.wanandroid.viewmodel import android.arch.lifecycle.MutableLiveData import com.lxm.module_library.base.BaseViewModel import com.lxm.module_library.helper.RxHelper import com.lxm.wanandroid.repository.model.Resource import com.lxm.wanandroid.repository.model.TreeBean import com.lxm.wanandroid.repository.remote.httpClient.RetrofitClient class TreeViewModel : BaseViewModel() { private val treeList = MutableLiveData>() val loadStatus by lazy { MutableLiveData>() } fun getTrees(): MutableLiveData> { RetrofitClient.getInstance(RetrofitClient.WAN_BASE_URL).getTrees() .compose(RxHelper.rxSchedulerHelper()) .subscribe({ treeList.postValue(it.data) }, { loadStatus.postValue(Resource.error()) }) return treeList } } ================================================ FILE: app/src/main/java/com/lxm/wanandroid/viewmodel/WelfareModelView.kt ================================================ package com.lxm.wanandroid.viewmodel import android.arch.lifecycle.MutableLiveData import com.lxm.module_library.base.BaseViewModel import com.lxm.module_library.helper.RxHelper import com.lxm.wanandroid.repository.model.Resource import com.lxm.wanandroid.repository.model.WelfareResponse import com.lxm.wanandroid.repository.remote.httpClient.RetrofitClient const val PAGE_SIZE = 20 class WelfareModelView : BaseViewModel() { var mPage = 1 val loadStatus by lazy { MutableLiveData>() } fun getWelfare(): MutableLiveData { val welfare = MutableLiveData() RetrofitClient.getInstance(RetrofitClient.GAN_BASE_URL).getWelfare("福利",PAGE_SIZE,mPage) .compose(RxHelper.rxSchedulerHelper()) .subscribe({ welfare.postValue(it) }, { loadStatus.postValue(Resource.error()) }) return welfare } } ================================================ FILE: app/src/main/res/drawable/ic_launcher_background.xml ================================================ ================================================ FILE: app/src/main/res/drawable/process_bg.xml ================================================ ================================================ FILE: app/src/main/res/drawable/selector_bg_tree_tag.xml ================================================ ================================================ FILE: app/src/main/res/drawable/selector_collect_check.xml ================================================ ================================================ FILE: app/src/main/res/drawable-v24/ic_launcher_foreground.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_collection.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_login.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_show_image.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_web_view.xml ================================================ ================================================ FILE: app/src/main/res/layout/article_banner.xml ================================================ ================================================ FILE: app/src/main/res/layout/article_fragment.xml ================================================ ================================================ FILE: app/src/main/res/layout/category_activity.xml ================================================ ================================================ FILE: app/src/main/res/layout/drawer_header_layout.xml ================================================ ================================================ FILE: app/src/main/res/layout/item_article.xml ================================================ ================================================ FILE: app/src/main/res/layout/item_banner.xml ================================================ ================================================ FILE: app/src/main/res/layout/item_image_view_pager.xml ================================================ ================================================ FILE: app/src/main/res/layout/item_tree.xml ================================================ ================================================ FILE: app/src/main/res/layout/item_tree_tag.xml ================================================ ================================================ FILE: app/src/main/res/layout/item_view.xml ================================================ ================================================ FILE: app/src/main/res/layout/item_view_welfare.xml ================================================ ================================================ FILE: app/src/main/res/layout/main_content.xml ================================================ ================================================ FILE: app/src/main/res/layout/tree_fragment.xml ================================================ ================================================ FILE: app/src/main/res/menu/main.xml ================================================ ================================================ FILE: app/src/main/res/menu/navigation.xml ================================================ ================================================ FILE: app/src/main/res/menu/webview_menu.xml ================================================ ================================================ FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml ================================================ ================================================ FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml ================================================ ================================================ FILE: app/src/main/res/values/attrs_circle_view.xml ================================================ ================================================ FILE: app/src/main/res/values/colors.xml ================================================ #008577 #00574B #D81B60 #343434 #F1F3F4 #faebeb #FFFAFA ================================================ FILE: app/src/main/res/values/strings.xml ================================================ WanAndroidKotlin 打开 关闭 ================================================ FILE: app/src/main/res/values/styles.xml ================================================ ================================================ FILE: app/src/test/java/com/lxm/wanandroid/ExampleUnitTest.kt ================================================ package com.lxm.wanandroid import org.junit.Test import org.junit.Assert.* /** * Example local unit test, which will execute on the development machine (host). * * See [testing documentation](http://d.android.com/tools/testing). */ class ExampleUnitTest { @Test fun addition_isCorrect() { assertEquals(4, 2 + 2) } } ================================================ FILE: build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext.kotlin_version = '1.3.21' repositories { google() jcenter() maven { url 'https://jitpack.io' } } dependencies { classpath 'com.android.tools.build:gradle:3.4.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { google() jcenter() maven { url 'https://jitpack.io' } } } task clean(type: Delete) { delete rootProject.buildDir } ext{ retrofitVersion = '2.2.0' okhttploggingVersion = '3.4.1' okhttpVersion = '3.4.1' rxjavaVersion = '2.0.1' rxandroidVersion = '2.0.1' rxbindingVersion = '2.0.0' constraintVersion = '1.1.2' butterknifeVwesion = '8.8.1' glideVersion = '4.9.0' glide_transformations = '3.0.1' } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ #Thu Jun 13 22:10:05 CST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip ================================================ FILE: gradle.properties ================================================ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx1536m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official ================================================ FILE: gradlew ================================================ #!/usr/bin/env sh ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: module_library/.gitignore ================================================ /build ================================================ FILE: module_library/build.gradle ================================================ apply plugin: 'com.android.library' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' android { compileSdkVersion 28 defaultConfig { minSdkVersion 21 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility = '1.8' targetCompatibility = '1.8' } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) //动画 implementation ('com.github.ozodrukh:CircularReveal:2.0.1@aar') { transitive = true; } implementation 'com.android.support:appcompat-v7:28.0.0' implementation "com.android.support:design:28.0.0" testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'com.android.support:design:28.0.0' // Retrofit & okHttp api "com.squareup.retrofit2:retrofit:$rootProject.retrofitVersion" api "com.squareup.retrofit2:converter-gson:$rootProject.retrofitVersion" api "com.squareup.retrofit2:adapter-rxjava2:$rootProject.retrofitVersion" api "com.squareup.okhttp3:logging-interceptor:$rootProject.okhttploggingVersion" api "com.squareup.okhttp3:okhttp:$rootProject.okhttpVersion" // RxBinging api "com.jakewharton.rxbinding2:rxbinding-appcompat-v7:$rootProject.rxbindingVersion" // Glide api "com.github.bumptech.glide:glide:$rootProject.glideVersion" annotationProcessor "com.github.bumptech.glide:compiler:$rootProject.glideVersion" // 高斯模糊和圆角等 api 'jp.wasabeef:glide-transformations:4.0.1' //page def paging_version = "1.0.0" api "android.arch.paging:runtime:$paging_version" testImplementation "android.arch.paging:common:$paging_version" api 'android.arch.paging:rxjava2:1.0.0-rc1' //lifecycle def lifecycle_version = "1.1.1" api "android.arch.lifecycle:extensions:$lifecycle_version" api "android.arch.lifecycle:viewmodel:$lifecycle_version" api "android.arch.lifecycle:livedata:$lifecycle_version" api "android.arch.lifecycle:runtime:$lifecycle_version" annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version" api "android.arch.lifecycle:reactivestreams:$lifecycle_version" //toast implementation 'me.drakeet.support:toastcompat:1.1.0' } repositories { mavenCentral() } ================================================ FILE: module_library/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: module_library/src/androidTest/java/com/lxm/module_library/ExampleInstrumentedTest.java ================================================ package com.lxm.module_library; import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; /** * Instrumented test, which will execute on an Android device. * * @see Testing documentation */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); assertEquals("com.lxm.module_library.test", appContext.getPackageName()); } } ================================================ FILE: module_library/src/main/AndroidManifest.xml ================================================ ================================================ FILE: module_library/src/main/java/com/lxm/module_library/anim/AnimManager.kt ================================================ package com.lxm.module_library.anim import android.content.Context import android.os.Build import android.view.View import android.view.animation.AnimationUtils /** * Created by Horrarndoo on 2017/9/11. * * */ object AnimManager { /** * Alpha and scaleX 动画 * Alpha 0->1 * ScaleX 0.8->1 * * @param context context * @param view view * @param startDelay 动画开始前延时(ms) * @param duration 动画持续时间(ms) */ fun animAlphaAndScaleX(context: Context, view: View, startDelay: Int, duration: Int) { view.alpha = 0f view.scaleX = 0.8f if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { view.animate() .alpha(1f) .scaleX(1f) .setStartDelay(startDelay.toLong()) .setDuration(duration.toLong()) .setInterpolator(AnimUtils.getFastOutSlowInInterpolator(context)) .start() } else { view.animate() .alpha(1f) .scaleX(1f) .setStartDelay(startDelay.toLong()) .setDuration(duration.toLong()) .setInterpolator(AnimationUtils.loadInterpolator(context, android.R.interpolator.linear)) .start() } } /** * Alpha and scale X Y 动画 * Alpha 0->1 * ScaleX 0->1 * ScaleY 0->1 * * @param context context * @param view view * @param startDelay 动画开始前延时(ms) * @param duration 动画持续时间(ms) */ fun animAlphaAndScale(context: Context, view: View, startDelay: Int, duration: Int) { view.alpha = 0f view.scaleX = 0f view.scaleY = 0f view.animate() .alpha(1f) .scaleX(1f) .scaleY(1f) .setStartDelay(startDelay.toLong()) .setDuration(duration.toLong()) .setInterpolator(AnimationUtils.loadInterpolator(context, android.R.interpolator.overshoot)).start() } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/anim/AnimUtils.kt ================================================ /* * Copyright 2015 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lxm.module_library.anim import android.animation.Animator import android.animation.TimeInterpolator import android.content.Context import android.os.Build import android.support.annotation.RequiresApi import android.transition.Transition import android.util.ArrayMap import android.util.Property import android.view.animation.AnimationUtils import android.view.animation.Interpolator import java.util.* /** * Utility methods for working with animations. */ object AnimUtils { private var fastOutSlowIn: Interpolator? = null private var fastOutLinearIn: Interpolator? = null private var linearOutSlowIn: Interpolator? = null fun getFastOutSlowInInterpolator(context: Context): Interpolator? { if (fastOutSlowIn == null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { fastOutSlowIn = AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in) } } return fastOutSlowIn } fun getFastOutLinearInInterpolator(context: Context): Interpolator? { if (fastOutLinearIn == null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { fastOutLinearIn = AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_linear_in) } } return fastOutLinearIn } fun getLinearOutSlowInInterpolator(context: Context): Interpolator? { if (linearOutSlowIn == null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { linearOutSlowIn = AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in) } } return linearOutSlowIn } /** * Linear interpolate between a and b with parameter t. */ fun lerp(a: Float, b: Float, t: Float): Float { return a + (b - a) * t } /** * An implementation of [Property] to be used specifically with fields of * type * `float`. This type-specific subclass enables performance benefit by allowing * calls to a [set()][.set] function that takes the primitive * `float` type and avoids autoboxing and other overhead associated with the * `Float` class. * * @param The class on which the Property is declared. */ abstract class FloatProperty(name: String) : Property(Float::class.java, name) { /** * A type-specific override of the [.set] that is faster when dealing * with fields of type `float`. */ abstract fun setValue(`object`: T, value: Float) override fun set(`object`: T, value: Float?) { setValue(`object`, value!!) } } /** * An implementation of [Property] to be used specifically with fields of * type * `int`. This type-specific subclass enables performance benefit by allowing * calls to a [set()][.set] function that takes the primitive * `int` type and avoids autoboxing and other overhead associated with the * `Integer` class. * * @param The class on which the Property is declared. */ abstract class IntProperty(name: String) : Property(Int::class.java, name) { /** * A type-specific override of the [.set] that is faster when dealing * with fields of type `int`. */ abstract fun setValue(`object`: T, value: Int) override fun set(`object`: T, value: Int) { setValue(`object`, value) } } /** * https://halfthought.wordpress.com/2014/11/07/reveal-transition/ * * * Interrupting Activity transitions can yield an OperationNotSupportedException when the * transition tries to pause the animator. Yikes! We can fix this by wrapping the Animator: */ @RequiresApi(api = Build.VERSION_CODES.KITKAT) class NoPauseAnimator(private val mAnimator: Animator) : Animator() { private val mListeners = ArrayMap() @RequiresApi(api = Build.VERSION_CODES.KITKAT) override fun addListener(listener: Animator.AnimatorListener) { val wrapper = AnimatorListenerWrapper(this, listener) if (!mListeners.containsKey(listener)) { mListeners[listener] = wrapper mAnimator.addListener(wrapper) } } override fun cancel() { mAnimator.cancel() } override fun end() { mAnimator.end() } override fun getDuration(): Long { return mAnimator.duration } override fun getInterpolator(): TimeInterpolator { return mAnimator.interpolator } override fun setInterpolator(timeInterpolator: TimeInterpolator) { mAnimator.interpolator = timeInterpolator } override fun getListeners(): ArrayList { return ArrayList(mListeners.keys) } override fun getStartDelay(): Long { return mAnimator.startDelay } override fun setStartDelay(delayMS: Long) { mAnimator.startDelay = delayMS } override fun isPaused(): Boolean { return mAnimator.isPaused } override fun isRunning(): Boolean { return mAnimator.isRunning } override fun isStarted(): Boolean { return mAnimator.isStarted } /* We don't want to override pause or resume methods because we don't want them * to affect mAnimator. public void pause(); public void resume(); public void addPauseListener(AnimatorPauseListener listener); public void removePauseListener(AnimatorPauseListener listener); */ override fun removeAllListeners() { mListeners.clear() mAnimator.removeAllListeners() } override fun removeListener(listener: Animator.AnimatorListener) { val wrapper = mListeners[listener] if (wrapper != null) { mListeners.remove(listener) mAnimator.removeListener(wrapper) } } override fun setDuration(durationMS: Long): Animator { mAnimator.duration = durationMS return this } override fun setTarget(target: Any?) { mAnimator.setTarget(target) } override fun setupEndValues() { mAnimator.setupEndValues() } override fun setupStartValues() { mAnimator.setupStartValues() } override fun start() { mAnimator.start() } } internal class AnimatorListenerWrapper(private val mAnimator: Animator, private val mListener: Animator.AnimatorListener) : Animator.AnimatorListener { override fun onAnimationStart(animator: Animator) { mListener.onAnimationStart(mAnimator) } override fun onAnimationEnd(animator: Animator) { mListener.onAnimationEnd(mAnimator) } override fun onAnimationCancel(animator: Animator) { mListener.onAnimationCancel(mAnimator) } override fun onAnimationRepeat(animator: Animator) { mListener.onAnimationRepeat(mAnimator) } } @RequiresApi(api = Build.VERSION_CODES.KITKAT) class TransitionListenerAdapter : Transition.TransitionListener { override fun onTransitionStart(transition: Transition) { } override fun onTransitionEnd(transition: Transition) { } override fun onTransitionCancel(transition: Transition) { } override fun onTransitionPause(transition: Transition) { } override fun onTransitionResume(transition: Transition) { } } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/anim/ToolbarAnimManager.kt ================================================ package com.lxm.module_library.anim import android.content.Context import android.support.v7.widget.ActionMenuView import android.support.v7.widget.Toolbar import android.widget.ImageButton import android.widget.TextView import com.lxm.module_library.anim.AnimManager /** * * @date 2017/9/11 * * * Toolbar动画Manager */ object ToolbarAnimManager { /** * Toolbar 进场动画 * * * ActionMenuView渐变动画 * * @param context context * @param toolbar toolbar */ fun animIn(context: Context, toolbar: Toolbar) { var ibIcon: ImageButton? = null var tvTitle: TextView? = null var amvTheme: ActionMenuView? = null val childCount = toolbar.childCount for (i in 0 until childCount) { val child = toolbar.getChildAt(i) if (child is ImageButton) { ibIcon = child continue } if (child is ActionMenuView) { amvTheme = child continue } if (child is TextView) { tvTitle = child } } if (ibIcon != null) { animNavigationIcon(context, ibIcon) } if (tvTitle != null) { animTitle(context, tvTitle) } if (amvTheme != null) { animMenu(context, amvTheme) } } /** * Toolbar Title动画 * * * NavigationIcon渐变动画 * * @param context context * @param imageButton 执行动画的view */ private fun animNavigationIcon(context: Context, imageButton: ImageButton) { AnimManager.animAlphaAndScaleX(context, imageButton, 500, 900) } /** * Toolbar Title动画 * * * ActionMenuView渐变动画 * * @param context context * @param textView 执行动画的view */ private fun animTitle(context: Context, textView: TextView) { AnimManager.animAlphaAndScaleX(context, textView, 500, 900) } /** * Toolbar ActionMenuView动画 * * * ActionMenuView渐变动画 * * @param context context * @param avm 执行动画的view */ private fun animMenu(context: Context, avm: ActionMenuView) { AnimManager.animAlphaAndScale(context, avm, 500, 200) // filter AnimManager.animAlphaAndScale(context, avm, 700, 200) // overflow } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/base/BaseActivity.kt ================================================ package com.lxm.module_library.base import android.arch.lifecycle.ViewModel import android.arch.lifecycle.ViewModelProviders import android.content.res.Configuration import android.content.res.Resources import android.graphics.drawable.AnimationDrawable import android.os.Build import android.support.annotation.LayoutRes import android.support.v7.app.AppCompatActivity import android.support.v7.widget.Toolbar import android.view.View import android.view.ViewGroup import android.view.ViewStub import android.widget.ImageView import android.widget.RelativeLayout import com.lxm.module_library.R import com.lxm.module_library.utils.ClassUtil abstract class BaseActivity : AppCompatActivity() { // ViewModel protected lateinit var viewModel: VM // 布局view protected lateinit var contentsView: View private lateinit var errorView: View private lateinit var loadingView: View private lateinit var mBaseView: View private var mAnimationDrawable: AnimationDrawable? = null protected fun getView(id: Int): T { return findViewById(id) as T } override fun setContentView(@LayoutRes layoutResID: Int) { mBaseView = layoutInflater.inflate(R.layout.activity_base, null, false) contentsView = layoutInflater.inflate(layoutResID, null, false) // content val params = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) contentsView.layoutParams = params val mContainer = mBaseView.findViewById(R.id.container) as RelativeLayout mContainer.addView(contentsView) window.setContentView(mBaseView) errorView = (findViewById(R.id.vs_error_refresh) as ViewStub).inflate() // 设置透明状态栏,兼容4.4 // StatusBarUtil.setColor(this, CommonUtils.getColor(R.color.colorTheme), 0); loadingView = (findViewById(R.id.vs_loading) as ViewStub).inflate() val img = loadingView.findViewById(R.id.img_progress) // 加载动画 mAnimationDrawable = img.drawable as AnimationDrawable // 默认进入页面就开启动画 if (!mAnimationDrawable!!.isRunning) { mAnimationDrawable!!.start() } setToolBar() contentsView.visibility = View.GONE initViewModel() } /** * 初始化ViewModel */ private fun initViewModel() { val viewModelClass = ClassUtil.getViewModel(this) if (viewModelClass != null) { this.viewModel = ViewModelProviders.of(this).get(viewModelClass) } } /** * 设置titlebar */ protected fun setToolBar() { setSupportActionBar(mBaseView.findViewById(R.id.tool_bar) as Toolbar) val actionBar = supportActionBar if (actionBar != null) { //去除默认Title显示 actionBar.setDisplayShowTitleEnabled(false) actionBar.setDisplayHomeAsUpEnabled(true) actionBar.setHomeAsUpIndicator(R.drawable.icon_back) } (mBaseView.findViewById(R.id.tool_bar) as Toolbar).setNavigationOnClickListener { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { finishAfterTransition() } else { onBackPressed() } } } override fun setTitle(text: CharSequence) { (mBaseView.findViewById(R.id.tool_bar) as Toolbar).title = text } protected fun showLoading() { if (loadingView != null && loadingView.visibility != View.VISIBLE) { loadingView.visibility = View.VISIBLE } // 开始动画 if (!mAnimationDrawable!!.isRunning) { mAnimationDrawable!!.start() } if (contentsView.visibility != View.GONE) { contentsView.visibility = View.GONE } if (errorView != null) { errorView.visibility = View.GONE } } protected fun showContentView() { if (loadingView != null && loadingView.visibility != View.GONE) { loadingView.visibility = View.GONE } // 停止动画 if (mAnimationDrawable!!.isRunning) { mAnimationDrawable!!.stop() } if (errorView != null) { errorView.visibility = View.GONE } if (contentsView.visibility != View.VISIBLE) { contentsView.visibility = View.VISIBLE } } protected fun showError() { if (loadingView != null && loadingView.visibility != View.GONE) { loadingView.visibility = View.GONE } // 停止动画 if (mAnimationDrawable!!.isRunning) { mAnimationDrawable!!.stop() } if (errorView != null) { errorView.visibility = View.VISIBLE // 点击加载失败布局 errorView.setOnClickListener { showLoading() onRetry() } } if (contentsView.visibility != View.GONE) { contentsView.visibility = View.GONE } } /** * 失败后点击刷新 */ protected fun onRetry() { } override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) if (newConfig.fontScale != 1f) { resources } } /** * 禁止改变字体大小 */ override fun getResources(): Resources { val res = super.getResources() val config = Configuration() config.setToDefaults() res.updateConfiguration(config, res.displayMetrics) return res } public override fun onDestroy() { super.onDestroy() } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/base/BaseFragment.kt ================================================ package com.lxm.module_library.base import android.arch.lifecycle.ViewModel import android.arch.lifecycle.ViewModelProviders import android.graphics.drawable.AnimationDrawable import android.os.Bundle import android.support.v4.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.ViewStub import android.widget.ImageView import android.widget.RelativeLayout import com.lxm.module_library.R import com.lxm.module_library.utils.ClassUtil import io.reactivex.disposables.CompositeDisposable abstract class BaseFragment : Fragment() { // ViewModel protected lateinit var viewModel: VM // 布局view protected lateinit var contentView: View // fragment是否显示了 protected var mIsVisible = false // 加载中 private val loadingView: View by lazy{ (getView(R.id.vs_loading) as ViewStub).inflate() } // 加载失败 private val errorView: View by lazy{ (getView(R.id.vs_error_refresh) as ViewStub).inflate() } // 动画 private var mAnimationDrawable: AnimationDrawable? = null private var mCompositeDisposable: CompositeDisposable? = null private var isViewCreated: Boolean = false // 界面是否已创建完成 private var isVisibleToUser: Boolean = false // 是否对用户可见 private var isDataLoaded: Boolean = false // 数据是否已请求, isNeedReload()返回false的时起作用 /** * ViewPager场景下,判断父fragment是否可见 * * @return */ private val isParentVisible: Boolean get() { val fragment = parentFragment return fragment == null || fragment is BaseFragment<*> && fragment.isVisibleToUser } /** * fragment再次可见时,是否重新请求数据,默认为flase则只请求一次数据 * * @return */ protected val isNeedReload: Boolean get() = false /** * 布局 */ protected abstract fun getLayoutID():Int // 实现具体的数据请求逻辑 protected abstract fun loadData() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val ll = inflater.inflate(R.layout.fragment_base, null) contentView = LayoutInflater.from(activity).inflate(getLayoutID(), null, false) val params = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) contentView.layoutParams = params val mContainer = ll.findViewById(R.id.container) mContainer.addView(contentView) return ll } /** * 在这里实现Fragment数据的缓加载. */ override fun setUserVisibleHint(isVisibleToUser: Boolean) { super.setUserVisibleHint(isVisibleToUser) this.isVisibleToUser = isVisibleToUser tryLoadData() } private fun tryLoadData() { if (isViewCreated && isVisibleToUser && isParentVisible && (isNeedReload || !isDataLoaded)) { loadData() isDataLoaded = true dispatchParentVisibleState() } } /** * ViewPager场景下,当前fragment可见,如果其子fragment也可见,则尝试让子fragment加载请求 */ private fun dispatchParentVisibleState() { val fragmentManager = childFragmentManager val fragments = fragmentManager.fragments if (fragments.isEmpty()) { return } for (child in fragments) { if (child is BaseFragment<*> && child.isVisibleToUser) { child.tryLoadData() } } } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) isViewCreated = true initLoadView() initViewModel() tryLoadData() } private fun initLoadView() { val img = loadingView.findViewById(R.id.img_progress) // 加载动画 mAnimationDrawable = img.drawable as AnimationDrawable // 默认进入页面就开启动画 if (!mAnimationDrawable?.isRunning!!) { mAnimationDrawable?.start() } contentView.visibility = View.GONE } /** * 初始化ViewModel */ private fun initViewModel() { val viewModelClass = ClassUtil.getViewModel(this) if (viewModelClass != null) { this.viewModel = ViewModelProviders.of(this).get(viewModelClass) } } protected fun getView(id: Int): T { return view!!.findViewById(id) as T } /** * 加载失败后点击后的操作 */ protected abstract fun onRetry() /** * 显示加载中状态 */ protected fun showLoading() { if (loadingView != null && loadingView.visibility != View.VISIBLE) { loadingView.visibility = View.VISIBLE } // 开始动画 if (!mAnimationDrawable!!.isRunning) { mAnimationDrawable!!.start() } if (contentView.visibility != View.GONE) { contentView.visibility = View.GONE } if (errorView != null) { errorView.visibility = View.GONE } } /** * 加载完成的状态 */ protected fun showContentView() { if (loadingView != null && loadingView.visibility != View.GONE) { loadingView.visibility = View.GONE } // 停止动画 if (mAnimationDrawable != null && mAnimationDrawable!!.isRunning) { mAnimationDrawable!!.stop() } if (errorView != null) { errorView.visibility = View.GONE } if (contentView.visibility != View.VISIBLE) { contentView.visibility = View.VISIBLE } } /** * 加载失败点击重新加载的状态 */ protected fun showError() { if (loadingView != null && loadingView.visibility != View.GONE) { loadingView.visibility = View.GONE } // 停止动画 if (mAnimationDrawable != null && mAnimationDrawable!!.isRunning) { mAnimationDrawable!!.stop() } if (errorView != null) { errorView.visibility = View.VISIBLE // 点击加载失败布局 errorView.setOnClickListener { showLoading() onRetry() } } else { } if (contentView.visibility != View.GONE) { contentView.visibility = View.GONE } } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/base/BaseViewModel.kt ================================================ package com.lxm.module_library.base import android.arch.lifecycle.ViewModel import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.Disposable open class BaseViewModel : ViewModel() { private var mCompositeDisposable: CompositeDisposable? = null protected fun addDisposable(disposable: Disposable) { if (this.mCompositeDisposable == null) { this.mCompositeDisposable = CompositeDisposable() } this.mCompositeDisposable?.add(disposable) } override fun onCleared() { super.onCleared() if (this.mCompositeDisposable != null && !mCompositeDisposable?.isDisposed!!) { this.mCompositeDisposable?.clear() } } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/base/NoViewModel.java ================================================ package com.lxm.module_library.base; import android.arch.lifecycle.ViewModel; public class NoViewModel extends ViewModel { } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/global/AppManager.java ================================================ package com.lxm.module_library.global; import android.app.Activity; import android.app.ActivityManager; import android.content.Context; import java.util.Stack; /** * Created by Horrarndoo on 2017/4/5. *

* AppManager 管理Activity栈 */ public class AppManager { private static Stack activityStack; private static AppManager instance; private AppManager() { } /** * 单一实例 */ public static AppManager getAppManager() { if (instance == null) { instance = new AppManager(); } return instance; } /** * 添加Activity到堆栈 */ public void addActivity(Activity activity) { if (activityStack == null) { activityStack = new Stack(); } activityStack.add(activity); } /** * 获取当前Activity(堆栈中最后一个压入的) */ public Activity currentActivity() { Activity activity = activityStack.lastElement(); return activity; } /** * 结束当前Activity(堆栈中最后一个压入的) */ public void finishActivity() { Activity activity = activityStack.lastElement(); finishActivity(activity); } /** * 结束指定的Activity */ public void finishActivity(Activity activity) { if (activity != null) { activityStack.remove(activity); activity.finish(); activity = null; } } /** * 结束指定类名的Activity */ public void finishActivity(Class cls) { for (Activity activity : activityStack) { if (activity.getClass().equals(cls)) { finishActivity(activity); } } } /** * 结束所有Activity */ public void finishAllActivity() { for (int i = 0, size = activityStack.size(); i < size; i++) { if (null != activityStack.get(i)) { activityStack.get(i).finish(); } } activityStack.clear(); } /** * 退出应用程序 */ public void AppExit(Context context) { try { finishAllActivity(); ActivityManager activityMgr = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); activityMgr.killBackgroundProcesses(context.getPackageName()); System.exit(0); } catch (Exception e) { } } public boolean isAppExit() { return activityStack == null || activityStack.isEmpty(); } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/global/GlobalApplication.java ================================================ package com.lxm.module_library.global; import android.app.Application; import android.content.Context; import android.os.Handler; import com.lxm.module_library.utils.PreferencesUtil; /** * Created by Horrarndoo on 2017/9/1. *

* 全局Application */ public class GlobalApplication extends Application { protected static Context context; protected static Handler handler; protected static int mainThreadId; private static GlobalApplication globalApplication; public static GlobalApplication getInstance() { return globalApplication; } @Override public void onCreate() { super.onCreate(); globalApplication = this; context = getApplicationContext(); handler = new Handler(); mainThreadId = android.os.Process.myTid(); PreferencesUtil.Companion.get(this); } /** * 获取上下文对象 * * @return context */ public static Context getContext() { return context; } /** * 获取全局handler * * @return 全局handler */ public static Handler getHandler() { return handler; } /** * 获取主线程id * * @return 主线程id */ public static int getMainThreadId() { return mainThreadId; } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/helper/RetrofitCreateHelper.java ================================================ package com.lxm.module_library.helper; import com.lxm.module_library.utils.AppUtils; import com.lxm.module_library.helper.okhttp.TrustManager; import com.lxm.module_library.helper.okhttp.cache.CacheInterceptor; import com.lxm.module_library.helper.okhttp.cache.HttpCache; import com.lxm.module_library.helper.okhttp.cookies.CookieManger; import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.Retrofit; import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; import java.util.concurrent.TimeUnit; /** * Created by Horrarndoo on 2017/9/7. *

*/ public class RetrofitCreateHelper { private static final int TIMEOUT_READ = 5; private static final int TIMEOUT_CONNECTION = 5; private static final HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor() .setLevel(HttpLoggingInterceptor.Level.BODY); private static CacheInterceptor cacheInterceptor = new CacheInterceptor(); private static OkHttpClient okHttpClient = new OkHttpClient.Builder() //SSL证书 .sslSocketFactory(TrustManager.getUnsafeOkHttpClient()) .hostnameVerifier(org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER) //打印日志 .addInterceptor(interceptor) //设置Cache拦截器 .addNetworkInterceptor(cacheInterceptor) .addInterceptor(cacheInterceptor) .cache(HttpCache.getCache()) // 设置 Cookie .cookieJar(new CookieManger(AppUtils.INSTANCE.getContext())) //time out .connectTimeout(TIMEOUT_CONNECTION, TimeUnit.SECONDS) .readTimeout(TIMEOUT_READ, TimeUnit.SECONDS) .writeTimeout(TIMEOUT_READ, TimeUnit.SECONDS) //失败重连 .retryOnConnectionFailure(true) .build(); public static T createApi(Class clazz, String url) { Retrofit retrofit = new Retrofit.Builder() .baseUrl(url) .client(okHttpClient) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .build(); return retrofit.create(clazz); } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/helper/RxHelper.java ================================================ package com.lxm.module_library.helper; import io.reactivex.*; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; /** * Created by Horrarndoo on 2017/9/12. *

*/ public class RxHelper { /** * 统一线程处理 *

* 发布事件io线程,接收事件主线程 */ public static ObservableTransformer rxSchedulerHelper() {//compose处理线程 return new ObservableTransformer() { @Override public ObservableSource apply(Observable upstream) { return upstream.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); } }; } /** * 生成Flowable * * @param t * @return Flowable */ public static Flowable createFlowable(final T t) { return Flowable.create(new FlowableOnSubscribe() { @Override public void subscribe(FlowableEmitter emitter) throws Exception { try { emitter.onNext(t); emitter.onComplete(); } catch (Exception e) { emitter.onError(e); } } }, BackpressureStrategy.BUFFER); } /** * 生成Observable * * @param t * @return Flowable */ public static Observable createObservable(final T t) { return Observable.create(new ObservableOnSubscribe() { @Override public void subscribe(ObservableEmitter emitter) throws Exception { try { emitter.onNext(t); emitter.onComplete(); } catch (Exception e) { emitter.onError(e); } } }); } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/helper/okhttp/TrustManager.java ================================================ package com.lxm.module_library.helper.okhttp; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; /** * Created by Horrarndoo on 2017/9/12. *

*/ public class TrustManager { public static SSLSocketFactory getUnsafeOkHttpClient() { try { // Create a trust manager that does not validate certificate chains final X509TrustManager[] trustAllCerts = new X509TrustManager[]{new X509TrustManager() { @Override public void checkClientTrusted( X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted( X509Certificate[] chain, String authType) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } }}; // Install the all-trusting trust manager final SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); // Create an ssl socket factory with our all-trusting manager final SSLSocketFactory sslSocketFactory = sslContext .getSocketFactory(); return sslSocketFactory; } catch (Exception e) { throw new RuntimeException(e); } } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/helper/okhttp/cache/CacheInterceptor.java ================================================ package com.lxm.module_library.helper.okhttp.cache; import com.lxm.module_library.utils.AppUtils; import com.lxm.module_library.utils.HttpUtils; import com.lxm.module_library.utils.NetworkConnectionUtils; import okhttp3.CacheControl; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; import java.io.IOException; /** * Created by Horrarndoo on 2017/9/12. *

* CacheInterceptor */ public class CacheInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); if (NetworkConnectionUtils.INSTANCE.isNetworkConnected(AppUtils.INSTANCE.getContext())) { // 有网络时, 缓存60s int maxAge = 10 ; request = request.newBuilder() .removeHeader("User-Agent") .header("User-Agent", HttpUtils.INSTANCE.getUserAgent()) .build(); Response response = chain.proceed(request); return response.newBuilder() .removeHeader("Pragma") .removeHeader("Cache-Control") .header("Cache-Control", "public, max-age=" + maxAge) .build(); } else { // 无网络时,缓存为4周 int maxStale = 60 * 60 * 24 * 28; request = request.newBuilder() .cacheControl(CacheControl.FORCE_CACHE) .removeHeader("User-Agent") .header("User-Agent", HttpUtils.INSTANCE.getUserAgent()) .build(); Response response = chain.proceed(request); return response.newBuilder() .removeHeader("Pragma") .removeHeader("Cache-Control") .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale) .build(); } } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/helper/okhttp/cache/HttpCache.java ================================================ package com.lxm.module_library.helper.okhttp.cache; import com.lxm.module_library.utils.AppUtils; import okhttp3.Cache; import java.io.File; /** * Created by Horrarndoo on 2017/9/12. *

*/ public class HttpCache { private static final int HTTP_RESPONSE_DISK_CACHE_MAX_SIZE = 50 * 1024 * 1024; public static Cache getCache() { return new Cache(new File(AppUtils.INSTANCE.getContext().getExternalCacheDir().getAbsolutePath() + File .separator + "data/NetCache"), HTTP_RESPONSE_DISK_CACHE_MAX_SIZE); } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/helper/okhttp/cookies/CookieManger.java ================================================ package com.lxm.module_library.helper.okhttp.cookies; import android.content.Context; import okhttp3.Cookie; import okhttp3.CookieJar; import okhttp3.HttpUrl; import java.util.List; /** * Created by CoderLengary */ public class CookieManger implements CookieJar { private static Context mContext; private static PersistentCookieStore cookieStore; public CookieManger(Context context) { mContext = context; if (cookieStore == null) { cookieStore = new PersistentCookieStore(mContext); } } @Override public void saveFromResponse(HttpUrl url, List cookies) { if (cookies != null && cookies.size() > 0) { for (Cookie item : cookies) { cookieStore.add(url, item); } } } @Override public List loadForRequest(HttpUrl url) { List cookies = cookieStore.get(url); return cookies; } public static void clearAllCookies() { cookieStore.removeAll(); } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/helper/okhttp/cookies/OkHttpCookies.java ================================================ package com.lxm.module_library.helper.okhttp.cookies; import okhttp3.Cookie; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class OkHttpCookies implements Serializable { private transient final Cookie cookies; private transient Cookie clientCookies; public OkHttpCookies(Cookie cookies) { this.cookies = cookies; } public Cookie getCookies() { Cookie bestCookies = cookies; if (clientCookies != null) { bestCookies = clientCookies; } return bestCookies; } private void writeObject(ObjectOutputStream out) throws IOException { out.writeObject(cookies.name()); out.writeObject(cookies.value()); out.writeLong(cookies.expiresAt()); out.writeObject(cookies.domain()); out.writeObject(cookies.path()); out.writeBoolean(cookies.secure()); out.writeBoolean(cookies.httpOnly()); out.writeBoolean(cookies.hostOnly()); out.writeBoolean(cookies.persistent()); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { String name = (String) in.readObject(); String value = (String) in.readObject(); long expiresAt = in.readLong(); String domain = (String) in.readObject(); String path = (String) in.readObject(); boolean secure = in.readBoolean(); boolean httpOnly = in.readBoolean(); boolean hostOnly = in.readBoolean(); Cookie.Builder builder = new Cookie.Builder(); builder = builder.name(name); builder = builder.value(value); builder = builder.expiresAt(expiresAt); builder = hostOnly ? builder.hostOnlyDomain(domain) : builder.domain(domain); builder = builder.path(path); builder = secure ? builder.secure() : builder; builder = httpOnly ? builder.httpOnly() : builder; clientCookies = builder.build(); } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/helper/okhttp/cookies/PersistentCookieStore.java ================================================ package com.lxm.module_library.helper.okhttp.cookies; import android.content.Context; import android.content.SharedPreferences; import android.text.TextUtils; import android.util.Log; import okhttp3.Cookie; import okhttp3.HttpUrl; import java.io.*; import java.util.*; import java.util.concurrent.ConcurrentHashMap; public class PersistentCookieStore { private static final String LOG_TAG = "PersistentCookieStore"; private static final String COOKIE_PREFS = "Cookies_Prefs"; private final Map> cookies; private final SharedPreferences cookiePrefs; public PersistentCookieStore(Context context) { cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0); cookies = new HashMap<>(); //将持久化的cookies缓存到内存中 即map cookies Map prefsMap = cookiePrefs.getAll(); for (Map.Entry entry : prefsMap.entrySet()) { String[] cookieNames = TextUtils.split((String) entry.getValue(), ","); for (String name : cookieNames) { String encodedCookie = cookiePrefs.getString(name, null); if (encodedCookie != null) { Cookie decodedCookie = decodeCookie(encodedCookie); if (decodedCookie != null) { if (!cookies.containsKey(entry.getKey())) { cookies.put(entry.getKey(), new ConcurrentHashMap()); } cookies.get(entry.getKey()).put(name, decodedCookie); } } } } } protected String getCookieToken(Cookie cookie) { return cookie.name() + "@" + cookie.domain(); } public void add(HttpUrl url, Cookie cookie) { String name = getCookieToken(cookie); if (!cookies.containsKey(url.host())) { cookies.put(url.host(), new ConcurrentHashMap()); } cookies.get(url.host()).put(name, cookie); //cookies持久化到本地 SharedPreferences.Editor prefsWriter = cookiePrefs.edit(); prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet())); prefsWriter.putString(name, encodeCookie(new OkHttpCookies(cookie))); prefsWriter.apply(); } public List get(HttpUrl url) { ArrayList ret = new ArrayList<>(); if (cookies.containsKey(url.host())) { ret.addAll(cookies.get(url.host()).values()); } return ret; } public boolean removeAll() { SharedPreferences.Editor prefsWriter = cookiePrefs.edit(); prefsWriter.clear(); prefsWriter.apply(); cookies.clear(); return true; } /** * cookies 序列化成 string * * @param cookie 要序列化的cookie * @return 序列化之后的string */ protected String encodeCookie(OkHttpCookies cookie) { if (cookie == null) return null; ByteArrayOutputStream os = new ByteArrayOutputStream(); try { ObjectOutputStream outputStream = new ObjectOutputStream(os); outputStream.writeObject(cookie); } catch (IOException e) { Log.d(LOG_TAG, "IOException in encodeCookie", e); return null; } return byteArrayToHexString(os.toByteArray()); } /** * 将字符串反序列化成cookies * * @param cookieString cookies string * @return cookie object */ protected Cookie decodeCookie(String cookieString) { byte[] bytes = hexStringToByteArray(cookieString); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); Cookie cookie = null; try { ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); cookie = ((OkHttpCookies) objectInputStream.readObject()).getCookies(); } catch (IOException e) { Log.d(LOG_TAG, "IOException in decodeCookie", e); } catch (ClassNotFoundException e) { Log.d(LOG_TAG, "ClassNotFoundException in decodeCookie", e); } return cookie; } /** * 二进制数组转十六进制字符串 * * @param bytes byte array to be converted * @return string containing hex values */ protected String byteArrayToHexString(byte[] bytes) { StringBuilder sb = new StringBuilder(bytes.length * 2); for (byte element : bytes) { int v = element & 0xff; if (v < 16) { sb.append('0'); } sb.append(Integer.toHexString(v)); } return sb.toString().toUpperCase(Locale.US); } /** * 十六进制字符串转二进制数组 * * @param hexString string of hex-encoded values * @return decoded byte array */ protected byte[] hexStringToByteArray(String hexString) { int len = hexString.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16)); } return data; } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/materialLogin/DefaultLoginView.java ================================================ package com.lxm.module_library.materialLogin; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.os.Build; import android.support.design.widget.TextInputLayout; import android.support.v4.content.ContextCompat; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.FrameLayout; import android.widget.TextView; import com.lxm.module_library.R; /** * Created by shem on 1/15/16. */ public class DefaultLoginView extends FrameLayout { public interface DefaultLoginViewListener { void onLogin(TextInputLayout loginUser, TextInputLayout loginPass); } private DefaultLoginViewListener listener; public DefaultLoginView(Context context) { this(context, null); } public DefaultLoginView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DefaultLoginView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public DefaultLoginView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context, attrs); } private void init(Context context, AttributeSet attrs) { LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.login_layout, this, true); TypedArray a = context.getTheme().obtainStyledAttributes( attrs, R.styleable.DefaultLoginView, 0, 0); TextView loginTitle = (TextView) findViewById(R.id.login_title); final TextInputLayout loginUser = (TextInputLayout) findViewById(R.id.login_user); final TextInputLayout loginPass = (TextInputLayout) findViewById(R.id.login_pass); TextView loginBtn = (TextView) findViewById(R.id.login_btn); findViewById(R.id.login_btn).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (listener != null) { listener.onLogin(loginUser, loginPass); } } }); try { String string = a.getString(R.styleable.DefaultLoginView_loginTitle); if (string != null) { loginTitle.setText(string); } string = a.getString(R.styleable.DefaultLoginView_loginHint); if (string != null) { loginUser.setHint(string); } string = a.getString(R.styleable.DefaultLoginView_loginPasswordHint); if (string != null) { loginPass.setHint(string); } string = a.getString(R.styleable.DefaultLoginView_loginActionText); if (string != null) { loginBtn.setText(string); } int color = a.getColor(R.styleable.DefaultLoginView_loginTextColor, ContextCompat.getColor(getContext(), R.color.material_login_login_text_color)); loginUser.getEditText().setTextColor(color); loginPass.getEditText().setTextColor(color); } finally { a.recycle(); } } public void setListener(DefaultLoginViewListener listener) { this.listener = listener; } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/materialLogin/DefaultRegisterView.java ================================================ package com.lxm.module_library.materialLogin; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.os.Build; import android.support.design.widget.TextInputLayout; import android.support.v4.content.ContextCompat; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.FrameLayout; import android.widget.TextView; import com.lxm.module_library.R; /** * Created by shem on 1/15/16. */ public class DefaultRegisterView extends FrameLayout implements RegisterView { public interface DefaultRegisterViewListener { void onRegister(TextInputLayout registerUser, TextInputLayout registerPass, TextInputLayout registerPassRep); } private DefaultRegisterViewListener listener; private View registerCancel; public DefaultRegisterView(Context context) { this(context, null); } public DefaultRegisterView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DefaultRegisterView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public DefaultRegisterView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context, attrs); } private void init(Context context, AttributeSet attrs) { LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.register_layout, this, true); TypedArray a = context.getTheme().obtainStyledAttributes( attrs, R.styleable.DefaultRegisterView, 0, 0); TextView registerTitle = (TextView) findViewById(R.id.register_title); final TextInputLayout registerUser = (TextInputLayout) findViewById(R.id.register_user); final TextInputLayout registerPass = (TextInputLayout) findViewById(R.id.register_pass); final TextInputLayout registerPassRep = (TextInputLayout) findViewById(R.id.register_pass_rep); TextView registerBtn = (TextView) findViewById(R.id.register_btn); registerCancel = findViewById(R.id.register_cancel); registerBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { listener.onRegister(registerUser, registerPass, registerPassRep); } }); try { String string = a.getString(R.styleable.DefaultRegisterView_registerTitle); if (string != null) { registerTitle.setText(string); } string = a.getString(R.styleable.DefaultRegisterView_registerHint); if (string != null) { registerUser.setHint(string); } string = a.getString(R.styleable.DefaultRegisterView_registerPasswordHint); if (string != null) { registerPass.setHint(string); } string = a.getString(R.styleable.DefaultRegisterView_registerRepeatPasswordHint); if (string != null) { registerPassRep.setHint(string); } string = a.getString(R.styleable.DefaultRegisterView_registerActionText); if (string != null) { registerBtn.setText(string); } int color = a.getColor(R.styleable.DefaultRegisterView_registerTextColor, ContextCompat.getColor(getContext(), R.color.material_login_register_text_color)); registerUser.getEditText().setTextColor(color); registerPass.getEditText().setTextColor(color); registerPassRep.getEditText().setTextColor(color); } finally { a.recycle(); } } public void setListener(DefaultRegisterViewListener listener) { this.listener = listener; } @Override public View getCancelRegisterView() { return registerCancel; } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/materialLogin/MaterialLoginView.java ================================================ package com.lxm.module_library.materialLogin; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Matrix; import android.graphics.Path; import android.graphics.PathMeasure; import android.graphics.RectF; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.support.design.widget.FloatingActionButton; import android.support.v4.view.ViewCompat; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.animation.AccelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Transformation; import android.widget.FrameLayout; import com.lxm.module_library.R; import io.codetail.animation.ViewAnimationUtils; /** * Created by shem on 1/15/16. */ @SuppressLint("RestrictedApi") public class MaterialLoginView extends FrameLayout { private static final String TAG = MaterialLoginView.class.getSimpleName(); private FloatingActionButton registerFab; private View registerCancel; private ViewGroup loginCard; private ViewGroup registerCard; private View registerView; private View loginView; public MaterialLoginView(Context context) { this(context, null); } public MaterialLoginView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MaterialLoginView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public MaterialLoginView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context, attrs); } private void init(Context context, AttributeSet attrs) { LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.login_view, this, true); loginCard = (ViewGroup) findViewById(R.id.login_card); registerCard = (ViewGroup) findViewById(R.id.register_card); registerFab = (FloatingActionButton) findViewById(R.id.register_fab); registerFab.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { animateRegister(); } else { //There's a bug in support implementation of FAB, so we firing animation with little delay so it won't be override by Android new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { animateRegister(); } }, 100); } } }); TypedArray a = context.getTheme().obtainStyledAttributes( attrs, R.styleable.MaterialLoginView, 0, 0); try { int loginViewId = a.getResourceId(R.styleable.MaterialLoginView_loginView, R.layout.default_login_view); inflate(getContext(), loginViewId, loginCard); loginView = loginCard.getChildAt(0); int registerViewId = a.getResourceId(R.styleable.MaterialLoginView_registerView, R.layout.default_register_view); inflate(getContext(), registerViewId, registerCard); registerView = registerCard.getChildAt(0); if (registerView instanceof RegisterView) { registerCancel = ((RegisterView) registerView).getCancelRegisterView(); }else if (registerView.findViewById(R.id.register_cancel) != null) { registerCancel = registerView.findViewById(R.id.register_cancel); } if (registerCancel != null) { registerCancel.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { animateLogin(); } }); } else { Log.d(TAG, "The register view should implement RegisterView interface or set a view with register_cancel id"); } registerFab.setImageResource( a.getResourceId(R.styleable.MaterialLoginView_registerIcon, R.drawable.ic_add)); boolean enabled = a.getBoolean(R.styleable.MaterialLoginView_registerEnabled, true); registerFab.setVisibility(enabled ? View.VISIBLE : View.GONE); } finally { a.recycle(); } } private void animateRegister() { Path path = new Path(); if (isRTL()) { RectF rect = new RectF(-41F, -40F, 241F, 242F); path.addArc(rect, -135F, -180F); path.lineTo(200F, -50F); } else { RectF rect = new RectF(-241F, -40F, 41F, 242F); path.addArc(rect, -45F, 180F); path.lineTo(-0F, -50F); } FabAnimation fabAnimation = new FabAnimation(path); fabAnimation.setDuration(400); fabAnimation.setInterpolator(new AccelerateInterpolator()); fabAnimation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { Animator animator = getCircularRevealAnimation(registerCard, isRTL() ? 250 : registerCard.getWidth() - 250, 400, 0f, 2F * registerCard.getHeight()); animator.setDuration(700); animator.setStartDelay(200); animator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { registerCard.setVisibility(View.VISIBLE); } @Override public void onAnimationEnd(Animator animation) { loginCard.setVisibility(View.GONE); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); animator.start(); } @Override public void onAnimationEnd(Animation animation) { registerFab.setVisibility(View.GONE); } @Override public void onAnimationRepeat(Animation animation) { } }); registerFab.startAnimation(fabAnimation); } private boolean isRTL() { return ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL; } private Animator getCircularRevealAnimation(View view, int centerX, int centerY, float startRadius, float endRadius) { return ViewAnimationUtils.createCircularReveal( view, centerX, centerY, startRadius, endRadius); } public void animateLogin() { registerCancel.animate().scaleX(0F).scaleY(0F).alpha(0F).rotation(90F). setDuration(200).setInterpolator(new AccelerateInterpolator()).start(); Animator animator = getCircularRevealAnimation(registerCard, registerCard.getWidth() / 2, registerCard.getHeight() / 2, 1f * registerCard.getHeight(), 0F); animator.setDuration(500); animator.setStartDelay(100); animator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { loginCard.setVisibility(View.VISIBLE); } @Override public void onAnimationEnd(Animator animation) { registerCard.setVisibility(View.GONE); registerCancel.setScaleX(1F); registerCancel.setScaleY(1F); registerCancel.setAlpha(1F); registerCancel.setRotation(0F); registerFab.setVisibility(View.VISIBLE); ObjectAnimator animX = ObjectAnimator.ofFloat(registerFab, "scaleX", 0F, 1F); ObjectAnimator animY = ObjectAnimator.ofFloat(registerFab, "scaleY", 0F, 1F); ObjectAnimator alpha = ObjectAnimator.ofFloat(registerFab, "alpha", 0F, 1F); ObjectAnimator rotation = ObjectAnimator.ofFloat(registerFab, "rotation", 90F, 0F); AnimatorSet animator = new AnimatorSet(); animator.playTogether(animX, animY, alpha, rotation); animator.setInterpolator(new AccelerateInterpolator()); animator.setDuration(200); animator.start(); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); animator.start(); } public View getLoginView() { return loginView; } public View getRegisterView() { return registerView; } class FabAnimation extends Animation { private PathMeasure measure; private float[] pos; public FabAnimation(Path path) { measure = new PathMeasure(path, false); pos = new float[]{0, 0}; } @Override protected void applyTransformation(float interpolatedTime, Transformation t) { measure.getPosTan(measure.getLength() * interpolatedTime, pos, null); Matrix matrix = t.getMatrix(); matrix.setTranslate(pos[0], pos[1]); matrix.preRotate(interpolatedTime * 45); t.setAlpha(1 - interpolatedTime); } } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/materialLogin/RegisterView.java ================================================ package com.lxm.module_library.materialLogin; import android.view.View; /** * Created by shem on 16/09/2016. */ public interface RegisterView { View getCancelRegisterView(); } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/statusbar/StatusBarUtil.java ================================================ package com.lxm.module_library.statusbar; import android.annotation.TargetApi; import android.app.Activity; import android.app.TabActivity; import android.content.Context; import android.graphics.Color; import android.os.Build; import android.support.annotation.ColorInt; import android.support.v4.widget.DrawerLayout; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.LinearLayout; /** * Created by Jaeger on 16/2/14. *

* Email: chjie.jaeger@gmail.com * GitHub: https://github.com/laobie */ public class StatusBarUtil { public static final int DEFAULT_STATUS_BAR_ALPHA = 112; /** * 设置状态栏颜色 * * @param activity 需要设置的 activity * @param color 状态栏颜色值 */ public static void setColor(Activity activity, @ColorInt int color) { setColor(activity, color, DEFAULT_STATUS_BAR_ALPHA); } /** * 设置状态栏颜色 * * @param activity 需要设置的activity * @param color 状态栏颜色值 * @param statusBarAlpha 状态栏透明度 */ public static void setColor(Activity activity, @ColorInt int color, int statusBarAlpha) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); activity.getWindow().setStatusBarColor(calculateStatusColor(color, statusBarAlpha)); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView(); int count = decorView.getChildCount(); if (count > 0 && decorView.getChildAt(count - 1) instanceof StatusBarView) { decorView.getChildAt(count - 1).setBackgroundColor(calculateStatusColor(color, statusBarAlpha)); } else { StatusBarView statusView = createStatusBarView(activity, color, statusBarAlpha); decorView.addView(statusView); } setRootView(activity); } } /** * 设置状态栏纯色 不加半透明效果 * * @param activity 需要设置的 activity * @param color 状态栏颜色值 */ public static void setColorNoTranslucent(Activity activity, @ColorInt int color) { setColor(activity, color, 0); } /** * 设置状态栏颜色(5.0以下无半透明效果,不建议使用) * * @param activity 需要设置的 activity * @param color 状态栏颜色值 */ @Deprecated public static void setColorDiff(Activity activity, @ColorInt int color) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { return; } activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); // 生成一个状态栏大小的矩形 ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView(); int count = decorView.getChildCount(); if (count > 0 && decorView.getChildAt(count - 1) instanceof StatusBarView) { decorView.getChildAt(count - 1).setBackgroundColor(color); } else { StatusBarView statusView = createStatusBarView(activity, color); decorView.addView(statusView); } setRootView(activity); } /** * 使状态栏半透明 *

* 适用于图片作为背景的界面,此时需要图片填充到状态栏 * * @param activity 需要设置的activity */ public static void setTranslucent(Activity activity) { setTranslucent(activity, DEFAULT_STATUS_BAR_ALPHA); } /** * 使状态栏半透明 *

* 适用于图片作为背景的界面,此时需要图片填充到状态栏 * * @param activity 需要设置的activity * @param statusBarAlpha 状态栏透明度 */ public static void setTranslucent(Activity activity, int statusBarAlpha) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { return; } setTransparent(activity); addTranslucentView(activity, statusBarAlpha); } /** * 针对根布局是 CoordinatorLayout, 使状态栏半透明 *

* 适用于图片作为背景的界面,此时需要图片填充到状态栏 * * @param activity 需要设置的activity * @param statusBarAlpha 状态栏透明度 */ public static void setTranslucentForCoordinatorLayout(Activity activity, int statusBarAlpha) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { return; } transparentStatusBar(activity); addTranslucentView(activity, statusBarAlpha); } /** * 设置状态栏全透明 * * @param activity 需要设置的activity */ public static void setTransparent(Activity activity) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { return; } transparentStatusBar(activity); setRootView(activity); } /** * 使状态栏透明(5.0以上半透明效果,不建议使用) *

* 适用于图片作为背景的界面,此时需要图片填充到状态栏 * * @param activity 需要设置的activity */ @Deprecated public static void setTranslucentDiff(Activity activity) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // 设置状态栏透明 activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); setRootView(activity); } } /** * 为DrawerLayout 布局设置状态栏变色 * * @param activity 需要设置的activity * @param drawerLayout DrawerLayout * @param color 状态栏颜色值 */ public static void setColorForDrawerLayout(Activity activity, DrawerLayout drawerLayout, @ColorInt int color) { setColorForDrawerLayout(activity, drawerLayout, color, DEFAULT_STATUS_BAR_ALPHA); } /** * 为DrawerLayout 布局设置状态栏颜色,纯色 * * @param activity 需要设置的activity * @param drawerLayout DrawerLayout * @param color 状态栏颜色值 */ public static void setColorNoTranslucentForDrawerLayout(Activity activity, DrawerLayout drawerLayout, @ColorInt int color) { setColorForDrawerLayout(activity, drawerLayout, color, 0); } /** * 为DrawerLayout 布局设置状态栏变色 * * @param activity 需要设置的activity * @param drawerLayout DrawerLayout * @param color 状态栏颜色值 * @param statusBarAlpha 状态栏透明度 */ public static void setColorForDrawerLayout(Activity activity, DrawerLayout drawerLayout, @ColorInt int color, int statusBarAlpha) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); activity.getWindow().setStatusBarColor(Color.TRANSPARENT); } else { activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } // 生成一个状态栏大小的矩形 // 添加 statusBarView 到布局中 ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0); if (contentLayout.getChildCount() > 0 && contentLayout.getChildAt(0) instanceof StatusBarView) { contentLayout.getChildAt(0).setBackgroundColor(calculateStatusColor(color, statusBarAlpha)); } else { StatusBarView statusBarView = createStatusBarView(activity, color); contentLayout.addView(statusBarView, 0); } // 内容布局不是 LinearLayout 时,设置padding top if (!(contentLayout instanceof LinearLayout) && contentLayout.getChildAt(1) != null) { contentLayout.getChildAt(1) .setPadding(contentLayout.getPaddingLeft(), getStatusBarHeight(activity) + contentLayout.getPaddingTop(), contentLayout.getPaddingRight(), contentLayout.getPaddingBottom()); } // 设置属性 ViewGroup drawer = (ViewGroup) drawerLayout.getChildAt(1); drawerLayout.setFitsSystemWindows(false); contentLayout.setFitsSystemWindows(false); contentLayout.setClipToPadding(true); drawer.setFitsSystemWindows(false); addTranslucentView(activity, statusBarAlpha); } /** * 为DrawerLayout 布局设置状态栏变色(5.0以下无半透明效果,不建议使用) * * @param activity 需要设置的activity * @param drawerLayout DrawerLayout * @param color 状态栏颜色值 */ @Deprecated public static void setColorForDrawerLayoutDiff(Activity activity, DrawerLayout drawerLayout, @ColorInt int color) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); // 生成一个状态栏大小的矩形 ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0); if (contentLayout.getChildCount() > 0 && contentLayout.getChildAt(0) instanceof StatusBarView) { contentLayout.getChildAt(0).setBackgroundColor(calculateStatusColor(color, DEFAULT_STATUS_BAR_ALPHA)); } else { // 添加 statusBarView 到布局中 StatusBarView statusBarView = createStatusBarView(activity, color); contentLayout.addView(statusBarView, 0); } // 内容布局不是 LinearLayout 时,设置padding top if (!(contentLayout instanceof LinearLayout) && contentLayout.getChildAt(1) != null) { contentLayout.getChildAt(1).setPadding(0, getStatusBarHeight(activity), 0, 0); } // 设置属性 ViewGroup drawer = (ViewGroup) drawerLayout.getChildAt(1); drawerLayout.setFitsSystemWindows(false); contentLayout.setFitsSystemWindows(false); contentLayout.setClipToPadding(true); drawer.setFitsSystemWindows(false); } } /** * 为 DrawerLayout 布局设置状态栏透明 * * @param activity 需要设置的activity * @param drawerLayout DrawerLayout */ public static void setTranslucentForDrawerLayout(Activity activity, DrawerLayout drawerLayout) { setTranslucentForDrawerLayout(activity, drawerLayout, DEFAULT_STATUS_BAR_ALPHA); } /** * 为 DrawerLayout 布局设置状态栏透明 * * @param activity 需要设置的activity * @param drawerLayout DrawerLayout */ public static void setTranslucentForDrawerLayout(Activity activity, DrawerLayout drawerLayout, int statusBarAlpha) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { return; } setTransparentForDrawerLayout(activity, drawerLayout); addTranslucentView(activity, statusBarAlpha); } /** * 为 DrawerLayout 布局设置状态栏透明 * * @param activity 需要设置的activity * @param drawerLayout DrawerLayout */ public static void setTransparentForDrawerLayout(Activity activity, DrawerLayout drawerLayout) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); activity.getWindow().setStatusBarColor(Color.TRANSPARENT); } else { activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0); // 内容布局不是 LinearLayout 时,设置padding top if (!(contentLayout instanceof LinearLayout) && contentLayout.getChildAt(1) != null) { contentLayout.getChildAt(1).setPadding(0, getStatusBarHeight(activity), 0, 0); } // 设置属性 ViewGroup drawer = (ViewGroup) drawerLayout.getChildAt(1); drawerLayout.setFitsSystemWindows(false); contentLayout.setFitsSystemWindows(false); contentLayout.setClipToPadding(true); drawer.setFitsSystemWindows(false); } /** * 为 DrawerLayout 布局设置状态栏透明(5.0以上半透明效果,不建议使用) * * @param activity 需要设置的activity * @param drawerLayout DrawerLayout */ @Deprecated public static void setTranslucentForDrawerLayoutDiff(Activity activity, DrawerLayout drawerLayout) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // 设置状态栏透明 activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); // 设置内容布局属性 ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0); contentLayout.setFitsSystemWindows(true); contentLayout.setClipToPadding(true); // 设置抽屉布局属性 ViewGroup vg = (ViewGroup) drawerLayout.getChildAt(1); vg.setFitsSystemWindows(false); // 设置 DrawerLayout 属性 drawerLayout.setFitsSystemWindows(false); } } /** * 为头部是 ImageView 的界面设置状态栏全透明 * * @param activity 需要设置的activity * @param needOffsetView 需要向下偏移的 View */ public static void setTransparentForImageView(Activity activity, View needOffsetView) { setTranslucentForImageView(activity, 0, needOffsetView); } /** * 为头部是 ImageView 的界面设置状态栏透明(使用默认透明度) * * @param activity 需要设置的activity * @param needOffsetView 需要向下偏移的 View */ public static void setTranslucentForImageView(Activity activity, View needOffsetView) { setTranslucentForImageView(activity, DEFAULT_STATUS_BAR_ALPHA, needOffsetView); } /** * 为头部是 ImageView 的界面设置状态栏透明 * * @param activity 需要设置的activity * @param statusBarAlpha 状态栏透明度 * @param needOffsetView 需要向下偏移的 View */ public static void setTranslucentForImageView(Activity activity, int statusBarAlpha, View needOffsetView) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { activity.getWindow().setStatusBarColor(Color.TRANSPARENT); activity.getWindow() .getDecorView() .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); if (activity instanceof TabActivity){ activity.getWindow()//兼容TabActivity .setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } } else { activity.getWindow() .setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } addTranslucentView(activity, statusBarAlpha); if (needOffsetView != null) { ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) needOffsetView.getLayoutParams(); if (layoutParams != null) { layoutParams.setMargins(0, getStatusBarHeight(activity), 0, 0); } } } public static void setMargin(Activity activity, View needOffsetView) { if (needOffsetView != null) { ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) needOffsetView.getLayoutParams(); if (layoutParams != null) { layoutParams.setMargins(0, getStatusBarHeight(activity), 0, 0); } } } /** * 为 fragment 头部是 ImageView 的设置状态栏透明 * * @param activity fragment 对应的 activity * @param needOffsetView 需要向下偏移的 View */ public static void setTranslucentForImageViewInFragment(Activity activity, View needOffsetView) { setTranslucentForImageViewInFragment(activity, DEFAULT_STATUS_BAR_ALPHA, needOffsetView); } /** * 为 fragment 头部是 ImageView 的设置状态栏透明 * * @param activity fragment 对应的 activity * @param needOffsetView 需要向下偏移的 View */ public static void setTransparentForImageViewInFragment(Activity activity, View needOffsetView) { setTranslucentForImageViewInFragment(activity, 0, needOffsetView); } /** * 为 fragment 头部是 ImageView 的设置状态栏透明 * * @param activity fragment 对应的 activity * @param statusBarAlpha 状态栏透明度 * @param needOffsetView 需要向下偏移的 View */ public static void setTranslucentForImageViewInFragment(Activity activity, int statusBarAlpha, View needOffsetView) { setTranslucentForImageView(activity, statusBarAlpha, needOffsetView); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { clearPreviousSetting(activity); } } @TargetApi(Build.VERSION_CODES.KITKAT) private static void clearPreviousSetting(Activity activity) { ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView(); int count = decorView.getChildCount(); if (count > 0 && decorView.getChildAt(count - 1) instanceof StatusBarView) { decorView.removeViewAt(count - 1); ViewGroup rootView = (ViewGroup) ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0); rootView.setPadding(0, 0, 0, 0); } } /** * 添加半透明矩形条 * * @param activity 需要设置的 activity * @param statusBarAlpha 透明值 */ private static void addTranslucentView(Activity activity, int statusBarAlpha) { ViewGroup contentView = (ViewGroup) activity.findViewById(android.R.id.content); if (contentView.getChildCount() > 1) { contentView.getChildAt(1).setBackgroundColor(Color.argb(statusBarAlpha, 0, 0, 0)); } else { contentView.addView(createTranslucentStatusBarView(activity, statusBarAlpha)); } } /** * 生成一个和状态栏大小相同的彩色矩形条 * * @param activity 需要设置的 activity * @param color 状态栏颜色值 * @return 状态栏矩形条 */ private static StatusBarView createStatusBarView(Activity activity, @ColorInt int color) { // 绘制一个和状态栏一样高的矩形 StatusBarView statusBarView = new StatusBarView(activity); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity)); statusBarView.setLayoutParams(params); statusBarView.setBackgroundColor(color); return statusBarView; } /** * 生成一个和状态栏大小相同的半透明矩形条 * * @param activity 需要设置的activity * @param color 状态栏颜色值 * @param alpha 透明值 * @return 状态栏矩形条 */ private static StatusBarView createStatusBarView(Activity activity, @ColorInt int color, int alpha) { // 绘制一个和状态栏一样高的矩形 StatusBarView statusBarView = new StatusBarView(activity); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity)); statusBarView.setLayoutParams(params); statusBarView.setBackgroundColor(calculateStatusColor(color, alpha)); return statusBarView; } /** * 设置根布局参数 */ private static void setRootView(Activity activity) { ViewGroup rootView = (ViewGroup) ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0); rootView.setFitsSystemWindows(true); rootView.setClipToPadding(true); } /** * 使状态栏透明 */ @TargetApi(Build.VERSION_CODES.KITKAT) private static void transparentStatusBar(Activity activity) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); activity.getWindow().setStatusBarColor(Color.TRANSPARENT); } else { activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } } /** * 创建半透明矩形 View * * @param alpha 透明值 * @return 半透明 View */ private static StatusBarView createTranslucentStatusBarView(Activity activity, int alpha) { // 绘制一个和状态栏一样高的矩形 StatusBarView statusBarView = new StatusBarView(activity); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity)); statusBarView.setLayoutParams(params); statusBarView.setBackgroundColor(Color.argb(alpha, 0, 0, 0)); return statusBarView; } /** * 获取状态栏高度 * * @param context context * @return 状态栏高度 */ public static int getStatusBarHeight(Context context) { // 获得状态栏高度 int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); return context.getResources().getDimensionPixelSize(resourceId); } /** * 计算状态栏颜色 * * @param color color值 * @param alpha alpha值 * @return 最终的状态栏颜色 */ private static int calculateStatusColor(@ColorInt int color, int alpha) { float a = 1 - alpha / 255f; int red = color >> 16 & 0xff; int green = color >> 8 & 0xff; int blue = color & 0xff; red = (int) (red * a + 0.5); green = (int) (green * a + 0.5); blue = (int) (blue * a + 0.5); return 0xff << 24 | red << 16 | green << 8 | blue; } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/statusbar/StatusBarView.java ================================================ package com.lxm.module_library.statusbar; import android.content.Context; import android.util.AttributeSet; import android.view.View; /** * Created by Jaeger on 16/6/8. * * Email: chjie.jaeger@gmail.com * GitHub: https://github.com/laobie */ public class StatusBarView extends View { public StatusBarView(Context context, AttributeSet attrs) { super(context, attrs); } public StatusBarView(Context context) { super(context); } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/AppUtils.kt ================================================ package com.lxm.module_library.utils import android.Manifest import android.annotation.SuppressLint import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Environment import android.os.Handler import android.support.v4.app.ActivityCompat import android.telephony.TelephonyManager import android.util.Log import android.view.inputmethod.InputMethodManager import android.widget.EditText import com.lxm.module_library.global.GlobalApplication import java.io.File /** * * App工具类 */ object AppUtils { /** * 获取上下文对象 * * @return 上下文对象 */ val context: Context get() = GlobalApplication.getContext() /** * 获取全局handler * * @return 全局handler */ private val handler: Handler get() = GlobalApplication.getHandler() /** * 获取主线程id * * @return 主线程id */ private val mainThreadId: Int get() = GlobalApplication.getMainThreadId() /** * 获取SD卡路径 * * @return 如果sd卡不存在则返回null */ private//判断sd卡是否存在 val sdPath: File? get() { var sdDir: File? = null val sdCardExist = Environment.getExternalStorageState() == Environment .MEDIA_MOUNTED if (sdCardExist) { sdDir = Environment.getExternalStorageDirectory() } return sdDir } /** * 判断是否运行在主线程 * * @return true:当前线程运行在主线程 * fasle:当前线程没有运行在主线程 */ private// 获取当前线程id, 如果当前线程id和主线程id相同, 那么当前就是主线程 val isRunOnUIThread: Boolean get() { val myTid = android.os.Process.myTid() return if (myTid == mainThreadId) { true } else false } /** * 获取版本名称 */ private fun getAppVersionName(context: Context): String? { var versionName: String? = null try { // ---get the package info--- val pm = context.packageManager val pi = pm.getPackageInfo(context.packageName, 0) versionName = pi.versionName if (versionName == null || versionName.length <= 0) { return "" } } catch (e: Exception) { Log.e("VersionInfo", "Exception", e) } return versionName } /** * 获取版本号 */ private fun getAppVersionCode(context: Context): Int { var versioncode = -1 try { // ---get the package info--- val pm = context.packageManager val pi = pm.getPackageInfo(context.packageName, 0) versioncode = pi.versionCode } catch (e: Exception) { Log.e("VersionInfo", "Exception", e) } return versioncode } @SuppressLint("MissingPermission") private fun getIMEI(context: Context): String? { val tm = context.getSystemService(Context .TELEPHONY_SERVICE) as TelephonyManager return if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) { tm.deviceId } else null } /** * 显示软键盘 */ private fun openSoftInput(et: EditText) { val inputMethodManager = et.context .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager inputMethodManager.showSoftInput(et, InputMethodManager.HIDE_NOT_ALWAYS) } /** * 隐藏软键盘 */ private fun hideSoftInput(et: EditText) { val inputMethodManager = et.context .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager inputMethodManager.hideSoftInputFromWindow(et.windowToken, InputMethodManager .HIDE_NOT_ALWAYS) } /** * 安装文件 * * @param data */ private fun promptInstall(context: Context, data: Uri) { val promptInstall = Intent(Intent.ACTION_VIEW) .setDataAndType(data, "application/vnd.android.package-archive") // FLAG_ACTIVITY_NEW_TASK 可以保证安装成功时可以正常打开 app promptInstall.flags = Intent.FLAG_ACTIVITY_NEW_TASK context.startActivity(promptInstall) } fun copy2clipboard(context: Context, text: String) { val cm = context.getSystemService(Context .CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newPlainText("clip", text) cm.primaryClip = clip } /** * 运行在主线程 * * @param r 运行的Runnable对象 */ private fun runOnUIThread(r: Runnable) { if (isRunOnUIThread) { // 已经是主线程, 直接运行 r.run() } else { // 如果是子线程, 借助handler让其运行在主线程 handler.post(r) } } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/BaseTools.java ================================================ package com.lxm.module_library.utils; import android.app.Activity; import android.app.ActivityManager; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; import android.view.View; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.TextView; import com.lxm.module_library.global.GlobalApplication; import java.lang.reflect.Field; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.List; import java.util.Locale; /** * Created by jingbin on 2017/2/13. */ public class BaseTools { //获取图片所在文件夹名称 public static String getDir(String path) { String subString = path.substring(0, path.lastIndexOf('/')); return subString.substring(subString.lastIndexOf('/') + 1, subString.length()); } public static int getWindowWidth(Context context) { // 获取屏幕分辨率 WindowManager wm = (WindowManager) (context .getSystemService(Context.WINDOW_SERVICE)); DisplayMetrics dm = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(dm); int mScreenWidth = dm.widthPixels; return mScreenWidth; } public static int getWindowHeigh(Context context) { // 获取屏幕分辨率 WindowManager wm = (WindowManager) (context .getSystemService(Context.WINDOW_SERVICE)); DisplayMetrics dm = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(dm); int mScreenHeigh = dm.heightPixels; return mScreenHeigh; } //获得状态栏/通知栏的高度 public static int getStatusBarHeight(Context context) { Class c = null; Object obj = null; Field field = null; int x = 0, statusBarHeight = 0; try { c = Class.forName("com.android.internal.R$dimen"); obj = c.newInstance(); field = c.getField("status_bar_height"); x = Integer.parseInt(field.get(obj).toString()); statusBarHeight = context.getResources().getDimensionPixelSize(x); } catch (Exception e1) { e1.printStackTrace(); } return statusBarHeight; } /** * 使用默认方式显示货币: * 例如:¥12,345.46 默认保留2位小数,四舍五入 * * @param d double * @return String */ public static String formatCurrency(double d) { String s = ""; try { DecimalFormat nf = (DecimalFormat) NumberFormat.getCurrencyInstance(Locale.CHINA); s = nf.format(d); } catch (Exception e) { e.printStackTrace(); return "" + d; } return s; } /** * 去掉无效小数点 ".00" */ public static String formatMoney(double d) { String tmp = formatCurrency(d); if (tmp.endsWith(".00")) { return tmp.substring(0, tmp.length() - 3); } else { return tmp; } } /** * 处于栈顶的Activity名 */ public String getTopActivityName(Context context) { ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List var2 = am.getRunningTasks(1); return ((ActivityManager.RunningTaskInfo) var2.get(0)).topActivity.getClassName(); } public static void setText(String text, TextView textView) { if (textView != null) { if (TextUtils.isEmpty(text)) { textView.setText(""); } else { textView.setText(text); } } } /** * 获取当前应用的版本号 */ public static String getVersionName() { // 获取packagemanager的实例 PackageManager packageManager = GlobalApplication.getInstance().getPackageManager(); // getPackageName()是你当前类的包名,0代表是获取版本信息 PackageInfo packInfo = null; try { packInfo = packageManager.getPackageInfo(GlobalApplication.getInstance().getPackageName(), 0); return packInfo.versionName; } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); return "1.0"; } } /** * 实现文本复制功能 * * @param content 复制的文本 */ public static void copy(String content) { if (!TextUtils.isEmpty(content)) { // 得到剪贴板管理器 ClipboardManager cmb = (ClipboardManager) GlobalApplication.getInstance().getSystemService(Context.CLIPBOARD_SERVICE); cmb.setText(content.trim()); // 创建一个剪贴数据集,包含一个普通文本数据条目(需要复制的数据) ClipData clipData = ClipData.newPlainText(null, content); // 把数据集设置(复制)到剪贴板 cmb.setPrimaryClip(clipData); } } /** * 清空剪切板内容 * 加上 manager.setText(null); 不然小米3Android6.0 清空无效 * 因为api过期使用最新注意使用 manager.getPrimaryClip(),不然小米3Android6.0 清空无效 */ public static void clearClipboard() { ClipboardManager manager = (ClipboardManager) GlobalApplication.getInstance().getSystemService(Context.CLIPBOARD_SERVICE); if (manager != null) { try { manager.setPrimaryClip(manager.getPrimaryClip()); manager.setText(null); } catch (Exception e) { } } } /** * 使用浏览器打开链接 */ public static void openLink(Context context, String content) { Uri issuesUrl = Uri.parse(content); Intent intent = new Intent(Intent.ACTION_VIEW, issuesUrl); context.startActivity(intent); } /** * 判断手机是否安装某个应用 * * @param context * @param appPackageName 应用包名 * @return true:安装,false:未安装 */ public static boolean isApplicationAvilible(Context context, String appPackageName) { try { // 获取packagemanager PackageManager packageManager = context.getPackageManager(); // 获取所有已安装程序的包信息 List pinfo = packageManager.getInstalledPackages(0); if (pinfo != null) { for (int i = 0; i < pinfo.size(); i++) { String pn = pinfo.get(i).packageName; if (appPackageName.equals(pn)) { return true; } } } return false; } catch (Exception ignored) { return false; } } /** * 隐藏软键盘 * * @param activity 要隐藏软键盘的activity */ public static void hideSoftKeyBoard(Activity activity) { final View v = activity.getWindow().peekDecorView(); if (v != null && v.getWindowToken() != null) { try { ((InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(activity.getCurrentFocus() .getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); } catch (Exception e) { Log.w("TAG", e.toString()); } } } /** * 显示软键盘 */ public static void showSoftKeyBoard(Activity activity, View view) { ((InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE)).showSoftInput(view, 0); } /** * 发起添加群流程。群号:Android 云阅交流群(727379132) 的 key 为: jSdY9xxzZ7xXG55_V8OUb8ds_YT6JjAn * 调用 joinQQGroup(jSdY9xxzZ7xXG55_V8OUb8ds_YT6JjAn) 即可发起手Q客户端申请加群 Android 云阅交流群(727379132) * * @param key 由官网生成的key */ public static void joinQQGroup(Context context, String key) { Intent intent = new Intent(); intent.setData(Uri.parse("mqqopensdkapi://bizAgent/qm/qr?url=http%3A%2F%2Fqm.qq.com%2Fcgi-bin%2Fqm%2Fqr%3Ffrom%3Dapp%26p%3Dandroid%26k%3D" + key)); // 此Flag可根据具体产品需要自定义,如设置,则在加群界面按返回,返回手Q主界面,不设置,按返回会返回到呼起产品界面 //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) try { context.startActivity(intent); } catch (Exception e) { // 未安装手Q或安装的版本不支持 ToastUtil.showToastLong("未安装手Q或安装的版本不支持~"); } } public static void joinQQChat(Context context, String qqNumber) { Intent intent = new Intent(); intent.setData(Uri.parse("mqqwpa://im/chat?chat_type=wpa&uin=" + qqNumber)); try { context.startActivity(intent); } catch (Exception e) { // 未安装手Q或安装的版本不支持 ToastUtil.showToastLong("未安装手Q或安装的版本不支持~"); } } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/BitmapUtils.kt ================================================ package com.lxm.module_library.utils import android.content.Context import android.content.res.Resources import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Canvas import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.os.Build import android.renderscript.Allocation import android.renderscript.Element import android.renderscript.RenderScript import android.renderscript.ScriptIntrinsicBlur import android.view.View import java.io.* /** * * 图片工具类 */ object BitmapUtils { /** * 将一个view转换成bitmap位图 * * @param view 要转换的View * @return view转换的bitmap */ fun viewToBitmap(view: View): Bitmap { val bitmap = Bitmap.createBitmap(view.measuredWidth, view.measuredHeight, Bitmap.Config.ARGB_8888) view.draw(Canvas(bitmap)) return bitmap } /** * 获取模糊虚化的bitmap * * @param context * @param bitmap 要模糊的图片 * @param radius 模糊等级 >=0 && <=25 * @return */ fun getBlurBitmap(context: Context, bitmap: Bitmap, radius: Int): Bitmap { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { blurBitmap(context, bitmap, radius) } else bitmap } /** * android系统的模糊方法 * * @param bitmap 要模糊的图片 * @param radius 模糊等级 >=0 && <=25 */ private fun blurBitmap(context: Context, bitmap: Bitmap, radius: Int): Bitmap { if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { val outBitmap = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap .Config.ARGB_8888) val rs = RenderScript.create(context) val blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)) val allIn = Allocation.createFromBitmap(rs, bitmap) val allOut = Allocation.createFromBitmap(rs, outBitmap) blurScript.setRadius(radius.toFloat()) blurScript.setInput(allIn) blurScript.forEach(allOut) allOut.copyTo(outBitmap) bitmap.recycle() rs.destroy() return outBitmap } else { return bitmap } } /** * 根据资源获取Bitmap */ open fun getFitSampleBitmap(resources: Resources, id:Int, width: Int, height: Int):Bitmap{ var options = BitmapFactory.Options() options.inJustDecodeBounds = true BitmapFactory.decodeResource(resources,id,options) options.inSampleSize = getFitInSampleSize(height,width,options) options.inJustDecodeBounds = false return BitmapFactory.decodeResource(resources,id) } /** * 按图片尺寸压缩 参数是bitmap * @param bitmap * @param pixelW * @param pixelH * @return */ fun compressImageFromBitmap(bitmap: Bitmap, pixelW: Int, pixelH: Int): Bitmap { val os = ByteArrayOutputStream() bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os) if (os.toByteArray().size / 1024 > 512) {//判断如果图片大于0.5M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出 os.reset() bitmap.compress(Bitmap.CompressFormat.JPEG, 50, os)//这里压缩50%,把压缩后的数据存放到baos中 } var `is` = ByteArrayInputStream(os.toByteArray()) val options = BitmapFactory.Options() options.inJustDecodeBounds = true options.inPreferredConfig = Bitmap.Config.RGB_565 BitmapFactory.decodeStream(`is`, null, options) options.inJustDecodeBounds = false options.inSampleSize = getFitInSampleSize(if (pixelH > pixelW) pixelW else pixelH, pixelW * pixelH,options) `is` = ByteArrayInputStream(os.toByteArray()) return BitmapFactory.decodeStream(`is`, null, options) } /** * 根据文件路径获取Bitmap */ private fun getFitSampleBitmap(path: String, width: Int, height: Int):Bitmap{ var options = BitmapFactory.Options() options.inJustDecodeBounds = true BitmapFactory.decodeFile(path,options) options.inSampleSize = getFitInSampleSize(height,width,options) options.inJustDecodeBounds = false return BitmapFactory.decodeFile(path,options) } /** * 根据字节流获取Bitmap */ fun getFitSimpleBitmap(inputStream: InputStream, filePath:String, width: Int, height: Int):Bitmap{ return getFitSampleBitmap(createStreamToFile(filePath,inputStream),width,height) } fun createStreamToFile(path: String,inputStream:InputStream):String{ var file = File(path) if (file.exists()){ file.delete() } file.createNewFile() var outputStream = FileOutputStream(file) var byte = ByteArray(1024) var len = 0 while ((inputStream.read(byte))!= -1){ len = inputStream.read(byte) outputStream.write(byte,0,len) } inputStream.close() outputStream.close() return path } /** * 获取压缩比例 */ private fun getFitInSampleSize( height:Int,width:Int,options: BitmapFactory.Options):Int{ var inSampleSize = 1 if (options.outWidth > width || options.outHeight > height){ var widthRadio : Int = Math.round(options.outWidth.toFloat() / width.toFloat()) var heightRadio : Int = Math.round(options.outHeight.toFloat() / height.toFloat()) inSampleSize = Math.min(widthRadio,heightRadio) } return inSampleSize } /** * Drawable To Bitmap */ private fun drawableToBitamp(drawable: Drawable): Bitmap { if (drawable is BitmapDrawable) { return drawable.bitmap } val w = drawable.intrinsicWidth val h = drawable.intrinsicHeight val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) val canvas = Canvas(bitmap) drawable.setBounds(0, 0, w, h) drawable.draw(canvas) return bitmap } /** * 压缩图片质量 */ fun compressImage(bitmap: Bitmap): Bitmap { val baos = ByteArrayOutputStream() //质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos) var options = 100 //循环判断如果压缩后图片是否大于50kb,大于继续压缩 while (baos.toByteArray().size / 1024 > 50) { baos.reset() bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos) options -= 10//每次都减少10 } //把压缩后的数据baos存放到ByteArrayInputStream中 val isBm = ByteArrayInputStream(baos.toByteArray()) //把ByteArrayInputStream数据生成图片 return BitmapFactory.decodeStream(isBm, null, null) } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/CheckNetwork.java ================================================ package com.lxm.module_library.utils; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; /** * 用于判断是不是联网状态 * * @author Dzy */ public class CheckNetwork { /** * 判断网络是否连通 */ public static boolean isNetworkConnected(Context context) { try { if(context!=null){ @SuppressWarnings("static-access") ConnectivityManager cm = (ConnectivityManager) context .getSystemService(context.CONNECTIVITY_SERVICE); NetworkInfo info = cm.getActiveNetworkInfo(); return info != null && info.isConnected(); }else{ /**如果context为空,就返回false,表示网络未连接*/ return false; } }catch (Exception e){ e.printStackTrace(); return false; } } public static boolean isWifiConnected(Context context) { if (context != null) { ConnectivityManager cm = (ConnectivityManager) context .getSystemService(context.CONNECTIVITY_SERVICE); NetworkInfo info = cm.getActiveNetworkInfo(); return info != null && (info.getType() == ConnectivityManager.TYPE_WIFI); } else { /**如果context为null就表示为未连接*/ return false; } } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/ClassUtil.kt ================================================ package com.lxm.module_library.utils import android.arch.lifecycle.AndroidViewModel import android.arch.lifecycle.ViewModel import com.lxm.module_library.base.NoViewModel import java.lang.reflect.ParameterizedType object ClassUtil { /** * 获取泛型ViewModel的class对象 */ fun getViewModel(obj: Any): Class? { val currentClass = obj.javaClass val tClass = getGenericClass(currentClass, ViewModel::class.java) return if (tClass == null || tClass == AndroidViewModel::class.java || tClass == NoViewModel::class.java) { null } else tClass } private fun getGenericClass(klass: Class<*>, filterClass: Class<*>): Class? { val type = klass.genericSuperclass if (type == null || type !is ParameterizedType) return null val types = type.actualTypeArguments for (t in types) { val tClass = t as Class if (filterClass.isAssignableFrom(tClass)) { return tClass } } return null } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/DateUtils.kt ================================================ package com.lxm.module_library.utils import android.annotation.SuppressLint import android.text.TextUtils import android.text.format.DateFormat import java.text.ParseException import java.text.SimpleDateFormat import java.util.* /** * Created by Horrarndoo on 2017/8/31. * * 日期时间工具类 */ object DateUtils { private const val ONE_SECOND_MILLIONS: Long = 1000 private const val ONE_MINUTE_MILLIONS = 60 * ONE_SECOND_MILLIONS private const val ONE_HOUR_MILLIONS = 60 * ONE_MINUTE_MILLIONS private const val ONE_DAY_MILLIONS = 24 * ONE_HOUR_MILLIONS private const val DAY_OF_YEAR = 365 /** * 日期格式为 2016-02-03 17:04:58 */ private const val PATTERN_DATE = "yyyy年MM月dd日" private const val PATTERN_TIME = "HH:mm:ss" private const val PATTERN_SPLIT = " " private const val PATTERN = PATTERN_DATE + PATTERN_SPLIT + PATTERN_TIME fun getShortTime(dateStr: String): String { val str: String val date = str2date(dateStr) val curDate = Date() val durTime = curDate.time - date!!.time val dayDiff = calculateDayDiff(date, curDate) return if (durTime <= 10 * ONE_MINUTE_MILLIONS) { "刚刚" } else if (durTime < ONE_HOUR_MILLIONS) { (durTime / ONE_MINUTE_MILLIONS).toString() + "分钟前" } else if (dayDiff == 0) { (durTime / ONE_HOUR_MILLIONS).toString() + "小时前" } else if (dayDiff == -1) { "昨天" + DateFormat.format("HH:mm", date) } else if (isSameYear(date, curDate) && dayDiff < -1) { DateFormat.format("MM-dd", date).toString() } else { DateFormat.format("yyyy-MM", date).toString() } } /** * 获取日期 PATTERN_DATE 部分 */ fun getDate(date: String): String { return if (TextUtils.isEmpty(date) || !date.contains(PATTERN_SPLIT)) { "" } else date.split(PATTERN_SPLIT.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0] } /** * 原有日期上累加月 * * @return 累加后的日期 PATTERN_DATE 部分 */ fun addMonth(date: String, moonCount: Int): String { var date = date if (TextUtils.isEmpty(date)) { // val df = SimpleDateFormat(PATTERN_DATE + PATTERN_SPLIT + PATTERN_TIME) val df = SimpleDateFormat.getDateTimeInstance() date = df.format(Date()) } val calendar = str2calendar(date) calendar!!.add(Calendar.MONTH, moonCount) return getDate(calendar2str(calendar)) } /** * 计算天数差 */ fun calculateDayDiff(targetTime: Date, compareTime: Date): Int { val sameYear = isSameYear(targetTime, compareTime) if (sameYear) { return calculateDayDiffOfSameYear(targetTime, compareTime) } else { var dayDiff = 0 // 累计年数差的整年天数 val yearDiff = calculateYearDiff(targetTime, compareTime) dayDiff += yearDiff * DAY_OF_YEAR // 累计同一年内的天数 dayDiff += calculateDayDiffOfSameYear(targetTime, compareTime) return dayDiff } } /** * 计算同一年内的天数差 */ fun calculateDayDiffOfSameYear(targetTime: Date?, compareTime: Date?): Int { if (targetTime == null || compareTime == null) { return 0 } val tarCalendar = Calendar.getInstance() tarCalendar.time = targetTime val tarDayOfYear = tarCalendar.get(Calendar.DAY_OF_YEAR) val compareCalendar = Calendar.getInstance() compareCalendar.time = compareTime val comDayOfYear = compareCalendar.get(Calendar.DAY_OF_YEAR) return tarDayOfYear - comDayOfYear } /** * 计算年数差 */ private fun calculateYearDiff(targetTime: Date?, compareTime: Date?): Int { if (targetTime == null || compareTime == null) { return 0 } val tarCalendar = Calendar.getInstance() tarCalendar.time = targetTime val tarYear = tarCalendar.get(Calendar.YEAR) val compareCalendar = Calendar.getInstance() compareCalendar.time = compareTime val comYear = compareCalendar.get(Calendar.YEAR) return tarYear - comYear } /** * 计算月数差 * * @param targetTime * @param compareTime * @return */ fun calculateMonthDiff(targetTime: String, compareTime: String): Int { return calculateMonthDiff(str2date(targetTime, PATTERN_DATE), str2date(compareTime, PATTERN_DATE)) } /** * 计算月数差 * * @param targetTime * @param compareTime * @return */ fun calculateMonthDiff(targetTime: Date?, compareTime: Date?): Int { val tarCalendar = Calendar.getInstance() tarCalendar.time = targetTime val tarYear = tarCalendar.get(Calendar.YEAR) val tarMonth = tarCalendar.get(Calendar.MONTH) val compareCalendar = Calendar.getInstance() compareCalendar.time = compareTime val comYear = compareCalendar.get(Calendar.YEAR) val comMonth = compareCalendar.get(Calendar.MONTH) return (tarYear - comYear) * 12 + tarMonth - comMonth } /** * 是否为同一年 */ private fun isSameYear(targetTime: Date?, compareTime: Date?): Boolean { if (targetTime == null || compareTime == null) { return false } val tarCalendar = Calendar.getInstance() tarCalendar.time = targetTime val tarYear = tarCalendar.get(Calendar.YEAR) val compareCalendar = Calendar.getInstance() compareCalendar.time = compareTime val comYear = compareCalendar.get(Calendar.YEAR) return tarYear == comYear } @SuppressLint("SimpleDateFormat") @JvmOverloads private fun str2date(str: String?, format: String = PATTERN): Date? { var date: Date? = null try { if (str != null) { val sdf = SimpleDateFormat(format) date = sdf.parse(str) } } catch (e: ParseException) { e.printStackTrace() } return date } @JvmOverloads private fun date2str(date: Date, format: String = PATTERN): String { val sdf = SimpleDateFormat(format, Locale.CHINA) return sdf.format(date) } private fun str2calendar(str: String): Calendar? { var calendar: Calendar? = null val date = str2date(str) if (date != null) { calendar = Calendar.getInstance() calendar!!.time = date } return calendar } fun str2calendar(str: String, format: String): Calendar? { var calendar: Calendar? = null val date = str2date(str, format) if (date != null) { calendar = Calendar.getInstance() calendar!!.time = date } return calendar } private fun calendar2str(calendar: Calendar): String { return date2str(calendar.time) } fun calendar2str(calendar: Calendar, format: String): String { return date2str(calendar.time, format) } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/DialogUtils.kt ================================================ package com.lxm.module_library.utils import android.app.Dialog import android.content.Context import android.content.DialogInterface import android.support.v7.app.AlertDialog /** * Created by Horrarndoo on 2017/8/31. * * 对话框工具类, 提供常用对话框显示, 使用support.v7包内的AlertDialog样式 */ object DialogUtils { fun showCommonDialog(context: Context, message: String, positiveText: String, negativeText: String, listener: DialogInterface.OnClickListener): Dialog { return AlertDialog.Builder(context) .setMessage(message) .setPositiveButton(positiveText, listener) .setNegativeButton(negativeText, null) .show() } fun showConfirmDialog(context: Context, message: String, positiveText: String, listener: DialogInterface.OnClickListener): Dialog { return AlertDialog.Builder(context) .setMessage(message) .setPositiveButton(positiveText, listener) .show() } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/DisplayUtils.kt ================================================ package com.lxm.module_library.utils import android.app.Activity import android.content.Context import android.util.DisplayMetrics import android.util.TypedValue import android.view.View import android.view.ViewGroup import com.lxm.module_library.global.GlobalApplication /** * Created by Horrarndoo on 2017/8/31. * * * 显示相关工具类 */ object DisplayUtils { /** * 将px值转换为dp值 */ fun px2dp(pxValue: Float, context: Context): Int { val scale = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, pxValue, context.resources.displayMetrics) return scale.toInt() } /** * 将dp值转换为px值 */ fun dp2px(dpValue: Float): Int { val scale = GlobalApplication.getInstance().getResources().getDisplayMetrics().density return (dpValue * scale + 0.5f).toInt() } /** * 将px值转换为sp值 */ fun px2sp(pxValue: Float, context: Context): Int { val scale = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, pxValue, context.resources.displayMetrics) return scale.toInt() } /** * 将sp值转换为px值 */ fun sp2px(spValue: Float, context: Context): Int { val scale = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, context.resources.displayMetrics) return scale.toInt() } /** * 获取屏幕宽度 */ fun getScreenWidthPixels(context: Activity): Int { val metric = DisplayMetrics() context.windowManager.defaultDisplay.getMetrics(metric) return metric.widthPixels } /** * 获取屏幕高度 */ fun getScreenHeightPixels(context: Activity): Int { val metric = DisplayMetrics() context.windowManager.defaultDisplay.getMetrics(metric) return metric.heightPixels } /** * 设置某个View的margin * * @param view 需要设置的view * @param isDp 需要设置的数值是否为DP * @param left 左边距 * @param right 右边距 * @param top 上边距 * @param bottom 下边距 * @return */ fun setViewMargin( view: View?, isDp: Boolean, left: Int, right: Int, top: Int, bottom: Int ): ViewGroup.LayoutParams? { if (view == null) { return null } var leftPx = left var rightPx = right var topPx = top var bottomPx = bottom val params = view.layoutParams var marginParams: ViewGroup.MarginLayoutParams? = null //获取view的margin设置参数 if (params is ViewGroup.MarginLayoutParams) { marginParams = params } else { //不存在时创建一个新的参数 marginParams = ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 250) } //根据DP与PX转换计算值 if (isDp) { leftPx = dp2px(left.toFloat()) rightPx = dp2px(right.toFloat()) topPx = dp2px(top.toFloat()) bottomPx = dp2px(bottom.toFloat()) } //设置margin marginParams.setMargins(leftPx, topPx, rightPx, bottomPx) view.layoutParams = marginParams view.requestLayout() return marginParams } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/FileUtils.kt ================================================ package com.lxm.module_library.utils import android.content.ContentResolver import android.content.Context import android.content.Intent import android.graphics.Bitmap import android.net.Uri import android.os.Environment import android.provider.MediaStore import android.text.TextUtils import com.lxm.module_library.R import com.lxm.module_library.helper.RxHelper import io.reactivex.Observable import io.reactivex.ObservableOnSubscribe import java.io.* import kotlin.experimental.and /** * * * 读取文件工具类 */ object FileUtils { /** * Convert byte[] to hex string.将byte转换成int, * 然后利用Integer.toHexString(int)来转换成16进制字符串。 * * @param src byte[] data * @return hex string */ fun bytesToHexString(src: ByteArray?): String? { val stringBuilder = StringBuilder("") if (src == null || src.size <= 0) { return null } for (i in src.indices) { val v = src[i] and 0xFF.toByte() val hv = Integer.toHexString(v.toInt()) if (hv.length < 2) { stringBuilder.append(0) } stringBuilder.append(hv) } return stringBuilder.toString() } /** * 根据文件名称和路径,获取sd卡中的文件,以File形式返回byte */ fun getFile(fileName: String, folder: String): File? { val state = Environment.getExternalStorageState() if (state == Environment.MEDIA_MOUNTED) { val pathFile = File(Environment.getExternalStorageDirectory().toString() + folder) // && !pathFile .isDirectory() if (!pathFile.exists()) { pathFile.mkdirs() } return File(pathFile, fileName) } return null } /** * 根据文件名称和路径,获取sd卡中的文件,判断文件是否存在,存在返回true */ fun checkFile(fileName: String, folder: String): Boolean { val targetFile = getFile(fileName, folder) return targetFile!!.exists() } /** * 根据Uri返回文件绝对路径 * 兼容了file:///开头的 和 content://开头的情况 */ fun getRealFilePathFromUri(context: Context, uri: Uri?): String? { if (null == uri) { return null } val scheme = uri.scheme var data: String? = null if (scheme == null) { data = uri.path } else if (ContentResolver.SCHEME_FILE.equals(scheme, ignoreCase = true)) { data = uri.path } else if (ContentResolver.SCHEME_CONTENT.equals(scheme, ignoreCase = true)) { val cursor = context.contentResolver.query(uri, arrayOf(MediaStore .Images.ImageColumns.DATA), null, null, null) if (null != cursor) { if (cursor.moveToFirst()) { val index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA) if (index > -1) { data = cursor.getString(index) } } cursor.close() } } return data } /** * 检查文件是否存在 */ fun checkDirPath(dirPath: String): String { if (TextUtils.isEmpty(dirPath)) { return "" } val dir = File(dirPath) if (!dir.exists()) { dir.mkdirs() } return dirPath } fun copyFile(sourcefile: File, targetFile: File) { var input: FileInputStream? = null var inbuff: BufferedInputStream? = null var out: FileOutputStream? = null var outbuff: BufferedOutputStream? = null try { input = FileInputStream(sourcefile) inbuff = BufferedInputStream(input) out = FileOutputStream(targetFile) outbuff = BufferedOutputStream(out) val b = ByteArray(1024 * 5) var len: Int len = inbuff.read(b) while (len != -1) { outbuff.write(b, 0, len) len = inbuff.read(b) } outbuff.flush() } catch (ex: Exception) { } finally { try { inbuff?.close() outbuff?.close() out?.close() input?.close() } catch (ex: Exception) { } } } /** * 保存图片到本机 * * @param context context * @param fileName 文件名 * @param file file * @param saveResultCallback 保存结果callback */ fun saveImage(context: Context, fileName: String, file: File, saveResultCallback: SaveResultCallback) { val disposable = Observable.create(ObservableOnSubscribe { e -> val appDir = File(Environment.getExternalStorageDirectory(), context.getString(R.string.app_name)) if (!appDir.exists()) { appDir.mkdir() } var saveFileName: String if (fileName.contains(".png") || fileName.contains(".gif")) { val fileFormat = fileName.substring(fileName.lastIndexOf(".")) saveFileName = MD5Utils.getMD5(fileName) + fileFormat } else { saveFileName = MD5Utils.getMD5(fileName) + ".png" } saveFileName = saveFileName.substring(20) val saveFile = File(appDir, saveFileName) try { val `is` = FileInputStream(file) val fos = FileOutputStream(saveFile) val buffer = ByteArray(1024 * 1024) var count: Int count = `is`.read(buffer) while (count > 0) { fos.write(buffer, 0, count) count = `is`.read(buffer) } fos.close() `is`.close() e.onNext(saveFile) } catch (exception: FileNotFoundException) { e.onError(exception) } catch (exception: IOException) { e.onError(exception) } }).compose(RxHelper.rxSchedulerHelper()) .subscribe({ saveFile -> saveResultCallback.onSavedSuccess() val uri = Uri.fromFile(saveFile) context.sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri)) }, { throwable -> if (throwable is FileNotFoundException) { saveResultCallback.onSavedFailed() throwable.printStackTrace() } else if (throwable is IOException) { saveResultCallback.onSavedFailed() throwable.printStackTrace() } }) } /** * 保存Bitmap到本机 * * @param context context * @param fileName bitmap文件名 * @param bmp bitmap * @param saveResultCallback 保存结果callback */ fun saveBitmap(context: Context, fileName: String, bmp: Bitmap, saveResultCallback: SaveResultCallback) { val disposable = Observable.create(ObservableOnSubscribe { e -> val appDir = File(Environment.getExternalStorageDirectory(), context.getString(R.string.app_name)) if (!appDir.exists()) { appDir.mkdir() } // 设置以当前时间格式为图片名称 var saveFileName = MD5Utils.getMD5(fileName) + ".png" saveFileName = saveFileName.substring(20) val file = File(appDir, saveFileName) try { val fos = FileOutputStream(file) bmp.compress(Bitmap.CompressFormat.PNG, 100, fos) fos.flush() fos.close() e.onNext(file) } catch (exception: FileNotFoundException) { e.onError(exception) } catch (exception: IOException) { e.onError(exception) } }).compose(RxHelper.rxSchedulerHelper()) .subscribe({ saveFile -> saveResultCallback.onSavedSuccess() val uri = Uri.fromFile(saveFile) context.sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri)) }, { throwable -> if (throwable is FileNotFoundException) { saveResultCallback.onSavedFailed() throwable.printStackTrace() } else if (throwable is IOException) { saveResultCallback.onSavedFailed() throwable.printStackTrace() } }) } interface SaveResultCallback { /** * 保存成功 */ fun onSavedSuccess() /** * 保存失败 */ fun onSavedFailed() } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/HtmlUtils.kt ================================================ package com.lxm.module_library.utils /** * Created by Horrarndoo on 2017/8/31. * * Html工具类 */ object HtmlUtils { /** * css样式,隐藏header */ private const val HIDE_HEADER_STYLE = "" /** * css style tag,需要格式化 */ private const val NEEDED_FORMAT_CSS_TAG = "" /** * js script tag,需要格式化 */ private const val NEEDED_FORMAT_JS_TAG = "" val MIME_TYPE = "text/html; charset=utf-8" val ENCODING = "utf-8" /** * 根据css链接生成Link标签 * * @param url String * @return String */ fun createCssTag(url: String): String { return String.format(NEEDED_FORMAT_CSS_TAG, url) } /** * 根据多个css链接生成Link标签 * * @param urls List * @return String */ fun createCssTag(urls: List): String { val sb = StringBuilder() for (url in urls) { sb.append(createCssTag(url)) } return sb.toString() } /** * 根据js链接生成Script标签 * * @param url String * @return String */ fun createJsTag(url: String): String { return String.format(NEEDED_FORMAT_JS_TAG, url) } /** * 根据多个js链接生成Script标签 * * @param urls List * @return String */ fun createJsTag(urls: List): String { val sb = StringBuilder() for (url in urls) { sb.append(createJsTag(url)) } return sb.toString() } /** * 根据样式标签,html字符串,js标签 * 生成完整的HTML文档 */ fun createHtmlData(html: String, cssList: List, jsList: List): String { val css = HtmlUtils.createCssTag(cssList) val js = HtmlUtils.createJsTag(jsList) return css + HIDE_HEADER_STYLE + html + js } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/HttpUtils.kt ================================================ package com.lxm.module_library.utils import android.os.Build import android.webkit.WebSettings import java.util.* import java.util.regex.Pattern /** * * HttpUtils 主要用于获取UserAgent */ object HttpUtils { /** * 获取UserAgent * * @return UserAgent */ val userAgent: String get() { var userAgent: String if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { try { userAgent = WebSettings.getDefaultUserAgent(AppUtils.context) } catch (e: Exception) { userAgent = System.getProperty("http.agent") } } else { userAgent = System.getProperty("http.agent") } val sb = StringBuffer() var i = 0 val length = userAgent.length while (i < length) { val c = userAgent[i] if (c <= '\u001f' || c >= '\u007f') { sb.append(String.format("\\u%04x", c.toInt())) } else { sb.append(c) } i++ } return sb.toString() } fun makeUA(): String { return Build.BRAND + "/" + Build.MODEL + "/" + Build.VERSION.RELEASE } fun returnImageUrlsFromHtml(html: String): Array? { val imageSrcList = ArrayList() val p = Pattern.compile("]*\\bsrc\\b\\s*=\\s*('|\")?([^'\"\n\rf>]+(\\" + ".jpg|\\.bmp|\\.eps|\\.gif|\\.mif|\\.miff|\\.png|\\.tif|\\.tiff|\\.svg|\\.wmf|\\" + ".jpe|\\.jpeg|\\.dib|\\.ico|\\.tga|\\.cut|\\.pic|\\b)\\b)[^>]*>", Pattern .CASE_INSENSITIVE) val m = p.matcher(html) var quote: String var src: String while (m.find()) { quote = m.group(1) src = if (quote == null || quote.trim { it <= ' ' }.isEmpty()) m.group(2).split("//s+".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0] else m .group(2) imageSrcList.add(src) } return if (imageSrcList.size == 0) { null } else imageSrcList.toTypedArray() } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/ImageUtils.kt ================================================ package com.lxm.module_library.utils import android.annotation.TargetApi import android.app.Activity import android.content.ContentUris import android.content.ContentValues import android.content.Context import android.content.Intent import android.database.Cursor import android.net.Uri import android.os.Build import android.os.Environment import android.provider.DocumentsContract import android.provider.MediaStore import android.support.v7.app.AlertDialog /** * 图片工具类 */ object ImageUtils { /** * 拍照 */ private const val REQUEST_CODE_FROM_CAMERA = 1 shl 10 /** * 相册 */ private const val REQUEST_CODE_FROM_ALBUM = 1 shl 12 /** * 裁剪 */ private const val REQUEST_CODE_CROP_IMAGE = 1 shl 14 /** * 存放拍照图片的uri地址 */ var imageUriFromCamera: Uri? = null /** * 存放裁剪图片的uri地址 */ var cropImageUri: Uri? = null /** * 显示获取照片不同方式对话框 */ @JvmOverloads fun showImagePickDialog(activity: Activity, addRequest: Int = 0) { val title = "选择获取图片方式" val items = arrayOf("拍照", "相册") AlertDialog.Builder(activity) .setTitle(title) .setItems(items) { dialog, which -> dialog.dismiss() when (which) { 0 -> pickImageFromCamera(activity, addRequest) 1 -> pickImageFromAlbum(activity, addRequest) else -> { } } } .setNegativeButton("取消", null) .show() } /** * 打开相机拍照获取图片 */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) fun pickImageFromCamera(activity: Activity, addRequest: Int) { // 先生成一个uri地址用于存放拍照获取的图片 imageUriFromCamera = createImageUri(activity) val intent = Intent() intent.action = MediaStore.ACTION_IMAGE_CAPTURE intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUriFromCamera) activity.startActivityForResult(intent, REQUEST_CODE_FROM_CAMERA + addRequest) } /** * 打开相机拍照获取图片 */ fun pickImageFromCamera(activity: Activity) { pickImageFromCamera(activity, 0) } /** * 打开本地相册选取图片 */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) fun pickImageFromAlbum(activity: Activity, addRequest: Int) { val intent = Intent() intent.action = Intent.ACTION_GET_CONTENT intent.type = "image/*" activity.startActivityForResult(intent, REQUEST_CODE_FROM_ALBUM + addRequest) } /** * 打开本地相册选取图片 */ fun pickImageFromAlbum(activity: Activity) { pickImageFromAlbum(activity, 0) } /** * 图片裁剪 */ fun cropImage(activity: Activity, srcUri: Uri) { cropImageUri = createImageUri(activity) val intent = Intent("com.android.camera.action.CROP") intent.setDataAndType(srcUri, "image/*") intent.putExtra("crop", "true") //////////////////////////////////////////////////////////////// // 1.宽高和比例都不设置时,裁剪框可以自行调整(比例和大小都可以随意调整) //////////////////////////////////////////////////////////////// // 2.只设置裁剪框宽高比(aspect)后,裁剪框比例固定不可调整,只能调整大小 ///////////////////////////////// // 3.裁剪后生成图片宽高(output)的设置和裁剪框无关,只决定最终生成图片大小 //////////////////////////////////////////////////////////////// // 4.裁剪框宽高比例(aspect)可以和裁剪后生成图片比例(output)不同,此时, // 会以裁剪框的宽为准,按照裁剪宽高比例生成一个图片,该图和框选部分可能不同, // 不同的情况可能是截取框选的一部分,也可能超出框选部分,向下延伸补足 //////////////////////////////////////////////////////////////// // aspectX aspectY 是裁剪框宽高的比例 intent.putExtra("aspectX", 1) intent.putExtra("aspectY", 1) // outputX outputY 是裁剪后生成图片的宽高 // intent.putExtra("outputX", 300); // intent.putExtra("outputY", 100); // return-data为true时,会直接返回bitmap数据,但是大图裁剪时会出现OOM,推荐下面为false时的方式 // return-data为false时,不会返回bitmap,但需要指定一个MediaStore.EXTRA_OUTPUT保存图片uri intent.putExtra("return-data", false) intent.putExtra(MediaStore.EXTRA_OUTPUT, cropImageUri) activity.startActivityForResult(intent, REQUEST_CODE_CROP_IMAGE) } /** * 创建一条图片uri,用于保存拍照后的照片 */ private fun createImageUri(context: Context): Uri? { val name = "boreImg" + System.currentTimeMillis() val values = ContentValues() values.put(MediaStore.Images.Media.TITLE, name) values.put(MediaStore.Images.Media.DISPLAY_NAME, "$name.jpeg") values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg") return context.contentResolver.insert(MediaStore.Images.Media .EXTERNAL_CONTENT_URI, values) } /** * 删除一条图片 */ fun deleteImageUri(context: Context, uri: Uri) { context.contentResolver.delete(uri, null, null) } /** * 用第三方应用app打开图片 */ fun openImageByOtherApp(context: Context, imageUri: Uri) { val intent = Intent() intent.action = Intent.ACTION_VIEW intent.setDataAndType(imageUri, "image/*") context.startActivity(intent) } /** * 根据Uri获取图片绝对路径,解决Android4.4以上版本Uri转换 */ fun getImageAbsolutePath19(context: Context?, imageUri: Uri?): String? { if (context == null || imageUri == null) return null if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, imageUri)) { if (isExternalStorageDocument(imageUri)) { val docId = DocumentsContract.getDocumentId(imageUri) val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() val type = split[0] if ("primary".equals(type, ignoreCase = true)) { return Environment.getExternalStorageDirectory().toString() + "/" + split[1] } } else if (isDownloadsDocument(imageUri)) { val id = DocumentsContract.getDocumentId(imageUri) val contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id)) return getDataColumn(context, contentUri, null, null) } else if (isMediaDocument(imageUri)) { val docId = DocumentsContract.getDocumentId(imageUri) val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() val type = split[0] var contentUri: Uri? = null if ("image" == type) { contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI } else if ("video" == type) { contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI } else if ("audio" == type) { contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI } val selection = MediaStore.Images.Media._ID + "=?" val selectionArgs = arrayOf(split[1]) return getDataColumn(context, contentUri, selection, selectionArgs) } } // MediaStore (and general) if ("content".equals(imageUri.scheme, ignoreCase = true)) { // Return the remote address return if (isGooglePhotosUri(imageUri)) imageUri.lastPathSegment else getDataColumn(context, imageUri, null, null) } else if ("file".equals(imageUri.scheme, ignoreCase = true)) { return imageUri.path }// File return null } private fun getDataColumn(context: Context, uri: Uri?, selection: String?, selectionArgs: Array?): String? { var cursor: Cursor? = null val column = MediaStore.Images.Media.DATA val projection = arrayOf(column) try { cursor = context.contentResolver.query(uri!!, projection, selection, selectionArgs, null) if (cursor != null && cursor.moveToFirst()) { val index = cursor.getColumnIndexOrThrow(column) return cursor.getString(index) } } finally { cursor?.close() } return null } /** * @param uri The Uri to check. * @return Whether the Uri authority is ExternalStorageProvider. */ private fun isExternalStorageDocument(uri: Uri): Boolean { return "com.android.externalstorage.documents" == uri.authority } /** * @param uri The Uri to check. * @return Whether the Uri authority is DownloadsProvider. */ private fun isDownloadsDocument(uri: Uri): Boolean { return "com.android.providers.downloads.documents" == uri.authority } /** * @param uri The Uri to check. * @return Whether the Uri authority is MediaProvider. */ private fun isMediaDocument(uri: Uri): Boolean { return "com.android.providers.media.documents" == uri.authority } /** * @param uri The Uri to check. * @return Whether the Uri authority is Google Photos. */ private fun isGooglePhotosUri(uri: Uri): Boolean { return "com.google.android.apps.photos.content" == uri.authority } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/JsonUtils.kt ================================================ package com.lxm.module_library.utils import com.google.gson.Gson import com.google.gson.JsonObject import com.google.gson.JsonSyntaxException import java.lang.reflect.Type /** * * Json转换工具类 */ object JsonUtils { private val mGson = Gson() /** * 将对象准换为json字符串 * * @param object * @param * @return */ fun serialize(`object`: T): String { return mGson.toJson(`object`) } /** * 将json字符串转换为对象 * * @param json * @param clz * @param * @return */ @Throws(JsonSyntaxException::class) fun deserialize(json: String, clz: Class): T { return mGson.fromJson(json, clz) } /** * 将json对象转换为实体对象 * * @param json * @param clz * @param * @return * @throws JsonSyntaxException */ @Throws(JsonSyntaxException::class) fun deserialize(json: JsonObject, clz: Class): T { return mGson.fromJson(json, clz) } /** * 将json字符串转换为对象 * * @param json * @param type * @param * @return */ @Throws(JsonSyntaxException::class) fun deserialize(json: String, type: Type): T { return mGson.fromJson(json, type) } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/MD5Utils.kt ================================================ package com.lxm.module_library.utils import java.security.MessageDigest /** * * MD5加密工具类 */ object MD5Utils { /** * MD5加密,32位 */ fun getMD5(str: String): String { val md5: MessageDigest try { md5 = MessageDigest.getInstance("MD5") } catch (e: Exception) { e.printStackTrace() return "" } val charArray = str.toCharArray() val byteArray = ByteArray(charArray.size) for (i in charArray.indices) { byteArray[i] = charArray[i].toByte() } val md5Bytes = md5.digest(byteArray) val hexValue = StringBuffer() for (i in md5Bytes.indices) { val `val` = md5Bytes[i].toInt() and 0xff if (`val` < 16) { hexValue.append("0") } hexValue.append(Integer.toHexString(`val`)) } return hexValue.toString() } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/MyDividerItemDecoration.java ================================================ package com.lxm.module_library.utils; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; import android.widget.LinearLayout; public class MyDividerItemDecoration extends RecyclerView.ItemDecoration { public static final int HORIZONTAL = LinearLayout.HORIZONTAL; public static final int VERTICAL = LinearLayout.VERTICAL; private static final String TAG = "DividerItem"; private static final int[] ATTRS = new int[]{android.R.attr.listDivider}; private Drawable mDivider; private boolean mIsShowBottomDivider; private boolean mIsShowFirstDivider; private boolean mIsShowSecondDivider; /** * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}. */ private int mOrientation; private final Rect mBounds = new Rect(); /** * Creates a divider {@link RecyclerView.ItemDecoration} that can be used with a * {@link android.support.v7.widget.LinearLayoutManager}. * * @param context Current context, it will be used to access resources. * @param orientation Divider orientation. Should be {@link #HORIZONTAL} or * {@link #VERTICAL}. * @param isShowBottomDivider true show bottom divider false not show bottom divider */ public MyDividerItemDecoration(Context context, int orientation, boolean isShowBottomDivider) { this(context, orientation, isShowBottomDivider, true, true); } public MyDividerItemDecoration(Context context, int orientation, boolean isShowBottomDivider, boolean isShowFirstDivider, boolean isShowSecondDivider) { mIsShowBottomDivider = isShowBottomDivider; mIsShowFirstDivider = isShowFirstDivider; mIsShowSecondDivider = isShowSecondDivider; final TypedArray a = context.obtainStyledAttributes(ATTRS); mDivider = a.getDrawable(0); if (mDivider == null) { Log.w(TAG, "@android:attr/listDivider was not set in the theme used for this " + "DividerItemDecoration. Please set that attribute all call setDrawable()"); } a.recycle(); setOrientation(orientation); } /** * Sets the orientation for this divider. This should be called if * {@link RecyclerView.LayoutManager} changes orientation. * * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL} */ public void setOrientation(int orientation) { if (orientation != HORIZONTAL && orientation != VERTICAL) { throw new IllegalArgumentException( "Invalid orientation. It should be either HORIZONTAL or VERTICAL"); } mOrientation = orientation; } /** * Sets the {@link Drawable} for this divider. * * @param drawable Drawable that should be used as a divider. */ public void setDrawable(@NonNull Drawable drawable) { if (drawable == null) { throw new IllegalArgumentException("Drawable cannot be null."); } mDivider = drawable; } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { if (parent.getLayoutManager() == null || mDivider == null) { return; } if (mOrientation == VERTICAL) { drawVertical(c, parent, state); } else { drawHorizontal(c, parent, state); } } private void drawVertical(Canvas canvas, RecyclerView parent, RecyclerView.State state) { canvas.save(); final int left; final int right; //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides. if (parent.getClipToPadding()) { left = parent.getPaddingLeft(); right = parent.getWidth() - parent.getPaddingRight(); canvas.clipRect(left, parent.getPaddingTop(), right, parent.getHeight() - parent.getPaddingBottom()); } else { left = 0; right = parent.getWidth(); } final int childCount = parent.getChildCount(); final int lastPosition = state.getItemCount() - 1; for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); final int childRealPosition = parent.getChildAdapterPosition(child); // mIsShowFirstDivider false绘制第一个view的divider if (childRealPosition == 0 && !mIsShowFirstDivider) { continue; } // mIsShowSecondDivider false绘制第二个view的divider if (childRealPosition == 1 && !mIsShowSecondDivider) { continue; } // mIsShowBottomDivider false的时候不绘制最后一个view的divider if (mIsShowBottomDivider || childRealPosition < lastPosition) { parent.getDecoratedBoundsWithMargins(child, mBounds); final int bottom = mBounds.bottom + Math.round(child.getTranslationY()); final int top = bottom - mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(canvas); } } canvas.restore(); } private void drawHorizontal(Canvas canvas, RecyclerView parent, RecyclerView.State state) { canvas.save(); final int top; final int bottom; //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides. if (parent.getClipToPadding()) { top = parent.getPaddingTop(); bottom = parent.getHeight() - parent.getPaddingBottom(); canvas.clipRect(parent.getPaddingLeft(), top, parent.getWidth() - parent.getPaddingRight(), bottom); } else { top = 0; bottom = parent.getHeight(); } final int childCount = parent.getChildCount(); final int lastPosition = state.getItemCount() - 1; for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); final int childRealPosition = parent.getChildAdapterPosition(child); // mIsShowFirstDivider false绘制第一个view的divider if (childRealPosition == 0 && !mIsShowFirstDivider) { continue; } // mIsShowSecondDivider false绘制第二个view的divider if (childRealPosition == 1 && !mIsShowSecondDivider) { continue; } //mIsShowBottomDivider false的时候不绘制最后一个view的divider if (mIsShowBottomDivider || childRealPosition < lastPosition) { parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds); final int right = mBounds.right + Math.round(child.getTranslationX()); final int left = right - mDivider.getIntrinsicWidth(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(canvas); } } canvas.restore(); } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { if (mDivider == null) { outRect.set(0, 0, 0, 0); return; } if (mOrientation == VERTICAL) { //parent.getChildCount() 不能拿到item的总数 //https://stackoverflow.com/questions/29666598/android-recyclerview-finding-out-first // -and-last-view-on-itemdecoration int lastPosition = state.getItemCount() - 1; int position = parent.getChildAdapterPosition(view); if (mIsShowBottomDivider || position < lastPosition) { outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); } else { outRect.set(0, 0, 0, 0); } } else { int lastPosition = state.getItemCount() - 1; int position = parent.getChildAdapterPosition(view); if (mIsShowBottomDivider || position < lastPosition) { outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); } else { outRect.set(0, 0, 0, 0); } } } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/NetworkConnectionUtils.kt ================================================ package com.lxm.module_library.utils import android.app.Activity import android.content.ComponentName import android.content.Context import android.content.Intent import android.net.ConnectivityManager import android.net.NetworkInfo import android.net.wifi.WifiConfiguration import android.net.wifi.WifiConfiguration.KeyMgmt import android.net.wifi.WifiManager import android.os.Build import java.io.IOException /** * * Wifi连接工具类 */ object NetworkConnectionUtils { /** * 连接指定 * * @param manager * @param wifiSSID * @return */ fun connectToSocketWifi(manager: WifiManager, wifiSSID: String): Boolean { val wifiConfiguration = WifiConfiguration() wifiConfiguration.SSID = "\"" + wifiSSID + "\"" wifiConfiguration.allowedKeyManagement.set(KeyMgmt.NONE) //小米手机MIUI7/华为EMUI4.1 需要webKey wifiConfiguration.wepKeys[0] = "\"" + "\"" var networkId = manager.addNetwork(wifiConfiguration) if (networkId != -1) { manager.enableNetwork(networkId, true) return true } else { val wifiConfiguration2 = WifiConfiguration() wifiConfiguration2.SSID = "\"" + wifiSSID + "\"" //wifiConfiguration.wepKeys[0] = "\"" + "\"";//去掉webKey //小米手机MIUI8不能有webKey wifiConfiguration2.allowedKeyManagement.set(KeyMgmt.NONE) networkId = manager.addNetwork(wifiConfiguration2) if (networkId != -1) { manager.enableNetwork(networkId, true) return true } } return false } /** * 获取要连接的wifi节点各个配置选项的加密类型 * * @param ssid * @return wifiConfiguration */ fun getWifiConfiguration(manager: WifiManager, ssid: String, password: String): WifiConfiguration { val wifiConfiguration = WifiConfiguration() wifiConfiguration.SSID = "\"" + ssid + "\"" val list = manager.scanResults for (scResult in list) { if (ssid == scResult.SSID) { val capabilities = scResult.capabilities if (capabilities.contains("WEP") || capabilities.contains("wep")) { wifiConfiguration.allowedKeyManagement.set(KeyMgmt.WPA_EAP) wifiConfiguration.preSharedKey = "\"" + password + "\"" } else if (capabilities.contains("WPA") || capabilities.contains("wpa")) { wifiConfiguration.allowedKeyManagement.set(KeyMgmt.WPA_PSK) wifiConfiguration.preSharedKey = "\"" + password + "\"" } else { wifiConfiguration.allowedKeyManagement.set(KeyMgmt.NONE) } } } return wifiConfiguration } /** * 给温控器成功发送联网命令后,连接温控器连接的wifi节点 * * @param context 上下文对象 * @param ssid ssid * @param password 密码 */ fun connectWifiSSID(context: Context, manager: WifiManager, ssid: String, password: String) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { WifiAutoConnectManager(manager).connect(ssid, password, WifiAutoConnectManager .getCipherType(context, ssid)) } else { val networkId = manager.addNetwork(getWifiConfiguration(manager, ssid, password)) if (networkId != -1) { manager.enableNetwork(networkId, true) } } } /** * 格式化RouterSSID * * @param strRouterSSID 要格式化的当前连接的路由ssid * @return 去除"\"后的RouterSSID字符串 */ fun formatRouterSSID(strRouterSSID: String): String { var strRouterSSID = strRouterSSID if (strRouterSSID.contains("\"")) { strRouterSSID = strRouterSSID.replace("\"".toRegex(), "") } return strRouterSSID } /** * Ping * 用于确定手机是否已经连接上指定设备ip地址 */ fun pingTest(IPOrDomainName: String): Boolean { var isSuccess = false val status: Int var result = "failed" val p: Process try { p = Runtime.getRuntime().exec("ping -c 1 $IPOrDomainName") // m_strForNetAddress是输入的网址或者Ip地址 status = p.waitFor() if (status == 0) { result = "success" isSuccess = true } } catch (e: IOException) { } catch (e: InterruptedException) { } return isSuccess } /** * 判断网络是否连接 */ fun isConnected(context: Context): Boolean { val cm = context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val info = cm.activeNetworkInfo if (null != info && info.isConnected) { if (info.state == NetworkInfo.State.CONNECTED) { return true } } return false } /** * 判断是否有网络 * * @return 返回值 */ fun isNetworkConnected(context: Context?): Boolean { if (context != null) { val mConnectivityManager = context .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val mNetworkInfo = mConnectivityManager.activeNetworkInfo if (mNetworkInfo != null) { return mNetworkInfo.isAvailable } } return false } /** * 判断是否是wifi连接 */ fun isWifi(context: Context): Boolean { val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager ?: return false val info = cm.activeNetworkInfo if (null != info) { if (info.type == ConnectivityManager.TYPE_WIFI) { return true } } return false } /** * 打开网络设置界面 */ fun openSetting(activity: Activity, requestCode: Int) { val intent = Intent("/") val cm = ComponentName("com.android.settings", "com.android.settings.WirelessSettings") intent.component = cm intent.action = Intent.ACTION_VIEW activity.startActivityForResult(intent, requestCode) } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/PreferencesUtil.kt ================================================ package com.lxm.module_library.utils import android.content.Context import android.content.SharedPreferences import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty /** * SharedPreferences工具类封装 委托调用 */ class PreferencesUtil(val key: String, val default: T) : ReadWriteProperty { companion object { lateinit var preferences: SharedPreferences private var mPreferencesName = "share_preference_default" public fun get(context: Context) { preferences = context.getSharedPreferences(context.packageName + mPreferencesName, Context.MODE_PRIVATE) } } override fun getValue(thisRef: Any?, property: KProperty<*>): T = findPreferences() override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) = setPreferences(value) private fun findPreferences(): T { return with(preferences) { val res: Any = when (default) { is Int -> getInt(key, default) is Boolean -> getBoolean(key, default) is Float -> getFloat(key, default) is String -> getString(key, default) is Long -> getLong(key, default) else -> throw IllegalArgumentException("This type can be saved into Preferences") } res as T } } private fun setPreferences(value: T) = with(preferences.edit()) { when (value) { is Int -> putInt(key, value) is Boolean -> putBoolean(key, value) is Float -> putFloat(key, value) is String -> putString(key, value) is Long -> putLong(key, value) else -> throw IllegalArgumentException("This type can be saved into Preferences") }.apply() } /** * 设置preferencesName * * @param preferencesName preferencesName */ private fun setPreferencesName(preferencesName: String) { mPreferencesName = preferencesName } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/RefreshHelper.kt ================================================ package com.lxm.module_library.utils import android.support.v4.content.ContextCompat import android.support.v7.widget.DividerItemDecoration import android.support.v7.widget.LinearLayoutManager import com.lxm.module_library.R import com.lxm.module_library.xrecycleview.XRecyclerView object RefreshHelper { /** * 默认不显示最后一个item的分割线 * * @param isShowFirstDivider 第一个item是否显示分割线 * @param isShowSecondDivider 第二个item是否显示分割线 */ @JvmOverloads fun init(recyclerView: XRecyclerView, isShowFirstDivider: Boolean = true, isShowSecondDivider: Boolean = true) { recyclerView.layoutManager = LinearLayoutManager(recyclerView.context) recyclerView.setPullRefreshEnabled(false) recyclerView.clearHeader() // val itemDecoration = MyDividerItemDecoration( // recyclerView.context, // DividerItemDecoration.VERTICAL, // false, // isShowFirstDivider, // isShowSecondDivider // ) // itemDecoration.setDrawable(ContextCompat.getDrawable(recyclerView.context, R.drawable.shape_line)!!) // recyclerView.addItemDecoration(itemDecoration) } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/ResourcesUtils.kt ================================================ package com.lxm.module_library.utils import android.content.res.ColorStateList import android.graphics.drawable.Drawable import android.view.View /** * * 资源工具类-加载资源文件 */ object ResourcesUtils { /** * 获取strings.xml资源文件字符串 * * @param id 资源文件id * @return 资源文件对应字符串 */ fun getString(id: Int): String { return AppUtils.context.resources.getString(id) } /** * 获取strings.xml资源文件字符串数组 * * @param id 资源文件id * @return 资源文件对应字符串数组 */ fun getStringArray(id: Int): Array { return AppUtils.context.resources.getStringArray(id) } /** * 获取drawable资源文件图片 * * @param id 资源文件id * @return 资源文件对应图片 */ fun getDrawable(id: Int): Drawable { return AppUtils.context.resources.getDrawable(id) } /** * 获取colors.xml资源文件颜色 * * @param id 资源文件id * @return 资源文件对应颜色值 */ fun getColor(id: Int): Int { return AppUtils.context.resources.getColor(id) } /** * 获取颜色的状态选择器 * * @param id 资源文件id * @return 资源文件对应颜色状态 */ fun getColorStateList(id: Int): ColorStateList? { return AppUtils.context.resources.getColorStateList(id) } /** * 获取dimens资源文件中具体像素值 * * @param id 资源文件id * @return 资源文件对应像素值 */ fun getDimen(id: Int): Int { return AppUtils.context.resources.getDimensionPixelSize(id) } /** * 加载布局文件 * * @param id 布局文件id * @return 布局view */ fun inflate(id: Int): View { return View.inflate(AppUtils.context, id, null) } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/ScreenAdapterUtils.kt ================================================ package com.lxm.module_library.utils import android.app.Activity import android.content.res.Resources /** *屏幕适配相关 * */ object ScreenAdapterUtils { /** * Return whether adapt screen. * * @return `true`: yes

`false`: no */ val isAdaptScreen: Boolean get() { val systemDm = Resources.getSystem().displayMetrics val appDm = AppUtils.context.resources.displayMetrics return systemDm.density != appDm.density } /** * Adapt the screen for vertical slide. * * @param activity The activity. * @param designWidthInPx The size of design diagram's width, in pixel. */ fun adaptScreen4VerticalSlide(activity: Activity, designWidthInPx: Int) { adaptScreen(activity, designWidthInPx, true) } /** * Adapt the screen for horizontal slide. * * @param activity The activity. * @param designHeightInPx The size of design diagram's height, in pixel. */ fun adaptScreen4HorizontalSlide(activity: Activity, designHeightInPx: Int) { adaptScreen(activity, designHeightInPx, false) } /** * Reference from: https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA */ private fun adaptScreen(activity: Activity, sizeInPx: Int, isVerticalSlide: Boolean) { val systemDm = Resources.getSystem().displayMetrics val appDm = AppUtils.context.resources.displayMetrics val activityDm = activity.resources.displayMetrics if (isVerticalSlide) { activityDm.density = activityDm.widthPixels / sizeInPx.toFloat() } else { activityDm.density = activityDm.heightPixels / sizeInPx.toFloat() } activityDm.scaledDensity = activityDm.density * (systemDm.scaledDensity / systemDm.density) activityDm.densityDpi = (160 * activityDm.density).toInt() appDm.density = activityDm.density appDm.scaledDensity = activityDm.scaledDensity appDm.densityDpi = activityDm.densityDpi } /** * Cancel adapt the screen. * * @param activity The activity. */ fun cancelAdaptScreen(activity: Activity) { val systemDm = Resources.getSystem().displayMetrics val appDm = AppUtils.context.resources.displayMetrics val activityDm = activity.resources.displayMetrics activityDm.density = systemDm.density activityDm.scaledDensity = systemDm.scaledDensity activityDm.densityDpi = systemDm.densityDpi appDm.density = systemDm.density appDm.scaledDensity = systemDm.scaledDensity appDm.densityDpi = systemDm.densityDpi } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/ScreenUtils.kt ================================================ package com.lxm.module_library.utils import android.app.Activity import android.app.ActivityGroup import android.content.Context import android.content.res.Resources import android.graphics.Bitmap import android.graphics.Rect import android.os.Build import android.support.v7.app.AppCompatActivity import android.util.DisplayMetrics import android.util.TypedValue import android.view.View import android.view.ViewGroup import android.view.WindowManager import java.lang.reflect.Field /** * * 屏幕相关工具类 */ class ScreenUtils private constructor() { init { /* cannot be instantiated */ throw UnsupportedOperationException("cannot be instantiated") } companion object { private val mStatusHeight = -1 /** * 获取屏幕的宽度 * * @param context * @return */ fun getScreenWidth(context: Context): Int { val manager = context .getSystemService(Context.WINDOW_SERVICE) as WindowManager val displayMetrics = DisplayMetrics() manager.defaultDisplay.getMetrics(displayMetrics) return displayMetrics.widthPixels } /** * 获取屏幕的高度 * * @param context * @return */ fun getScreenHeight(context: Context): Int { val manager = context .getSystemService(Context.WINDOW_SERVICE) as WindowManager val displayMetrics = DisplayMetrics() manager.defaultDisplay.getMetrics(displayMetrics) return displayMetrics.heightPixels } /** * 获取当前屏幕截图,不包含状态栏 * * @param activity * @return bp */ fun snapShotWithoutStatusBar(activity: Activity): Bitmap? { val view = activity.window.decorView view.isDrawingCacheEnabled = true view.buildDrawingCache() val bmp = view.drawingCache ?: return null val frame = Rect() activity.window.decorView.getWindowVisibleDisplayFrame(frame) val statusBarHeight = frame.top val bp = Bitmap.createBitmap(bmp, 0, statusBarHeight, bmp.width, bmp.height - statusBarHeight) view.destroyDrawingCache() view.isDrawingCacheEnabled = false return bp } /** * 获取actionbar的像素高度,默认使用android官方兼容包做actionbar兼容 * * @return */ fun getActionBarHeight(context: Context): Int { var actionBarHeight = 0 if (context is AppCompatActivity && context .supportActionBar != null) { actionBarHeight = context.supportActionBar!!.height } else if (context is Activity && context.actionBar != null) { actionBarHeight = context.actionBar!!.height } else if (context is ActivityGroup) { if (context.currentActivity is AppCompatActivity && (context.currentActivity as AppCompatActivity) .supportActionBar != null) { actionBarHeight = (context .currentActivity as AppCompatActivity).supportActionBar!!.height } else if (context.currentActivity is Activity && (context.currentActivity as Activity).actionBar != null) { actionBarHeight = (context.currentActivity as Activity) .actionBar!!.height } } if (actionBarHeight != 0) { return actionBarHeight } val tv = TypedValue() if (context.theme.resolveAttribute(android.support.v7.appcompat.R.attr .actionBarSize, tv, true)) { if (context.theme.resolveAttribute(android.support.v7.appcompat.R.attr .actionBarSize, tv, true)) { actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, context .resources.displayMetrics) } } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (context.theme.resolveAttribute(android.R.attr.actionBarSize, tv, true)) { actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, context .resources.displayMetrics) } } else { if (context.theme.resolveAttribute(android.support.v7.appcompat.R.attr .actionBarSize, tv, true)) { actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, context .resources.displayMetrics) } } return actionBarHeight } /** * 反射获取 */ fun getStatusBarHeight(context: Context): Int { val c: Class<*> val obj: Any val field: Field val x: Int return try { c = Class.forName("com.android.internal.R\$dimen") obj = c.newInstance() field = c.getField("status_bar_height") x = Integer.parseInt(field.get(obj).toString()) context.resources.getDimensionPixelSize(x) } catch (e1: Exception) { e1.printStackTrace() 75 } } /** * 获取系统状态栏高度(会随着修改displayMetrics而改变) */ fun getStatusBarHeight(): Int { val resources = AppUtils.context.resources val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android") return resources.getDimensionPixelSize(resourceId) } /** * 获取系统状态栏高度(系统本身处于的状态) */ fun getStatusBarHeightSystem(): Int { val resources = Resources.getSystem() val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android") return resources.getDimensionPixelSize(resourceId) } /** * 设置view margin * * @param v * @param l * @param t * @param r * @param b */ fun setMargins(v: View, l: Int, t: Int, r: Int, b: Int) { if (v.layoutParams is ViewGroup.MarginLayoutParams) { val p = v.layoutParams as ViewGroup.MarginLayoutParams p.setMargins(l, t, r, b) v.requestLayout() } } } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/SnackbarUtils.kt ================================================ package com.lxm.module_library.utils import android.graphics.Color import android.support.design.widget.Snackbar import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.widget.LinearLayout import android.widget.TextView import com.lxm.module_library.R /** * Snackbar工具类 */ object SnackbarUtils { private const val Info = 1 private const val Confirm = 2 private const val Warning = 3 private const val Alert = 4 private var red = -0xbbcca private var green = -0xb350b0 private var blue = -0xde6a0d private var orange = -0x3ef9 /** * 短显示Snackbar,自定义颜色 * * @param view * @param message * @param messageColor * @param backgroundColor * @return */ fun getShort(view: View, message: String, messageColor: Int, backgroundColor: Int): Snackbar { val snackbar = Snackbar.make(view, message, Snackbar.LENGTH_SHORT) setSnackbarColor(snackbar, messageColor, backgroundColor) return snackbar } /** * 长显示Snackbar,自定义颜色 * * @param view * @param message * @param messageColor * @param backgroundColor * @return */ fun getLong(view: View, message: String, messageColor: Int, backgroundColor: Int): Snackbar { val snackbar = Snackbar.make(view, message, Snackbar.LENGTH_LONG) setSnackbarColor(snackbar, messageColor, backgroundColor) return snackbar } /** * 自定义时常显示Snackbar,自定义颜色 * * @param view * @param message * @param messageColor * @param backgroundColor * @return */ fun getIndefinite(view: View, message: String, duration: Int, messageColor: Int, backgroundColor: Int): Snackbar { val snackbar = Snackbar.make(view, message, Snackbar.LENGTH_INDEFINITE).setDuration(duration) setSnackbarColor(snackbar, messageColor, backgroundColor) return snackbar } /** * 短显示Snackbar,可选预设类型 * * @param view * @param message * @param type * @return */ fun getShort(view: View, message: String, type: Int): Snackbar { val snackbar = Snackbar.make(view, message, Snackbar.LENGTH_SHORT) switchType(snackbar, type) return snackbar } /** * 长显示Snackbar,可选预设类型 * * @param view * @param message * @param type * @return */ fun getLong(view: View, message: String, type: Int): Snackbar { val snackbar = Snackbar.make(view, message, Snackbar.LENGTH_LONG) switchType(snackbar, type) return snackbar } /** * 自定义时常显示Snackbar,可选预设类型 * * @param view * @param message * @param type * @return */ fun getIndefinite(view: View, message: String, duration: Int, type: Int): Snackbar { val snackbar = Snackbar.make(view, message, Snackbar.LENGTH_INDEFINITE).setDuration(duration) switchType(snackbar, type) return snackbar } //选择预设类型 private fun switchType(snackbar: Snackbar, type: Int) { when (type) { Info -> setSnackbarColor(snackbar, blue) Confirm -> setSnackbarColor(snackbar, green) Warning -> setSnackbarColor(snackbar, orange) Alert -> setSnackbarColor(snackbar, Color.YELLOW, red) } } /** * 设置Snackbar背景颜色 * * @param snackbar * @param backgroundColor */ fun setSnackbarColor(snackbar: Snackbar, backgroundColor: Int) { val view = snackbar.view view?.setBackgroundColor(backgroundColor) } /** * 设置Snackbar文字和背景颜色 * * @param snackbar * @param messageColor * @param backgroundColor */ fun setSnackbarColor(snackbar: Snackbar, messageColor: Int, backgroundColor: Int) { val view = snackbar.view if (view != null) { view.setBackgroundColor(backgroundColor) (view.findViewById(R.id.snackbar_text) as TextView).setTextColor(messageColor) } } /** * 向Snackbar中添加view * * @param snackbar * @param layoutId * @param index 新加布局在Snackbar中的位置 */ fun addView(snackbar: Snackbar, layoutId: Int, index: Int) { val snackbarview = snackbar.view val snackbarLayout = snackbarview as Snackbar.SnackbarLayout val add_view = LayoutInflater.from(snackbarview.getContext()).inflate(layoutId, null) val p = LinearLayout.LayoutParams(LinearLayout.LayoutParams .WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT) p.gravity = Gravity.CENTER_VERTICAL snackbarLayout.addView(add_view, index, p) } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/StatusBarUtils.kt ================================================ package com.lxm.module_library.utils import android.app.Activity import android.graphics.Color import android.os.Build import android.support.annotation.ColorInt import android.support.v7.widget.Toolbar import android.view.View import android.view.ViewGroup import android.view.WindowManager /** * * StatusBar工具类 * */ object StatusBarUtils { /** * 设置状态栏颜色 * * @param activity 需要设置的 activity * @param color 状态栏颜色值 */ private fun setColor(activity: Activity, @ColorInt color: Int) { setBarColor(activity, color) } /** * 设置状态栏背景色 * 4.4以下不处理 * 4.4使用默认沉浸式状态栏 * * @param color 要为状态栏设置的颜色值 */ private fun setBarColor(activity: Activity, color: Int) { val win = activity.window val decorView = win.decorView //沉浸式状态栏(4.4-5.0透明,5.0以上半透明) win.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) //android5.0以上设置透明效果 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { //让应用的主体内容占用系统状态栏的空间 val option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE decorView.systemUiVisibility = decorView.systemUiVisibility or option win.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) //设置状态栏背景色 win.statusBarColor = color } } /** * 设置状态栏全透明 * * @param activity 需要设置的activity */ fun setTransparent(activity: Activity) { setColor(activity, Color.LTGRAY) } /** * 修正 Toolbar 的位置 * 在 Android 4.4 版本下无法显示内容在 StatusBar 下,所以无需修正 Toolbar 的位置 * * @param toolbar Toolbar */ fun fixToolbar(toolbar: Toolbar, activity: Activity) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { val statusHeight = ScreenUtils.getStatusBarHeight(activity) val layoutParams = toolbar.layoutParams as ViewGroup.MarginLayoutParams layoutParams.setMargins(0, statusHeight, 0, 0) } } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/StringUtils.kt ================================================ package com.lxm.module_library.utils import android.text.TextUtils import java.util.regex.Pattern /** * 字符串工具类 */ object StringUtils { /** * 判断字符串是否有值,如果为null或者是空字符串或者只有空格或者为"null"字符串,则返回true,否则则返回false */ fun isEmpty(value: String?): Boolean { return !(value != null && !"".equals(value.trim { it <= ' ' }, ignoreCase = true) && !"null".equals(value.trim { it <= ' ' }, ignoreCase = true)) } /** * 判断字符串是否是邮箱 * * @param email email * @return 字符串是否是邮箱 */ fun isEmail(email: String): Boolean { val str = "^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(" + "([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$" val p = Pattern.compile(str) val m = p.matcher(email) return m.matches() } /** * 判断手机号字符串是否合法 * * @param phoneNumber 手机号字符串 * @return 手机号字符串是否合法 */ fun isPhoneNumberValid(phoneNumber: String): Boolean { var isValid = false val expression = "^1[3|4|5|7|8]\\d{9}$" val pattern = Pattern.compile(expression) val matcher = pattern.matcher(phoneNumber) if (matcher.matches()) { isValid = true } return isValid } /** * 判断手机号字符串是否合法 * * @param areaCode 区号 * @param phoneNumber 手机号字符串 * @return 手机号字符串是否合法 */ fun isPhoneNumberValid(areaCode: String, phoneNumber: String): Boolean { if (TextUtils.isEmpty(phoneNumber)) { return false } if (phoneNumber.length < 5) { return false } if (TextUtils.equals(areaCode, "+86") || TextUtils.equals(areaCode, "86")) { return isPhoneNumberValid(phoneNumber) } var isValid = false val expression = "^[0-9]*$" val pattern = Pattern.compile(expression) val matcher = pattern.matcher(phoneNumber) if (matcher.matches()) { isValid = true } return isValid } /** * 判断字符串是否是手机号格式 * * @param areaCode 区号 * @param phoneNumber 手机号字符串 * @return 字符串是否是手机号格式 */ fun isPhoneFormat(areaCode: String, phoneNumber: String): Boolean { if (TextUtils.isEmpty(phoneNumber)) { return false } if (phoneNumber.length < 7) { return false } var isValid = false val expression = "^[0-9]*$" val pattern = Pattern.compile(expression) val matcher = pattern.matcher(phoneNumber) if (matcher.matches()) { isValid = true } return isValid } /** * 判断字符串是否为纯数字 * * @param str 字符串 * @return 是否纯数字 */ fun isNumber(str: String): Boolean { for (i in 0 until str.length) { if (!Character.isDigit(str[i])) { return false } } return true } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/TimestampUtils.kt ================================================ package com.lxm.module_library.utils import java.text.SimpleDateFormat import java.util.* object TimestampUtils { /** * 获取当前的时间戳,时区为北京 * * @return */ //时间戳的格式必须为 yyyy-MM-dd HH:mm:ss val currentTimestamp: String? get() { var timestamp: String? = null val format = SimpleDateFormat.getDateTimeInstance() timestamp = format.format(Date()) return timestamp } /** * 获取当前的时间戳,时区为北京 * * @return */ fun getCurrentTime(times: Long): String { //时间戳的格式必须为 yyyy-MM-dd HH:mm:ss val date = Date(java.lang.Long.valueOf(times)) val format = SimpleDateFormat.getDateTimeInstance() val time = format.format(date) SimpleDateFormat.getDateTimeInstance() .format(Date()) return time } //法国时间:东一区 fun getDateTimeByGMT(timeZone: Int): String { val dff = SimpleDateFormat.getDateTimeInstance() when (timeZone) { 1 -> dff.timeZone = TimeZone.getTimeZone("GMT+1") 8 -> dff.timeZone = TimeZone.getTimeZone("GMT+8") } return dff.format(Date()) } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/ToastUtil.java ================================================ package com.lxm.module_library.utils; import android.annotation.SuppressLint; import android.text.TextUtils; import android.widget.Toast; import com.lxm.module_library.global.GlobalApplication; import me.drakeet.support.toast.ToastCompat; /** * Created by jingbin on 2016/12/14. * 单例Toast,兼容索尼部分手机不弹提示的问题,和vivo7.1.1部分手机崩溃问题 * An Android library to hook and fix Toast BadTokenException * https://github.com/PureWriter/ToastCompat */ public class ToastUtil { private static ToastCompat mToast; @SuppressLint("ShowToast") public static void showToast(String text) { if (!TextUtils.isEmpty(text)) { if (mToast == null) { mToast = ToastCompat.makeText(GlobalApplication.getInstance(), text, Toast.LENGTH_SHORT); } else { mToast.cancel(); mToast = ToastCompat.makeText(GlobalApplication.getInstance(), text, Toast.LENGTH_SHORT); } mToast.setDuration(Toast.LENGTH_SHORT); mToast.setText(text); mToast.show(); } } @SuppressLint("ShowToast") public static void showToastLong(String text) { if (!TextUtils.isEmpty(text)) { if (mToast == null) { mToast = ToastCompat.makeText(GlobalApplication.getInstance(), text, Toast.LENGTH_LONG); } else { mToast.cancel(); mToast = ToastCompat.makeText(GlobalApplication.getInstance(), text, Toast.LENGTH_LONG); } mToast.setDuration(Toast.LENGTH_LONG); mToast.setText(text); mToast.show(); } } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/ToastUtils.kt ================================================ package com.lxm.module_library.utils import android.content.Context import android.widget.Toast import com.lxm.module_library.helper.RxHelper import io.reactivex.Observable /** * * toast工具类封装 */ object ToastUtils { private var mToast: Toast? = null /** * 显示一个toast提示 * * @param resourceId toast字符串资源id */ fun showToast(resourceId: Int) { showToast(ResourcesUtils.getString(resourceId)) } /** * 显示一个toast提示 * * @param text toast字符串 * @param duration toast显示时间 */ @JvmOverloads fun showToast(text: String, duration: Int = Toast.LENGTH_SHORT) { showToast(AppUtils.context, text, duration) } /** * 显示一个toast提示 * * @param context context 上下文对象 * @param text toast字符串 * @param duration toast显示时间 */ fun showToast(context: Context, text: String, duration: Int) { /** * 保证运行在主线程 */ val disposable = Observable.just(0) .compose(RxHelper.rxSchedulerHelper()) .subscribe { if (mToast == null) { mToast = Toast.makeText(context, text, duration) } else { mToast!!.setText(text) mToast!!.duration = duration } mToast!!.show() } } } /** * 显示一个toast提示 * * @param text toast字符串 */ ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/UnicodeUtils.kt ================================================ package com.lxm.module_library.utils /** * Created by Horrarndoo on 2017/10/11. * */ object UnicodeUtils { /** * utf-8 转换成 unicode * * @param inStr * @return */ fun utf8ToUnicode(inStr: String): String { val myBuffer = inStr.toCharArray() val sb = StringBuffer() for (i in 0 until inStr.length) { val ub = Character.UnicodeBlock.of(myBuffer[i]) if (ub === Character.UnicodeBlock.BASIC_LATIN) { //英文及数字等 sb.append(myBuffer[i]) } else if (ub === Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS) { //全角半角字符 val j = myBuffer[i].toInt() - 65248 sb.append(j.toChar()) } else { //汉字 val s = myBuffer[i].toShort() val hexS = Integer.toHexString(s.toInt()) val unicode = "\\u$hexS" sb.append(unicode.toLowerCase()) } } return sb.toString() } /** * unicode 转换成 utf-8 * * @param theString * @return */ fun unicodeToUtf8(theString: String): String { var aChar: Char val len = theString.length val outBuffer = StringBuffer(len) var x = 0 while (x < len) { aChar = theString[x++] if (aChar == '\\') { aChar = theString[x++] if (aChar == 'u') { var value = 0 for (i in 0..3) { aChar = theString[x++] value = when (aChar) { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> (value shl 4) + aChar.toInt() - '0'.toInt() 'a', 'b', 'c', 'd', 'e', 'f' -> (value shl 4) + 10 + aChar.toInt() - 'a'.toInt() 'A', 'B', 'C', 'D', 'E', 'F' -> (value shl 4) + 10 + aChar.toInt() - 'A'.toInt() else -> throw IllegalArgumentException( "Malformed \\uxxxx encoding.") } } outBuffer.append(value.toChar()) } else { if (aChar == 't') { aChar = '\t' } else if (aChar == 'r') aChar = '\r' else if (aChar == 'n') aChar = '\n' else if (aChar == 'f') aChar = 'f' outBuffer.append(aChar) } } else outBuffer.append(aChar) } return outBuffer.toString() } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/utils/WifiAutoConnectManager.kt ================================================ package com.lxm.module_library.utils import android.content.Context import android.net.wifi.WifiConfiguration import android.net.wifi.WifiManager /** * * 兼容Android 6.0以上手机连接wifi */ class WifiAutoConnectManager(internal var wifiManager: WifiManager) { /** * 定义几种加密方式,一种是WEP,一种是WPA,还有没有密码的情况 */ enum class WifiCipherType { WIFICIPHER_WEP, WIFICIPHER_WPA, WIFICIPHER_NOPASS, WIFICIPHER_INVALID } fun connect(ssid: String, password: String, type: WifiCipherType) { val thread = Thread(ConnectRunnable(ssid, password, type)) thread.start() } /** * 查看以前是否也配置过这个网络 * * @param SSID * @return */ private fun isExsits(SSID: String): WifiConfiguration? { val existingConfigs = wifiManager .configuredNetworks for (existingConfig in existingConfigs) { if (existingConfig.SSID == "\"" + SSID + "\"") { return existingConfig } } return null } private fun createWifiInfo(SSID: String, Password: String, Type: WifiCipherType): WifiConfiguration { val config = WifiConfiguration() config.allowedAuthAlgorithms.clear() config.allowedGroupCiphers.clear() config.allowedKeyManagement.clear() config.allowedPairwiseCiphers.clear() config.allowedProtocols.clear() config.SSID = "\"" + SSID + "\"" if (Type == WifiCipherType.WIFICIPHER_NOPASS) { config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE) } if (Type == WifiCipherType.WIFICIPHER_WEP) { if (!StringUtils.isEmpty(Password)) { if (isHexWepKey(Password)) { config.wepKeys[0] = Password } else { config.wepKeys[0] = "\"" + Password + "\"" } } config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN) config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED) config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE) config.wepTxKeyIndex = 0 } // wpa if (Type == WifiCipherType.WIFICIPHER_WPA) { config.preSharedKey = "\"" + Password + "\"" config.hiddenSSID = true config.allowedAuthAlgorithms .set(WifiConfiguration.AuthAlgorithm.OPEN) config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP) config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK) config.allowedPairwiseCiphers .set(WifiConfiguration.PairwiseCipher.TKIP) config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP) config.allowedPairwiseCiphers .set(WifiConfiguration.PairwiseCipher.CCMP) config.status = WifiConfiguration.Status.ENABLED } return config } /** * 打开wifi功能 * @return */ private fun openWifi(): Boolean { var bRet = true if (!wifiManager.isWifiEnabled) { bRet = wifiManager.setWifiEnabled(true) } return bRet } /** * 关闭WIFI */ private fun closeWifi() { if (wifiManager.isWifiEnabled) { wifiManager.isWifiEnabled = false } } internal inner class ConnectRunnable(private val ssid: String, private val password: String, private val type: WifiCipherType) : Runnable { override fun run() { openWifi() // 开启wifi功能需要一段时间(我在手机上测试一般需要1-3秒左右),所以要等到wifi // 状态变成WIFI_STATE_ENABLED的时候才能执行下面的语句 while (wifiManager.wifiState == WifiManager.WIFI_STATE_ENABLING) { try { // 为了避免程序一直while循环,让它睡个100毫秒检测…… Thread.sleep(100) } catch (ie: InterruptedException) { } } val tempConfig = isExsits(ssid) if (tempConfig != null) { val b = wifiManager.enableNetwork(tempConfig.networkId, true) } else { val wifiConfig = createWifiInfo(ssid, password, type) ?: return val netID = wifiManager.addNetwork(wifiConfig) val enabled = wifiManager.enableNetwork(netID, true) val connected = wifiManager.reconnect() } } } companion object { private val TAG = WifiAutoConnectManager::class.java .simpleName private fun isHexWepKey(wepKey: String): Boolean { val len = wepKey.length // WEP-40, WEP-104, and some vendors using 256-bit WEP (WEP-232?) return if (len != 10 && len != 26 && len != 58) { false } else isHex(wepKey) } private fun isHex(key: String): Boolean { for (i in key.length - 1 downTo 0) { val c = key[i] val b = !(c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f') if (b) { return false } } return true } /** * 获取ssid的加密方式 * * @param context Context * @param ssid String * @return */ fun getCipherType(context: Context, ssid: String): WifiCipherType { val wifiManager = context .getSystemService(Context.WIFI_SERVICE) as WifiManager val list = wifiManager.scanResults for (scResult in list) { if (!StringUtils.isEmpty(scResult.SSID) && scResult.SSID == ssid) { val capabilities = scResult.capabilities if (!StringUtils.isEmpty(capabilities)) { return if (capabilities.contains("WPA") || capabilities.contains("wpa")) { WifiCipherType.WIFICIPHER_WPA } else if (capabilities.contains("WEP") || capabilities.contains("wep")) { WifiCipherType.WIFICIPHER_WEP } else { WifiCipherType.WIFICIPHER_NOPASS } } } } return WifiCipherType.WIFICIPHER_INVALID } } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/xrecycleview/BaseRefreshHeader.java ================================================ package com.lxm.module_library.xrecycleview; /** * Created by jianghejie on 15/11/22. */ interface BaseRefreshHeader { int STATE_NORMAL = 0; int STATE_RELEASE_TO_REFRESH = 1; int STATE_REFRESHING = 2; int STATE_DONE = 3; void onMove(float delta); boolean releaseAction(); void refreshComplate(); int getVisiableHeight(); } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/xrecycleview/LoadingMoreFooter.java ================================================ package com.lxm.module_library.xrecycleview; import android.content.Context; import android.graphics.drawable.AnimationDrawable; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.lxm.module_library.R; public class LoadingMoreFooter extends LinearLayout { public final static int STATE_LOADING = 0; public final static int STATE_COMPLETE = 1; public final static int STATE_NOMORE = 2; private TextView mText; private AnimationDrawable mAnimationDrawable; private ImageView mIvProgress; public LoadingMoreFooter(Context context) { super(context); initView(context); } /** * @param context * @param attrs */ public LoadingMoreFooter(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public void initView(Context context) { LayoutInflater.from(context).inflate(R.layout.refresh_footer, this); mText = (TextView) findViewById(R.id.msg); mIvProgress = (ImageView) findViewById(R.id.iv_progress); mAnimationDrawable = (AnimationDrawable) mIvProgress.getDrawable(); if (!mAnimationDrawable.isRunning()) { mAnimationDrawable.start(); } setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); } public void setState(int state) { switch (state) { case STATE_LOADING: if (!mAnimationDrawable.isRunning()) { mAnimationDrawable.start(); } mIvProgress.setVisibility(View.VISIBLE); mText.setText(getContext().getText(R.string.listview_loading)); this.setVisibility(View.VISIBLE); break; case STATE_COMPLETE: if (mAnimationDrawable.isRunning()) { mAnimationDrawable.stop(); } mText.setText(getContext().getText(R.string.listview_loading)); this.setVisibility(View.GONE); break; case STATE_NOMORE: if (mAnimationDrawable.isRunning()) { mAnimationDrawable.stop(); } mText.setText(getContext().getText(R.string.nomore_loading)); mIvProgress.setVisibility(View.GONE); this.setVisibility(View.VISIBLE); break; } } public void reSet() { this.setVisibility(GONE); } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/xrecycleview/WrapAdapter.java ================================================ package com.lxm.module_library.xrecycleview; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.StaggeredGridLayoutManager; import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; /** * Created by yangcai on 2016/1/28. */ public class WrapAdapter extends RecyclerView.Adapter { private static final int TYPE_REFRESH_HEADER = -5; private static final int TYPE_HEADER = -4; private static final int TYPE_NORMAL = 0; private static final int TYPE_FOOTER = -3; private RecyclerView.Adapter adapter; private SparseArray mHeaderViews; private SparseArray mFootViews; private int headerPosition = 1; public WrapAdapter(SparseArray headerViews, SparseArray footViews, RecyclerView.Adapter adapter) { this.adapter = adapter; this.mHeaderViews = headerViews; this.mFootViews = footViews; } @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); if (manager instanceof GridLayoutManager) { final GridLayoutManager gridManager = ((GridLayoutManager) manager); gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { return (isHeader(position) || isFooter(position)) ? gridManager.getSpanCount() : 1; } }); } } @Override public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { super.onViewAttachedToWindow(holder); ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams && (isHeader(holder.getLayoutPosition()) || isFooter(holder.getLayoutPosition()))) { StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; p.setFullSpan(true); } } public boolean isHeader(int position) { return position >= 0 && position < mHeaderViews.size(); } public boolean isFooter(int position) { return position < getItemCount() && position >= getItemCount() - mFootViews.size(); } public boolean isRefreshHeader(int position) { return position == 0; } public int getHeadersCount() { return mHeaderViews.size(); } public int getFootersCount() { return mFootViews.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == TYPE_REFRESH_HEADER) { return new SimpleViewHolder(mHeaderViews.get(0)); } else if (viewType == TYPE_HEADER) { return new SimpleViewHolder(mHeaderViews.get(headerPosition++)); } else if (viewType == TYPE_FOOTER) { return new SimpleViewHolder(mFootViews.get(0)); } return adapter.onCreateViewHolder(parent, viewType); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (isHeader(position)) { return; } int adjPosition = position - getHeadersCount(); int adapterCount; if (adapter != null) { adapterCount = adapter.getItemCount(); if (adjPosition < adapterCount) { adapter.onBindViewHolder(holder, adjPosition); } } } @Override public int getItemCount() { if (adapter != null) { return getHeadersCount() + getFootersCount() + adapter.getItemCount(); } else { return getHeadersCount() + getFootersCount(); } } @Override public int getItemViewType(int position) { if (isRefreshHeader(position)) { return TYPE_REFRESH_HEADER; } if (isHeader(position)) { return TYPE_HEADER; } if (isFooter(position)) { return TYPE_FOOTER; } int adjPosition = position - getHeadersCount(); int adapterCount; if (adapter != null) { adapterCount = adapter.getItemCount(); if (adjPosition < adapterCount) { return adapter.getItemViewType(adjPosition); } } return TYPE_NORMAL; } @Override public long getItemId(int position) { if (adapter != null && position >= getHeadersCount()) { int adjPosition = position - getHeadersCount(); int adapterCount = adapter.getItemCount(); if (adjPosition < adapterCount) { return adapter.getItemId(adjPosition); } } return -1; } @Override public void unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver observer) { if (adapter != null) { adapter.unregisterAdapterDataObserver(observer); } } @Override public void registerAdapterDataObserver(RecyclerView.AdapterDataObserver observer) { if (adapter != null) { adapter.registerAdapterDataObserver(observer); } } private class SimpleViewHolder extends RecyclerView.ViewHolder { public SimpleViewHolder(View itemView) { super(itemView); } } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/xrecycleview/XRecyclerView.java ================================================ package com.lxm.module_library.xrecycleview; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.StaggeredGridLayoutManager; import android.util.AttributeSet; import android.util.SparseArray; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; /** * Created by jingbin on 2016/1/28. */ public class XRecyclerView extends RecyclerView { private LoadingListener mLoadingListener; private WrapAdapter mWrapAdapter; private SparseArray mHeaderViews = new SparseArray(); private SparseArray mFootViews = new SparseArray(); private boolean pullRefreshEnabled = true; private boolean loadingMoreEnabled = true; private YunRefreshHeader mRefreshHeader; private boolean isLoadingData; public int previousTotal; public boolean isnomore; private float mLastY = -1; private static final float DRAG_RATE = 1.75f; // 是否是额外添加FooterView private boolean isOther = false; public XRecyclerView(Context context) { this(context, null); } public XRecyclerView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public XRecyclerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } private void init(Context context) { if (pullRefreshEnabled) { YunRefreshHeader refreshHeader = new YunRefreshHeader(context); mHeaderViews.put(0, refreshHeader); mRefreshHeader = refreshHeader; } LoadingMoreFooter footView = new LoadingMoreFooter(context); addFootView(footView, false); mFootViews.get(0).setVisibility(GONE); } /** * 改为公有。供外添加view使用,使用标识 * 注意:使用后不能使用 上拉加载,否则添加无效 * 使用时 isOther 传入 true,然后调用 noMoreLoading即可。 */ public void addFootView(final View view, boolean isOther) { mFootViews.clear(); mFootViews.put(0, view); this.isOther = isOther; } /** * 相当于加一个空白头布局: * 只有一个目的:为了滚动条显示在最顶端 * 因为默认加了刷新头布局,不处理滚动条会下移。 * 和 setPullRefreshEnabled(false) 一块儿使用 * 使用下拉头时,此方法不应被使用! */ public void clearHeader() { mHeaderViews.clear(); ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1); View view = new View(getContext()); view.setLayoutParams(params); mHeaderViews.put(0, view); } public void addHeaderView(View view) { if (pullRefreshEnabled && !(mHeaderViews.get(0) instanceof YunRefreshHeader)) { YunRefreshHeader refreshHeader = new YunRefreshHeader(getContext()); mHeaderViews.put(0, refreshHeader); mRefreshHeader = refreshHeader; } mHeaderViews.put(mHeaderViews.size(), view); } private void loadMoreComplete() { isLoadingData = false; View footView = mFootViews.get(0); if (previousTotal <= getLayoutManager().getItemCount()) { if (footView instanceof LoadingMoreFooter) { ((LoadingMoreFooter) footView).setState(LoadingMoreFooter.STATE_COMPLETE); } else { footView.setVisibility(View.GONE); } } else { if (footView instanceof LoadingMoreFooter) { ((LoadingMoreFooter) footView).setState(LoadingMoreFooter.STATE_NOMORE); } else { footView.setVisibility(View.GONE); } isnomore = true; } previousTotal = getLayoutManager().getItemCount(); } public void noMoreLoading() { isLoadingData = false; final View footView = mFootViews.get(0); isnomore = true; if (footView instanceof LoadingMoreFooter) { ((LoadingMoreFooter) footView).setState(LoadingMoreFooter.STATE_NOMORE); } else { footView.setVisibility(View.GONE); } // 额外添加的footView if (isOther) { footView.setVisibility(View.VISIBLE); } } public void refreshComplete() { // mRefreshHeader.refreshComplate(); if (isLoadingData) { loadMoreComplete(); } else { mRefreshHeader.refreshComplate(); } } @Override public void setAdapter(Adapter adapter) { mWrapAdapter = new WrapAdapter(mHeaderViews, mFootViews, adapter); super.setAdapter(mWrapAdapter); adapter.registerAdapterDataObserver(mDataObserver); } @Override public void onScrollStateChanged(int state) { super.onScrollStateChanged(state); if (state == RecyclerView.SCROLL_STATE_IDLE && mLoadingListener != null && !isLoadingData && loadingMoreEnabled) { LayoutManager layoutManager = getLayoutManager(); int lastVisibleItemPosition; if (layoutManager instanceof GridLayoutManager) { lastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition(); } else if (layoutManager instanceof StaggeredGridLayoutManager) { int[] into = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()]; ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(into); lastVisibleItemPosition = findMax(into); } else { lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition(); } if (layoutManager.getChildCount() > 0 && lastVisibleItemPosition >= layoutManager.getItemCount() - 1 && layoutManager.getItemCount() > layoutManager.getChildCount() && !isnomore && mRefreshHeader.getState() < YunRefreshHeader.STATE_REFRESHING) { View footView = mFootViews.get(0); isLoadingData = true; if (footView != null) { if (footView instanceof LoadingMoreFooter) { ((LoadingMoreFooter) footView).setState(LoadingMoreFooter.STATE_LOADING); } else { footView.setVisibility(View.VISIBLE); } } if (isNetWorkConnected(getContext())) { mLoadingListener.onLoadMore(); } else { postDelayed(new Runnable() { @Override public void run() { mLoadingListener.onLoadMore(); } }, 1000); } } } } @Override public boolean onTouchEvent(MotionEvent ev) { if (mLastY == -1) { mLastY = ev.getRawY(); } switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mLastY = ev.getRawY(); break; case MotionEvent.ACTION_MOVE: final float deltaY = ev.getRawY() - mLastY; mLastY = ev.getRawY(); if (isOnTop() && pullRefreshEnabled) { mRefreshHeader.onMove(deltaY / DRAG_RATE); if (mRefreshHeader.getVisiableHeight() > 0 && mRefreshHeader.getState() < YunRefreshHeader.STATE_REFRESHING) { return false; } } break; default: mLastY = -1; // reset if (isOnTop() && pullRefreshEnabled) { if (mRefreshHeader.releaseAction()) { if (mLoadingListener != null) { mLoadingListener.onRefresh(); isnomore = false; previousTotal = 0; final View footView = mFootViews.get(0); if (footView instanceof LoadingMoreFooter) { if (footView.getVisibility() != View.GONE) { footView.setVisibility(View.GONE); } } } } } break; } return super.onTouchEvent(ev); } private int findMax(int[] lastPositions) { int max = lastPositions[0]; for (int value : lastPositions) { if (value > max) { max = value; } } return max; } private int findMin(int[] firstPositions) { int min = firstPositions[0]; for (int value : firstPositions) { if (value < min) { min = value; } } return min; } public boolean isOnTop() { if (mHeaderViews == null || mHeaderViews.size() == 0) { return false; } View view = mHeaderViews.get(0); if (view.getParent() != null) { return true; } else { return false; } } private final RecyclerView.AdapterDataObserver mDataObserver = new RecyclerView.AdapterDataObserver() { @Override public void onChanged() { mWrapAdapter.notifyDataSetChanged(); } @Override public void onItemRangeInserted(int positionStart, int itemCount) { mWrapAdapter.notifyItemRangeInserted(positionStart, itemCount); } @Override public void onItemRangeChanged(int positionStart, int itemCount) { mWrapAdapter.notifyItemRangeChanged(positionStart, itemCount); } @Override public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { mWrapAdapter.notifyItemRangeChanged(positionStart, itemCount, payload); } @Override public void onItemRangeRemoved(int positionStart, int itemCount) { mWrapAdapter.notifyItemRangeRemoved(positionStart, itemCount); } @Override public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { mWrapAdapter.notifyItemMoved(fromPosition, toPosition); } }; public void setLoadingListener(LoadingListener listener) { mLoadingListener = listener; } public void setPullRefreshEnabled(boolean pullRefreshEnabled) { this.pullRefreshEnabled = pullRefreshEnabled; } public void setLoadingMoreEnabled(boolean loadingMoreEnabled) { this.loadingMoreEnabled = loadingMoreEnabled; if (!loadingMoreEnabled) { if (mFootViews != null) { mFootViews.remove(0); } } else { if (mFootViews != null) { LoadingMoreFooter footView = new LoadingMoreFooter(getContext()); addFootView(footView, false); } } } public void setLoadMoreGone() { if (mFootViews == null) { return; } View footView = mFootViews.get(0); if (footView != null && footView instanceof LoadingMoreFooter) { mFootViews.remove(0); } } public interface LoadingListener { void onRefresh(); void onLoadMore(); } /** * 检测网络是否可用 * * @param context * @return */ public static boolean isNetWorkConnected(Context context) { if (context != null) { ConnectivityManager mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo(); if (mNetworkInfo != null) { return mNetworkInfo.isConnected(); } } return false; } public void reset() { isnomore = false; previousTotal = 0; final View footView = mFootViews.get(0); if (footView instanceof LoadingMoreFooter) { ((LoadingMoreFooter) footView).reSet(); } } /** * 是否在刷新数据 */ public boolean isLoadingData() { return isLoadingData; } } ================================================ FILE: module_library/src/main/java/com/lxm/module_library/xrecycleview/YunRefreshHeader.java ================================================ package com.lxm.module_library.xrecycleview; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.drawable.AnimationDrawable; import android.os.Handler; import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.lxm.module_library.R; /** * Created by yangcai on 2016/1/27. */ public class YunRefreshHeader extends LinearLayout implements BaseRefreshHeader { private Context mContext; private AnimationDrawable animationDrawable; private TextView msg; private int mState = STATE_NORMAL; private int mMeasuredHeight; private LinearLayout mContainer; public YunRefreshHeader(Context context) { this(context, null); } public YunRefreshHeader(Context context, AttributeSet attrs) { this(context, attrs, 0); } public YunRefreshHeader(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mContext = context; initView(); } private void initView() { LayoutInflater.from(mContext).inflate(R.layout.refresh_header, this); ImageView img = (ImageView) findViewById(R.id.img); animationDrawable = (AnimationDrawable) img.getDrawable(); if (animationDrawable.isRunning()) { animationDrawable.stop(); } msg = (TextView) findViewById(R.id.msg); measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); mMeasuredHeight = getMeasuredHeight(); setGravity(Gravity.CENTER_HORIZONTAL); mContainer = (LinearLayout) findViewById(R.id.container); mContainer.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 0)); this.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); } @Override public void onMove(float delta) { if (getVisiableHeight() > 0 || delta > 0) { setVisiableHeight((int) delta + getVisiableHeight()); if (mState <= STATE_RELEASE_TO_REFRESH) { // 未处于刷新状态,更新箭头 if (getVisiableHeight() > mMeasuredHeight) { setState(STATE_RELEASE_TO_REFRESH); } else { setState(STATE_NORMAL); } } } } private void setState(int state) { if (state == mState) return; switch (state) { case STATE_NORMAL: if (animationDrawable.isRunning()) { animationDrawable.stop(); } msg.setText(R.string.listview_header_hint_normal); break; case STATE_RELEASE_TO_REFRESH: if (mState != STATE_RELEASE_TO_REFRESH) { if (!animationDrawable.isRunning()) { animationDrawable.start(); } msg.setText(R.string.listview_header_hint_release); } break; case STATE_REFRESHING: msg.setText(R.string.refreshing); break; case STATE_DONE: msg.setText(R.string.refresh_done); break; default: } mState = state; } @Override public boolean releaseAction() { boolean isOnRefresh = false; int height = getVisiableHeight(); if (height == 0) // not visible. isOnRefresh = false; if (getVisiableHeight() > mMeasuredHeight && mState < STATE_REFRESHING) { setState(STATE_REFRESHING); isOnRefresh = true; } // refreshing and header isn't shown fully. do nothing. if (mState == STATE_REFRESHING && height <= mMeasuredHeight) { //return; } int destHeight = 0; // default: scroll back to dismiss header. // is refreshing, just scroll back to show all the header. if (mState == STATE_REFRESHING) { destHeight = mMeasuredHeight; } smoothScrollTo(destHeight); return isOnRefresh; } @Override public void refreshComplate() { setState(STATE_DONE); new Handler().postDelayed(new Runnable() { public void run() { reset(); } }, 500); } public void reset() { smoothScrollTo(0); setState(STATE_NORMAL); } private void smoothScrollTo(int destHeight) { ValueAnimator animator = ValueAnimator.ofInt(getVisiableHeight(), destHeight); animator.setDuration(300).start(); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { setVisiableHeight((int) animation.getAnimatedValue()); } }); animator.start(); } private void setVisiableHeight(int height) { if (height < 0) height = 0; // ` LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0); lp.height = height; mContainer.setLayoutParams(lp); } @Override public int getVisiableHeight() { return mContainer.getHeight(); } public int getState() { return mState; } } ================================================ FILE: module_library/src/main/res/drawable/app_loading_anim.xml ================================================ ================================================ FILE: module_library/src/main/res/drawable/common_progress_cirle.xml ================================================ ================================================ FILE: module_library/src/main/res/drawable/header_loading_anim.xml ================================================ ================================================ FILE: module_library/src/main/res/drawable/ic_add.xml ================================================ ================================================ FILE: module_library/src/main/res/drawable/ic_clear.xml ================================================ ================================================ FILE: module_library/src/main/res/drawable/login_back.xml ================================================ ================================================ FILE: module_library/src/main/res/drawable/shape_line.xml ================================================ ================================================ FILE: module_library/src/main/res/layout/activity_base.xml ================================================ ================================================ FILE: module_library/src/main/res/layout/custom_login_view.xml ================================================ ================================================ FILE: module_library/src/main/res/layout/custom_register_view.xml ================================================ ================================================ FILE: module_library/src/main/res/layout/default_login_view.xml ================================================ ================================================ FILE: module_library/src/main/res/layout/default_register_view.xml ================================================ ================================================ FILE: module_library/src/main/res/layout/fragment_base.xml ================================================ ================================================ FILE: module_library/src/main/res/layout/layout_loading_error.xml ================================================ ================================================ FILE: module_library/src/main/res/layout/layout_loading_view.xml ================================================ ================================================ FILE: module_library/src/main/res/layout/login_layout.xml ================================================ ================================================ FILE: module_library/src/main/res/layout/login_view.xml ================================================ ================================================ FILE: module_library/src/main/res/layout/refresh_footer.xml ================================================ ================================================ FILE: module_library/src/main/res/layout/refresh_header.xml ================================================ ================================================ FILE: module_library/src/main/res/layout/register_layout.xml ================================================ ================================================ FILE: module_library/src/main/res/values/attrs.xml ================================================ ================================================ FILE: module_library/src/main/res/values/colors.xml ================================================ #343434 #EBEBEB #D4D4D4 #BBBBBB #FFDDDDDD #ff333333 #585858 #999 #666 #ffffffff #777D7D7D #343434 #11EE69 #ff5151 #ffcdce #000 #000 ================================================ FILE: module_library/src/main/res/values/dimen.xml ================================================ 250dp ================================================ FILE: module_library/src/main/res/values/drawables.xml ================================================ @drawable/abc_item_background_holo_light ================================================ FILE: module_library/src/main/res/values/strings.xml ================================================ module_library 下拉刷新... 释放刷新... 努力加载中... 没有更多内容了 正在刷新... 刷新完成... 上次更新时间: 分享 复制链接 浏览器打开 刷新 添加到收藏 LOGIN Name Password GO REGISTER Repeat Password NEXT ================================================ FILE: module_library/src/main/res/values/styles.xml ================================================ ================================================ FILE: module_library/src/main/res/values-w600dp/dimen.xml ================================================ 400dp ================================================ FILE: module_library/src/test/java/com/lxm/module_library/ExampleUnitTest.java ================================================ package com.lxm.module_library; import org.junit.Test; import static org.junit.Assert.*; /** * Example local unit test, which will execute on the development machine (host). * * @see Testing documentation */ public class ExampleUnitTest { @Test public void addition_isCorrect() { assertEquals(4, 2 + 2); } } ================================================ FILE: settings.gradle ================================================ include ':app', ':module_library'