一个用户菜单的需求,本来是想单独做成一个活动的,不过由于需要在按菜单键时显示在视频活动的上层,最终决定是做成一个PopupWindow,依托于视频活动的上下文对象。
kotlin和java的写法有些差别,另外根据情况需要入参mContext,
基本架构用到了databinding和viewmodel以及协程,根据需求需要网络请求,请求放在协程io里。
PopupWindow里没有oncreate方法,但是kotlin有init方法,可以做初始化的一些工作。
class UserPop(var mContext: AppCompatActivity?, width: Int = ViewGroup.LayoutParams.MATCH_PARENT, height: Int = ViewGroup.LayoutParams.MATCH_PARENT) : PopupWindow(width, height) {
private var mRoot: View?=null
private var binding: PopUserMenuBinding? = PopUserMenuBinding
.inflate(mContext!!.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater, null, false)
private var viewModel: PopViewModel?
private var privacyVM: PrivacyViewModel?
private var mLifecycleOwner: LifecycleOwner? = mContext!!
private var userGlobal: ViewTreeObserver.OnGlobalLayoutListener?=null
private var mLaunchingJob: Job? = null
init {
binding!!.lifecycleOwner = mLifecycleOwner
contentView = binding!!.root
viewModel = ViewModelProvider(mContext!!,
BaseViewModelFactory(MyApplication.mApplication,
MyApplication.mApplication.getmRepository()))[PopViewModel::class.java]
privacyVM=ViewModelProvider(mContext!!,
ViewModelProvider.AndroidViewModelFactory(MyApplication.mApplication))[PrivacyViewModel::class.java]
initView()
observeData()
getLoginInfo()
getVersion()
getVipDisCount(System.currentTimeMillis()) //会员h5
//历史和收藏改用Flow方式获取
// getRecentData()
// getCollectionData()
}
private fun observeData() {
viewModel!!.vipDetail.observe(mLifecycleOwner!!){
if (it.code==0){
...
}
}
}
private fun getVipDisCount(time: Long){
mLifecycleOwner!!.lifecycleScope.launch {
viewModel!!.getVipDiscount(mContext!!, time)
}
}
自定义弹窗的显示位置showAtLocation
fun show(root: View?) {
mRoot=root
if (root != null) {
try {
showAtLocation(root, Gravity.CLIP_VERTICAL, 0, ConvertUtil.dip2px(326f))
LiveLog.log(LiveLog.LOG_LIVE, tag, "pop44")
} catch (e: Exception) {
val handler = Handler(Looper.getMainLooper())
handler.postDelayed({
showAtLocation(root, Gravity.CLIP_VERTICAL, 0, ConvertUtil.dip2px(326f))
LiveLog.log(LiveLog.LOG_LIVE, tag, "pop51")
}, 1000)
}
} else {
LiveLog.log(LiveLog.LOG_LIVE, tag, "pop58")
}
}
**
弹窗消失时取消订阅的Observer, 释放变量,取消未完成的后台协程
**
override fun dismiss() {
super.dismiss()
//remove callback
removeGlobalListener()
privacyVM!!.h5Result.removeObservers(mLifecycleOwner!!)
viewModel!!.recentResults.removeObservers(mLifecycleOwner!!)
viewModel!!.collectionResults.removeObservers(mLifecycleOwner!!)
if (mLaunchingJob!=null&&mLaunchingJob!!.isActive){
mLaunchingJob!!.cancel()
mLaunchingJob=null
}
viewModel=null
mRoot=null
binding=null
mLifecycleOwner=null
mContext=null
LiveLog.log(LiveLog.LOG_LIVE, tag, "dismiss")
}
}
管理viewModel生成的工厂类
class BaseViewModelFactory(private val application: Application, private val repository:DataRepository) : ViewModelProvider.AndroidViewModelFactory(application) {
override fun <T : ViewModel?> create(modelClass: Class<T>): T = with(modelClass){
when{
isAssignableFrom(PopViewModel::class.java)->PopViewModel(repository)
else->{
throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
}
}
}as T
}
initView()中设置可获焦,设置背景以响应返回键。
设置UI,初始化adapter和设置监听
private fun initView() {
isFocusable = true
binding?.loginIcon= ContextCompat.getDrawable(mContext!!, R.drawable.login_default)
binding?.loginTitle=mContext!!.getString(R.string.user_card_mine_title)
binding?.loginDetail=mContext!!.getString(R.string.user_card_mine_detail)
binding?.versionCode="* * *"
val info=OnLineUtils.getMemberDetailDTO()
if(info!=null&& !info.copyWriting.isNullOrEmpty()){
binding?.vipTitle=info.name
binding?.vipDetail=info.copyWriting
}else{
binding?.vipTitle="——"
binding?.vipDetail=mContext!!.getString(R.string.user_card_vip_detail)
}
val dw = ColorDrawable(0x00000000)
setBackgroundDrawable(dw) //设置背景可使popwindow响应返回键
/* binding!!.bg.isFocusable=true
binding!!.bg.setOnKeyListener { v, keyCode, event ->
if (event.action == KeyEvent.ACTION_DOWN && keyCode==KeyEvent.KEYCODE_BACK){
dismiss()
return@setOnKeyListener true
}else false
}*/
//recent
recentAdapter= HistoryAdapter(this, R.layout.list_card, HistoryAdapter.DiffCallBack())
//collection
collectionAdapter = FavoriteAdapter(this, R.layout.list_card, FavoriteAdapter.DiffCallBack())
binding?.recent?.recent_part_recyclerview?.apply {
adapter = recentAdapter
layoutManager = LinearLayoutManager(mContext!!, LinearLayoutManager.HORIZONTAL, false)
addItemDecoration(CardItemDecoration(0, ConvertUtil.dip2px(cardItemSpacing)))
setSelectedItemOffset(offsetStart, offsetEnd)
}
binding?.collection?.collection_recyclerview?.apply {
adapter = collectionAdapter
layoutManager = LinearLayoutManager(mContext!!, LinearLayoutManager.HORIZONTAL, false)
addItemDecoration(CardItemDecoration(0, ConvertUtil.dip2px(cardItemSpacing)))
setSelectedItemOffset(offsetStart, offsetEnd)
}
//加载中占位
recentAdapter.replaceData(listOf(History("-1000", mContext!!.getString(R.string.loading), mContext!!.getString(R.string.loading))))
collectionAdapter.replaceData(listOf(Favorite("-1000", mContext!!.getString(R.string.loading), mContext!!.getString(R.string.loading))))
initKeyListener()
initClickListener()
initFocusListener()
}
弹窗中的网络请求放在协程中,通过viewmodel中的方法调用。
suspend fun getVipDiscount(context: Context, timeStamp:Long){
withContext(Dispatchers.IO){
val resultString=async { NetRequest().postVipDetailRequest(context = context, timestamp = timeStamp) }
val result=resultString.await()
try {
LiveLog.log(LiveLog.LOG_LIVE," getVipDiscount result: $result")
val memberDetail=JSONObject.parseObject(result, MemberDetailResult::class.java)
if (memberDetail?.code==0&&memberDetail.data!=null) _vipDetail.postValue(memberDetail)
}catch (e:Throwable){
e.printStackTrace()
}
}
}
本文分享了一种将用户菜单实现为PopupWindow的方法,该菜单在视频活动上方显示。通过设置可获焦和背景,使得能够响应返回键。UI布局、适配器初始化和监听器配置也在文中提及。网络请求使用协程并在ViewModel中处理。
1524

被折叠的 条评论
为什么被折叠?



