Android轩辕剑之ActionBar之三
传送门 ☞ 轮子的专栏 ☞ 转载请注明 ☞ http://blog.csdn.net/leverage_1229
6添加Action Provider
与action view类似,Action Provider(由ActionProvider类定义的)用一个定制的布局代替一个action item,它还需要对所有这些item行为的控制。当你在action bar中给一个菜单项声明一个action item时,它不仅要一个定制的布局来控制这个菜单项的外观,而且当它显示在溢出菜单中时,还要处理它的默认事件。无论是在action bar中还是在溢出菜单中,它都能够提供一个子菜单。例如,ActionProvider的扩展类ShareActionProvider,它通过在action bar中显示一个有效的共享目标列表来方便共享操作。与使用传统的调用ACTION_SEND类型Intent对象的action item不同,你能够声明一个ShareActionProvider对象来处理一个action item。这种action provider会保留一个带有处理ACTION_SEND类型Intent对象的应用程序的下拉列表,即使这个菜单项显示在溢出菜单中。所以当你使用像这样的Action Provider时,你不必处理有关这个菜单项的用户事件。
要给一个操作项声明一个操作提供器,就要在菜单资源中对应的<item>元素中定义android:actionProviderClass属性,提供器要使用完整的类名。
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/menu_share" android:title="@string/share" android:showAsAction="ifRoom" android:actionProviderClass="android.widget.ShareActionProvider" /> ... </menu>在这个例子中,用ShareActionProvider类作为action provider,在这里,action provider需要菜单项的控制,并处理它们在action bar中的外观和行为以及在溢出菜单中的行为。你必须依然给这个菜单项提供一个用于溢出菜单的文本标题。
尽管action provider提供了它在溢出菜单中显示时所能执行的默认操作,但是Activity(或Fragment)也能够通过处理来自onOptionsItemSelected()回调方法的点击事件来重写这个默认操作。如果你不在这个回调方法中处理点击事件,那么action provider会接收onPerformDefaultAction()回调来处理事件。但是,如果action provider提供了一个子菜单,那么Activity将不会接收onOptionsItemSelected()回调,因为子菜单的显示替代了选择时调用的默认菜单行为。
6.1使用ShareActionProvider类
如果你想要在action bar中提供一个“共享”操作,以充分利用安装在设备上的其他应用程序(如,把一张图片共享给短短信或社交应用程序使用),那么使用ShareActionProvider类是一个有效的方法,而不是添加一个调用ACTION_SEND类型Intent对象的action item。当你给一个action item使用ShareActionProvider类时,它会呈现一个带有能够处理ACTION_SEND类型Intent对象的应用程序的下拉列表。创建子菜单的所有逻辑,包括共享目标的封装、点击事件的处理(包在溢出菜单中的项目显示)等,都在ShareActionProvider类中实现了---你需要编写的唯一的代码是给对应的菜单项声明action provider,并指定共享的Intent对象。
默认情况,ShareActionProvider对象会基于用户的使用频率来保留共享目标的排列顺序。使用频率高的目标应用程序会显示在下来列表的上面,并且最常用的目标会作为默认共享目标直接显示在action bar。默认情况下,排序信息被保存在由DEFAULT_SHARE_HISTORY_FILE_NAME指定名称的私有文件中。如果你只使用一种操作类型ShareActionProvider类或它的一个子类,那么你应该继续使用这个默认的历史文件,而不需要做任何事情。但是,如果你使用了不同类型的多个操作的ShareActionProvider类或它的一个子类,那么为了保持它们自己的历史,每种ShareActionProvider类都应该指定它们自己的历史文件。给每种ShareActionProvider类指定不同的历史文件,就要调用setShareHistoryFileName()方法,并且提供一个XML文件的名字(如,custom_share_history.xml)
注意:尽管ShareActionProvider类是基于使用频率来排列共享目标的,但是这种行为是可扩展的,并且ShareActionProvider类的扩展能够基于历史文件执行不同的行为和排序。
要添加ShareActionProvider对象,只需简单的给android.actionProviderClass属性设定android.widget.ShareActionProvider属性值就可以了。唯一要做的事情是定义你要用于共享的Intent对象,你必须先调用getActionProvider()方法来获取跟菜单项匹配的ShareActionProvider对象,然后调用setShareIntent()方法。
如果对于共享的Intent对象的格式依赖于被选择的菜单项,或其他在Activity生存周期内改变的变量,那么你应该把ShareActionProvider对象保存在一个成员属性里,并在需要的时候调用setShareIntent()方法来更新它。
private ShareActionProvider mShareActionProvider; ... @Override public boolean onCreateOptionsMenu(Menu menu) { mShareActionProvider = (ShareActionProvider) menu.findItem(R.id.menu_share).getActionProvider(); mShareActionProvider.setShareIntent(getDefaultShareIntent()); return true; }上例中ShareActionProvider对象处理所有的跟这个菜单项有关的用户交互,并且不需要处理来自onOptionsItemSelected()回调方法的点击事件。
6.2创建自定义的Action Provider
当你想要创建一个有动态行为和在溢出菜单中有默认图标的action view时,,继承ActionProvider类来定义这些行为是一个比好的的方案。创建自己的action provider,提供一个有组织的可重用的组件,而不是在Fragment或Activity的代码中处理各种action item的变换和行为。要创建自己的action provider,只需简单的继承ActionProvider类,并且实现合适的回调方法。你应该实现以下重要的回调方法:6.2.1ActionProvider()
这个构造器把应用程序的Context对象传递个操作提供器,你应该把它保存在一个成员变量中,以便其他的回调方法使用。6.2.2OnCreateActionView()
这是你给菜单项定义action view的地方。使用从构造器中接收的Context对象,获取一个LayoutInflater对象的实例,并且用XML资源来填充action view,然后注册事件监听器。public View onCreateActionView() { LayoutInflater layoutInflater = LayoutInflater.from(mContext); View view = layoutInflater.inflate(R.layout.action_provider, null); ImageButton button = (ImageButton) view.findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // Do something... } }); return view; }
6.2.3onPerformDefaultAction()
在选中溢出菜单中的菜单时,系统会调用这个方法,并且action provider应该这对这个选中的菜单项执行默认的操作。但是,如果你的action provider提供了一个子菜单,即使是溢出菜单中一个菜单项的子菜单,那么也要通过onPrepareSubMenu()回调方法来显示子菜单。这样onPerformDefaultAction()在子菜单显示时就不会被调用。注意:实现了onOptionsItemSelected()回调方法的Activity或Frament对象能够通过处理item-selected事件(并且返回true)来覆盖action provider的默认行为,这种情况下,系统不会调用onPerformDefaultAction()回调方法。7添加导航标签
当你想要在一个Activity中提供导航标签时,使用action bar的选项标签是一个非常好的选择(而不是使用TabWidget类),因为系统会调整action bar选项标签来适应不同尺寸的屏幕的需要---在屏幕足够宽的时候,导航选项标签会被放到主action bar中;当屏幕太窄的时候,选项标签会被放到一个分离的横条中。要使用选项标签在Fragmengt之间切换,你必须在每次选择一个选项标签时执行一个Fragment事务处理。如果你不熟悉如何使用FragmentTransaction对象来改变Fragment,请阅读先前Fragment章节。首先,你的布局必须包含一个用于放置跟每个Fragment对象关联的选项标签的ViewGroup对象。并且要确保这个ViewGroup对象有一个资源ID,以便你能够在选项标签的切换代码中能够引用它。另外,如果选项标签的内容填充在Activity的布局中(不包括action bar),那么Activity不需要任何布局(你甚至不需要调用setContentView()方法)。相反,你能够把每个Fragment对象放到默认的根ViewGroup对象中,你能够用android.R.id.content ID来引用这个ViewGroup对象(在Fragment执行事务期间,你能够在下面的示例代码中看到如何使用这个ID的。
决定了Fragment对象在布局中的显示位置后,添加选项标签的基本过程如下:
实现ActionBar.TabListener接口。这个接口中回调方法会响应选项标签上的用户事件,以便你能够切换Fragment对象;
对于每个要添加的选项标签,都要实例化一个ActionBar.Tab对象,并且调用setTabListener()方法设置ActionBar.Tab对象的事件监听器。还可以用setText()或setIcon()方法来设置选项标签的标题或图标;
通过调用addTab()方法,把每个选项标签添加到操作栏。
在查看ActionBar.TabListener接口时,注意到回调方法只提供了被选择的ActionBar.Tab对象和执行Fragment事务处理的FragmentTransaction对象---没有说明任何有关切换什么Fragment。因此。你必须定义自己的每个ActionBar.Tab之间的关联,以及ActionBar.Tab所代表的适合的Fragment对象(为了执行合适的Fragment事务处理)。依赖你的设计,会有几种不同的方法来定义这种关联。在下面的例子中,ActionBar.TabListener接口的实现提供了一个构造器,这样每个新的选项标签都会使用它自己的监听器实例。每个监听器实例都定义了几个在对应Fragment对象上执行事务处理时必须的几个成员变量。例如,以下示例是ActionBar.TabListener接口的一种实现,在这个实现中,每个选项标签都使用了它自己的监听器实例。
public static class TabListener<T extends Fragment> implements ActionBar.TabListener { private Fragment mFragment; private final Activity mActivity; private final String mTag; private final Class<T> mClass; public TabListener(Activity activity, String tag, Class<T> clz) { mActivity = activity; mTag = tag; mClass = clz; } public void onTabSelected(Tab tab, FragmentTransaction ft) { if (mFragment == null) { mFragment = Fragment.instantiate(mActivity, mClass.getName()); ft.add(android.R.id.content, mFragment, mTag); } else { ft.attach(mFragment); } } public void onTabUnselected(Tab tab, FragmentTransaction ft) { if (mFragment != null) { ft.detach(mFragment); } } public void onTabReselected(Tab tab, FragmentTransaction ft) { } }注意:针对每个回调中的Fragment事务处理,你都不必调用FragmentTransaction.commit()方法---系统会调用这个方法,并且如果你自己调用了这个方法,有可能会抛出一个异常。你也不能把这些Fragment事务处理添加到后台堆栈中。在这个例子中,当对应的选项标签被选择时,监听器只是简单的把一个Fragment对象附加(attach()方法)到Activity布局上---或者,如果没有实例化,就会创建这个Fragment对象,并且把它添加(add()方法)到布局中(android.R.id.content ViewGroup的一个子类),当这个选项标签解除选择时,对应的Fragment对象也会被解除与布局的依附关系。
ActionBar.TabListener的实现做了大量的工作,剩下的事情就是创建每个ActionBar.Tab对象并把它添加到ActionBar对象中,另外,你必须调用setNavigationMode(NAVIGATION_MODE_TABS)方法来让选项标签可见。如果选项标签的标题实际指示了当前的View对象,你也可以通过调用setDisplayShowTitleEnabled(false)方法来禁用Activity的标题。
例如,下面的代码使用上面定义的监听器在action bar中添加了两个选项标签。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActionBar actionBar = getActionBar(); actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); actionBar.setDisplayShowTitleEnabled(false); Tab tab = actionBar.newTab() .setText(R.string.artist) .setTabListener(new TabListener<ArtistFragment>( this, "artist", ArtistFragment.class)); actionBar.addTab(tab); tab = actionBar.newTab() .setText(R.string.album) .setTabListener(new TabListener<AlbumFragment>( this, "album", AlbumFragment.class)); actionBar.addTab(tab); }如果Activity终止了,那么你应该保存当前选择的选项标签的状态,以便当用户再次返回时,你能够打开合适的选项标签。在保存状态的时刻,你能够用getSelectedNavigationIndex()方法查询当前的被选择的选项标签。这个方法返回被选择的选项标签的索引位置。
注意:在某些情况下,Android系统会把action bar选项标签作为一个下拉列表来显示,以便确保操作栏的最优化显示。并且保存每个Fragment所必须的状态是至关重要的,因为当用户用选项标签在Fragment对象间切换时,它会查看Fragment在离开时样子。