前言

   焦点一般在TV设备,投影仪设备开发中使用很多。因为这些设备不带触控与键鼠输入,而是使用遥控器。本博客讲解与记录焦点开发的一些功能与细节。 

在xml里关于焦点的属性

<!-- 控制视图是否可以获取焦点。默认情况下,这是“自动”,它让框架确定用户是否可以将焦点移动到视图。通过将此属性设置为 true,视图可以获取焦点。通过将其设置为“false”,视图将不会获得焦点。
    此值不会影响直接调用 {@link android.view.ViewrequestFocus} 的行为,无论此视图如何,它都会始终请求焦点。
    它只影响焦点导航尝试移动焦点的位置。 -->
    <attr name="focusable" format="boolean|enum">

    <!-- 控制视图是否可以在触摸模式下获得焦点的布尔值。如果对于一个视图是这样,则该视图可以在单击时获得焦点,并且如果单击另一个未将此属性设置为 true 的视图,则可以保持焦点。
    这个开启后是有一个副作用的,点击一个View需要点击二次,第一次点击变成了获取焦点,第二次点击才会执行onClickListener-->
    <attr name="focusableInTouchMode" format="boolean" />

    <!-- 此视图是否为默认焦点视图。每个键盘导航集群只有一个视图可以将此属性设置为 true。
    请参阅 {@link android.view.ViewsetFocusedByDefault(boolean)}。 -->
    <attr name="focusedByDefault" format="boolean" />

    <!--此视图在获得焦点但未在其背景中定义 {@link android.R.attrstate_focused} 时是否应使用默认焦点突出显示。 -->
    <attr name="defaultFocusHighlightEnabled" format="boolean" />

    <!-- 屏幕阅读器辅助工具是否应将此视图视为可聚焦单元。
    请参阅 {@link android.view.ViewsetScreenReaderFocusable(boolean)}。
    默认值 {@code false} 让屏幕阅读器考虑其他信号,例如可聚焦性或文本的存在,以决定它关注的内容。-->
    <attr name="screenReaderFocusable" format="boolean" />

下面的焦点指定通常并不需要设置,因为系统会自动检索焦点目标,除非你有特殊的需求

android:nextFocusForward="@+id/code_et" 下一个焦点目标

android:nextFocusUp="@id/book"  定义指定方向的焦点

android:nextFocusDown="@id/book" 定义指定方向的焦点

android:nextFocusLeft="@id/book" 定义指定方向的焦点

android:nextFocusRight="@id/book" 定义指定方向的焦点

android:nextClusterForward="@id/book" 定义下一个键盘导航集群

android:descendantFocusability属性

android:descendantFocusability是View的一个属性。可以理解是viewGroup和其子控件焦点相关的属性。

  • beforeDescendants :viewGroup会优先其子类控件而获取到焦点
  • afterDescendants :viewGroup只有当其子类控件不需要获取焦点时才获取焦点
  • blocksDescendants :viewGroup会覆盖子类控件而直接获得焦点

设置默认焦点

mBinding.piano.requestFocus()

监听某个view的焦点变化

获得焦点或者失去焦点

mBinding.piano.setOnFocusChangeListener { view, b ->
            Log.e("zh", "piano $b ")
        }

监听指定View的焦点按下的键值

mBinding.brightnessSeekBar.setOnKeyListener(View.OnKeyListener { view, i, keyEvent ->
            if (i == KeyEvent.KEYCODE_ENTER) {
                if (mUnfoldBrightnessLayout) {
                    mUnfoldBrightnessLayout = false
                    mBinding.brightnessLayout.isFocusable = true
                    mBinding.brightnessLayout.isSelected = false
                    mBinding.unfoldBrightnessGroup.visibility = View.GONE
                    mBinding.brightnessLayout.requestFocus()
                    animationUp(mBinding.brightnessIcon2)
                }
                true
            }
            false
        })

 

监听全局焦点

private val focusChangeListener: ViewTreeObserver.OnGlobalFocusChangeListener = object : ViewTreeObserver.OnGlobalFocusChangeListener {
        override fun onGlobalFocusChanged(oldFocus: View?, newFocus: View?) {
            Log.e("zh", "oldFocus ==" + oldFocus + " id:" + oldFocus?.id)
            Log.e("zh", "newFocus ==" + newFocus + " id:" + newFocus?.id)
            if (newFocus != null) {
                if (mBinding.viewPage.currentItem == 1 && (newFocus.id == R.id.hut
                            || newFocus.id == R.id.tv || newFocus.id == R.id.song || newFocus.id == R.id.story)
                ) {
                    mBinding.viewPage.currentItem = 0
                    return
                }
                if (mBinding.viewPage.currentItem == 1 && (newFocus.id == R.id.draw
                            || newFocus.id == R.id.book || newFocus.id == R.id.piano || newFocus.id == R.id.chess)
                ) {
                    mBinding.viewPage.currentItem = 2
                    return
                }
                if ((mBinding.viewPage.currentItem == 0 || mBinding.viewPage.currentItem == 2) && (newFocus.id == R.id.news
                            || newFocus.id == R.id.lwlx || newFocus.id == R.id.time || newFocus.id == R.id.chest
                            || newFocus.id == R.id.film || newFocus.id == R.id.applicationMarket || newFocus.id == R.id.weather
                            || newFocus.id == R.id.music)
                ) {
                    mBinding.viewPage.currentItem = 1
                    return
                }
            }
        }
    }

注册

override fun onCreate(savedInstanceState: Bundle?) {
        //略。。
        mBinding.root.getViewTreeObserver().addOnGlobalFocusChangeListener(focusChangeListener)
    }

    override fun onDestroy() {
        super.onDestroy()
        mBinding.root.viewTreeObserver.removeOnGlobalFocusChangeListener(focusChangeListener)
    }

子View焦点状态跟随父布局

android:duplicateParentState="true"

TV开发中常用函数

/**
 * 设置焦点放大
 */
fun View.setFocusEnlarge(float: Float) {
    this.setOnFocusChangeListener { view, b ->
        if (b) {
            this.scaleX = float
            this.scaleY = float
        } else {
            this.scaleX = 1.0f
            this.scaleY = 1.0f
        }
    }
}


/**
 * 鼠标选中放大
 */
fun View.setHoverEnlarge(float: Float){
    this.setOnHoverListener { v, event ->
        when(event.action){
            MotionEvent.ACTION_HOVER_ENTER->{
                this.scaleX = float
                this.scaleY = float
            }
            MotionEvent.ACTION_HOVER_EXIT->{
                this.scaleX = 1.0f
                this.scaleY = 1.0f
            }
            MotionEvent.ACTION_HOVER_MOVE->{}
        }
        false
    }
}