Android View 的焦点控制基础

时间:2024-11-09 08:28:31

搜索了一些控制规则记录一下

    • 默认的焦点控制规则
    • 寻焦规则、调用流程(链接)
    • Android中的焦点控制方法
      • 一、与焦点相关的属性(API 26及以上版本可用)
      • 二、在代码中控制焦点的方法
      • 三、设置焦点移动方向(已知控件ID的情况下)
      • 四、请求焦点的方法
      • 五、View和Activity获取焦点的回调
      • 六、如何设置View支持聚焦和不支持聚焦,是否所有View都支持焦点?可以做到只获取点击事件不要焦点么?
  • 不太懂焦点控制,记录一下,不见的好用
      • 在支持遥控器和触摸的 RecyclerView 嵌套场景下内层 item 获取焦点问题分析(豆包分析)
  • 参考地址

默认的焦点控制规则

  1. 布局顺序规则
    • LinearLayout
      • 在垂直方向的LinearLayout中,焦点顺序是按照子View在布局文件中的添加顺序从上到下排列的。例如,以下布局中焦点会从button1开始,按顺序转移到button2button3
      <?xml version="1.0" encoding="utf-8"?>
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:orientation="vertical">
          <Button
              android:id="@+id/button1"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="Button 1"/>
          <Button
              android:id="@+id/button2"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="Button 2"/>
          <Button
              android:id="@+id/button3"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="Button 3"/>
      </LinearLayout>
      
      • 在水平方向的LinearLayout中,焦点顺序是从左到右,同样是基于子View的添加顺序。
    • RelativeLayout
      • 焦点顺序相对复杂。它会考虑View之间的相对位置和添加顺序等因素。一般先从添加到布局中的较早的可聚焦View开始。如果有明确的相对位置设置,如android:layout_belowandroid:layout_toRightOf等属性,会影响焦点的转移顺序。例如:
      <?xml version="1.0" encoding="utf-8"?>
      <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="match_parent">
          <Button
              android:id="@+id/button4"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="Button 4"/>
          <Button
              android:id="@+id/button5"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="Button 5"
              android:layout_below="@+id/button4"/>
      </RelativeLayout>
      
      • 在这里,焦点可能先在button4上,当按“下”方向键时,由于button5button4的下方,焦点会转移到button5
    • FrameLayout和其他布局
      • FrameLayout通常会将焦点给予第一个可聚焦的子View,不过其主要用于堆叠View,焦点规则相对简单。其他布局(如GridLayout等)也有各自基于布局结构和添加顺序的焦点规则。在GridLayout中,焦点通常按照从左到右、从上到下的顺序在单元格中的可聚焦View之间转移。
  2. View类型规则
    • 可聚焦和不可聚焦View
      • 像Button、EditText等默认是可聚焦的,而一些用于装饰或者不具有交互功能的View(如普通的TextView,在没有特殊设置下)通常是不可聚焦的。可以通过android:focusable属性来设置一个View是否可聚焦。例如,将一个TextView设置为可聚焦:
      <TextView
          android:id="@+id/textView"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="This is a text view"
          android:focusable="true"/>
      
    • 输入框(EditText)特殊规则
      • 当EditText获得焦点时,系统会自动弹出软键盘(如果设备有软键盘且配置为自动弹出)。可以通过在AndroidManifest.xml文件中的对应的Activity标签中设置android:windowSoftInputMode属性来控制软键盘的行为。例如,设置为stateHidden可以让EditText获得焦点时不弹出软键盘:
      <activity
          android:name=".MainActivity"
          android:windowSoftInputMode="stateHidden">
      </activity>
      
      • EditText在获得焦点后,会有光标闪烁等视觉提示,表示可以进行输入操作。并且在软键盘弹出后,可以通过软键盘上的按键进行文本输入等操作。
    • 按钮(Button)特殊规则
      • 按钮在获得焦点时,会有一个淡淡的焦点边框(具体样式可以通过主题等进行修改)。当用户使用导航键(如方向键)移动焦点时,焦点会按照既定顺序在按钮之间切换,按钮会呈现出被选中的视觉效果,并且可以通过点击确定键(如回车键)来触发按钮的点击事件。
  3. 焦点转移规则
    • 通过导航键转移
      • 通常使用导航键(如上下左右方向键)来转移焦点。在触摸屏设备上,如果开启了辅助功能中的方向键导航功能,也可以通过屏幕上虚拟的方向键来控制焦点转移。当用户按下导航键时,焦点会按照布局中定义的顺序或者相对位置关系移动到下一个合适的可聚焦View。如果到达布局的最后一个可聚焦View,再次按下“下”方向键,焦点可能会返回到布局中的第一个可聚焦View(具体行为也可能因设备和系统设置略有不同)。
    • 代码控制焦点转移
      • 可以在代码中通过requestFocus()方法来让一个View获取焦点。例如,在一个Activity中:
      import android.os.Bundle;
      import android.view.View;
      import android.widget.Button;
      import androidx.appcompat.app.AppCompatActivity;
      public class MainActivity extends AppCompatActivity {
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
              Button button = findViewById(R.id.button);
              button.requestFocus();
          }
      }
      
      • 这里通过findViewById找到按钮,然后使用requestFocus方法让按钮获取焦点。同时,也可以使用clearFocus()方法来清除一个View的焦点。例如,在某个事件处理方法中:
      public void clearButtonFocus(View view) {
          Button button = findViewById(R.id.button);
          button.clearFocus();
      }
      
    • 触摸事件与焦点关系
      • 在触摸屏设备上,当用户点击一个可聚焦的View时,焦点通常会转移到被点击的View。不过,这种行为也可以通过设置android:clickableandroid:focusable属性来调整。如果一个View设置为clickable = truefocusable = false,那么用户点击它时不会获得焦点,但点击事件仍然会触发。例如:
      <Button
          android:id="@+id/button6"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="Button 6"
          android:clickable="true"
          android:focusable="false"/>
      

寻焦规则、调用流程(链接)

https://blog.51cto.com/u_16099225/11818445

在 Android 中,是有一套自己的焦点查找的算法,简单来说,就近原则,就是按方向就近查找下一个符合条件的 View。
寻址调用流程:

View.focusSearch
ViewRootImpl.focusSearch
FocusFinder.findNextFocus
用户是否指定寻址规则
FocusFinder.findNextUserSpecifiedFocus
FocusFinder.findNextFocus
View.findUserSetNextFocus
FocusFinder.findNextFocusInAbsoluteDirection

不推荐重写 focusSearch() 方法,只使用属性控制也能满足我们的需求。

Android中的焦点控制方法

https://blog.****.net/2401_84537540/article/details/138227635

一、与焦点相关的属性(API 26及以上版本可用)

  1. android:focusedByDefault="true"

    • 功能
      • 此属性用于指定某个视图(View)在界面加载时自动获取焦点。例如,在一个包含多个按钮的布局中,如果你希望某个特定按钮在界面一显示出来就获得焦点,就可以在该按钮的XML布局定义中添加这个属性。
    • 版本兼容性
      • 在Android版本低于26的系统中,使用此属性会导致应用在运行时出现错误。如果你的应用需要兼容低版本系统,就不能使用这个属性。在这种情况下,可以考虑在代码中使用requestFocus()方法来实现类似的功能。例如,在ActivityFragmentonCreate方法中:
      public class MainActivity extends AppCompatActivity {
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
              Button button = findViewById(R.id.button);
              button.requestFocus();
          }
      }
      
  2. android:defaultFocusHighlightEnabled="true"

    • 功能
      • 当这个属性设置为true时,原生的Android控件在获取焦点时会显示高亮效果。这种高亮效果可以让用户直观地看到当前哪个控件具有焦点,从而提升用户体验。例如,按钮在获得焦点时可能会显示一个边框或者改变背景颜色等高亮效果。
    • 版本兼容性
      • android:focusedByDefault="true"类似,此属性也仅在API 26及以上版本可用。如果在低版本系统中使用,会引发错误。当此属性设置为false时,相应的控件在获得焦点时将不会显示高亮效果,这在某些特定的界面设计需求下可能会用到,例如为了实现自定义的焦点视觉效果而禁用原生的高亮显示。

二、在代码中控制焦点的方法

  1. setFocusable(boolean focusable)
    • 功能
      • 使用btnTest.setFocusable(true);这种方式可以在代码中动态地设置一个控件是否能够获得焦点。当focusable参数为true时,控件具备获得焦点的能力;当设置为false时,控件将不能获得焦点。例如,在某些情况下,你可能希望根据用户的操作或者应用的状态来决定某个控件是否可以被聚焦。假设btnTest是一个按钮,在用户未完成某个前置操作时,你可以将按钮设置为不可聚焦:
      public class MainActivity extends AppCompatActivity {
          private Button btnTest;
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
              btnTest = findViewById(R.id.btnTest);
              // 假设用户未登录时按钮不可聚焦
              if (!isUserLoggedIn()) {
                  btnTest.setFocusable(false);
              }
          }
          private boolean isUserLoggedIn() {
              // 这里返回用户是否登录的真实状态
              return false;
          }
      }
      
  2. setFocusableInTouchMode(boolean focusableInTouchMode)
    • 功能
      • 此方法用于设置在触摸模式下控件是否能够获得焦点。在一些设备上,特别是那些支持多种交互方式(如遥控器操作和触摸操作)的设备(例如智能电视)上非常有用。例如,当focusableInTouchMode设置为true时,即使在触摸操作模式下,控件也能够获得焦点,这样用户无论是通过遥控器的方向键还是直接触摸屏幕都可以使控件获得焦点。假设你正在开发一个适用于智能电视的应用,并且有一个列表视图(ListView),你希望用户在触摸屏幕时列表项也能获得焦点:
      public class MainActivity extends AppCompatActivity {
          private ListView listView;
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
              listView = findViewById(R.id.listView);
              listView.setFocusableInTouchMode(true);
          }
      }
      
  3. setFocusedByDefault(boolean focusedByDefault)(API 26及以上版本可用)
    • 功能
      • android:focusedByDefault="true"属性类似,此方法用于在代码中设置控件是否在界面加载时默认获得焦点。同样,在低版本系统(低于API 26)中使用此方法会出现错误。如果需要在低版本兼容的情况下实现类似功能,可以考虑在ActivityFragment的生命周期方法中使用requestFocus()方法。例如:
      public class MainActivity extends AppCompatActivity {
          private Button btnTest;
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
              btnTest = findViewById(R.id.btnTest);
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                  btnTest.setFocusedByDefault(true);
              } else {
                  btnTest.requestFocus();
              }
          }
      }
      
  4. setDefaultFocusHighlightEnabled(boolean defaultFocusHighlightEnabled)(API 26及以上版本可用)
    • 功能
      • 用于在代码中设置控件是否显示原生的焦点高亮效果。当defaultFocusHighlightEnabled设置为true时,控件在获得焦点时将显示原生的高亮效果(如果在API 26及以上版本);当设置为false时,将不显示原生高亮效果。这在你想要自定义焦点视觉效果时非常有用。例如:
      public class MainActivity extends AppCompatActivity {
          private Button btnTest;
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
              btnTest = findViewById(R.id.btnTest);
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                  btnTest.setDefaultFocusHighlightEnabled(false);
              }
          }
      }
      

三、设置焦点移动方向(已知控件ID的情况下)

  1. android:nextFocusUp="@id/tv_test"
    • 功能
      • 此属性用于在XML布局中定义当用户按下方向键中的“上”键时,焦点应该转移到哪个控件。在上述示例中,当在当前控件上按下“上”键时,焦点将转移到idtv_test的控件上。这种方式可以帮助你精确地控制焦点在界面中的移动路径,确保用户在使用方向键导航时能够按照你期望的顺序在各个控件之间切换焦点。例如,在一个表单布局中,你可以通过这种方式设置当用户在某个输入框按下“上”键时,焦点转移到上一个相关的输入框或者提示标签上。
  2. android:nextFocusDown="@id/tv_test"
    • 功能
      • android:nextFocusUp类似,当用户按下方向键中的“下”键时,焦点将转移到idtv_test的控件。这在垂直排列的多个控件之间控制焦点移动非常有用。例如,在一个垂直排列的按钮组中,通过设置这个属性,可以确保焦点按照按钮的排列顺序从上到下(或从下到上)依次移动。
  3. android:nextFocusLeft="@id/tv_test"
    • 功能
      • 当用户按下方向键中的“左”键时,焦点将转移到idtv_test的控件。在水平排列的控件布局中,这个属性可以帮助你控制焦点在水平方向上的移动。例如,在一个游戏界面中,有多个水平排列的可交互元素,通过设置nextFocusLeftnextFocusRight属性,可以让用户方便地通过方向键在这些元素之间切换焦点。
  4. android:nextFocusRight="@id/tv_test"
    • 功能
      • 当用户按下方向键中的“右”键时,焦点将转移到idtv_test的控件。与android:nextFocusLeft属性配合使用,可以完整地控制焦点在水平方向上的移动路径。例如,在一个菜单界面中,有多个水平排列的菜单项,通过合理设置这两个属性,可以让用户通过方向键流畅地在菜单项之间切换焦点,提升用户体验。

四、请求焦点的方法

  1. requestFocus()

    • 功能
      • requestFocus()方法是在代码中手动请求焦点的常用方式。它可以应用于各种可聚焦的视图(View)。例如,在一个包含多个输入框(EditText)的表单中,当用户点击某个按钮后,你可能希望特定的输入框立即获得焦点以便用户进行输入。假设editText是一个输入框,在按钮的点击事件处理方法中可以这样使用:
      public class MainActivity extends AppCompatActivity {
          private EditText editText;
          private Button button;
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
              editText = findViewById(R.id.editText);
              button = findViewById(R.id.button);
              button.setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View v) {
                      editText.requestFocus();
                  }
              });
          }
      }
      
    • 使用场景和注意事项
      • 此方法在很多场景下都非常有用,比如在界面初始化后引导用户输入、在某些操作完成后将焦点切换到特定控件等。需要注意的是,在一些复杂的布局结构或者存在多个可聚焦控件竞争焦点的情况下,requestFocus()的效果可能会受到布局规则和其他相关属性(如focusablefocusableInTouchMode等)的影响。如果在调用requestFocus()后发现焦点没有正确地转移到目标控件,可以检查相关的属性设置和布局结构是否正确。
  2. requestFocusFromTouch()

    • 功能
      • requestFocus()类似,但requestFocusFromTouch()方法主要用于处理触摸事件相关的焦点请求。在触摸操作模式下,当你希望某个控件在被触摸后获得焦点时,可以使用这个方法。例如,在一个自定义的视图(CustomView)中,当用户触摸该视图时,你希望它获得焦点:
      public class CustomView