Intereting Posts

Вкладка «Панель управления Android» и «Фокус клавиатуры»

проблема

У меня очень простая деятельность с двумя вкладками, и я пытаюсь обрабатывать ввод с клавиатуры в пользовательском представлении. Это отлично работает … пока я не поменяю вкладки. Как только я поменяю вкладки, я никогда не смогу снова захватить события. Однако в другом приложении открытие диалога и его закрытие позволят моим ключевым событиям пройти. Не делая этого, я не нашел способ снова получить ключевые события.

В чем проблема? Я не могу найти способ получить ключевые события, как только я поменяю вкладки, и мне любопытно, что их едят. Этот пример довольно короткий и кстати.

Код

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

Solutions Collecting From Web of "Вкладка «Панель управления Android» и «Фокус клавиатуры»"

Я решил свою проблему, поэтому решил, что смогу поделиться решением. Если есть какая-то формулировка, пожалуйста, исправьте меня в комментарии; Я стараюсь быть настолько точным, насколько могу, но я не специалист по Android. Этот ответ также должен служить отличным примером того, как обрабатывать сводные вкладки ActionBar в целом. Независимо от того, нравится ли дизайн кода решения, он должен быть полезен.

Следующая ссылка помогла мне разобраться в моей проблеме: http://code.google.com/p/android/issues/detail?id=2705

Решение

Оказывается, есть два важных вопроса. Во-первых, если представление представляет собой android: focusable и android: focusableInTouchMode, то на планшете сотовой связи можно ожидать, что нажатие на него и подобное сфокусируют его. Это, однако, не обязательно верно. Если этот вид также имеет вид android: clickable, то нажатие будет фокусировать взгляд. Если он не доступен, он не будет сфокусирован прикосновением.

Кроме того, при замене фрагмента возникает проблема, очень похожая на то, когда создается экземпляр представления для действия. Некоторые изменения должны быть сделаны только после того, как иерархия View полностью подготовлена.

Если вы вызываете «requestFocus ()» в представлении внутри фрагмента до того, как иерархия View полностью подготовлена, View действительно подумает, что он сфокусирован; Однако, если мягкая клавиатура встала, она фактически не отправит какие-либо события в это представление! Хуже того, если этот просмотр можно щелкнуть, нажатие на него в этот момент не будет устранять проблему с клавиатурой, так как View считает, что он действительно сфокусирован и нечего делать. Если кто-то должен был сфокусировать другое представление, а затем снова вернуться к этому, так как он одновременно доступен для клики и фокусировки, он бы действительно фокусировался, а также сразу вводил клавиатуру в это представление.

Учитывая эту информацию, правильный подход к настройке фокуса на замену на вкладку заключается в том, чтобы опубликовать исполняемый файл в иерархии представления для фрагмента после его замены и только затем вызвать requestFocus (). Вызов requestFocus () после того, как иерархия View полностью подготовлена, оба будут фокусировать View, а также прямой ввод с клавиатуры на него, как мы хотим. Он не попадет в это странное сфокусированное состояние, где сфокусирован взгляд, но ввод на клавиатуре каким-то образом не направлен на него, как это произойдет, если вы вызове requestFocus () до того, как иерархия View будет полностью подготовлена.

Также важно, что использование тега requestFocus в XML макета фрагмента будет наиболее часто называть 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"?> <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>