类 FocusTraversalPolicy 的使用方法

FocusTraversalPolicy 定义一种顺序,按此顺序遍历具有特定焦点循环根的 Component。实例可以将此策略应用于任意焦点循环根,允许它们在整个 Container 共享。当焦点循环根的组件层次结构更改时,不必重新初始化它们。


FocusTraversalPolicy 的核心职责是提供算法,在 UI 中进行向前或向后遍历时确定下一个和上一个要聚焦的 Component。每个 FocusTraversalPolicy 还必须提供算法,确定遍历循环中第一个、最后一个,以及默认的组件。进行正常的正向和反向遍历时,分别使用第一个和最后一个 Component。默认的 Component 是向下遍历到一个新的焦点遍历循环时接收焦点的第一个 Component。FocusTraversalPolicy 可以随意提供确定窗口初始 Component 的算法。初始 Component 是首次将窗口设置为可见时接收焦点的第一个 Component。

import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.awt.event.*;

public class MainFrame extends JFrame {

    private static final long serialVersionUID = 1L;
    private JPanel jContentPane = null;
    private JTextField jTextField = null;
    private JTextField jTextField1 = null;
    private JTextField jTextField2 = null;
    private JTextField jTextField3 = null;
    private JTextField jTextField4 = null;
    private JTextField jTextField5 = null;
    private JTextField jTextField6 = null;
    private JTextField jTextField7 = null;
    private JTextField jTextField8 = null;
    private JTextField jTextField9 = null;
    static MyOwnFocusTraversalPolicy newPolicy;

    /**
     * This method initializes jTextField    
     *     
     * @return javax.swing.JTextField    
     */
    private JTextField getJTextField() {
        if (jTextField == null) {
            jTextField = new JTextField();
            jTextField.setLocation(new Point(37, 30));
            jTextField.setSize(new Dimension(200, 22));
            jTextField.addActionListener(new MyActionListener());
        }
        return jTextField;
    }

    /**
     * This method initializes jTextField1    
     *     
     * @return javax.swing.JTextField    
     */
    private JTextField getJTextField1() {
        if (jTextField1 == null) {
            jTextField1 = new JTextField();
            jTextField1.setBounds(new Rectangle(37, 60, 200, 22));
            jTextField1.addActionListener(new MyActionListener());
        }
        return jTextField1;
    }

    /**
     * This method initializes jTextField2    
     *     
     * @return javax.swing.JTextField    
     */
    private JTextField getJTextField2() {
        if (jTextField2 == null) {
            jTextField2 = new JTextField();
            jTextField2.setBounds(new Rectangle(37, 90, 200, 22));
            jTextField2.addActionListener(new MyActionListener());
        }
        return jTextField2;
    }

    /**
     * This method initializes jTextField3    
     *     
     * @return javax.swing.JTextField    
     */
    private JTextField getJTextField3() {
        if (jTextField3 == null) {
            jTextField3 = new JTextField();
            jTextField3.setBounds(new Rectangle(37, 120, 200, 22));
            jTextField3.addActionListener(new MyActionListener());
        }
        return jTextField3;
    }

    /**
     * This method initializes jTextField4    
     *     
     * @return javax.swing.JTextField    
     */
    private JTextField getJTextField4() {
        if (jTextField4 == null) {
            jTextField4 = new JTextField();
            jTextField4.setBounds(new Rectangle(37, 150, 200, 22));
            jTextField4.addActionListener(new MyActionListener());
        }
        return jTextField4;
    }

    /**
     * This method initializes jTextField5    
     *     
     * @return javax.swing.JTextField    
     */
    private JTextField getJTextField5() {
        if (jTextField5 == null) {
            jTextField5 = new JTextField();
            jTextField5.setBounds(new Rectangle(255, 30, 200, 22));
            jTextField5.addActionListener(new MyActionListener());
        }
        return jTextField5;
    }

    /**
     * This method initializes jTextField6    
     *     
     * @return javax.swing.JTextField    
     */
    private JTextField getJTextField6() {
        if (jTextField6 == null) {
            jTextField6 = new JTextField();
            jTextField6.setBounds(new Rectangle(255, 60, 200, 22));
            jTextField6.addActionListener(new MyActionListener());
        }
        return jTextField6;
    }

    /**
     * This method initializes jTextField7    
     *     
     * @return javax.swing.JTextField    
     */
    private JTextField getJTextField7() {
        if (jTextField7 == null) {
            jTextField7 = new JTextField();
            jTextField7.setBounds(new Rectangle(255, 90, 200, 22));
            jTextField7.addActionListener(new MyActionListener());
        }
        return jTextField7;
    }

    /**
     * This method initializes jTextField8    
     *     
     * @return javax.swing.JTextField    
     */
    private JTextField getJTextField8() {
        if (jTextField8 == null) {
            jTextField8 = new JTextField();
            jTextField8.setBounds(new Rectangle(255, 120, 200, 22));
            jTextField8.addActionListener(new MyActionListener());
        }
        return jTextField8;
    }

    /**
     * This method initializes jTextField9    
     *     
     * @return javax.swing.JTextField    
     */
    private JTextField getJTextField9() {
        if (jTextField9 == null) {
            jTextField9 = new JTextField();
            jTextField9.setBounds(new Rectangle(255, 150, 200, 22));
            jTextField9.addActionListener(new MyActionListener());
        }
        return jTextField9;
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                MainFrame thisClass = new MainFrame();
                thisClass.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                thisClass.setVisible(true);
                thisClass.setFocusTraversalPolicy(newPolicy);
            }
        });
    }

    /**
     * This is the default constructor
     */
    public MainFrame() {
        super();
        initialize();
    }

    /**
     * This method initializes this
     * 
     * @return void
     */
    private void initialize() {
        this.setSize(491, 259);
        this.setContentPane(getJContentPane());
        this.setTitle("JFrame");
    }

    /**
     * This method initializes jContentPane
     * 
     * @return javax.swing.JPanel
     */
    private JPanel getJContentPane() {
        if (jContentPane == null) {
            jContentPane = new JPanel();
            jContentPane.setLayout(null);
            jContentPane.add(getJTextField(), null);
            jContentPane.add(getJTextField1(), null);
            jContentPane.add(getJTextField2(), null);
            jContentPane.add(getJTextField3(), null);
            jContentPane.add(getJTextField4(), null);
            jContentPane.add(getJTextField5(), null);
            jContentPane.add(getJTextField6(), null);
            jContentPane.add(getJTextField7(), null);
            jContentPane.add(getJTextField8(), null);
            jContentPane.add(getJTextField9(), null);
            Vector<Component> order = new Vector<Component>(10);
            order.add(getJTextField());
            order.add(getJTextField1());
            order.add(getJTextField2());
            order.add(getJTextField3());
            order.add(getJTextField4());
            order.add(getJTextField5());
            order.add(getJTextField6());
            order.add(getJTextField7());
            order.add(getJTextField8());
            order.add(getJTextField9());
            newPolicy = new MyOwnFocusTraversalPolicy(order);
        }
        return jContentPane;
    }

    class MyActionListener implements ActionListener { // 所有文本框都加入此ActionListener
        public void actionPerformed(ActionEvent e) {
            ((JTextField) e.getSource()).transferFocus();// 按【Enter】键,focus往下一个组件
        }
    }

    
    //内置类,实现TAB按照指定规则设置
    public static class MyOwnFocusTraversalPolicy extends FocusTraversalPolicy {
        Vector<Component> order;

        public MyOwnFocusTraversalPolicy(Vector<Component> order) {
            this.order = new Vector<Component>(order.size());
            this.order.addAll(order);
        }

        public Component getComponentAfter(Container focusCycleRoot,
                Component aComponent) {
            int idx = (order.indexOf(aComponent) + 1) % order.size();
            return order.get(idx);
        }

        public Component getComponentBefore(Container focusCycleRoot,
                Component aComponent) {
            int idx = order.indexOf(aComponent) - 1;
            if (idx < 0) {
                idx = order.size() - 1;
            }
            return order.get(idx);
        }

        public Component getDefaultComponent(Container focusCycleRoot) {
            return order.get(0);
        }

        public Component getLastComponent(Container focusCycleRoot) {
            return order.lastElement();
        }

        public Component getFirstComponent(Container focusCycleRoot) {
            return order.get(0);
        }
    }
}  

本程序还可以实现按键盘【Enter】键循环遍历所有文本框。
### 解决 ListView 中嵌套 RecyclerView 时遥控器无法聚焦到屏幕外元素的问题 在 Android 开发中,当 `ListView` 嵌套 `RecyclerView` 时,遥控器(例如电视设备上的 D-pad)可能无法正确聚焦到屏幕外的元素。这种问题通常与焦点管理、视图布局以及滚动行为有关。以下是针对此问题的专业解决方案。 #### 1. 焦点管理的优化 Android 的焦点系统需要明确知道哪些视图可以获取焦点,并且需要确保这些视图在屏幕上可见时能够被正确聚焦。如果嵌套的 `RecyclerView` 中的某些项不在当前屏幕范围内,则遥控器无法正确聚焦到它们。 可以通过以下方式解决: - 设置 `RecyclerView` 和其子项的焦点属性为 `focusable="true"` 和 `focusableInTouchMode="true"`。 - 确保父级 `ListView` 不会拦截子级 `RecyclerView` 的焦点事件。 ```xml <androidx.recyclerview.widget.RecyclerView android:focusable="true" android:focusableInTouchMode="true" android:descendantFocusability="afterDescendants" /> ``` 通过设置 `descendantFocusability="afterDescendants"`[^1],可以确保子项优先获取焦点。 #### 2. 动态调整滚动位置 当 `RecyclerView` 的内容超出屏幕范围时,需要动态调整滚动位置以确保目标项可见。可以使用 `smoothScrollToPosition` 方法来实现这一点。 ```java recyclerView.smoothScrollToPosition(targetPosition); ``` 此外,可以监听焦点变化事件,并根据当前焦点调整滚动位置: ```java recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() { @Override public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { return false; } @Override public void onTouchEvent(RecyclerView rv, MotionEvent e) { } @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { } }); recyclerView.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { if (recyclerView.getLayoutManager() instanceof LinearLayoutManager) { LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); int firstVisibleItem = layoutManager.findFirstVisibleItemPosition(); int lastVisibleItem = layoutManager.findLastVisibleItemPosition(); // 如果目标项不在可见范围内,则滚动到该位置 if (targetPosition < firstVisibleItem || targetPosition > lastVisibleItem) { recyclerView.smoothScrollToPosition(targetPosition); } } }); ``` #### 3. 使用自定义 FocusTraversalPolicy 为了更精确地控制焦点移动顺序,可以实现一个自定义的 `FocusTraversalPolicy`。 ```java public class CustomFocusTraversalPolicy extends android.view.FocusFinder.FocusTraversalPolicy { @Override public View onFocusSearch(View focused, int direction) { // 自定义焦点搜索逻辑 return super.onFocusSearch(focused, direction); } } recyclerView.setFocusTraversalPolicy(new CustomFocusTraversalPolicy()); ``` 此方法允许开发者定义焦点在不同方向上的移动规则,从而避免焦点跳过或卡住的情况[^2]。 #### 4. 调整 LayoutManager 行为 如果 `RecyclerView` 的 `LayoutManager` 是 `LinearLayoutManager` 或其他线性布局管理器,可以尝试调整其参数以优化滚动和焦点行为。 ```java LinearLayoutManager layoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) { @Override public boolean canScrollVertically() { return true; // 确保垂直滚动始终可用 } }; recyclerView.setLayoutManager(layoutManager); ``` 此外,可以通过重写 `onMeasure` 方法来确保 `RecyclerView` 的测量行为符合预期。 ```java @Override protected void onMeasure(int widthSpec, int heightSpec) { super.onMeasure(widthSpec, MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST)); } ``` #### 5. 处理嵌套滚动冲突 当 `ListView` 和 `RecyclerView` 同时存在时,可能会出现滚动冲突。可以通过以下方式解决: - 在 `RecyclerView` 上启用嵌套滚动支持: ```java recyclerView.setNestedScrollingEnabled(true); ``` - 在 `ListView` 上禁用嵌套滚动支持,以避免冲突: ```java listView.setNestedScrollingEnabled(false); ``` #### 示例代码整合 以下是综合上述建议的完整示例代码: ```java recyclerView.setFocusable(true); recyclerView.setFocusableInTouchMode(true); recyclerView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); recyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) { @Override public boolean canScrollVertically() { return true; } }); recyclerView.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { if (recyclerView.getLayoutManager() instanceof LinearLayoutManager) { LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); int firstVisibleItem = layoutManager.findFirstVisibleItemPosition(); int lastVisibleItem = layoutManager.findLastVisibleItemPosition(); if (targetPosition < firstVisibleItem || targetPosition > lastVisibleItem) { recyclerView.smoothScrollToPosition(targetPosition); } } }); recyclerView.setFocusTraversalPolicy(new CustomFocusTraversalPolicy() { @Override public View onFocusSearch(View focused, int direction) { // 自定义焦点搜索逻辑 return super.onFocusSearch(focused, direction); } }); ``` ### 注意事项 - 确保所有视图的焦点属性配置正确,避免焦点丢失或卡死。 - 在调试过程中,可以使用 `Log` 输出焦点状态,以便快速定位问题。 - 如果目标设备是电视或其他非触摸设备,需特别注意遥控器的方向键行为。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值