Jetpack 可与 MediaRouteActionProvider 组合
Jetpack Composable with MediaRouteActionProvider
我目前正在将 Chromcast 集成到我们的应用程序中。我们正在为所有 UI 元素使用 Jetpack Compose。我们正在使用 TopAppBar 可组合项,我正在尝试使用 MediaRouteActionProvider 向其添加 Chromecast 按钮。我能找到使用 MediaRouteActionProvider 的唯一方法是使用 menu.xml 并在 onCreateOptionsMenu 中扩充菜单。
有谁知道在 Jetpack Compose 的菜单上下文之外使用 ActionProvider 的方法,还是我现在坚持使用菜单?
更新:决定再次破解此问题。
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.mediarouter.app.MediaRouteDialogFactory
import androidx.mediarouter.media.MediaControlIntent
import androidx.mediarouter.media.MediaRouteSelector
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastState
import com.google.android.gms.cast.framework.CastStateListener
class MediaRouteViewModel(context: Context) : ViewModel(), CastStateListener {
var isCastingAvailable = false
private set
val castingStateLiveData = MutableLiveData(CastingState.UNKNOWN)
val mDialogFactory = MediaRouteDialogFactory.getDefault()
val mSelector: MediaRouteSelector = MediaRouteSelector.Builder()
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
.build()
init {
try {
isCastingAvailable = true
val castingContext = CastContext.getSharedInstance(context)
castingContext.addCastStateListener(this)
} catch (e: Exception) {
// handle me please
}
}
fun shouldShowChooserFragment(): Boolean {
return when (castingStateLiveData.value) {
CastingState.NOT_CONNECTED -> true
CastingState.UNKNOWN -> true
CastingState.NO_DEVICES_AVAILABLE -> true
CastingState.CONNECTING -> false
CastingState.CONNECTED -> false
else -> false
}
}
enum class CastingState {
CONNECTED,
CONNECTING,
NOT_CONNECTED,
NO_DEVICES_AVAILABLE,
UNKNOWN
}
class Factory(val context: Context) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MediaRouteViewModel(context = context) as T
}
}
override fun onCastStateChanged(p0: Int) {
val castingState = when (p0) {
CastState.CONNECTED -> CastingState.CONNECTED
CastState.CONNECTING -> CastingState.CONNECTING
CastState.NOT_CONNECTED -> CastingState.NOT_CONNECTED
CastState.NO_DEVICES_AVAILABLE -> CastingState.NO_DEVICES_AVAILABLE
else -> CastingState.UNKNOWN
}
castingStateLiveData.postValue(castingState)
}
}
@Composable
fun MediaRouter(
modifier: Modifier,
iconWidth: Dp,
fragmentManager: FragmentManager
) {
val context = LocalContext.current
val mediaRouteProviderViewModel: MediaRouteViewModel =
viewModel(factory = MediaRouteViewModel.Factory(context))
// Can use the casting state to change the painter accordingly
val castingState = mediaRouteProviderViewModel.castingStateLiveData.observeAsState()
if (mediaRouteProviderViewModel.isCastingAvailable) {
Box(modifier = modifier.size(iconWidth)) {
Image(
painter = painterResource(id = R.drawable.ic_baseline_cast_connected_24),
contentDescription = null,
modifier = Modifier
.clickable {
val shouldShowChooserFragment = mediaRouteProviderViewModel.shouldShowChooserFragment()
val fragmentTag = if (shouldShowChooserFragment) "MediaRouteChooserDialogFragment" else "MediaRouteControllerDialogFragment"
val fragment = if (shouldShowChooserFragment) {
mediaRouteProviderViewModel.mDialogFactory
.onCreateChooserDialogFragment()
.apply {
routeSelector = mediaRouteProviderViewModel.mSelector
}
} else {
mediaRouteProviderViewModel.mDialogFactory.onCreateControllerDialogFragment()
}
val transaction = fragmentManager.beginTransaction()
transaction.add(fragment, fragmentTag)
transaction.commitAllowingStateLoss()
},
contentScale = ContentScale.FillBounds
)
}
}
}
旧 Post:
我确信在 compose 中有更好的方法来执行此操作,但这至少可以完成工作。
class MediaRouteViewModel(context: Context) : ViewModel() {
private val mediaRouteActionProvider = MediaRouteActionProvider(context)
val buttonView: View
init {
buttonView = mediaRouteActionProvider.onCreateActionView()
mediaRouteActionProvider.routeSelector = MediaRouteSelector.Builder()
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
.build()
}
fun onClick() {
mediaRouteActionProvider.onPerformDefaultAction()
}
}
@Composable
fun MediaRouter(modifier: Modifier, iconWidth: Dp) {
val context = LocalContext.current
val mediaRouteProviderViewModel = MediaRouteViewModel(context)
Box(modifier = modifier.size(iconWidth)) {
AndroidView(factory = {
mediaRouteProviderViewModel.buttonView
}, modifier = Modifier.fillMaxSize())
Box(modifier = Modifier
.fillMaxSize()
.clickable { mediaRouteProviderViewModel.onClick() })
}
}
我目前正在将 Chromcast 集成到我们的应用程序中。我们正在为所有 UI 元素使用 Jetpack Compose。我们正在使用 TopAppBar 可组合项,我正在尝试使用 MediaRouteActionProvider 向其添加 Chromecast 按钮。我能找到使用 MediaRouteActionProvider 的唯一方法是使用 menu.xml 并在 onCreateOptionsMenu 中扩充菜单。
有谁知道在 Jetpack Compose 的菜单上下文之外使用 ActionProvider 的方法,还是我现在坚持使用菜单?
更新:决定再次破解此问题。
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.mediarouter.app.MediaRouteDialogFactory
import androidx.mediarouter.media.MediaControlIntent
import androidx.mediarouter.media.MediaRouteSelector
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastState
import com.google.android.gms.cast.framework.CastStateListener
class MediaRouteViewModel(context: Context) : ViewModel(), CastStateListener {
var isCastingAvailable = false
private set
val castingStateLiveData = MutableLiveData(CastingState.UNKNOWN)
val mDialogFactory = MediaRouteDialogFactory.getDefault()
val mSelector: MediaRouteSelector = MediaRouteSelector.Builder()
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
.build()
init {
try {
isCastingAvailable = true
val castingContext = CastContext.getSharedInstance(context)
castingContext.addCastStateListener(this)
} catch (e: Exception) {
// handle me please
}
}
fun shouldShowChooserFragment(): Boolean {
return when (castingStateLiveData.value) {
CastingState.NOT_CONNECTED -> true
CastingState.UNKNOWN -> true
CastingState.NO_DEVICES_AVAILABLE -> true
CastingState.CONNECTING -> false
CastingState.CONNECTED -> false
else -> false
}
}
enum class CastingState {
CONNECTED,
CONNECTING,
NOT_CONNECTED,
NO_DEVICES_AVAILABLE,
UNKNOWN
}
class Factory(val context: Context) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MediaRouteViewModel(context = context) as T
}
}
override fun onCastStateChanged(p0: Int) {
val castingState = when (p0) {
CastState.CONNECTED -> CastingState.CONNECTED
CastState.CONNECTING -> CastingState.CONNECTING
CastState.NOT_CONNECTED -> CastingState.NOT_CONNECTED
CastState.NO_DEVICES_AVAILABLE -> CastingState.NO_DEVICES_AVAILABLE
else -> CastingState.UNKNOWN
}
castingStateLiveData.postValue(castingState)
}
}
@Composable
fun MediaRouter(
modifier: Modifier,
iconWidth: Dp,
fragmentManager: FragmentManager
) {
val context = LocalContext.current
val mediaRouteProviderViewModel: MediaRouteViewModel =
viewModel(factory = MediaRouteViewModel.Factory(context))
// Can use the casting state to change the painter accordingly
val castingState = mediaRouteProviderViewModel.castingStateLiveData.observeAsState()
if (mediaRouteProviderViewModel.isCastingAvailable) {
Box(modifier = modifier.size(iconWidth)) {
Image(
painter = painterResource(id = R.drawable.ic_baseline_cast_connected_24),
contentDescription = null,
modifier = Modifier
.clickable {
val shouldShowChooserFragment = mediaRouteProviderViewModel.shouldShowChooserFragment()
val fragmentTag = if (shouldShowChooserFragment) "MediaRouteChooserDialogFragment" else "MediaRouteControllerDialogFragment"
val fragment = if (shouldShowChooserFragment) {
mediaRouteProviderViewModel.mDialogFactory
.onCreateChooserDialogFragment()
.apply {
routeSelector = mediaRouteProviderViewModel.mSelector
}
} else {
mediaRouteProviderViewModel.mDialogFactory.onCreateControllerDialogFragment()
}
val transaction = fragmentManager.beginTransaction()
transaction.add(fragment, fragmentTag)
transaction.commitAllowingStateLoss()
},
contentScale = ContentScale.FillBounds
)
}
}
}
旧 Post:
我确信在 compose 中有更好的方法来执行此操作,但这至少可以完成工作。
class MediaRouteViewModel(context: Context) : ViewModel() {
private val mediaRouteActionProvider = MediaRouteActionProvider(context)
val buttonView: View
init {
buttonView = mediaRouteActionProvider.onCreateActionView()
mediaRouteActionProvider.routeSelector = MediaRouteSelector.Builder()
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
.build()
}
fun onClick() {
mediaRouteActionProvider.onPerformDefaultAction()
}
}
@Composable
fun MediaRouter(modifier: Modifier, iconWidth: Dp) {
val context = LocalContext.current
val mediaRouteProviderViewModel = MediaRouteViewModel(context)
Box(modifier = modifier.size(iconWidth)) {
AndroidView(factory = {
mediaRouteProviderViewModel.buttonView
}, modifier = Modifier.fillMaxSize())
Box(modifier = Modifier
.fillMaxSize()
.clickable { mediaRouteProviderViewModel.onClick() })
}
}