Tabulaciones de la barra de herramientas de Android y enfoque del teclado

Problema

Tengo una actividad muy simple con dos pestañas, y estoy tratando de manejar la entrada de teclado en una vista personalizada. Esto funciona muy bien … hasta que cambie las pestañas. Una vez que cambie las pestañas, nunca puedo obtener los eventos para capturar de nuevo. En otra aplicación, abrir un cuadro de diálogo y, a continuación, cerrarlo, sin embargo, permitiría a mis eventos clave pasar. Sin hacer eso no he encontrado ninguna manera de conseguir mis acontecimientos claves otra vez.

¿Cuál es el problema aquí? No puedo encontrar ninguna forma de obtener eventos clave una vez que cambie las pestañas, y estoy curioso de lo que está comiendo. Este ejemplo es bastante corto y directo.

Código

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

He resuelto mi propio problema, así que pensé compartir la solución. Si hay algún problema de redacción, por favor, corrija en un comentario; Estoy tratando de ser lo más preciso que puedo, pero no soy totalmente un experto en Android. Esta respuesta también debe servir como un excelente ejemplo de cómo manejar intercambiar tabulaciones de ActionBar en general. Si o no le gusta el diseño del código de la solución, debe ser útil.

El siguiente enlace me ayudó a averiguar mi problema: http://code.google.com/p/android/issues/detail?id=2705

Solución

Resulta que hay dos asuntos importantes a la mano. En primer lugar, si una vista es a la vez android: focusable y android: focusableInTouchMode, entonces en una tableta de nido de abeja se podría esperar que tocarlo y similares se centrará. Sin embargo, esto no es necesariamente cierto. Si esa vista pasa a ser también androide: clicable, a continuación, en efecto, tapping se centrará la vista. Si no se puede hacer clic, no se enfocará mediante el tacto.

Además, al intercambiar un fragmento hay un problema muy similar al instanciar la primera vista de una actividad. Ciertos cambios sólo deben realizarse después de que la jerarquía View esté completamente preparada.

Si llama a "requestFocus ()" en una vista dentro de un fragmento antes de que la jerarquía de Vista esté completamente preparada, la Vista realmente pensará que está enfocada; Sin embargo, si el teclado suave está hacia arriba, ¡en realidad no enviará ningún evento a esa vista! Aún peor, si esa vista es clicable, al tocarla en este punto no solucionará este problema de enfoque de teclado, ya que la Vista piensa que está realmente enfocado y no hay nada que hacer. Si uno fuese a enfocar alguna otra vista, y luego pulsa de nuevo en ésta, sin embargo, ya que es a la vez que se puede hacer clic y se puede enfocar, se enfocaría y también dirigiría la entrada del teclado a esta vista.

Dada esa información, el enfoque correcto para establecer el enfoque en el cambio a una pestaña es publicar una ejecución en la jerarquía Ver para el fragmento después de que se cambie en, y sólo entonces llamar a requestFocus (). Llamar requestFocus () después de que la jerarquía View esté completamente preparada, tanto la vista como la entrada directa del teclado se enfocarán a ella, como queramos. No entrará en ese estado extraño enfocado donde la vista está enfocada, pero la entrada del teclado no está dirigida a ella de alguna manera, como ocurrirá si llama requestFocus () antes de que la jerarquía View esté completamente preparada.

También es importante, usar la etiqueta "requestFocus" dentro del XML del diseño de un fragmento llamará más requestFocus () demasiado pronto. No hay razón alguna para usar esa etiqueta en el diseño de un fragmento. Fuera de un fragmento, tal vez … pero no dentro.

En el código, he añadido un EditText a la parte superior del fragmento sólo para probar los comportamientos de cambio de enfoque de toma y tocar la vista personalizada también alternará el teclado virtual. Cuando cambie las pestañas, el enfoque también debe predeterminarse en la vista personalizada. Traté de comentar el código de manera efectiva.

Código

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> 
  • La ubicación devuelta es nula cuando el proveedor es gps?
  • Lazy Loading no funciona correctamente
  • ¿Cómo ejecutar la solicitud web en su propio hilo?
  • ¿Cómo puedo esperar 10 segundos sin bloquear la interfaz de usuario de la aplicación en android
  • Robotium - personalizar la duración PAUSE en la clase Sleeper
  • Cómo borrar la imagen con el dedo en Android
  • GetWidth () y getHeight () de la vista devuelve 0
  • Acceso a archivos de recursos en Android
  • Inicio de sesión en bibliotecas de "código de biblioteca Java" para aplicaciones de Android
  • ¿Es importante la versión de JDK que utilizo en Android Studio?
  • Los elementos no tienen el mismo ancho cuando se utiliza RecyclerView GridLayoutManager para establecer el espaciado de columnas por ItemDecoration
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.