Android的动作条选项卡和键盘焦点 [英] Android Actionbar Tabs and Keyboard Focus

查看:131
本文介绍了Android的动作条选项卡和键盘焦点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题

我有一台带有两个选项卡非常简单的活动,我试图来处理键盘输入的自定义视图。这个伟大的工程...直到我换标签。有一次,我换标签,我永远无法得到的事件再次捕捉。在其他应用程序,打开一个对话框,然后关闭它,但是,这让我的关键事件经过。如果不这样做,我发现没有办法再得到我的重要事件。

有什么问题吗?我找不到任何方式来获得关键事件,一旦我换标签,并很好奇,到底是什么他们。这个例子是pretty的短,重点突出。

code

main.xml中

 < XML版本=1.0编码=UTF-8&GT?;
    < LinearLayout中的xmlns:机器人=htt​​p://schemas.android.com/apk/res/android
      机器人:方向=垂直
      机器人:layout_width =FILL_PARENT
      机器人:layout_height =FILL_PARENT
    >
    <的FrameLayout
      机器人:ID =@ + ID / actionbar_content
      机器人:layout_width =match_parent
      机器人:layout_height =match_parent
    />
< / LinearLayout中>
 

my_fragment.xml

 < XML版本=1.0编码=UTF-8&GT?;
<的LinearLayout
  的xmlns:机器人=htt​​p://schemas.android.com/apk/res/android
  机器人:方向=垂直
  机器人:layout_width =match_parent
  机器人:layout_height =match_parent>
    <查看
      类=com.broken.keyboard.KeyboardTestActivity $ MyView的
      机器人:后台=#777777
      机器人:可聚焦=真
      机器人:focusableInTouchMode =真
      机器人:layout_width =FILL_PARENT
      机器人:layout_height =FILL_PARENT
    >
        <不是requestFocus />
    < /视图>
< / LinearLayout中>
 

KeyboardTestActivity.java

 包com.broken.keyboard;

进口android.app.ActionBar;
进口android.app.Activity;
进口android.app.Fragment;
进口android.app.FragmentManager;
进口android.os.Bundle;
进口android.util.AttributeSet;
进口android.util.Log;
进口android.view.KeyEvent;
进口android.view.LayoutInflater;
进口android.view.MotionEvent;
进口android.view.View;
进口android.view.ViewGroup;
进口android.view.inputmethod.InputMethodManager;

进口android.app.FragmentTransaction;
进口android.app.ActionBar.Tab;
进口android.content.Context;

公共类KeyboardTestActivity延伸活动{

    公共静态类MyView的扩展视图{
        公共无效toggleKeyboard()
        {((InputMethodManager)的getContext()getSystemService(Context.INPUT_METHOD_SERVICE))toggleSoftInput(0,0); }

        公共MyView的(上下文的背景下)
        {超(上下文); }

        公共MyView的(上下文的背景下,ATTRS的AttributeSet)
        {超(背景下,ATTRS); }

        公共MyView的(上下文的背景下,ATTRS的AttributeSet,INT defStyle)
        {超(背景下,ATTRS,defStyle); }


        //首先我尝试,在这里我想获得preSSES
        @覆盖
        公共布尔的onkeydown(INT键code,KeyEvent的事件){
            Log.i(BDBG,关键就下考虑!);
            返回super.onKeyDown(键code,事件);
        }

        //切换键盘上的联系!
        @覆盖
        公共布尔的onTouchEvent(MotionEvent事件)
        {
            如果((event.getAction()及MotionEvent.ACTION_MASK)== MotionEvent.ACTION_DOWN)
            {
                toggleKeyboard();
            }
            返回super.onTouchEvent(事件);
        }
    }

    //极其简单的片段
    公共类MyFragment扩展片段{
        @覆盖
        公共查看onCreateView(LayoutInflater充气,容器的ViewGroup,
                捆绑savedInstanceState){
            视图V = inflater.inflate(R.layout.my_fragment,集装箱,假);
            返回伏;
        }
    }

    //简单的选项卡监听器
    公共静态类MyTabListener实现ActionBar.TabListener
    {
        私人FragmentManager mFragmentManager = NULL;
        私人片段mFragment = NULL;
        私人字符串MTAG = NULL;
        公共MyTabListener(FragmentManager fragmentManager,片段的片段,字符串变量)
        {
            mFragmentManager = fragmentManager;
            mFragment =片段;
            MTAG =标签;
        }
        @覆盖
        公共无效onTabReselected(TAB键,FragmentTransaction英尺){
            // 没做什么
        }

        @覆盖
        公共无效onTabSelected(TAB键,FragmentTransaction英尺){
            mFragmentManager.beginTransaction()
                .replace(R.id.actionbar_content,mFragment,MTAG)
                。承诺();
        }

        @覆盖
        公共无效onTabUnselected(TAB键,FragmentTransaction英尺){
            mFragmentManager.beginTransaction()
                卸下摆臂(mFragment)
                。承诺();
        }

    }

    FragmentManager mFragmentManager;
    动作条mActionBar;

    / **第一次创建活动时调用。 * /
    @覆盖
    公共无效的onCreate(包savedInstanceState){
        super.onCreate(savedInstanceState);
        的setContentView(R.layout.main);

        //获得的片段经理
        mFragmentManager = getFragmentManager();
        mActionBar = getActionBar();

        //删除活动标题,使制表符空间
        mActionBar.setDisplayShowTitleEnabled(假);

        mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        //添加标签
        mActionBar.addTab(mActionBar.newTab()
                .setText(标签1)
                .setTabListener(新MyTabListener(getFragmentManager(),新MyFragment(),Frag1)));

        mActionBar.addTab(mActionBar.newTab()
                .setText(标签2)
                .setTabListener(新MyTabListener(getFragmentManager(),新MyFragment(),Frag2)));


    }

    //别的地方我试试,不行的比视图
    @覆盖
    公共布尔的onkeydown(INT键code,KeyEvent的事件){
        Log.i(BDBG,重点坠落在活动!);
        返回super.onKeyDown(键code,事件);
    }
}
 

解决方案

我已经解决我自己的问题,所以我想我会分享的解决方案。如果有一些措辞的问题,请大家指正在评论;我想尽可能准确,因为我可以,但我不完全的机器人专家。这个答案也应该成为如何处理换出动作条的标签,一般一个很好的例子。不管是不是人喜欢的解决方案,code设计的,它应该是有用的。

下面这个链接帮我找出我的问题:<一href="http://$c$c.google.com/p/android/issues/detail?id=2705">http://$c$c.google.com/p/android/issues/detail?id=2705

解决方案

事实证明,有手头的两个重要问题。首先,如果一个视图既机器人:可聚焦和android:focusableInTouchMode,然后在一个蜂窝平板人们可能会想到,点击它和类似将侧重它。然而,这未必是真实的。如果查看发生在是机器人:点击,那么确实攻将集中视图。如果不是点击,它不会被聚焦触摸

此外,换出一个片段时,有一个问题非常相似,当第一个实例化视图的活动。某些更改需要作出后,才视图层次是完全prepared。

如果你在一个片段中的观点不是requestFocus()前视图层次是完全prepared,查看确实会认为这是重点;但是,如果软键盘时,它实际上并不会发送任何事件的看法!更糟的是,如果该视图是可以点击的,在这一点上点击它不会修复此键盘焦点问题,因为观点认为它确实是重点,没有什么做的。如果一个人却注重一些其他查看,然后点击返回到这一块,因为它是既可以点击的,可聚焦它确实集中,也直接键盘输入这一观点。

鉴于信息,正确的方法,在交换到一个选项卡设置重点是发布可运行到View层次结构的片段后,它换入,然后才调用requestFocus的()。调用requestFocus的()视图层次是完全prepared都将视图以及直接键盘输入聚焦到它,因为我们想要的。它不会进入那个奇怪的聚焦状态,其视图集中,但键盘输入以某种方式不指向它,就好像调用requestFocus的()前视图层次是完全prepared会发生。

同样重要的是,使用的片段的布局XML中的requestFocus的标签将最调用requestFocus的()为时尚早。我们没有理由永远使用该标记的片段的布局。一个片段之外,也许......但不是之内。

在code,我已经添加了一个EditText该片段的顶部只是用于测试点按对焦改变行为,并攻自定义视图也会切换软键盘。当交换选项卡,重点还应该默认为自定义视图。我试图有效地评论了code。

code

KeyboardTestActivity.java

 包com.broken.keyboard;

进口android.app.ActionBar;
进口android.app.Activity;
进口android.app.Fragment;
进口android.app.FragmentManager;
进口android.os.Bundle;
进口android.util.AttributeSet;
进口android.util.Log;
进口android.view.KeyEvent;
进口android.view.LayoutInflater;
进口android.view.MotionEvent;
进口android.view.View;
进口android.view.ViewGroup;
进口android.view.inputmethod.InputMethodManager;

进口android.app.FragmentTransaction;
进口android.app.ActionBar.Tab;
进口android.content.Context;

公共类KeyboardTestActivity延伸活动{

    / **
     *本类包装除了标签的动作条,
     *而它们之间的正常交换。此外,它
     *还提供了一个监听器接口,通过它可以
     *另外反应的标签的变化。最后,还
     *提供了一个回调后,标签已更改和
     *一个可运行已经邮寄至View层次结构,确保
     *片段的交易已经完成。这使得
     *正确调用requestFocus()的时机,等
     *类似的方法。
     *
     * @author nacitar sevaht
     *
     * /
    公共静态类ActionBarTabManager
    {
        公共静态接口TabChangeListener
        {
            / **
             *当选择一个新的标签调用。
             *
             *参数标签此标签的片段的标签。
             * /
            公共抽象无效onTabSelected(字符串标签);

            / **
             *调用当选择一个新的标签,但在
             *一个Runnable已经被后门柱执行
             *到视图层次结构,确保片段
             *交易完成。
             *
             *参数标签此标签的片段的标签。
             * /
            公共抽象无效onTabSelectedPost(字符串标签);

            / **
             *当当前选定的标签重新选择调用。
             *
             *参数标签此标签的片段的标签。
             * /
            公共抽象无效onTabReselected(字符串标签);

            / **
             *当一个新的选项卡中选择,之前调用{@link onTabSelected}
             *通知该previously所选标签(如果有的话),它是无
             *再选择。
             *
             *参数标签此标签的片段的标签。
             * /
            公共抽象无效onTabUnselected(字符串标签);


        }

        //变量
        活动mActivity = NULL;
        动作条mActionBar = NULL;
        FragmentManager mFragmentManager = NULL;
        TabChangeListener mListener = NULL;
        查看mContainer = NULL;
        可运行mTabSelectedPostRunnable = NULL;

        / **
         *这个类的构造函数。
         *
         *参数活动上,我们将放置动作条选项卡上的活动。
         *参数containerId容器的布局ID,preferable一个{@link的FrameLayout}
         *将包含片段。
         *参数监听器监听器与哪一个可以应对标签更改事件。
         * /
        公共ActionBarTabManager(活动活动,诠释containerId,TabChangeListener监听器)
        {
            mActivity =活动;
            如果(mActivity == NULL)
                抛出新的RuntimeException(ActionBarTabManager需要一个有效的活动!);

            mActionBar = mActivity.getActionBar();
            如果(mActionBar == NULL)
                抛出新的RuntimeException(ActionBarTabManager需要与动作条的活动。);

            mContainer = activity.findViewById(containerId);

            如果(mContainer == NULL)
                抛出新的RuntimeException(ActionBarTabManager需要一个有效的容器(的FrameLayout,preferably)。);

            mListener =侦听器;
            mFragmentManager = mActivity.getFragmentManager();

            //强制标签导航模式
            mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
        }

        / **
         *简单Runnable接口来调用听众的{@link onTabSelectedPost}方法。
         *
         * @author nacitar sevaht
         *
         * /
        私有类TabSelectedPostRunnable实现Runnable
        {
            字符串MTAG = NULL;
            公共TabSelectedPostRunnable(字符串标签)
            {
                MTAG =标签;
            }
            @覆盖
            公共无效的run(){
                如果(mListener!= NULL){
                    mListener.onTabSelectedPost(MTAG);
                }
            }

        }

        / **
         *内部TabListener。这个类作为一个很好的例子
         *如何正确处理交换标签了。它也是
         *交换后调用用户的监听器。
         *
         * @author nacitar sevaht
         *
         * /
        私有类TabListener实现ActionBar.TabListener
        {
            私人片段mFragment = NULL;
            私人字符串MTAG = NULL;
            公共TabListener(片段片段,字符串变量)
            {
                mFragment =片段;
                MTAG =标签;
            }
            私人布尔后期(Runnable的可运行)
            {
                返回mContainer.post(可运行);
            }
            @覆盖
            公共无效onTabReselected(TAB键,FragmentTransaction英尺){
                //无片段交换必要的逻辑

                如果(mListener!= NULL){
                    mListener.onTabReselected(MTAG);
                }

            }
            @覆盖
            公共无效onTabSelected(TAB键,FragmentTransaction英尺){
                mFragmentManager.beginTransaction()
                    .replace(mContainer.getId(),mFragment,MTAG)
                    。承诺();
                如果(mListener!= NULL){
                    mListener.onTabSelected(MTAG);
                }
                //发布可运行此选项卡
                交(新TabSelectedPostRunnable(MTAG));
            }

            @覆盖
            公共无效onTabUnselected(TAB键,FragmentTransaction英尺){
                mFragmentManager.beginTransaction()
                    卸下摆臂(mFragment)
                    。承诺();
                如果(mListener!= NULL){
                    mListener.onTabUnselected(MTAG);
                }
            }

        }

        / **
         *简单打包加入了纯文本的标签。更强大
         *方法可以增加。
         *
         *参数标题显示的文本选项卡上。
         *参数片段时,选择该选项卡交换中的片段。
         *参数标签该标签的唯一的标签。
         * /
        公共无效addTab(字符串标题,片段的片段,字符串变量)
        {
            //该选项卡监听器是至关重要的位置。
            mActionBar.addTab(mActionBar.newTab()
                    .setText(标题)
                    .setTabListener(新TabListener(片段,标签)));
        }

    }
    / **
     *触摸时,用来切换屏幕键盘上的一个简单的自定义视图,
     *也打印时收到一个关键的事件日志信息。
     *
     * @author nacitar sevaht
     *
     * /
    公共静态类MyView的扩展视图{
        公共无效toggleKeyboard()
        {((InputMethodManager)的getContext()getSystemService(Context.INPUT_METHOD_SERVICE))toggleSoftInput(0,0); }

        公共MyView的(上下文的背景下)
        {超(上下文); }

        公共MyView的(上下文的背景下,ATTRS的AttributeSet)
        {超(背景下,ATTRS); }

        公共MyView的(上下文的背景下,ATTRS的AttributeSet,INT defStyle)
        {超(背景下,ATTRS,defStyle); }


        @覆盖
        公共布尔的onkeydown(INT键code,KeyEvent的事件){
            Log.i(BDBG,键(+键code +)坠落在自定义视图!);
            返回true;
        }

        //切换键盘上的联系!
        @覆盖
        公共布尔的onTouchEvent(MotionEvent事件)
        {
            如果((event.getAction()及MotionEvent.ACTION_MASK)== MotionEvent.ACTION_DOWN)
            {
                toggleKeyboard();
            }
            返回super.onTouchEvent(事件);
        }
    }

    //极其简单的片段
    公共类MyFragment扩展片段{
        @覆盖
        公共查看onCreateView(LayoutInflater充气,容器的ViewGroup,
                捆绑savedInstanceState){
            视图V = inflater.inflate(R.layout.my_fragment,集装箱,假);
            返回伏;
        }
    }

    公共类MyTabChangeListener实现ActionBarTabManager.TabChangeListener
    {
        公共无效onTabReselected(字符串标签){}
        公共无效onTabSelected(字符串标签){}
        公共无效onTabSelectedPost(字符串标签)
        {
            // TODO:注:通常,人们将有条件地设置基于标签时关注的焦点。
            //但我们的样本中,两个标签具有相同的片段布局。
            查看查看= findViewById(R.id.myview);
            如果(查看== NULL)
            {
                抛出新的RuntimeException(标签与(\标签+标签+\)应该有,我们正在寻找的观点,但不!);
            }
            view.requestFocus();
        }
        公共无效onTabUnselected(字符串标签){}
    }

    //我们的标签管理
    ActionBarTabManager mActionBarTabManager = NULL;

    //我们的监听器
    MyTabChangeListener mListener =新MyTabChangeListener();

    //第一次创建活动时调用。
    @覆盖
    公共无效的onCreate(包savedInstanceState){
        super.onCreate(savedInstanceState);
        的setContentView(R.layout.main);

        //实例化标签管理器
        mActionBarTabManager =新ActionBarTabManager(这一点,R.id.actionbar_content,mListener);

        //删除活动标题,使制表符空间
        getActionBar()setDisplayShowTitleEnabled(假)。

        //添加标签
        mActionBarTabManager.addTab(标签1,新的MyFragment(),Frag1);
        mActionBarTabManager.addTab(标签2,新的MyFragment(),Frag2);
    }
}
 

main.xml中

 &LT; XML版本=1.0编码=UTF-8&GT?;
&LT; LinearLayout中的xmlns:机器人=htt​​p://schemas.android.com/apk/res/android
    机器人:方向=垂直
    机器人:layout_width =FILL_PARENT
    机器人:layout_height =FILL_PARENT
    &GT;
    &LT;的FrameLayout
        机器人:ID =@ + ID / actionbar_content
        机器人:layout_width =match_parent
        机器人:layout_height =match_parent
    /&GT;
&LT; / LinearLayout中&GT;
 

my_fragment.xml

 &LT; XML版本=1.0编码=UTF-8&GT?;
&LT;的LinearLayout
  的xmlns:机器人=htt​​p://schemas.android.com/apk/res/android
  机器人:方向=垂直
  机器人:layout_width =match_parent
  机器人:layout_height =match_parent&GT;
    &LT;的EditText
        机器人:layout_width =FILL_PARENT
        机器人:layout_height =WRAP_CONTENT
        /&GT;

    &LT;! - 注意查看是在较低的情况下,在这里 - &GT;
    &LT;查看
        类=com.broken.keyboard.KeyboardTestActivity $ MyView的
        机器人:ID =@ + ID / MyView的
        机器人:后台=#777777
        机器人:可点击=真
        机器人:可聚焦=真
        机器人:focusableInTouchMode =真
        机器人:layout_width =FILL_PARENT
        机器人:layout_height =match_parent
    /&GT;
&LT; / LinearLayout中&GT;
 

Problem

I have a very simple activity with two tabs, and I'm trying to handle keyboard input in a custom view. This works great... until I swap tabs. Once I swap tabs, I can never get the events to capture again. In another application, opening a Dialog and then closing it, however, would allow my key events to go through. Without doing that I've found no way of getting my key events again.

What's the issue here? I can't find any way to get key events once I swap tabs, and am curious what's eating them. This example is pretty short and to the point.

Code

main.xml

<?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:orientation="vertical"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
    >
    <FrameLayout
      android:id="@+id/actionbar_content" 
      android:layout_width="match_parent"
      android:layout_height="match_parent"
    />
</LinearLayout>

my_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
    <view 
      class="com.broken.keyboard.KeyboardTestActivity$MyView"
      android:background="#777777"
      android:focusable="true"
      android:focusableInTouchMode="true"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
    >
        <requestFocus/>
    </view>
</LinearLayout>

KeyboardTestActivity.java

package com.broken.keyboard;

import android.app.ActionBar;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;

import android.app.FragmentTransaction;
import android.app.ActionBar.Tab;
import android.content.Context;

public class KeyboardTestActivity extends Activity {

    public static class MyView extends View {
        public void toggleKeyboard()
        { ((InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE)).toggleSoftInput(0, 0); }

        public MyView(Context context)
        { super(context); }

        public MyView(Context context, AttributeSet attrs)
        { super(context, attrs); }

        public MyView(Context context, AttributeSet attrs, int defStyle)
        { super(context, attrs, defStyle); }


        // FIRST PLACE I TRY, WHERE I WANT TO GET THE PRESSES
        @Override
        public boolean onKeyDown(int keyCode, KeyEvent event) {
            Log.i("BDBG", "Key went down in view!");
            return super.onKeyDown(keyCode,event);
        }

        // Toggle keyboard on touch!
        @Override
        public boolean onTouchEvent(MotionEvent event)
        {
            if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN)
            {
                toggleKeyboard();
            }
            return super.onTouchEvent(event);
        }
    }

    // Extremely simple fragment
    public class MyFragment extends Fragment {        
        @Override
        public View onCreateView (LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            View v = inflater.inflate(R.layout.my_fragment, container, false);
            return v;
        }
    }

    // Simple tab listener
    public static class MyTabListener implements ActionBar.TabListener
    {
        private FragmentManager mFragmentManager=null;
        private Fragment mFragment=null;
        private String mTag=null;
        public MyTabListener(FragmentManager fragmentManager, Fragment fragment,String tag)
        {
            mFragmentManager=fragmentManager;
            mFragment=fragment;
            mTag=tag;
        }
        @Override
        public void onTabReselected(Tab tab, FragmentTransaction ft) {
            // do nothing
        }

        @Override
        public void onTabSelected(Tab tab, FragmentTransaction ft) {
            mFragmentManager.beginTransaction()
                .replace(R.id.actionbar_content, mFragment, mTag)
                .commit();
        }

        @Override
        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
            mFragmentManager.beginTransaction()
                .remove(mFragment)
                .commit();
        }

    }

    FragmentManager mFragmentManager;
    ActionBar mActionBar;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // Retrieve the fragment manager
        mFragmentManager=getFragmentManager();
        mActionBar=getActionBar();

        // remove the activity title to make space for tabs
        mActionBar.setDisplayShowTitleEnabled(false);

        mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        // Add the tabs
        mActionBar.addTab(mActionBar.newTab()
                .setText("Tab 1")
                .setTabListener(new MyTabListener(getFragmentManager(), new MyFragment(),"Frag1")));

        mActionBar.addTab(mActionBar.newTab()
                .setText("Tab 2")
                .setTabListener(new MyTabListener(getFragmentManager(), new MyFragment(),"Frag2")));


    }

    // OTHER PLACE I TRY, DOESN'T WORK BETTER THAN IN THE VIEW
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        Log.i("BDBG", "Key went down in activity!");
        return super.onKeyDown(keyCode,event);
    }
}

解决方案

I've solved my own problem, so I thought I'd share the solution. If there's some wording issue, please correct me in a comment; I'm trying to be as accurate as I can but I'm not entirely an android expert. This answer should also serve as an excellent example of how to handle swapping out ActionBar tabs in general. Whether or not one likes the design of the solution code, it should be useful.

The following link helped me figure out my issue: http://code.google.com/p/android/issues/detail?id=2705

Solution

It turns out, there are two important issues at hand. Firstly, if a View is both android:focusable and android:focusableInTouchMode, then on a honeycomb tablet one might expect that tapping it and similar would focus it. This, however, is not necessarily true. If that View happens to also be android:clickable, then indeed tapping will focus the view. If it is not clickable, it will not be focused by touch.

Furthermore, when swapping out a fragment there's an issue very similar to when first instantiating the view for an activity. Certain changes need to be made only after the View hierarchy is completely prepared.

If you call "requestFocus()" on a view within a fragment before the View hierarchy is completely prepared, the View will indeed think that it is focused; however, if the soft keyboard is up, it will not actually send any events to that view! Even worse, if that View is clickable, tapping it at this point will not fix this keyboard focus issue, as the View thinks that it is indeed focused and there is nothing to do. If one was to focus some other view, and then tap back onto this one, however, as it is both clickable and focusable it would indeed focus and also direct keyboard input to this view.

Given that information, the correct approach to setting the focus upon swapping to a tab is to post a runnable to the View hierarchy for the fragment after it is swapped in, and only then call requestFocus(). Calling requestFocus() after the View hierarchy is fully prepared will both focus the View as well as direct keyboard input to it, as we want. It will not get into that strange focused state where the view is focused but the keyboard input is somehow not directed to it, as will happen if calling requestFocus() prior to the View hierarchy being fully prepared.

Also important, using the "requestFocus" tag within the XML of a fragment's layout will most call requestFocus() too early. There is no reason to ever use that tag in a fragment's layout. Outside of a fragment, maybe.. but not within.

In the code, I've added an EditText to the top of the fragment just for testing tap focus change behaviors, and tapping the custom View will also toggle the soft keyboard. When swapping tabs, the focus should also default to the custom view. I tried to comment the code effectively.

Code

KeyboardTestActivity.java

package com.broken.keyboard;

import android.app.ActionBar;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;

import android.app.FragmentTransaction;
import android.app.ActionBar.Tab;
import android.content.Context;

public class KeyboardTestActivity extends Activity {

    /**
     * This class wraps the addition of tabs to the ActionBar,
     * while properly swapping between them.  Furthermore, it
     * also provides a listener interface by which you can
     * react additionally to the tab changes.  Lastly, it also
     * provides a callback for after a tab has been changed and
     * a runnable has been post to the View hierarchy, ensuring
     * the fragment transactions have completed.  This allows
     * proper timing of a call to requestFocus(), and other
     * similar methods.
     * 
     * @author nacitar sevaht
     *
     */
    public static class ActionBarTabManager 
    {
        public static interface TabChangeListener
        {
            /**
             * Invoked when a new tab is selected.
             * 
             * @param tag The tag of this tab's fragment.
             */
            public abstract void onTabSelected(String tag);

            /**
             * Invoked when a new tab is selected, but after
             * a Runnable has been executed after being post
             * to the view hierarchy, ensuring the fragment
             * transaction is complete.
             * 
             * @param tag The tag of this tab's fragment.
             */
            public abstract void onTabSelectedPost(String tag);

            /**
             * Invoked when the currently selected tab is reselected.
             * 
             * @param tag The tag of this tab's fragment.
             */
            public abstract void onTabReselected(String tag);

            /**
             * Invoked when a new tab is selected, prior to {@link onTabSelected}
             * notifying that the previously selected tab (if any) that it is no
             * longer selected.
             * 
             * @param tag The tag of this tab's fragment.
             */
            public abstract void onTabUnselected(String tag);


        }

        // Variables
        Activity mActivity = null;
        ActionBar mActionBar = null;
        FragmentManager mFragmentManager = null;
        TabChangeListener mListener=null;
        View mContainer = null;
        Runnable mTabSelectedPostRunnable = null;

        /**
         * The constructor of this class.
         * 
         * @param activity The activity on which we will be placing the actionbar tabs.
         * @param containerId The layout id of the container, preferable a  {@link FrameLayout}
         *        that will contain the fragments.
         * @param listener A listener with which one can react to tab change events.
         */
        public ActionBarTabManager(Activity activity, int containerId, TabChangeListener listener)
        {
            mActivity = activity;
            if (mActivity == null)
                throw new RuntimeException("ActionBarTabManager requires a valid activity!");

            mActionBar = mActivity.getActionBar();
            if (mActionBar == null)
                throw new RuntimeException("ActionBarTabManager requires an activity with an ActionBar.");

            mContainer = activity.findViewById(containerId);

            if (mContainer == null)
                throw new RuntimeException("ActionBarTabManager requires a valid container (FrameLayout, preferably).");

            mListener = listener;
            mFragmentManager = mActivity.getFragmentManager();

            // Force tab navigation mode
            mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
        }

        /**
         * Simple Runnable to invoke the {@link onTabSelectedPost} method of the listener.
         * 
         * @author nacitar sevaht
         *
         */
        private class TabSelectedPostRunnable implements Runnable
        {
            String mTag = null;
            public TabSelectedPostRunnable(String tag)
            {
                mTag=tag;
            }
            @Override
            public void run() {
                if (mListener != null) {
                    mListener.onTabSelectedPost(mTag);
                }
            }

        }

        /**
         * Internal TabListener.  This class serves as a good example
         * of how to properly handles swapping the tabs out.  It also
         * invokes the user's listener after swapping.
         * 
         * @author nacitar sevaht
         *
         */
        private class TabListener implements ActionBar.TabListener
        {
            private Fragment mFragment=null;
            private String mTag=null;
            public TabListener(Fragment fragment, String tag)
            {
                mFragment=fragment;
                mTag=tag;
            }
            private boolean post(Runnable runnable)
            {
                return mContainer.post(runnable);
            }
            @Override
            public void onTabReselected(Tab tab, FragmentTransaction ft) {
                // no fragment swapping logic necessary

                if (mListener != null) {
                    mListener.onTabReselected(mTag);
                }

            }
            @Override
            public void onTabSelected(Tab tab, FragmentTransaction ft) {
                mFragmentManager.beginTransaction()
                    .replace(mContainer.getId(), mFragment, mTag)
                    .commit();
                if (mListener != null) {
                    mListener.onTabSelected(mTag);
                }
                // Post a runnable for this tab
                post(new TabSelectedPostRunnable(mTag));
            }

            @Override
            public void onTabUnselected(Tab tab, FragmentTransaction ft) {
                mFragmentManager.beginTransaction()
                    .remove(mFragment)
                    .commit();
                if (mListener != null) {
                    mListener.onTabUnselected(mTag);
                }
            }

        }

        /**
         * Simple wrapper for adding a text-only tab.  More robust
         * approaches could be added.
         * 
         * @param title The text to display on the tab.
         * @param fragment The fragment to swap in when this tab is selected.
         * @param tag The unique tag for this tab.
         */
        public void addTab(String title, Fragment fragment, String tag)
        {
            // The tab listener is crucial here.
            mActionBar.addTab(mActionBar.newTab()
                    .setText(title)
                    .setTabListener(new TabListener(fragment, tag)));   
        }

    }
    /**
     * A simple custom view that toggles the on screen keyboard when touched,
     * and also prints a log message whenever a key event is received.
     * 
     * @author nacitar sevaht
     *
     */
    public static class MyView extends View {
        public void toggleKeyboard()
        { ((InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE)).toggleSoftInput(0, 0); }

        public MyView(Context context)
        { super(context); }

        public MyView(Context context, AttributeSet attrs)
        { super(context, attrs); }

        public MyView(Context context, AttributeSet attrs, int defStyle)
        { super(context, attrs, defStyle); }


        @Override
        public boolean onKeyDown(int keyCode, KeyEvent event) {
            Log.i("BDBG", "Key (" + keyCode + ") went down in the custom view!");
            return true;
        }

        // Toggle keyboard on touch!
        @Override
        public boolean onTouchEvent(MotionEvent event)
        {
            if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN)
            {
                toggleKeyboard();
            }
            return super.onTouchEvent(event);
        }
    }

    // Extremely simple fragment
    public class MyFragment extends Fragment {
        @Override
        public View onCreateView (LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            View v = inflater.inflate(R.layout.my_fragment, container, false);
            return v;
        }
    }

    public class MyTabChangeListener implements ActionBarTabManager.TabChangeListener
    {
        public void onTabReselected(String tag) { }
        public void onTabSelected(String tag) { }
        public void onTabSelectedPost(String tag)
        {
            // TODO: NOTE: typically, one would conditionally set the focus based upon the tag.
            //             but in our sample, both tabs have the same fragment layout.
            View view=findViewById(R.id.myview);
            if (view == null)
            {
                throw new RuntimeException("Tab with tag of (\""+tag+"\") should have the view we're looking for, but doesn't!");
            }
            view.requestFocus();
        }
        public void onTabUnselected(String tag) { }
    }

    // Our tab manager
    ActionBarTabManager mActionBarTabManager = null;

    // Our listener
    MyTabChangeListener mListener = new MyTabChangeListener();

    // Called when the activity is first created.
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // instantiate our tab manager
        mActionBarTabManager = new ActionBarTabManager(this,R.id.actionbar_content,mListener);

        // remove the activity title to make space for tabs
        getActionBar().setDisplayShowTitleEnabled(false);

        // Add the tabs
        mActionBarTabManager.addTab("Tab 1", new MyFragment(), "Frag1");
        mActionBarTabManager.addTab("Tab 2", new MyFragment(), "Frag2");
    }
}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <FrameLayout
        android:id="@+id/actionbar_content" 
        android:layout_width="match_parent"
        android:layout_height="match_parent"
    />
</LinearLayout>

my_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
    <EditText
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        />

    <!-- note that view is in lower case here -->
    <view 
        class="com.broken.keyboard.KeyboardTestActivity$MyView"
        android:id="@+id/myview"
        android:background="#777777"
        android:clickable="true"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:layout_width="fill_parent"
        android:layout_height="match_parent"
    />
</LinearLayout>

这篇关于Android的动作条选项卡和键盘焦点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆