💪 持续更新。WanJetpack使用Jetpack MVVM开发架构、单Activity多Fragment设计,项目结构清晰,代码简洁优雅,追求最官方的实现方式。欢迎star,非常感谢。已用到知识点:LiveData、ViewModel、DataBinding、ViewBinding、coroutines、Hilt、Paging3、Room、Navigation、TabLayout、BottomNavigationView、RecycleView、ViewPager2、Banner、Glide、Cookie、Retrofit2、启动页面、深色主题、沉浸式模式、Kotlin高阶函数。
玩Android demo。用Jetpack MVVM开发架构、单Activity多Fragment项目设计,项目结构清晰,代码简洁优雅,追求最官方的实现方式。用到以下知识点: LiveData、ViewModel、DataBinding(包括双向绑定、BindingAdapter的使用)、ViewBinding、coroutines(包含flow、suspend、livedata协程构造器、flow协程构造器的使用)、Hilt、Paging3(包含RemoteMediator、加载状态)、Room、Navigation(通过ViewModel共享数据)、Banner(kotlin简单实现)、TabLayout、BottomNavigationView、RecycleView(包含ListAdapter、ConcatAdapter、PagingDataAdapter的使用)、ViewPager2、Glide、Cookie、Retrofit2、启动页面、深色主题、沉浸式模式、Kotlin高阶函数。
private val itemId = MutableLiveData<String>()
val result = itemId.switchMap {
liveData { emit(fetchItem(it)) }
}
Paging 库 3.0.0正式版已发布,普天同庆!Paging 库可帮助您加载和显示来自本地存储或网络中更大的数据集中的数据页面。此方法可让您的应用更高效地利用网络带宽和系统资源。Paging 库的组件旨在契合推荐的 Android 应用架构,流畅集成其他 Jetpack 组件,并提供一流的 Kotlin 支持。
官方demo:
Paging 库包含以下功能:
Paging 组件及其在应用架构的集成:
定义数据源 : 数据源的定义取决于您从哪里加载数据。您仅需实现 PagingSource 或者 PagingSource 与 RemoteMediator 的组合:
PagingSource :
// 自定义PagingSource类
private const val ARTICLE_STARTING_PAGE_INDEX = 0
class HomeArticlePagingSource(
private val api: WanJetpackApi
) : PagingSource<Int, ApiArticle>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, ApiArticle> {
val page = params.key ?: ARTICLE_STARTING_PAGE_INDEX
return try {
val response = api.getHomeArticle(page)
val datas = response.data.datas
LoadResult.Page(
data = datas,
prevKey = if (page == ARTICLE_STARTING_PAGE_INDEX) null else page - 1,
nextKey = if (page == response.data.pageCount) null else page + 1,
)
} catch (exception: Exception) {
LoadResult.Error(exception)
}
}
override fun getRefreshKey(state: PagingState<Int, ApiArticle>): Int? {
return null
}
}
PagingData :
//Repository:
fun getHomeArticle(): Flow<PagingData<ApiArticle>> {
return Pager(
config = PagingConfig(enablePlaceholders = false, pageSize = HOME_ARTICLE_PAGE_SIZE),
pagingSourceFactory = { HomeArticlePagingSource(api) }
).flow
}
//ViewModel:
fun getHomeArticle(): Flow<PagingData<ApiArticle>> {
val newResult: Flow<PagingData<ApiArticle>> =
repository.getHomeArticle().cachedIn(viewModelScope)
currentArticleResult = newResult
return newResult
}
PagingDataAdapter :
LoadType : 是个 enum 类,包含三种状态:REFRESH、PREPEND、APPEND。在 PagingSource 的 LoadParams 类中用到。
LoadState : 是个 sealed(密封) 类。
加载状态的三个场景:下拉刷新、上拉加载更多、首次进入页面中间的滚动条(及加载失败提醒)
显示加载状态 : 可通过两种方法在界面中使用 LoadState:使用监听器,以及使用特殊的列表适配器在 RecyclerView 列表中直接显示加载状态。
// addLoadStateListener 方式。
articleAdapter.addLoadStateListener {
when (it.refresh) {
is LoadState.NotLoading -> {
progressBar.visibility = View.INVISIBLE
recyclerView.visibility = View.VISIBLE
}
is LoadState.Loading -> {
progressBar.visibility = View.VISIBLE
recyclerView.visibility = View.INVISIBLE
}
is LoadState.Error -> {
val state = it.refresh as LoadState.Error
progressBar.visibility = View.INVISIBLE
Toast.makeText(this, "Load Error: ${state.error.message}", Toast.LENGTH_SHORT).show()
}
}
}
// loadStateFlow 方式
// collectLatest 是个 suspend 函数,所以要在协程或者另一个 suspend 中调用
lifecycleScope.launch {
pagingAdapter.loadStateFlow.collectLatest {
progressBar.isVisible = it.refresh is LoadState.Loading
retry.isVisible = it.refresh !is LoadState.Loading
errorMsg.isVisible = it.refresh is LoadState.Error
}
}
class LoadStateViewHolder(
parent: ViewGroup,
retry: () -> Unit
) : RecyclerView.ViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.load_state_item, parent, false)
) {
private val binding = LoadStateItemBinding.bind(itemView)
private val progressBar: ProgressBar = binding.progressBar
private val errorMsg: TextView = binding.errorMsg
private val retry: Button = binding.retryButton
.also {
it.setOnClickListener { retry() }
}
fun bind(loadState: LoadState) {
if (loadState is LoadState.Error) {
errorMsg.text = loadState.error.localizedMessage
}
progressBar.isVisible = loadState is LoadState.Loading
retry.isVisible = loadState is LoadState.Error
errorMsg.isVisible = loadState is LoadState.Error
}
}
// Adapter that displays a loading spinner when
// state = LoadState.Loading, and an error message and retry
// button when state is LoadState.Error.
class ExampleLoadStateAdapter(
private val retry: () -> Unit
) : LoadStateAdapter<LoadStateViewHolder>() {
override fun onCreateViewHolder(
parent: ViewGroup,
loadState: LoadState
) = LoadStateViewHolder(parent, retry)
override fun onBindViewHolder(
holder: LoadStateViewHolder,
loadState: LoadState
) = holder.bind(loadState)
}
pagingAdapter
.withLoadStateHeaderAndFooter(
header = ExampleLoadStateAdapter(adapter::retry),
footer = ExampleLoadStateAdapter(adapter::retry)
)
Pager : Pager().flow 把 PagingSource 转换为 PagingData。在Repository中用到
RemoteMediator : 在Pager()中用到。
PagingConfig : 在Pager()中用到
PagingState : 在自定义 PagingSource 的 getRefreshKey()方法中用到,在自定义RemoteMediator的load()方法中也用到了。
参考博客:目前Paging已经发布3.0正式版,下面这个博客是alpha版本的,但可以参考:
binding.articleList.setHasFixedSize(true)
binding.articleList.isNestedScrollingEnabled = false
CoordinatorLayout、NestedScrollView、CollapsingToolbarLayout、AppBarLayout、MaterialToolbart
待优化场景,搜索场景:search在 Android 系统的协助下使用搜索对话框或搜索微件传递搜索查询
try {
val decorView = window.decorView::class.java
val field = decorView.getDeclaredField("mSemiTransparentBarColor")
field.isAccessible = true
field.setInt(window.decorView, Color.TRANSPARENT)
} catch (e: Exception) {
}