ActionBar是Activity开发的标配,但是从5.0开始逐渐由Toolbar取代,主要因为Toolbar使用方式更加简单也更容易定制。为了能够更好的理解和使用Toolbar,有必要先对Actionbar的功能做深入的了解。这里简单使用ActionBar实现Tab切换效果,然后在用Toolbar来替换实现。
ActionBar的内容组成如上图所示,图中的每个部分都有对应的设置方法。首先从最开始的Home开始讲起,Home通常用来导航到前一个页面,当然用户也可以自己拦截Home的点击事件。
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
Toast.makeText(getApplicationContext(), "点击Home", Toast.LENGTH_SHORT).show();
return true;
}
...
}
后面的Logo和Title设置比较简单,不再详述。接下来需要注意如果想要展示CustomView需要设置setDisplayShowCustomEnable(true)。
实际上ActionBar除了简单的展示标题和菜单按钮还可以做导航,比如下拉列表和Tab导航栏,不过需要设置actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS),调用这个方法会发现已经被废弃了,也就是说Android官方已经不再推荐使用这种导航方式。这里为了能够使用ActionBar自带的导航效果还是使用这个接口。
ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
ActionBar.Tab tab1 = actionBar.newTab().setText("魏国");
tab1.setTabListener(new MyTabListener(UserFragment.newInstance(UserFragment.WEI)));
ActionBar.Tab tab2 = actionBar.newTab().setText("蜀国");
tab2.setTabListener(new MyTabListener(UserFragment.newInstance(UserFragment.SHU)));
ActionBar.Tab tab3 = actionBar.newTab().setText("吴国");
tab3.setTabListener(new MyTabListener(UserFragment.newInstance(UserFragment.WU)));
actionBar.addTab(tab1);
actionBar.addTab(tab2);
actionBar.addTab(tab3);
private static class MyTabListener implements ActionBar.TabListener {
private Fragment fragment;
public MyTabListener(Fragment fragment) {
this.fragment = fragment;
}
@Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
ft.replace(R.id.main_container, fragment);
}
@Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
ft.remove(fragment);
}
@Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
}
};
定义的每个Tab都需要添加一个TabListener对象,TabListener监听当前的Tab被选中还是被置空,里面都会传递一个FragmentTransaction对象,这个事务不必提交调用者做提交操作。每个Tab都会被绑定一个Fragment对象,通过切换Tab时切换不同的Fragment实现Tab导航效果。
接着为每个Activity和每个Fragment都添加onCreateOptionsMenu方法,并且配置好Menu文件。
// Activity方法
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.option_menu, menu);
return super.onCreateOptionsMenu(menu);
}
// Fragment里的方法
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
if (WEI.equalsIgnoreCase(state)) {
inflater.inflate(R.menu.wei, menu);
} else if (SHU.equalsIgnoreCase(state)) {
inflater.inflate(R.menu.shu, menu);
MenuItem menuItem = menu.findItem(R.id.action_file);
uploadActionProvider = (UploadActionProvider) MenuItemCompat.getActionProvider(menuItem);
} else {
inflater.inflate(R.menu.wu, menu);
}
}
这种实现并不会导致冲突,系统很好地处理了Activity和Fragment都定义了OptionsMenu等情况,Activity的Menu排在前,Fragment的Menu排在后面,而且只有被添加到当前视图树的Fragment才会展示Menu。这样每次切换Fragment的时候就会同时切换ActionBar的Menu按钮。
接下来查看每个menu文件的配置,Activity的OptionsMenu配置如下,只是普通的Action,最后的showAsAction需要使用app命名空间,否则不会起作用,因为这里使用的是getSupportActionBar也就是support包里的对象,需要用自定义的命名空间。
// Activity的menu
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_share"
android:title="发送"
android:icon="@drawable/ic_send"
app:showAsAction="ifRoom" />
menu>
第一个Fragment的OptionMenu布局,包含了一个actionViewClass配置的属性,这个属性的类需要是一个支持压缩和打开的类。
"1.0" encoding="utf-8"?>
// ActionView需要实现的接口
public interface CollapsibleActionView {
// 打开ActionView时候的动作
public void onActionViewExpanded();
// 压缩ActionView时候的动作
public void onActionViewCollapsed();
}
对应的SeachBarView实现代码如下
public class SearchBar extends LinearLayout implements CollapsibleActionView {
private LinearLayout searchLayout;
private EditText editText;
private Button submit;
private ImageView actionView;
public SearchBar(Context context) {
this(context, null);
}
public SearchBar(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SearchBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
// 包含一个图片一个EditText和一个Button的布局
setOrientation(LinearLayout.HORIZONTAL);
View view = LayoutInflater.from(getContext()).
inflate(R.layout.layout_search_bar, this, false);
addView(view);
ImageView imageView = new ImageView(getContext());
LayoutParams params = new LayoutParams(CommonUtils.dp2px(30),
CommonUtils.dp2px(30), Gravity.CENTER_VERTICAL);
imageView.setImageResource(R.drawable.sun_set);
addView(imageView, params);
this.actionView = imageView;
this.searchLayout = (LinearLayout) view;
this.editText = (EditText) view.findViewById(R.id.edit_query);
this.submit = (Button) view.findViewById(R.id.submit);
this.submit.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getContext(), editText.getText().toString(), Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onActionViewExpanded() {
// 打开的时候图片隐藏,搜索界面展示
this.actionView.setVisibility(View.GONE);
this.searchLayout.setVisibility(View.VISIBLE);
}
@Override
public void onActionViewCollapsed() {
// 关闭的时候隐藏搜索界面,展示图片
this.searchLayout.setVisibility(View.GONE);
this.actionView.setVisibility(View.VISIBLE);
}
}
实现非常简单,就是支持用户压缩和展开ActionView的功能。第二个和第三个都是自定义的ActionView,它们里面的View完全由用户自己控制,需要按照ActionViewProvider来实现。
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_file"
android:title="上传"
app:actionProviderClass="com.example.design.provider.UploadActionProvider"
android:icon="@drawable/ic_cloud_upload"
app:showAsAction="always" />
menu>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_new"
android:title="新建"
android:icon="@drawable/ic_assignment"
app:actionProviderClass="com.example.design.provider.FileActionProvider"
app:showAsAction="ifRoom" />
menu>
ActionProvider可以实现为普通的View或者SubMenu的方式,首先来看实现为普通View的方式。
public class UploadActionProvider extends ActionProvider {
private int count = 0;
private TextView mTxtCount;
public UploadActionProvider(Context context) {
super(context);
}
// 生成自定义的View
@Override
public View onCreateActionView() {
LayoutInflater inflater = LayoutInflater.from(getContext());
View view = inflater.inflate(R.layout.upload_layout, null);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getContext().getApplicationContext(), "正在下载中...", Toast.LENGTH_SHORT).show();
setCount(++count);
}
});
mTxtCount = (TextView) view.findViewById(R.id.download_count);
return view;
}
public void setCount(int count) {
if (mTxtCount != null) {
mTxtCount.setText(String.valueOf(count));
}
}
}
可以看到自定义的View实现只需要在onCreateActionView里配置好各种界面展示元素和控制事件就可以了,这种实现通常可以用来做一些比如购物车数量未读消息数量等需要更新的简单ActionView。除了上面的简单View,ActionProvider实现还支持SubMenu,这种方式就需要覆盖两个方法。
public class FileActionProvider extends ActionProvider {
public FileActionProvider(Context context) {
super(context);
}
@Override
public View onCreateActionView() {
return null;
}
@Override
public boolean hasSubMenu() {
return true;
}
@Override
public void onPrepareSubMenu(SubMenu subMenu) {
super.onPrepareSubMenu(subMenu);
subMenu.clear();
subMenu.add("新建文件").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
Toast.makeText(getContext(), "新建文件", Toast.LENGTH_SHORT).show();
return true;
}
});
subMenu.add("打开文件").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
Toast.makeText(getContext(), "打开文件", Toast.LENGTH_SHORT).show();
return true;
}
});
subMenu.add("删除文件").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
Toast.makeText(getContext(), "删除文件", Toast.LENGTH_SHORT).show();
return true;
}
});
}
}
hasSubMenu需要返回true,同时在onPrepareSubMenu方法里添加各种子菜单对象。这种方式产生的子菜单比较死板,用户也可以在onCreateActionView生成自定义View,同时配合PopupMenu来实现子菜单功能。
Android 5.0之后不再推荐使用ActionBar和它的导航功能,直接使用Toolbar来替代ActionBar是一种更好的开发方式。替换是需要在Activity的布局文件中添加Toolbar对象并把它的引用设置为ActionBar对象。
"http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.design.ToolbarActivity">
.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="56dp">
.support.v7.widget.Toolbar>
.support.design.widget.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="45dp">
.support.design.widget.TabLayout>
.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent">
.support.v4.view.ViewPager>
整个Activity的布局如上面的代码所示,Tab切换的功能有TabLayout实现,切换效果则通过ViewPager+Fragment来实现。接下来只需要为Toobar设置为ActionBar对象,ViewPager设置Adapter展示Fragment就可以了。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_toolbar);
toolbar = (Toolbar) findViewById(R.id.toolbar);
// 设置toolbar为Actionbar对象
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
tabLayout = (TabLayout) findViewById(R.id.tab_layout);
viewPager = (ViewPager) findViewById(R.id.view_pager);
fragmentList.add(UserFragment.newInstance(UserFragment.WEI));
fragmentList.add(UserFragment.newInstance(UserFragment.SHU));
fragmentList.add(UserFragment.newInstance(UserFragment.WU));
viewPager.setAdapter(new FragmentStatePagerAdapter(getSupportFragmentManager()) {
@Override
public int getCount() {
return fragmentList.size();
}
@Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
@Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
});
tabLayout.setupWithViewPager(viewPager);
}
其他的都不需要改变,查看实现结果和之前的效果完全一样,可见Toolbar的替换是非常轻松简单地。