赞
踩
先放开源代码:仿开眼App仓库地址,项目的git展示也在github中,大家自行查看
App使用
BottomNavigationView
+ViewPager2
实现底部导航栏,点击切换界面。App部分页面设置沉浸式状态栏提升体验,RV列表添加动画,更加丝滑一些。对RV列表添加FloatingActionButton
,可以在用户滑很远想回到上面的时候一键返回。整体结构借鉴了林潼学长的MVVM(比我之前的结构好…)
首页有两个页面,分别是推荐和日报,使用TabLayout
和ViewPager2
实现页面的切换
社区有三个界面,广场、发现和人气。广场用PhotoView
+ ViewPager2
实现图片的查看(第一次加载的时候有些慢),发现页面实现了Banner轮播图,分类等功能,人气为请求到的各种榜单。
通知没什么好说的…
我的页面也是体现本App一大特色的地方,实现了一键换肤、用户信息修改和收藏功能,前者通过全局的ViewModel
实现,设置颜色主题后,通知所有界面主要颜色改变。后两个使用Room
实现本地持久化存储。
视频播放调第三方库,可旋转全屏观看,在界面内可点击收藏,视频播放下面有相关视频推荐和评论。
视频内容点击更多可展开。
实现该功能需要获取一个全局的ViewModel
,供全局观察,当在设置好颜色之后,将appViewModel.appColor
的值改变,在别的界面通过Observe
观察其变化,发现其值发生改变,立即修改一些控件的颜色与主题一致。
在Application
中,自己定义一个工厂获取到Application
的实例,并以此创建ViewModel
可以在全局使用。将创建的ViewModel
保存到自己的ViewModelStoreOwner
中。
// 供全局引用 val appViewModel: AppViewModel by lazy { App.appViewModelInstance } class App : Application(), ViewModelStoreOwner { private var mFactory: ViewModelProvider.Factory? = null private lateinit var mAppViewModelStore: ViewModelStore companion object { @SuppressLint("StaticFieldLeak") lateinit var context: Context lateinit var appViewModelInstance: AppViewModel } // 获取全局的 ViewModel private fun getAppViewModelProvider(): ViewModelProvider { return ViewModelProvider(this, this.getAppFactory()) } // 自己定义 Factory private fun getAppFactory(): ViewModelProvider.Factory { if (mFactory == null) { mFactory = ViewModelProvider.AndroidViewModelFactory.getInstance(this) } return mFactory as ViewModelProvider.Factory } override fun onCreate() { super.onCreate() context = applicationContext mAppViewModelStore = ViewModelStore() appViewModelInstance = getAppViewModelProvider()[AppViewModel::class.java] } override fun getViewModelStore(): ViewModelStore { return mAppViewModelStore } }
在其他界面使用该ViewModel
,setUiTheme()是一个工具函数,用于修改控件颜色。
appViewModel.run {
appColor.observe(this@DailyFragment) {
setUiTheme(it, mDatabind.includeList.floatbtn, mDatabind.includeList.includeRecyclerview.swipeRefresh)
}
}
但该实现方式有很大的缺点,就是可能忘记在某个界面设置颜色>_<,但目前只能想到这样实现换肤功能。
我对一些可能经常复用的逻辑代码封装了很多扩展函数,比如初始化控件等。
// 初始化 ViewPager2 fun ViewPager2.init( fragment: Fragment, fragments: ArrayList<Fragment>, isUserInputEnabled: Boolean = true ): ViewPager2 { //是否可滑动 this.isUserInputEnabled = isUserInputEnabled //设置适配器 adapter = object : FragmentStateAdapter(fragment) { override fun createFragment(position: Int) = fragments[position] override fun getItemCount() = fragments.size } return this } // 初始化 FloatButton fun RecyclerView.initFloatBtn(floatBtn: FloatingActionButton) { // 监听recyclerview滑动到顶部的时候,把向上返回顶部的按钮隐藏 addOnScrollListener(object : RecyclerView.OnScrollListener() { @SuppressLint("RestrictedApi") override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) if (!canScrollVertically(-1)) { floatBtn.visibility = View.INVISIBLE } } }) floatBtn.backgroundTintList = ColorUtil.getOneColorStateList(App.context) floatBtn.setOnClickListener { val layoutManager = layoutManager as LinearLayoutManager // 如果当前recyclerview 最后一个视图位置的索引大于等于20,则迅速返回顶部,否则带有滚动动画效果返回到顶部 if (layoutManager.findLastVisibleItemPosition() >= 30) { scrollToPosition(0) // 没有动画迅速返回到顶部 } else { smoothScrollToPosition(0) // 有滚动动画返回到顶部 } } } // 初始化BottomNavigationView,在init内即可实现其与ViewPager2的结合 fun BottomNavigationView.init(navigationItemSelectedAction: (Int) -> Unit) : BottomNavigationView { itemIconTintList = ColorUtil.getColorStateList(ColorUtil.getColor(App.context)) itemTextColor = ColorUtil.getColorStateList(App.context) setOnNavigationItemSelectedListener { navigationItemSelectedAction.invoke(it.itemId) true } return this } ......
定义一个NetStateManager
,提供实例给外部,在MainActivity中监听网络状态,可以管理多个界面网络状态的变化。
class NetStateManager private constructor() { val mNetworkStateCallback = EventLiveData<NetState>() companion object { val instance: NetStateManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { NetStateManager() } } } // MainActivity中 private fun onNetworkStateChanged(netState: NetState) { if (netState.isSuccess) { Toast.makeText(applicationContext, "我现在有网哦!", Toast.LENGTH_SHORT).show() } else { Toast.makeText(applicationContext, "我怎么断网了呀!", Toast.LENGTH_SHORT).show() } }
在本项目中,除了在网络上请求数据,还使用Room
数据库框架实现本地数据持久化存储,主要存储我的喜欢以及用户的个人信息。二者数据都在仓库层中获取。
// 从网络获取数据 fun loadNotify() = fire(Dispatchers.IO) { val response = ApiLoad.loadNotify() val length = response.messageList.size val list = mutableListOf<NotifyData>() for (i in 0 until length) { val messageData = NotifyData( response.messageList[i].title, response.messageList[i].content, response.messageList[i].date, response.nextPageUrl ) list.add(messageData) } Result.success(list) } // 从数据库中获取数据 fun loadMyLike() : LiveData<List<VideoInfoBean>> { return videoDao.loadAllVideos() }
实现悬浮按钮,用户滑动过多通过该按钮一键返回顶部,提升体验。
@Override public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull FloatingActionButton child, @NonNull View directTargetChild, @NonNull View target, int nestedScrollAxes) { // 判断是否是垂直滚动,是则返回true return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); } @SuppressLint("RestrictedApi") @Override public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull FloatingActionButton child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) { // 如果向上滑动则隐藏 child.setVisibility(View.INVISIBLE); } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) { // 向下滑动则展示出来 child.show(); } }
class DiffCallBack(private val oldList: MutableList<VideoInfoBean>, private val newList: MutableList<VideoInfoBean>) : DiffUtil.Callback() { override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { return oldList[oldItemPosition] === newList[newItemPosition] } override fun getOldListSize(): Int { return oldList.size } override fun getNewListSize(): Int { return newList.size } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { return oldList[oldItemPosition] == newList[newItemPosition] } } // 应用,在喜欢界面删除数据 fun removeData(position: Int) { val oldList = mList mList?.removeAt(position) val diffCallBack = DiffUtil.calculateDiff(DiffCallBack(oldList, mList)) diffCallBack.dispatchUpdatesTo(this) }
这部分在我的课件里有所讲述,这里就不多交代了。通过实现这四个接口来实现功能。
interface OnHelperCallBack {
fun onMove(fromPosition: Int, targetPosition: Int)
fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder, actionState: Int)
fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder)
fun remove(viewHolder: RecyclerView.ViewHolder, direction: Int, position: Int)
}
// 应用,在LikeActivity中
callBack = RecyclerTouchHelpCallBack(object : RecyclerTouchHelpCallBack.OnHelperCallBack { override fun onMove(fromPosition: Int, targetPosition: Int) { likeAdapter.mList?.let { it1 -> callBack.itemMove(likeAdapter, it1, fromPosition, targetPosition) } } override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder, actionState: Int) { // 选中修改Item样式 viewHolder.itemView.alpha = 1f viewHolder.itemView.scaleX = 1.2f viewHolder.itemView.scaleY = 1.2f } override fun clearView( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder ) { likeAdapter.mList // 松手修改Item样式 viewHolder.itemView.alpha = 1f viewHolder.itemView.scaleX = 1f viewHolder.itemView.scaleY = 1f } // 删除数据 override fun remove( viewHolder: RecyclerView.ViewHolder, direction: Int, position: Int ) { // 在数据库中删除 videoDao.deleteVideo(it[position]) likeAdapter.removeData(position) } }) callBack.edit = true ItemTouchHelper(callBack).attachToRecyclerView(rvLike)
如果大家觉得有用,希望点个star~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。