android2.x到现在的4.0有很大的差别,而手机开发者又没有经历过3.x版本的过渡,所以对于手机开发者的压力是比较大的,需要学习和适应大量新的组件与api
本文主要总结对ActionBar和Menu的学习
注意:这里以4.0为base
作为开发者,应该阅读下面重要新闻
谷歌敦促Android开发者停止使用菜单按钮
先说几句题外话
我个人觉得,应该保留实体menu按键,如果没有实体按键,那应该如何与menu交互?android提供了方案,使用action bar,action bar是显示在屏幕上的
等等,似乎有点问题,这也就是说:如果开发者需要menu,那么就需要消耗一部分屏幕的空间,虽然现在的手机很少有3寸以下的屏幕了,但是也没大到增加一个action bar而不使用户与开发者心疼的地步,起码我是这么觉得的
之前我可以隐藏状态栏标题栏但是保留menu,以后估计不行了
开始学习,以api demos - App - Action Bar中内容为主
boolean android.app.Activity.onCreateOptionsMenu(Menu menu)还是保留下来了的
- MenuItem actionItem = menu.add("Action Button");
- actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
setShowAsAction是设置ActionBar中menu的显示方式的
显示方式一共有如下5种:
从不显示;如果有空间就显示;总是显示;显示时跟随文字;默认收缩其action view
-
- public static final int SHOW_AS_ACTION_NEVER = 0;
-
- public static final int SHOW_AS_ACTION_IF_ROOM = 1;
-
-
-
-
-
-
- public static final int SHOW_AS_ACTION_ALWAYS = 2;
-
-
-
-
-
- public static final int SHOW_AS_ACTION_WITH_TEXT = 4;
-
-
-
-
-
-
- public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8;
其中SHOW_AS_ACTION_WITH_TEXT需要注意一下
在手机中,竖屏状态下text是不会显示的,切换到横屏才会显示text
SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW也需要特别说明一下
menu的每个item都可以设置action view,如果设置了,那么这个view默认是显示的,设置了SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW后,则默认不显示
另外需要说明的是:
现在手机有两种,一种是有实体按键的手机,不在action bar显示的item,按menu则会看到剩余的item
另一种是没有实体menu键的手机,他们通过action bar等触发menu,所有的item都会在action bar显示,“显示不下”的item会在action bar最后一个item触发显示剩余item列表,类似于之前我们按menu键后出现的“more”
ActionBar还提供了Tab功能
4.0中大家发现:TabActivity已经称为了过时的class,那么新的标签页如何实现,ActionBar提供一种简单的方式
- final ActionBar bar = getActionBar();
- final int tabCount = bar.getTabCount();
- final String text = "Tab " + tabCount;
- bar.addTab(bar.newTab()
- .setText(text)
- .setTabListener(new TabListener(new TabContentFragment(text))));
其中TabListener要实现ActionBar.TabListener,并实现如下几个函数
- private class TabListener implements ActionBar.TabListener {
- public void onTabSelected(Tab tab, FragmentTransaction ft) {}
- public void onTabUnselected(Tab tab, FragmentTransaction ft) {}
- public void onTabReselected(Tab tab, FragmentTransaction ft) {}
- }
函数从名称就能看出是做什么的了,不再赘述
上面是ApiDemos的示例,只需addTab即可
- void android.app.ActionBar.addTab(Tab tab)
- void android.app.ActionBar.addTab(Tab tab, boolean setSelected)
- void android.app.ActionBar.addTab(Tab tab, int position)
- void android.app.ActionBar.addTab(Tab tab, int position, boolean setSelected
除此之外,android也提供了remove和removeAll的api,不再列举
api demos中给出了Action Bar中使用menu的简单演示,效果如下
上面三个menu,下面两个,点击Sort之后弹出二级菜单
只需在xml中进行配置即可
- <menu xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:id="@+id/action_search"
- android:icon="@android:drawable/ic_menu_search"
- android:title="@string/action_bar_search"
- android:showAsAction="ifRoom"
- android:actionViewClass="android.widget.SearchView" />
- <item android:id="@+id/action_add"
- android:icon="@android:drawable/ic_menu_add"
- android:title="@string/action_bar_add" />
- <item android:id="@+id/action_edit"
- android:icon="@android:drawable/ic_menu_edit"
- android:showAsAction="always"
- android:title="@string/action_bar_edit" />
- <item android:id="@+id/action_share"
- android:icon="@android:drawable/ic_menu_share"
- android:title="@string/action_bar_share"
- android:showAsAction="ifRoom" />
- <item android:id="@+id/action_sort"
- android:icon="@android:drawable/ic_menu_sort_by_size"
- android:title="@string/action_bar_sort"
- android:showAsAction="ifRoom">
- <menu>
- <item android:id="@+id/action_sort_size"
- android:icon="@android:drawable/ic_menu_sort_by_size"
- android:title="@string/action_bar_sort_size"
- android:onClick="onSort" />
- <item android:id="@+id/action_sort_alpha"
- android:icon="@android:drawable/ic_menu_sort_alphabetically"
- android:title="@string/action_bar_sort_alpha"
- android:onClick="onSort" />
- </menu>
- </item>
- </menu>
当把手机变成横屏的时候,由于空间充足,Sort就会显示在Action Bar中,下图是点击Sort后的效果
我们点击By size和Alphabetically会发现,Sort的图标会跟着改变
- public void onSort(MenuItem item) {
- mSortMode = item.getItemId();
-
- invalidateOptionsMenu();
- }
invalidateOptionsMenu会通知系统menu发生了改变,触发onPrepareOptionsMenu和onCreateOptionsMenu,然后再看看onPrepareOptionsMenu做了些什么
- public boolean onPrepareOptionsMenu(Menu menu) {
- if (mSortMode != -1) {
- Drawable icon = menu.findItem(mSortMode).getIcon();
- menu.findItem(R.id.action_sort).setIcon(icon);
- }
- return super.onPrepareOptionsMenu(menu);
- }
调用的时候会判断状态,根据状态设置Sort图标
当我们点击Search按钮的时候,它的Action View会显示出来
- <item android:id="@+id/action_search"
- android:icon="@android:drawable/ic_menu_search"
- android:title="@string/action_bar_search"
- android:showAsAction="ifRoom"
- android:actionViewClass="android.widget.SearchView" />
xml中显示了Action View调用的是android.widget.SearchView这个class
- public boolean onCreateOptionsMenu(Menu menu) {
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.actions, menu);
- SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
- searchView.setOnQueryTextListener(this);
- return true;
- }
在这里为SearchView设置监听,onQueryTextChange在输入框文字修改时触发,onQueryTextSubmit在提交搜索时触发
在xml也可以设置ActionProvider,例如
- <item android:id="@+id/menu_item_action_provider_action_bar"
- android:showAsAction="ifRoom"
- android:title="@string/action_bar_settings"
- android:actionProviderClass="com.example.android.apis.app.ActionBarSettingsActionProviderActivity$SettingsActionProvider"/>
ActionProvider的使用暂时不做过多讨论
上面的图片是一个Action Bar,可以分为几个部分,他们都可以设置为显示或者不显示,从左至右依次为:
ActionBar.DISPLAY_HOME_AS_UP:类似于提示作用,上图为一个脱字符(Up caret),不能单独只显示这个。。
ActionBar.DISPLAY_SHOW_HOME:大大的android图片为home,当然这个默认为apk的图标,可以通过ActionBar.DISPLAY_USE_LOGO来设置使用什么图片
ActionBar.DISPLAY_SHOW_TITLE:接下来的是title
ActionBar.DISPLAY_SHOW_CUSTOM:然后是定制的View,可以通过void android.app.ActionBar.setCustomView(View view, LayoutParams layoutParams)设置
这里以Email为例,简单分析一下ActionBar在实际中的应用
上面是在模拟器上登录原生Email的截图
这里说一下所谓的原生:
原生不是指大家买来手机之后没有修改过任何内容,软件本身的样子。也不是指google的各种亲儿子本身的样子,因为无论是前者还是后者,最起码都是要经过优化和修复bug的,不是亲儿子那就很有可能进行了整容,让你看不出原来的样子
这里的原生是用google发布的源码编译出来的apk,安装到手机或模拟器上面大家就可以看到
我们主要看看Email如何使用ActionBar和menu的
页面可以发现,Email既有AcionBar又有menu,menu被放在了底部,先看ActionBar
Email的主Activity是EmailActivity,我就不画类图了,因为总是画不明白,怕误导大家,所以就用类似堆栈log的形式来介绍
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- ……
- initUIController();
- ……
- }
-
- private void initUIController() {
- mUIController = UiUtilities.useTwoPane(this)
- ? new UIControllerTwoPane(this) : new UIControllerOnePane(this);
- }
这里初始化了一个UIController,其中的关系是这样的
UIControllerBase为父类,是一个抽象类,下面有两个实现类分别为UIControllerOnePane和UIControllerTwoPane(实在理解不了为什么这么命名。。。)
UIControllerOnePane为手机UI实现,UIControllerTwoPane为平板UI实现,我们这里只考虑UIControllerOnePane
- public UIControllerBase(EmailActivity activity) {
- mActivity = activity;
- mFragmentManager = activity.getFragmentManager();
- mRefreshManager = RefreshManager.getInstance(mActivity);
- mActionBarController = createActionBarController(activity);
- if (DEBUG_FRAGMENTS) {
- FragmentManager.enableDebugLogging(true);
- }
- }
这里包含了一个ActionBarController,createActionBarController是抽象方法,下面是UIControllerOnePane的实现
- @Override
- protected ActionBarController createActionBarController(Activity activity) {
-
-
- return new ActionBarController(activity, activity.getLoaderManager(),
- activity.getActionBar(), new ActionBarControllerCallback());
- }
最关键的就是ActionBarController了,它包含ActionBar所有内容
- public ActionBarController(Context context, LoaderManager loaderManager,
- ActionBar actionBar, Callback callback) {
- mContext = context;
- mLoaderManager = loaderManager;
- mActionBar = actionBar;
- mCallback = callback;
- mDelayedOperations = new DelayedOperations(Utility.getMainThreadHandler());
- mAllFoldersLabel = mContext.getResources().getString(
- R.string.action_bar_mailbox_list_title);
- mAccountsSelectorAdapter = new AccountSelectorAdapter(mContext);
-
-
- mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_CUSTOM);
-
-
- mActionBar.setCustomView(R.layout.action_bar_custom_view);
- mActionBarCustomView = (ViewGroup) mActionBar.getCustomView();
-
-
- mAccountSpinnerContainer =
- UiUtilities.getView(mActionBarCustomView, R.id.account_spinner_container);
- mAccountSpinner = UiUtilities.getView(mActionBarCustomView, R.id.account_spinner);
- mAccountSpinnerDefaultBackground = mAccountSpinner.getBackground();
-
- mAccountSpinnerLine1View = UiUtilities.getView(mActionBarCustomView, R.id.spinner_line_1);
- mAccountSpinnerLine2View = UiUtilities.getView(mActionBarCustomView, R.id.spinner_line_2);
- mAccountSpinnerCountView = UiUtilities.getView(mActionBarCustomView, R.id.spinner_count);
-
-
- mAccountDropdown = new AccountDropdownPopup(mContext);
- mAccountDropdown.setAdapter(mAccountsSelectorAdapter);
-
- mAccountSpinner.setOnClickListener(new View.OnClickListener() {
- @Override public void onClick(View v) {
- if (mAccountsSelectorAdapter.getCount() > 0) {
- mAccountDropdown.show();
- }
- }
- });
- }
从上面可以看出,ActionBar实际上也不复杂,只是设置了显示选项和一个CustomView,复杂的内容几乎全部集中在这个CustomView上了
-
- mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_CUSTOM);
-
-
- mActionBar.setCustomView(R.layout.action_bar_custom_view);
- mActionBarCustomView = (ViewGroup) mActionBar.getCustomView();
当点击CustomView时候会有弹出下拉框,mAccountDropdown就发挥作用了
- private class AccountDropdownPopup extends ListPopupWindow
设置了mAccountSpinner.setOnClickListener,调用到mAccountDropdownshow()
mAccountDropdownshow显示由mAccountsSelectorAdapter提供
- public class AccountSelectorAdapter extends CursorAdapter
其中的数据由CursorWithExtras com.android.email.activity.ActionBarController.mCursor提供
mCursor只在一个地方进行刷新,那就是加载账户邮箱信息的时候
-
-
-
- private void loadAccountMailboxInfo(final long accountId, final long mailboxId) {
- mLoaderManager.restartLoader(LOADER_ID_ACCOUNT_LIST, null,
- new LoaderCallbacks<Cursor>() {
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- return AccountSelectorAdapter.createLoader(mContext, accountId, mailboxId);
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
- mCursor = (AccountSelectorAdapter.CursorWithExtras) data;
- updateTitle();
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {
- mCursor = null;
- updateTitle();
- }
- });
- }
我们可以发现,加载完毕之后Cursor更新了,然后updateTitle,不用去看函数也能猜出这里是更新ActionBar
为了刷新列表,调用了下面语句
- mAccountsSelectorAdapter.swapCursor(mCursor);
updateTitle就不再进行分析了,里面几乎都是刷新那个CustomView的内容
下面看看底部的菜单,之前的学习中,api demos里并没有演示这种情况,我们用hierarchyviewer查看,发现页面上下都是一个ActionBarContainer的容器
上面的ActionBar我们已经知道它是怎么来的了,那下面的呢?原因在于manifest的配置
- <activity
- android:name=".activity.EmailActivity"
- android:uiOptions="splitActionBarWhenNarrow"
- >
- </activity>
splitActionBarWhenNarrow使得ActionBar被拆分成上下两个部分
splitActionBarWhenNarrow 用于显示Activity在窄屏设备(如竖屏手机)上运行时的所有menu项。当然,如果手机有menu键的时候,当menu项过多而显示不下的时候按Menu键即可
这时迷题已解,没有什么神秘的地方了,看看menu配置,一目了然
- <menu xmlns:android="http://schemas.android.com/apk/res/android">
- <item
- android:id="@+id/compose"
- android:orderInCategory="100"
- android:alphabeticShortcut="c"
- android:title="@string/compose_action"
- android:icon="@drawable/ic_menu_compose_normal_holo_light"
- android:showAsAction="ifRoom"
- />
- <item
- android:id="@+id/search"
- android:orderInCategory="200"
- android:alphabeticShortcut="s"
- android:title="@string/search_action"
- android:icon="@drawable/ic_menu_search_holo_light"
- android:showAsAction="ifRoom"
- />
- <item
- android:id="@+id/show_all_mailboxes"
- android:orderInCategory="300"
- android:alphabeticShortcut="c"
- android:title="@string/mailbox_list_account_selector_show_all_folders"
- android:icon="@drawable/ic_menu_move_to_holo_light"
- android:showAsAction="ifRoom"
- />
- <item
- android:id="@+id/refresh"
- android:orderInCategory="400"
- android:alphabeticShortcut="r"
- android:title="@string/refresh_action"
- android:icon="@drawable/ic_menu_refresh_holo_light"
- android:showAsAction="ifRoom"
- />
-
- <item
- android:id="@+id/newer"
- android:orderInCategory="1500"
- android:icon="@drawable/menu_item_newer"
- android:showAsAction="always"
- android:visible="false"
- />
- <item
- android:id="@+id/older"
- android:orderInCategory="1600"
- android:icon="@drawable/menu_item_older"
- android:showAsAction="always"
- android:visible="false"
- />
- <item
- android:id="@+id/mailbox_settings"
- android:orderInCategory="2000"
- android:title="@string/mailbox_settings_action"
- android:icon="@android:drawable/ic_menu_preferences"
- />
- <item
- android:id="@+id/account_settings"
- android:orderInCategory="3000"
- android:title="@string/settings_action"
- android:icon="@android:drawable/ic_menu_preferences"
- />
- </menu>
当我们横屏的时候,menu就会跑到上面去了
下面是带回退导航的
他能使用户回到上一个页面(并不是上一个activity,回到上一个activity可以通过按back键实现)
- mActionBar.setDisplayOptions(showUp ? ActionBar.DISPLAY_HOME_AS_UP : 0, ActionBar.DISPLAY_HOME_AS_UP);
在之前的refreshInernal中已经设置过了
当我们打开或新建一个邮件的时候,页面会跳转到新的activity
- public class MessageCompose extends Activity implements OnClickListener, OnFocusChangeListener, DeleteMessageConfirmationDialog.Callback, InsertQuickResponseDialog.Callback
这里我们看到了,页面依然有回退导航,点击之后能回退到上一个页面,当然,不再是当前的activity了
那么它是如何做到的,代码如下
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (handleCommand(item.getItemId())) {
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- private boolean handleCommand(int viewId) {
- switch (viewId) {
- case android.R.id.home:
- onBack(false );
- return true;
- ……
- }
-
-
-
-
-
- private void onBack(boolean systemKey) {
- finish();
- if (isOpenedFromWithinApp()) {
-
- return;
- }
-
- if (isOpenedFromWidget() || !systemKey) {
-
-
- startActivity(Welcome.createOpenAccountInboxIntent(this, mAccount.mId));
- }
- }
点击回退导航时,触发的menu是android.R.id.home
剩下的事情就好办了,这里交给了onBack来处理
至此Email应用的ActionBar和menu就基本分析完了,如果有不准确的地方还希望大家指正
最后附上别人的几篇翻译,内容很基础,也比api demos多,值得一看
Android 用户界面---操作栏(Action Bar 一) :基本操作
Android 用户界面---操作栏(Action Bar 二) :分离式ActionBar与回退导航
Android 用户界面---操作栏(Action Bar 三) :ActionView
Android 用户界面---操作栏(Action Bar 四) :ActionProvider与Tab
Android 用户界面---操作栏(Action Bar 五) :主要是关于样式