RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1586187
Accepted
Sirop4ik
Sirop4ik
Asked:2024-07-05 03:26:45 +0000 UTC2024-07-05 03:26:45 +0000 UTC 2024-07-05 03:26:45 +0000 UTC

返回屏幕时如何将焦点分配到同一位置?

  • 772

我正在开发一个关于 AndroidTV 的项目,该平台的独特之处在于用户使用遥控器将焦点移动到屏幕上。

这是屏幕:

在此输入图像描述

当您专注工作时,有两个主要任务:

  1. 当您从左向右移动时,反之亦然,焦点不应混淆位置。例如,用户单击鼠标右键Left Panel 0 -> Right Panel 0,在右侧面板中我们移动到Right Panel 2左侧单击,焦点移动到,Left Panel 0因为用户将焦点移动到右侧面板。此功能已使用focusRestorer并已在运行。

  2. 当右侧面板中的用户单击(例如:) 时RigthPanel 1,它会打开Second Screen,然后用户单击后退并返回到First Screen,此处预计用于打开屏幕的按钮将成为焦点,即Right Panel 1,但对于某些这是因为它在 30% 的情况下有效,并且焦点不是集中在预期的按钮上,而是另一个按钮上。

视频 -> https://drive.google.com/file/d/1NCal4kxx0op74-Yj5v00wOBcnCRSSlUb/view

以下是您可以复制并运行的代码:

private const val FIRST_SCREEN_ROUTE = "first_screen"
private const val SECOND_SCREEN_ROUTE = "second_screen"
private const val DEFAULT_FOCUS_POSITION = -1

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Test_delete_itTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    shape = RectangleShape
                ) {
                    Greeting()
                }
            }
        }
    }
}

@Composable
fun Greeting() {
    val navigator: NavHostController = rememberNavController()

    NavHost(
        navController = navigator,
        startDestination = FIRST_SCREEN_ROUTE
    ) {
        composable(FIRST_SCREEN_ROUTE) {
            DisposableEffect(Unit) {
                Log.e("HERE", "1 CREATED first_screen_route")

                onDispose {
                    Log.e("HERE", "DISPOSED first_screen_route")
                }
            }

            FirstScreen(onClick = {
                Log.e("HERE", "NAVIGATION TO SECOND SCREEN")
                navigator.navigate(SECOND_SCREEN_ROUTE)
            })
        }

        composable(SECOND_SCREEN_ROUTE) {
            DisposableEffect(Unit) {
                Log.e("HERE", "CREATED second_screen_route")

                onDispose {
                    Log.e("HERE", "DISPOSED second_screen_route")
                }
            }

            SecondScreen()
        }
    }
}

@Composable
fun SecondScreen() {
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.Red.copy(alpha = 0.1f)),
        contentAlignment = Alignment.Center
    ) {
        Text(text = "SECOND SCREEN")
    }
}

@Composable
fun FirstScreen(
    onClick: () -> Unit
) {
    var focusBtnIdx by rememberSaveable { mutableIntStateOf(DEFAULT_FOCUS_POSITION) }

    Row(modifier = Modifier
        .fillMaxSize()
    ) {
        LeftPanel()
        RightPanel(onClick = onClick, focusBtnIdx = focusBtnIdx, setFocusBtnIdx = { focusBtnIdx = it })
    }
}

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun RowScope.LeftPanel() {
    val firstItemFr = remember { FocusRequester() }
    val buttons by rememberSaveable { mutableStateOf(List(5) { "Button ${it + 1}" }) }

    LaunchedEffect(Unit) {
        this.coroutineContext.job.invokeOnCompletion {
            try { firstItemFr.requestFocus() }
            catch (e: Exception) {/* do nothing */ }
        }
    }

    TvLazyColumn(
        modifier = Modifier
            .focusRestorer { firstItemFr }
            .background(Color.Blue.copy(alpha = 0.1f))
            .fillMaxHeight()
            .weight(1f),
        verticalArrangement = Arrangement.spacedBy(8.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        itemsIndexed(
            items = buttons,
            key = { idx, _ -> idx }
        ) { idx, _ ->
            Button(
                modifier = Modifier
                    .let { modifier ->
                        if (idx == 0) {
                            modifier.focusRequester(firstItemFr)
                        } else {
                            modifier
                        }
                    },
                onClick = {}
            ) {
                Text(text = "Left Panel: $idx")
            }
        }
    }
}

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun RowScope.RightPanel(
    onClick: () -> Unit,
    focusBtnIdx: Int,
    setFocusBtnIdx: (Int) -> Unit
) {
    val firstItemFr = remember { FocusRequester() }

    LaunchedEffect(Unit) {
        this.coroutineContext.job.invokeOnCompletion {
            try {
                Log.e("HERE", ">>> REQUEST FOCUS")
                if (focusBtnIdx != DEFAULT_FOCUS_POSITION) {
                    firstItemFr.requestFocus()
                    Log.e("HERE", "<<< REQUEST FOCUS")
                }
            }
            catch (e: Exception) {
                /* do nothing */
                Log.e("HERE", "FOCUS ERROR: $e")
            }
        }
    }

    Column(
        modifier = Modifier
            .background(Color.Green.copy(alpha = 0.1f))
            .fillMaxHeight()
            .weight(1f),
        verticalArrangement = Arrangement.spacedBy(8.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        val buttons: List<String> by rememberSaveable { mutableStateOf(List(4) { "Button ${it + 1}" }) }

        TvLazyVerticalGrid(
            modifier = Modifier
                .focusRestorer { firstItemFr }
                .padding(16.dp),
            columns = TvGridCells.Fixed(2),
            verticalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            itemsIndexed(
                items = buttons,
                key = { idx, _ -> idx }
            ) { idx, _ ->
                Button(
                    modifier = Modifier
                        .padding(8.dp)
                        .let {
                            Log.e("HERE", "1 RightPanel: $idx")
                            if (idx == focusBtnIdx || (focusBtnIdx == DEFAULT_FOCUS_POSITION && idx == 0)) {
                                Log.e("HERE", "2 RightPanel: $idx")
                                it.focusRequester(firstItemFr)
                            } else {
                                it
                            }
                        },
                    onClick = {
                        setFocusBtnIdx(idx)
                        onClick()
                    }
                ) {
                    Text(text = "Right Panel: $idx")
                }
            }
        }
    }
}

日志显示焦点已分配并在所需的按钮上调用,但由于某种原因,屏幕上的焦点位于另一个按钮上。

假设实现本身存在错误focusRequester

我缺少什么?

android
  • 1 1 个回答
  • 20 Views

1 个回答

  • Voted
  1. Best Answer
    Sirop4ik
    2024-07-10T01:36:12Z2024-07-10T01:36:12Z

    最后我找到了这个解决方案 -

    import android.os.Bundle
    import android.util.Log
    import androidx.activity.ComponentActivity
    import androidx.activity.compose.setContent
    import androidx.compose.foundation.background
    import androidx.compose.foundation.layout.Arrangement
    import androidx.compose.foundation.layout.Box
    import androidx.compose.foundation.layout.Column
    import androidx.compose.foundation.layout.Row
    import androidx.compose.foundation.layout.RowScope
    import androidx.compose.foundation.layout.fillMaxHeight
    import androidx.compose.foundation.layout.fillMaxSize
    import androidx.compose.foundation.layout.padding
    import androidx.compose.foundation.lazy.LazyColumn
    import androidx.compose.foundation.lazy.grid.GridCells
    import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
    import androidx.compose.foundation.lazy.grid.itemsIndexed
    import androidx.compose.foundation.lazy.itemsIndexed
    import androidx.compose.runtime.Composable
    import androidx.compose.runtime.DisposableEffect
    import androidx.compose.runtime.getValue
    import androidx.compose.runtime.mutableStateOf
    import androidx.compose.runtime.saveable.rememberSaveable
    import androidx.compose.ui.Alignment
    import androidx.compose.ui.ExperimentalComposeUiApi
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.focus.FocusRequester
    import androidx.compose.ui.focus.focusProperties
    import androidx.compose.ui.focus.focusRequester
    import androidx.compose.ui.graphics.Color
    import androidx.compose.ui.graphics.RectangleShape
    import androidx.compose.ui.input.key.Key
    import androidx.compose.ui.input.key.KeyEventType
    import androidx.compose.ui.input.key.key
    import androidx.compose.ui.input.key.onPreviewKeyEvent
    import androidx.compose.ui.input.key.type
    import androidx.compose.ui.platform.LocalLifecycleOwner
    import androidx.compose.ui.unit.dp
    import androidx.lifecycle.Lifecycle
    import androidx.lifecycle.LifecycleEventObserver
    import androidx.navigation.NavHostController
    import androidx.navigation.compose.NavHost
    import androidx.navigation.compose.composable
    import androidx.navigation.compose.rememberNavController
    import androidx.tv.material3.Button
    import androidx.tv.material3.Surface
    import androidx.tv.material3.Text
    import com.krokosha.test_delete_it.ui.theme.Test_delete_itTheme
    
    private const val FIRST_SCREEN_ROUTE = "first_screen"
    private const val SECOND_SCREEN_ROUTE = "second_screen"
    private val focusRequesterMngFactory = FocusRequesterMng.Factory()
    private val focusRequesterWrapFactory = FocusRequesterWrap.Factory()
    private const val LEFT_PANEL_KEY = "LeftPanel"
    private const val RIGHT_PANEL_KEY = "RightPanel"
    
    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                Test_delete_itTheme {
                    Surface(
                        modifier = Modifier.fillMaxSize(),
                        shape = RectangleShape
                    ) {
                        Greeting()
                    }
                }
            }
        }
    }
    
    @Composable
    fun Greeting() {
        val navigator: NavHostController = rememberNavController()
    
        NavHost(
            navController = navigator,
            startDestination = FIRST_SCREEN_ROUTE
        ) {
            composable(FIRST_SCREEN_ROUTE) {
                FirstScreen(onClick = {
                    Log.e("HERE", "NAVIGATION TO SECOND SCREEN")
                    navigator.navigate(SECOND_SCREEN_ROUTE)
                })
            }
    
            composable(SECOND_SCREEN_ROUTE) {
                SecondScreen()
            }
        }
    }
    
    @Composable
    fun SecondScreen() {
        Box(
            modifier = Modifier
                .fillMaxSize()
                .background(Color.Red.copy(alpha = 0.1f)),
            contentAlignment = Alignment.Center
        ) {
            Text(text = "SECOND SCREEN")
        }
    }
    
    @Composable
    fun FirstScreen(
        onClick: () -> Unit
    ) {
        val focusRequesterWrap: FocusRequesterWrap = focusRequesterWrapFactory.getBy(key = LEFT_PANEL_KEY)
        
        Row(modifier = Modifier
            .fillMaxSize()
        ) {
            LeftPanel(
                onClick = onClick,
                focusRequester = focusRequesterWrap.focusRequester
            )
    
            RightPanel(
                onClick = onClick,
                onBackBtnClicked = { focusRequesterWrap.requestFocus() }
            )
        }
    }
    
    @Composable
    fun RowScope.LeftPanel(
        onClick: () -> Unit,
        focusRequester: FocusRequester
    ) {
        val focusRequesterMng: FocusRequesterMng = focusRequesterMngFactory.getBy(
            key = LEFT_PANEL_KEY,
            parentFocusRequester = focusRequester,
            isInFocusOnInit = true
        )
    
        val buttons: List<String> by rememberSaveable { mutableStateOf(List(5) { "Button ${it + 1}" }) }
        val lifecycleOwner = LocalLifecycleOwner.current
    
        DisposableEffect(Unit) {
            val observer = LifecycleEventObserver { _, event ->
                if (event == Lifecycle.Event.ON_RESUME && focusRequesterMng.isNeedRestore) {
                    focusRequesterMng.onRestoreFocus()
                }
            }
    
            lifecycleOwner.lifecycle.addObserver(observer)
    
            onDispose {
                lifecycleOwner.lifecycle.removeObserver(observer)
            }
        }
    
        LazyColumn(
            modifier = focusRequesterMng.parentModifier
                .background(Color.Blue.copy(alpha = 0.1f))
                .fillMaxHeight()
                .weight(1f),
            verticalArrangement = Arrangement.spacedBy(8.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            itemsIndexed(
                items = buttons,
                key = { idx, _ -> idx }
            ) { idx, _ ->
                Button(
                    modifier = Modifier
                        .let { modifier ->
                            if (idx == 0) {
                                focusRequesterMng.childModifier
                            } else {
                                modifier
                            }
                        },
                    onClick = {
                        focusRequesterMngFactory.onNavigateOutFrom(focusRequesterMng = focusRequesterMng)
                        onClick()
                    }
                ) {
                    Text(text = "Left Panel: $idx")
                }
            }
        }
    }
    
    @Composable
    fun RowScope.RightPanel(
        onClick: () -> Unit,
        onBackBtnClicked: () -> Unit
    ) {
        val focusRequesterMng: FocusRequesterMng = focusRequesterMngFactory.getBy(key = RIGHT_PANEL_KEY)
        val buttons: List<String> by rememberSaveable { mutableStateOf(List(4) { "Button ${it + 1}" }) }
    
        val lifecycleOwner = LocalLifecycleOwner.current
    
        DisposableEffect(Unit) {
            val observer = LifecycleEventObserver { _, event ->
                if (event == Lifecycle.Event.ON_RESUME && focusRequesterMng.isNeedRestore) {
                    focusRequesterMng.onRestoreFocus()
                }
            }
    
            lifecycleOwner.lifecycle.addObserver(observer)
    
            onDispose {
                lifecycleOwner.lifecycle.removeObserver(observer)
            }
        }
    
        Column(
            modifier = focusRequesterMng
                .parentModifier
                .onPreviewKeyEvent {
                    when {
                        KeyEventType.KeyUp == it.type && Key.Back == it.key -> {
                            onBackBtnClicked()
                            true
                        }
    
                        else -> false
                    }
                }
                .background(Color.Green.copy(alpha = 0.1f))
                .fillMaxHeight()
                .weight(1f),
            verticalArrangement = Arrangement.spacedBy(8.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            LazyVerticalGrid(
                modifier = Modifier.padding(16.dp),
                columns = GridCells.Fixed(2),
                verticalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                itemsIndexed(
                    items = buttons,
                    key = { idx, _ -> idx }
                ) { idx, _ ->
                    Button(
                        modifier = Modifier
                            .padding(8.dp)
                            .let { modifier ->
                                if (idx == 0) focusRequesterMng.childModifier
                                else modifier
                            }
                        ,
                        onClick = {
                            focusRequesterMngFactory.onNavigateOutFrom(focusRequesterMng = focusRequesterMng)
                            onClick()
                        }
                    ) {
                        Text(text = "Right Panel: $idx")
                    }
                }
            }
        }
    }
    
    class FocusRequesterMng private constructor(
        val id: String,
        val parentModifier: Modifier,
        val parentFocusRequester: FocusRequester,
        val childModifier: Modifier,
        val childFocusRequester: FocusRequester,
        var isNeedRestore: Boolean
    ) {
        class Factory {
            private val focusRequesterMngMap: MutableMap<String, FocusRequesterMng> = mutableMapOf()
    
            fun getBy(
                key: String,
                parentFocusRequester: FocusRequester = FocusRequester(),
                isInFocusOnInit: Boolean = false
            ): FocusRequesterMng {
                val focusRequesterMng: FocusRequesterMng = focusRequesterMngMap
                    .getOrPut(key) {
                        create(
                            id = key,
                            parentFocusRequester = parentFocusRequester,
                            isInFocusOnInit = isInFocusOnInit
                        )
                    }
    
                if (isInFocusOnInit && focusRequesterMng.isNeedRestore) {
                    focusRequesterMngMap.forEach { (key, mng) -> mng.isNeedRestore = key == focusRequesterMng.id }
                }
    
                return focusRequesterMng
            }
    
            // Whenever we have a navigation event, need to call this before actually navigating.
            fun onNavigateOutFrom(focusRequesterMng: FocusRequesterMng) {
                focusRequesterMngMap.forEach { (key, mng) ->
                    if (key == focusRequesterMng.id) {
                        mng.onNavigateOut()
                    } else {
                        mng.resetNeedsRestore()
                    }
                }
            }
        }
    
        @OptIn(ExperimentalComposeUiApi::class)
        fun onNavigateOut() {
            isNeedRestore = true
            parentFocusRequester.saveFocusedChild()
        }
    
        fun resetNeedsRestore() {
            isNeedRestore = false
        }
    
        fun onRestoreFocus() {
            childFocusRequester.requestFocus()
            resetNeedsRestore()
        }
    
        companion object {
            /**
             * Returns a set of modifiers [FocusRequesterMng] which can be used for restoring focus and
             * specifying the initially focused item.
             */
            @OptIn(ExperimentalComposeUiApi::class)
            fun create(
                id: String = "",
                parentFocusRequester: FocusRequester = FocusRequester(),
                isInFocusOnInit: Boolean = false
            ): FocusRequesterMng {
                val childFocus = FocusRequester()
    
                val parentModifier = Modifier
                    .focusRequester(parentFocusRequester)
                    .focusProperties {
                        exit = {
                            parentFocusRequester.saveFocusedChild()
                            FocusRequester.Default
                        }
                        enter = {
                            if (parentFocusRequester.restoreFocusedChild()) {
                                FocusRequester.Cancel
                            } else {
                                childFocus
                            }
                        }
                    }
    
                val childModifier = Modifier.focusRequester(childFocus)
    
                return FocusRequesterMng(
                    id = id,
                    parentModifier = parentModifier,
                    parentFocusRequester = parentFocusRequester,
                    childModifier = childModifier,
                    childFocusRequester = childFocus,
                    isNeedRestore = isInFocusOnInit
                )
            }
        }
    }
    
    class FocusRequesterWrap private constructor() {
        class Factory {
            private val focusRequesterWrapMap: MutableMap<String, FocusRequesterWrap> = mutableMapOf()
    
            fun getBy(key: String): FocusRequesterWrap {
                return focusRequesterWrapMap.getOrPut(key) { FocusRequesterWrap() }
            }
        }
    
        val focusRequester: FocusRequester by lazy { FocusRequester() }
    
        fun requestFocus() {
            try { focusRequester.requestFocus() }
            catch (e: Exception) { /* do nothing */ }
        }
    }
    

    这样我就能够配置焦点,使其按预期工作

    • 0

相关问题

  • 来自片段的列表落后于 BottomNavigationView

  • 无法将变量从 Activity 传递到 Fragment

  • 构建与完成的片段略有不同的片段的最佳方法是什么?

  • 如何更改来自服务器的响应中的日期格式?

  • 谷歌地图在应用程序的发布版本中不起作用

  • 材料设计按钮。单击按钮上的可选区域!

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    我看不懂措辞

    • 1 个回答
  • Marko Smith

    请求的模块“del”不提供名为“default”的导出

    • 3 个回答
  • Marko Smith

    "!+tab" 在 HTML 的 vs 代码中不起作用

    • 5 个回答
  • Marko Smith

    我正在尝试解决“猜词”的问题。Python

    • 2 个回答
  • Marko Smith

    可以使用哪些命令将当前指针移动到指定的提交而不更改工作目录中的文件?

    • 1 个回答
  • Marko Smith

    Python解析野莓

    • 1 个回答
  • Marko Smith

    问题:“警告:检查最新版本的 pip 时出错。”

    • 2 个回答
  • Marko Smith

    帮助编写一个用值填充变量的循环。解决这个问题

    • 2 个回答
  • Marko Smith

    尽管依赖数组为空,但在渲染上调用了 2 次 useEffect

    • 2 个回答
  • Marko Smith

    数据不通过 Telegram.WebApp.sendData 发送

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5