Android系统的应用组件之一:ActionBar,是一个相当重要的组件,为了提升用户与移动应用的良好交互性,我们十分有必要好好学习一下它的原理及用法(例如通过点击ActionBar的相关Item启动系统服务或者启动类似于微博、空间的分享功能),除此之外,利用ActionBar结合Fragment创建出类似于TabHost的效果也非常有用。
以下对本演示程序结合运行效果并进行代码解释,细节可参看源码附件:
1. 手工添加ActionBar中的MenuItem和普通的MenuItem:Action Bar Mechanics
ActionBarMechanicsActivity.java
public class ActionBarMechanicsActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* * Action Bar属于窗口属性,但是但是要使用它,应该在窗口开始渲染界面之前, * 通过特定的请求激活(如下),通常情况下在AndroidManifest.xml文件对应的 * Activity节点通过设置android:theme="@android:style/Theme.WithActionBar"亦可。 * 也可以通过自定义的Theme设置自定义Action Bar, * 只需要在自定义主题中添加:- true
即可(略)。 */ getWindow().requestFeature(Window.FEATURE_ACTION_BAR); // (笔者尝试注释掉该行仍可正常运行,特此说明) setContentView(R.layout.activity_action_bar_mechanics); } @Override public boolean onCreateOptionsMenu(Menu menu) { /* * Menu Item默认情况下不会显示在Action Bar中。对于大多数设备, * Menu Item都会在实体按钮(menu)被按下的时候显示。 */ menu.add("Normal Item"); MenuItem actionItem = menu.add("Action Button"); /* * 被添加的Menu Item只会在Action Bar拥有足够的空间的时候显示, * 为了防止Action Bar被过量的Menu Item挤爆变得拥挤不堪,那些 * 过多的Menu Item将被放置在额外的屏幕空间内,通过点击MENU实体按钮可见。 */ actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); /* * 很多时候,我们只需要使用一些富有含义的png图片,就可以避免因为 * 使用文字带来的单调,而那些富有含义的png图片,Android OS已经为我们提供了。 */ actionItem.setIcon(android.R.drawable.ic_menu_share); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { /* * 不论是Normal的MenuItem还是From Action Bar的MenuItem, * 它们都同时被同时监听,利用同一个回调函数即可处理响应事件。 */ Toast.makeText(this, item.getTitle(), Toast.LENGTH_SHORT).show(); return true; } }
2.ActionBar结合Fragment创建类似于TabHost的可切换界面
ActionBarTabsActivity.java
import android.annotation.SuppressLint; import android.app.ActionBar; import android.app.ActionBar.Tab; import android.app.Activity; import android.app.Fragment; import android.app.FragmentTransaction; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import android.widget.Toast; public class ActionBarTabsActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.action_bar_tabs); } public void onAdd(View v) { ActionBar bar = getActionBar(); int tabCount = bar.getTabCount(); String text = "Tab" + tabCount; // 添加Tab,并且为每一个Tab添加监听器 bar.addTab(bar.newTab().setText(text) .setTabListener(new TabListener(new TabContentFragment(text)))); } public void onRemove(View v) { ActionBar bar = getActionBar(); if(bar.getTabCount() > 0) { bar.removeTabAt(bar.getTabCount() - 1); } } public void onToggle(View v) { ActionBar bar = getActionBar(); // 实现每次点击Toggle按钮,则接环ActionBar显示模式 if(bar.getNavigationMode() == ActionBar.NAVIGATION_MODE_TABS) { bar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); bar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE, ActionBar.DISPLAY_SHOW_TITLE); } else { bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE); } } public void onClear(View v) { ActionBar bar = getActionBar(); if(bar.getTabCount() > 0) { bar.removeAllTabs(); } } @SuppressLint("ValidFragment") private class TabContentFragment extends Fragment { private String mText; @SuppressLint("ValidFragment") public TabContentFragment(String mText) { this.mText = mText; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View fragView = inflater.inflate(R.layout.action_bar_tabs_content, container, false); TextView text = (TextView) fragView.findViewById(R.id.tv_content); text.setText(mText); return fragView; } private String getText() { return this.mText; } } /** * A TabListener receives event callbacks from the action bar as tabs * are deselected, selected, and reselected. A FragmentTransaction * is provided to each of these callbacks; if any operations are added * to it, it will be committed at the end of the full tab switch operation. * This lets tab switches be atomic without the app needing to track * the interactions between different tabs. * 每個ActionBar的tab各自註冊了一個TabListener實例,當tab被點擊時或不被点击时,其对应的监听器 * 执行事件对应的回调函数。此时会产生一个实例FragmentTransaction ft用于处理tab切换的事务。 * 这使得整个应用本身不需要在全局的角度去处理不同的tab之间的切换。 */ private class TabListener implements ActionBar.TabListener { private TabContentFragment mFragment; public TabListener(TabContentFragment mFragment) { this.mFragment = mFragment; } @Override public void onTabSelected(Tab tab, FragmentTransaction ft) { // 把当前this.mFragment添加到布局视图R.id.fragment_content,同时设置tab的标签名为this.mFragment.getText() ft.add(R.id.fragment_content, this.mFragment, this.mFragment.getText()); } @Override public void onTabUnselected(Tab tab, FragmentTransaction ft) { ft.remove(this.mFragment); } @Override public void onTabReselected(Tab tab, FragmentTransaction ft) { Toast.makeText(ActionBarTabsActivity.this, "Reselected..." , Toast.LENGTH_SHORT).show(); } } }
R.layout.action_bar_tabs.xml
3.使用ActionBar处理菜单事件
ActionBarUsageActivity.java
import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.widget.SearchView; import android.widget.Toast; import android.widget.SearchView.OnQueryTextListener; import android.widget.TextView; public class ActionBarUsageActivity extends Activity implements OnQueryTextListener { TextView mSearchText; // 显示事件响应效果 int mSortMode = -1; // 保存排序模式,默认为-1 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mSearchText = new TextView(this); setContentView(mSearchText); } @Override public boolean onCreateOptionsMenu(Menu menu) { // 取得MenuInflater,用于将xml资源转换成Menu实例 MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.actions, menu); // 取得id为R.id.action_search的MenuItem,由于xml文件中已经指定android:actionViewClass="android.widget.SearchView" // 所以通过getActionView()便可以显式转换为SearchView SearchView searchView = (SearchView)menu.findItem(R.id.action_search).getActionView(); // 注册搜索输入栏的事件监听器,来自于实现接口:android.widget.SearchView.OnQueryTextListener // 事件回调方法为:onQueryTextSubmit()开始搜索事件;onQueryTextChange()文本改变事件 searchView.setOnQueryTextListener(this); return true; } @Override public boolean onQueryTextSubmit(String query) { Toast.makeText(this, query, Toast.LENGTH_SHORT).show(); return true; } @Override public boolean onQueryTextChange(String newText) { this.mSearchText.setText(newText); return true; } @Override public boolean onMenuItemSelected(int featureId, MenuItem item) { Toast.makeText(this, item.getTitle(), Toast.LENGTH_SHORT).show(); return true; } /** * 在xml文件中显式声明的点击事件回调函数:android:onClick="onSort" * @param item */ public void onSort(MenuItem item) { this.mSortMode = item.getItemId(); // 发送变更请求,通知Menu更新其内容,响应方法为onPrepareOptionsMenu(Menu menu) invalidateOptionsMenu(); } @Override public boolean onPrepareOptionsMenu(Menu menu) { if(this.mSortMode != -1) { String strMode = menu.findItem(this.mSortMode).getTitle().toString(); menu.findItem(R.id.action_sort).setTitle("Sort: " + strMode); } return super.onPrepareOptionsMenu(menu); } }
R.menu.actions.xml
4.ActionBar创建ActionProvider
ActionBarProviderActivity.java
import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.provider.Settings; import android.view.ActionProvider; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ImageButton; import android.widget.ShareActionProvider; import android.widget.Toast; public class ActionBarProviderActivity extends Activity { private static final String SHARED_FILE_NAME = "share.png"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); copyPrivateRawResourceToPubliclyAccessibleFile(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // 加载xml文件中的各种MenuItem getMenuInflater().inflate(R.menu.action_bar_action_provider, menu); // 通过ID取得对应MenuItem MenuItem actionItem = menu.findItem(R.id.menu_item_share_action_provider_action_bar); // 取得在xml中声明的Action Provider Class实例 ShareActionProvider actionProvider = (ShareActionProvider)actionItem.getActionProvider(); // 设置默认的共享文件名: public static final String DEFAULT_SHARE_HISTORY_FILE_NAME = "share_history.xml"; actionProvider.setShareHistoryFileName(ShareActionProvider.DEFAULT_SHARE_HISTORY_FILE_NAME); // 设置共享意图 actionProvider.setShareIntent(createShareIntent()); MenuItem overflowItem = menu.findItem(R.id.menu_item_share_action_provider_overflow); ShareActionProvider overflowProvider = (ShareActionProvider)overflowItem.getActionProvider(); overflowProvider.setShareHistoryFileName(ShareActionProvider.DEFAULT_SHARE_HISTORY_FILE_NAME); overflowProvider.setShareIntent(createShareIntent()); return true; } private Intent createShareIntent() { Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.setType("iamge/*"); /* * Returns the absolute path on the filesystem * where a file created with openFileOutput is stored. * getFileStreamPath("share.png")可以从被保存在文件系统的文件输出流中 * 读取对应文件名的绝对路径 */ Uri uri = Uri.fromFile(getFileStreamPath(SHARED_FILE_NAME)); // 为Intent附加资源 shareIntent.putExtra(Intent.EXTRA_STREAM, uri); return shareIntent; } private void copyPrivateRawResourceToPubliclyAccessibleFile() { InputStream inputStream = null; FileOutputStream fileOutputStream = null; try { // Open a data stream for reading a raw resource inputStream = getResources().openRawResource(R.raw.robot); // Open a private file associated with this Context's application package for writing. // Creates the file if it doesn't already exist. fileOutputStream = openFileOutput( SHARED_FILE_NAME, Context.MODE_WORLD_READABLE | Context.MODE_APPEND); byte[] buffer = new byte[1024]; int len = 0; while((len = inputStream.read(buffer)) != -1) { fileOutputStream.write(buffer, 0, len); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { inputStream.close(); fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } @Override public boolean onMenuItemSelected(int featureId, MenuItem item) { /* * 如果被点击的MenuItem存在于ActionBar,那么此方法将不会被调用。 * 因为对于被点击的MenuItem的整个事件监听以及处理,都被封装到 * Provider activity中了,即由我们对ImageButton设定的监听器处理。 * 那么如果被点击的MenuItem不存在于ActionBar中呢?请参看内部 * 静态类型SettingSActionProvider的onPerformDefaultAction方法, * 注意:此时的返回值应该为false,读者可以尝试将其修改为true查看有趣的运行结果。 */ Toast.makeText(this, "From host activity: " + item.getTitle(), Toast.LENGTH_SHORT).show(); return false; } public static class SettingSActionProvider extends ActionProvider { private static final Intent mSettingsIntent = new Intent(Settings.ACTION_SETTINGS); private final Context mContext; public SettingSActionProvider(Context context) { super(context); // Call requires API level 14 this.mContext = context; } @Override public View onCreateActionView() { // 取得当前上下文的LayoutInflater实例 LayoutInflater inflater = LayoutInflater.from(mContext); // 将xml形式的MenuItem转换为View实例 View view = inflater.inflate(R.layout.action_bar_settings_action_provider, null); // 取得按钮控件 ImageButton imgbSettings = (ImageButton) view.findViewById(R.id.imgb_setting); imgbSettings.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 当按钮被点击,执行Activity跳转 mContext.startActivity(mSettingsIntent); } }); return view; } @Override public boolean onPerformDefaultAction() { /* * 为了体验此方法的作用效果,我们还需要为当前host activity添加一个 * 不存在ActionBar中的MenuItem,通过点击实体MENU显现。 * 当被点击的MenuItem不存在于ActionBar中的时候,并且host activity显式 * 声明不会负责处理响应事件,即onMenuItemSelected()返回值为false,此时的 * 响应事件在此执行处理。 * 其实也可将mContext.startActivity(mSettingsIntent);放置在onMenuItemSelected()中的return之前, * 但是这么干就不能体现创建自定义SettingSActionProvider类型的优势(组件复用)所在了。 */ mContext.startActivity(mSettingsIntent); return true; } } }
R.menu.action_bar_action_provider.xml
说明:ShareActionProvider功能需要在真机上运行才能有效果
5.关于ActionBar内容的相关操作
ActionBarDisplayOptionsActivity.java
import android.app.ActionBar; import android.app.ActionBar.Tab; import android.app.ActionBar.TabListener; import android.app.Activity; import android.app.FragmentTransaction; import android.os.Bundle; import android.util.Log; import android.view.Gravity; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup.LayoutParams; public class ActionBarDisplayOptionsActivity extends Activity implements OnClickListener, TabListener { private static final String TAG = "ActionBarDisplayOptionsActivity"; private View mCustomView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.action_bar_display_opstions); findViewById(R.id.btn_cycle_custom_gravity).setOnClickListener(this); findViewById(R.id.btn_home_as_up).setOnClickListener(this); findViewById(R.id.btn_show_custom).setOnClickListener(this); findViewById(R.id.btn_show_home).setOnClickListener(this); findViewById(R.id.btn_show_title).setOnClickListener(this); findViewById(R.id.btn_use_logo).setOnClickListener(this); findViewById(R.id.btn_navigation).setOnClickListener(this); this.mCustomView = getLayoutInflater().inflate(R.layout.action_bar_display_opstions_custom, null); ActionBar bar = getActionBar(); bar.setCustomView(this.mCustomView, new ActionBar.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); bar.addTab(bar.newTab().setText("Tab 1").setTabListener(this)); bar.addTab(bar.newTab().setText("Tab 2").setTabListener(this)); bar.addTab(bar.newTab().setText("Tab 3").setTabListener(this)); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.display_options_menu_item, menu); return true; } @Override public void onClick(View v) { ActionBar bar = getActionBar(); int flags = 0; switch (v.getId()) { case R.id.btn_home_as_up: flags = ActionBar.DISPLAY_HOME_AS_UP; break; case R.id.btn_show_home: flags = ActionBar.DISPLAY_SHOW_HOME; break; case R.id.btn_use_logo: flags = ActionBar.DISPLAY_USE_LOGO; break; case R.id.btn_show_title: flags = ActionBar.DISPLAY_SHOW_TITLE; break; case R.id.btn_show_custom: flags = ActionBar.DISPLAY_SHOW_CUSTOM; break; case R.id.btn_navigation: bar.setNavigationMode(bar.getNavigationMode() == ActionBar.NAVIGATION_MODE_STANDARD? ActionBar.NAVIGATION_MODE_TABS:ActionBar.NAVIGATION_MODE_STANDARD); return; case R.id.btn_cycle_custom_gravity: ActionBar.LayoutParams lp = (android.app.ActionBar.LayoutParams) this.mCustomView.getLayoutParams(); int newGravity = 0; switch (lp.gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { case Gravity.START: newGravity = Gravity.CENTER_HORIZONTAL; break; case Gravity.CENTER_HORIZONTAL: newGravity = Gravity.END; break; case Gravity.END: newGravity = Gravity.START; break; } // 这样的写法看起来有点晦涩,其实也可以简单一点:lp.gravity = newGravity; lp.gravity = lp.gravity & ~Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK | newGravity; Log.i(TAG, "lp.gravity:" + lp.gravity); bar.setCustomView(this.mCustomView, lp); return; } Log.i(TAG, "bar.getDisplayOptions(): " + bar.getDisplayOptions()); Log.i(TAG, "flags: " + flags); int change = bar.getDisplayOptions() ^ flags; Log.i(TAG, "change: " + change); bar.setDisplayOptions(change, flags); } @Override public void onTabSelected(Tab tab, FragmentTransaction ft) { // TODO Auto-generated method stub } @Override public void onTabUnselected(Tab tab, FragmentTransaction ft) { // TODO Auto-generated method stub } @Override public void onTabReselected(Tab tab, FragmentTransaction ft) { // TODO Auto-generated method stub } }
R.layout.action_bar_display_opstions.xml
R.layout.action_bar_display_opstions_custom.xml
本演示程序的AndroidManifest.xml