Android操作栏标签和键盘焦点

问题

我有一个非常简单的活动,有两个标签,我正在尝试在自定义视图中处理键盘输入。 这很好用……直到我交换标签。 一旦我交换标签,我就永远无法再次捕获事件。 在另一个应用程序中,打开一个Dialog然后关闭它将允许我的关键事件通过。 没有这样做,我发现没有办法再次获得我的关键事件。

这有什么问题? 一旦我换掉标签,我找不到任何方法来获取关键事件,我很好奇吃了什么。 这个例子非常简短而且非常重要。

main.xml中

    

my_fragment.xml

       

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); } } 

我已经解决了自己的问题,所以我想我会分享解决方案。 如果有一些措辞问题,请在评论中纠正我; 我想尽可能准确,但我不完全是一个Android专家。 这个答案也应该成为如何处理一般交换ActionBar标签的一个很好的例子。 无论是否喜欢解决方案代码的设计,它都应该是有用的。

以下链接帮助我解决了我的问题: http : //code.google.com/p/android/issues/detail?id = 2705

事实certificate,手头有两个重要问题。 首先,如果一个视图同时是android:focusable和android:focusableInTouchMode,那么在蜂窝平板电脑上可能会有人认为点击它会类似于它。 然而,这不一定是真的。 如果该视图恰好也是 android:clickable,那么确实点击将会聚焦视图。 如果它不可点击,则不会通过触摸进行聚焦。

此外,当交换片段时,存在与首次实例化活动视图时非常相似的问题。 仅在完全准备View层次结构后才需要进行某些更改。

如果在完全准备View层次结构之前在片段内的视图上调用“requestFocus()”,View将确实认为它是焦点; 但是,如果软键盘已启动,它实际上不会向该视图发送任何事件! 更糟糕的是,如果该视图是可点击的,那么此时点击它将无法解决此键盘焦点问题,因为View认为它确实是重点并且无所事事。 如果要关注其他视图,然后再点击这个视图,那么,因为它既可点击又可聚焦,它确实会聚焦并将键盘输入定向到此视图。

给定该信息,在交换到选项卡时设置焦点的正确方法是在交换后将可运行发布到片段的View层次结构,然后再调用requestFocus()。 在View层次结构完全准备好之后调用requestFocus()将同时关注View以及直接键盘输入,如我们所愿。 它不会进入视图聚焦的奇怪聚焦状态,但键盘输入在某种程度上不是指向它,如果在View层次结构完全准备之前调用requestFocus()会发生这种情况。

同样重要的是,在片段布局的XML中使用“requestFocus”标签,大多数都会过早地调用requestFocus()。 没有理由在片段的布局中使用该标记。 在一个片段之外,也许……但不在其中。

在代码中,我在片段的顶部添加了一个EditText,用于测试自拍焦点更改行为,点击自定义视图也会切换软键盘。 交换选项卡时,焦点也应默认为自定义视图。 我试图有效地评论代码。

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"?>    

my_fragment.xml

 < ?xml version="1.0" encoding="utf-8"?>