bytedance/scene

Compose兼容

Iridescentangle opened this issue · 8 comments

有考虑兼容Compose嘛(╹▽╹)尝试在Scene中嵌入ComposeView报错

java.lang.IllegalStateException: ViewTreeLifecycleOwner not found from com.bytedance.scene.view.NavigationFrameLayout{d382d6a V.E...... ......ID 0,0-1080,2026 #7f08035d app:id/navigation_scene_content}
        at androidx.compose.ui.platform.WindowRecomposer_androidKt.createLifecycleAwareWindowRecomposer(WindowRecomposer.android.kt:275)
        at androidx.compose.ui.platform.WindowRecomposer_androidKt.createLifecycleAwareWindowRecomposer$default(WindowRecomposer.android.kt:259)
        at androidx.compose.ui.platform.WindowRecomposerFactory$Companion$LifecycleAware$1.createRecomposer(WindowRecomposer.android.kt:103)
        at androidx.compose.ui.platform.WindowRecomposerPolicy.createAndInstallWindowRecomposer$ui_release(WindowRecomposer.android.kt:159)
        at androidx.compose.ui.platform.WindowRecomposer_androidKt.getWindowRecomposer(WindowRecomposer.android.kt:234)
        at androidx.compose.ui.platform.AbstractComposeView.resolveParentCompositionContext(ComposeView.android.kt:244)
        at androidx.compose.ui.platform.AbstractComposeView.ensureCompositionCreated(ComposeView.android.kt:251)
        at androidx.compose.ui.platform.AbstractComposeView.onAttachedToWindow(ComposeView.android.kt:283)
        at android.view.View.dispatchAttachedToWindow(View.java:20123)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3430)
        at android.view.ViewGroup.addViewInner(ViewGroup.java:5193)
        at android.view.ViewGroup.addView(ViewGroup.java:4979)
        at android.view.ViewGroup.addView(ViewGroup.java:4919)
        at android.view.ViewGroup.addView(ViewGroup.java:4892)
        at com.bytedance.scene.navigation.NavigationSceneManager.moveState(NavigationSceneManager.java:413)
        at com.bytedance.scene.navigation.NavigationSceneManager.access$1800(NavigationSceneManager.java:92)
        at com.bytedance.scene.navigation.NavigationSceneManager$PushOptionOperation.execute(NavigationSceneManager.java:900)
        at com.bytedance.scene.navigation.NavigationSceneManager.executePendingOperation(NavigationSceneManager.java:301)
        at com.bytedance.scene.navigation.NavigationScene.dispatchActivityCreated(NavigationScene.java:584)
        at com.bytedance.scene.group.GroupSceneManager.moveState(GroupSceneManager.java:969)
        at com.bytedance.scene.group.GroupSceneManager.moveState(GroupSceneManager.java:964)
        at com.bytedance.scene.group.GroupSceneManager.access$1000(GroupSceneManager.java:208)
        at com.bytedance.scene.group.GroupSceneManager$MoveStateOperation.execute(GroupSceneManager.java:641)
        at com.bytedance.scene.group.GroupSceneManager.executeOperation(GroupSceneManager.java:241)
        at com.bytedance.scene.group.GroupSceneManager.add(GroupSceneManager.java:395)
        at com.bytedance.scene.group.GroupScene.add(GroupScene.java:197)
        at com.bytedance.scene.group.GroupScene.add(GroupScene.java:142)
qii commented

疑,你是怎么使用的,Compose 这个东西不是理论上可以内嵌到任何的 ViewGroup 里面吗?

https://developer.android.google.cn/jetpack/compose/interop/adding#reuse-view

假设我们要在应用中将用户问候文本迁移到 Jetpack Compose。我们可以在 XML 布局中添加以下内容:
为了将其迁移到 Compose,我们可以将 View 替换为保留了相同布局参数和 id 的 ComposeView
然后,在使用了该 XML 布局的 Activity 或 Fragment 中,我们可以获取 ComposeView,并调用 setContent 方法,以向其中添加 Compose 内容:

我是按照这种方式来做的

class ComposeScene : Scene() {
    private lateinit var composeView : ComposeView
    override fun onCreateView(p0: LayoutInflater, p1: ViewGroup, p2: Bundle?): View {
        composeView = ComposeView(requireSceneContext())
        return composeView
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        composeView.setContent {
            CardTheme {
                ProvideWindowInsets {
                    val systemUiController = rememberSystemUiController()
                    val darkIcons = MaterialTheme.colors.isLight
                    SideEffect {
                        systemUiController.setSystemBarsColor(Color.Transparent, darkIcons = darkIcons)
                    }
                    val navController = rememberNavController()
//                    val navigationActions = remember(navController){
//
//                    }
                    Greeting()
                }
            }
        }
    }
    @Composable
    private fun Greeting() {
       Text(
           text = "我是测试文字",
           modifier = Modifier.padding(horizontal = 10.dp, vertical = 5.dp)
       )
    }
}
qii commented

在你的首页的 Activity 的 onCreate 里面补充这段代码

        ViewTreeLifecycleOwner.set(window.decorView, this)
        ViewTreeViewModelStoreOwner.set(window.decorView, this)
        ViewTreeSavedStateRegistryOwner.set(window.decorView, this)
qii commented

头疼啊,公司内部的 androidx 版本普通很旧,所以这个框架也不好直接升级 androidx 的依赖

qii commented

还有一种做法,我感觉这种更合适

import android.view.View
import androidx.compose.runtime.MonotonicFrameClock
import androidx.compose.runtime.PausableMonotonicFrameClock
import androidx.compose.runtime.Recomposer
import androidx.compose.ui.platform.AndroidUiDispatcher
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import com.bytedance.scene.Scene
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.launch
import kotlin.coroutines.EmptyCoroutineContext

  fun Scene.createLifecycleAwareViewTreeRecomposer(): Recomposer {
    val currentThreadContext = AndroidUiDispatcher.CurrentThread
    val pausableClock = currentThreadContext[MonotonicFrameClock]?.let {
        PausableMonotonicFrameClock(it).apply { pause() }
    }
    val contextWithClock = currentThreadContext + (pausableClock ?: EmptyCoroutineContext)
    val recomposer = Recomposer(contextWithClock)
    val runRecomposeScope = CoroutineScope(contextWithClock)
    val viewTreeLifecycleOwner = this
    // Removing the view holding the ViewTreeRecomposer means we may never be reattached again.
    // Since this factory function is used to create a new recomposer for each invocation and
    // doesn't reuse a single instance like other factories might, shut it down whenever it
    // becomes detached. This can easily happen as part of setting a new content view.
    this.view.addOnAttachStateChangeListener(
        object : View.OnAttachStateChangeListener {
            override fun onViewAttachedToWindow(v: View?) {}
            override fun onViewDetachedFromWindow(v: View?) {
                this@createLifecycleAwareViewTreeRecomposer.view.removeOnAttachStateChangeListener(this)
                recomposer.cancel()
            }
        }
    )
    viewTreeLifecycleOwner.lifecycle.addObserver(
        object : LifecycleEventObserver {
            override fun onStateChanged(lifecycleOwner: LifecycleOwner, event: Lifecycle.Event) {
                val self = this
                when (event) {
                    Lifecycle.Event.ON_CREATE ->
                        // Undispatched launch since we've configured this scope
                        // to be on the UI thread
                        runRecomposeScope.launch(start = CoroutineStart.UNDISPATCHED) {
                            try {
                                recomposer.runRecomposeAndApplyChanges()
                            } finally {
                                // If runRecomposeAndApplyChanges returns or this coroutine is
                                // cancelled it means we no longer care about this lifecycle.
                                // Clean up the dangling references tied to this observer.
                                lifecycleOwner.lifecycle.removeObserver(self)
                            }
                        }
                    Lifecycle.Event.ON_START -> pausableClock?.resume()
                    Lifecycle.Event.ON_STOP -> pausableClock?.pause()
                    Lifecycle.Event.ON_DESTROY -> {
                        recomposer.cancel()
                    }
                    Lifecycle.Event.ON_PAUSE -> {
                        // Nothing
                    }
                    Lifecycle.Event.ON_RESUME -> {
                        // Nothing
                    }
                    Lifecycle.Event.ON_ANY -> {
                        // Nothing
                    }
                }
            }
        }
    )
    return recomposer
}

然后你的 Scene,补充 composeView.setParentCompositionContext(createLifecycleAwareViewTreeRecomposer())

class ComposeScene : Scene() {
     private lateinit var composeView : ComposeView
    override fun onCreateView(p0: LayoutInflater, p1: ViewGroup, p2: Bundle?): View {
        composeView = ComposeView(requireSceneContext())
        return composeView
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        ViewTreeLifecycleOwner.set(this.view, this)
        ViewTreeViewModelStoreOwner.set(this.view, this)
        ViewTreeSavedStateRegistryOwner.set(this.view, fragmentActivity())

        composeView.setParentCompositionContext(createLifecycleAwareViewTreeRecomposer())
        composeView.setContent {
            CardTheme {
                ProvideWindowInsets {
                    val systemUiController = rememberSystemUiController()
                    val darkIcons = MaterialTheme.colors.isLight
                    SideEffect {
                        systemUiController.setSystemBarsColor(Color.Transparent, darkIcons = darkIcons)
                    }
                    val navController = rememberNavController()
//                    val navigationActions = remember(navController){
//
//                    }
                    Greeting()
                }
            }
        }
    }
    @Composable
    private fun Greeting() {
       Text(
           text = "我是测试文字",
           modifier = Modifier.padding(horizontal = 10.dp, vertical = 5.dp)
       )
    }
}

好的...我试一哈,多谢大佬指点ღ( ´・ᴗ・` )

@qii 是可以的,多谢!!