[英]Android Actionbar Tabs and Keyboard Focus
我有一個非常簡單的活動有兩個選項卡,我正在嘗試在自定義視圖中處理鍵盤輸入。 這很好用......直到我交換標簽。 一旦我交換標簽,我就永遠無法再次捕獲事件。 在另一個應用程序中,打開一個Dialog然后關閉它會允許我的關鍵事件通過。 沒有這樣做,我找不到再次獲得關鍵事件的方法。
這是什么問題? 一旦我換掉標簽,我找不到任何方法來獲取關鍵事件,我很好奇吃了什么。 這個例子非常簡短而且非常重要。
<?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>
<?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>
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
事實證明,手頭有兩個重要問題。 首先,如果一個視圖同時是android:focusable和android:focusableInTouchMode,那么在蜂窩平板電腦上可能會有人認為點擊它會類似於它。 然而,這不一定是真的。 如果那個View恰好也是 android:clickable,那么確實點擊會聚焦視圖。 如果它不可點擊,則不會通過觸摸進行聚焦。
此外,當交換片段時,存在與首次實例化活動視圖時非常相似的問題。 僅在完全准備View層次結構后才需要進行某些更改。
如果在完全准備View層次結構之前在片段內的視圖上調用“requestFocus()”,View將確實認為它是聚焦的; 但是,如果軟鍵盤已啟動,它實際上不會向該視圖發送任何事件! 更糟糕的是,如果該視圖是可點擊的,那么此時點擊它將無法解決此鍵盤焦點問題,因為View認為它確實是焦點而且無所事事。 如果要關注其他視圖,然后再點擊這個視圖,那么,因為它既可點擊又可聚焦,它確實會聚焦並將鍵盤輸入定向到此視圖。
給定該信息,在交換到選項卡時設置焦點的正確方法是在交換后將可運行的片段發布到片段的View層次結構,然后再調用requestFocus()。 在View層次結構完全准備好之后調用requestFocus()將同時關注View以及直接鍵盤輸入,如我們所願。 它不會進入視圖聚焦的奇怪的聚焦狀態,但鍵盤輸入在某種程度上不是指向它,如果在View層次結構完全准備之前調用requestFocus()會發生這種情況。
同樣重要的是,在片段布局的XML中使用“requestFocus”標簽將會過早地調用requestFocus()。 沒有理由在片段的布局中使用該標記。 在一個片段之外,也許......但不在其中。
在代碼中,我在片段的頂部添加了一個EditText,用於測試自拍焦點更改行為,點擊自定義視圖也會切換軟鍵盤。 交換選項卡時,焦點也應默認為自定義視圖。 我試圖有效地評論代碼。
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");
}
}
<?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>
<?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>
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.