转载请注明出处:http://blog.csdn.net/sweetvvck/article/details/38645297
通过前面的几篇博客,大家看到了Google是如何解释action bar和fragment以及推荐的用法。俗话说没有demo的博客不是好博客,下面我会介绍一下action bar和fragment在实战中的应用,以及相关demo源码,希望和大家相互交流。
了解过fragment的同学们应该都知道,fragment是android 3.0版本才出现的的,因此如果要在支持android 3.0一下版本的工程中使用fragment的话是需要添加Support Library的。具体如何添加我就不再赘述,可以看我前面的博客Android学习路线(二十一)运用Fragment构建动态UI——创建一个Fragment,下面的项目支持到API Level最低为8,所以项目中也会使用到Support Library。
作为一个有上进心的Android开发者,我们是希望项目的设计符合Android Design的。Android Design是Google官方推荐的应用设计原则,不了解Android Design的同学可以去了解下,我这里有官方翻译文档。
我发现“知乎”的App设计是符合Android Design的,那么我们的项目就来模仿知乎的主界面。首先看下效果图:
我们来分析一下这样的界面应该怎么实现,从上图可以看出“知乎”android端使用了action bar和drawerlayout,同时drawer中item切换主界面应该是fragment。
新建一个工程FakeZhihu:
从上图可以看到,使用最新的adt插件创建android项目时,如果选择的Minimum Required SDK为8,而Target SDK大于它的话,系统会自动在项目中导入Support v4包;在创建项目向导最后一步可以选择Navigation Type,如果选择了Navigation Drawer,adt工具会在创建项目时自动生成DrawerLayout相关示例代码。但由于DrawerLayout是在高版本的API中出现的,因此adt工具会帮助导入Support v7 appcompat包,这样DrawerLayout就可以兼容到Android2.2了。没有使用最新版的adt工具也没有关系,我提供的demo里有Support v4包和Support v7包,大家可以直接使用。
下面来看看代码如何实现,android默认的holo主题只提供两种色调,和官方的action bar比较可以看出“知乎”的action bar的颜色以及action bar上action item的颜色以及title的字体大小都是自定义的,那么我们来模仿它自定义一下action bar。
首先我们打开res目录下的style文件,自定义一个主题和action bar的style,然后在自定义主题中引用自定义的action bar的style:
- <?xml version="1.0" encoding="utf-8"?>
- <resources>
- <!-- the theme applied to the application or activity -->
- <style name="CustomActionBarTheme"
- parent="@style/Theme.AppCompat.Light.DarkActionBar">
- <item name="android:actionBarStyle">@style/MyActionBar</item>
- <!-- Support library compatibility -->
- <item name="actionBarStyle">@style/MyActionBar</item>
- </style>
- <!-- ActionBar styles -->
- <style name="MyActionBar"
- parent="@style/Widget.AppCompat.Light.ActionBar.Solid.Inverse">
- <item name="android:background">@drawable/actionbar_background</item>
- <item name="android:titleTextStyle">@style/MyTitleStyle</item>
- <!-- Support library compatibility -->
- <item name="background">@drawable/actionbar_background</item>
- <item name="titleTextStyle">@style/MyTitleStyle</item>
- </style>
- <style name="MyTitleStyle"
- parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Title.Inverse">
- <item name="android:textSize">20dp</item>
- </style>
- </resources>
完成自定义主题和style后要记得在manifest文件中应用:
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.sweetvvck.fakezhihu"
- android:versionCode="1"
- android:versionName="1.0" >
- <uses-sdk
- android:minSdkVersion="8"
- android:targetSdkVersion="19" />
- <application
- android:allowBackup="true"
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name"
- android:theme="@style/CustomActionBarTheme" >
- <activity
- android:name="com.sweetvvck.fakezhihu.MainActivity"
- android:label="@string/app_name" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
- </manifest>
接下来要给app添加DrawerLayout了,修改MainActivity的布局文件,添加一个DrawerLayout,内容非常简单,其中包含一个Drawer和内容布局的Container:
- <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/drawer_layout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context="com.sweetvvck.fakezhihu.MainActivity" >
- <FrameLayout
- android:id="@+id/container"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
- <fragment
- android:id="@+id/navigation_drawer"
- android:name="com.sweetvvck.fakezhihu.NavigationDrawerFragment"
- android:layout_width="@dimen/navigation_drawer_width"
- android:layout_height="match_parent"
- android:layout_gravity="start" />
- </android.support.v4.widget.DrawerLayout>
创建完DrawerLayout布局后,我们来为Drawer定义一个fragment,我们可以看到知乎的Drawer中只是包含了一个ListView。这个ListView的第一项和其它项的布局不一样,我们可以想到用ListView加上headerView来实现,知道这些后,我们来创建一个NavigationDrawerFragment继承自Fragment,这个fragment的布局包含一个ListView:
- <ListView xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#fff"
- android:choiceMode="singleChoice"
- android:divider="#c3c3c3"
- android:dividerHeight="0.5dp"
- tools:context="com.sweetvvck.fakezhihu.NavigationDrawerFragment" />
- <string-array name="item_title">
- <item>首页</item>
- <item>发现</item>
- <item>关注</item>
- <item>收藏</item>
- <item>草稿</item>
- <item>搜索</item>
- <item>提问</item>
- <item>设置</item>
- </string-array>
- String[] itemTitle = getResources().getStringArray(R.array.item_title);
- int[] itemIconRes = {
- R.drawable.ic_drawer_home,
- R.drawable.ic_drawer_explore,
- R.drawable.ic_drawer_follow,
- R.drawable.ic_drawer_collect,
- R.drawable.ic_drawer_draft,
- R.drawable.ic_drawer_search,
- R.drawable.ic_drawer_question,
- R.drawable.ic_drawer_setting};
- for (int i = 0; i < itemTitle.length; i++) {
- DrawerListItem item = new DrawerListItem(getResources().getDrawable(itemIconRes[i]), itemTitle[i]);
- mData.add(item);
- }
实现这样的效果有两个步骤:
第一:在ListView中指定android:choiceMode="singleChoice";
第二:给ListView的Item的布局设置一个特殊的背景drawable,这个drawable包含当状态为activated时的背景和常态下的背景;同时这个item布局中的图片src和文字颜色也要坐相应的设置;
item的背景:
- <?xml version="1.0" encoding="utf-8"?>
- <selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_activated="true" android:drawable="@drawable/activated_background_color" />
- <item android:drawable="@android:color/transparent" />
- </selector>
- <?xml version="1.0" encoding="utf-8"?>
- <selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_activated="true" android:drawable="@drawable/ic_drawer_home_pressed" />
- <item android:drawable="@drawable/ic_drawer_home_normal" />
- </selector>
- <?xml version="1.0" encoding="utf-8"?>
- <!-- Copyright (C) 2011 Google Inc. All Rights Reserved. -->
- <selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false"
- android:color="#ff999999"/>
- <item android:state_activated="true"
- android:color="@android:color/white" />
- <item
- android:color="#636363" />
- </selector>
考虑到用户在第一次使用app的时候可能不知道有Drawer的存在,我们可以在app第一次被启动时让Drawer处于打开状态,之后再默认隐藏,这是实际项目中常用的手段,这里我们用sharedpreference来实现:
- // 通过这个flag判断用户是否已经知道drawer了,第一次启动应用显示出drawer(抽屉),之后启动应用默认将其
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
- mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false);
- /**
- * 宿主activity要实现的回调接口
- * 用于activity与该fragment之间通讯
- */
- public static interface NavigationDrawerCallbacks {
- /**
- * 当drawer中的某个item被选择是调用该方法
- */
- void onNavigationDrawerItemSelected(String title);
- }
- @Override
- public void onNavigationDrawerItemSelected(String title) {
- FragmentManager fragmentManager = getSupportFragmentManager();
- FragmentTransaction ft = fragmentManager.beginTransaction();
- currentFragment = fragmentManager.findFragmentByTag(title);
- if(currentFragment == null) {
- currentFragment = ContentFragment.newInstance(title);
- ft.add(R.id.container, currentFragment, title);
- }
- if(lastFragment != null) {
- ft.hide(lastFragment);
- }
- if(currentFragment.isDetached()){
- ft.attach(currentFragment);
- }
- ft.show(currentFragment);
- lastFragment = currentFragment;
- ft.commit();
- onSectionAttached(title);
- }
- package com.sweetvvck.fakezhihu;
- import java.util.ArrayList;
- import java.util.List;
- import android.app.Activity;
- import android.content.SharedPreferences;
- import android.content.res.Configuration;
- import android.os.Bundle;
- import android.preference.PreferenceManager;
- import android.support.v4.app.ActionBarDrawerToggle;
- import android.support.v4.app.Fragment;
- import android.support.v4.view.GravityCompat;
- import android.support.v4.widget.DrawerLayout;
- import android.support.v7.app.ActionBar;
- import android.support.v7.app.ActionBarActivity;
- import android.view.LayoutInflater;
- import android.view.Menu;
- import android.view.MenuInflater;
- import android.view.MenuItem;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.AdapterView;
- import android.widget.ListView;
- import android.widget.Toast;
- /**
- * 用于管理交互和展示抽屉导航的Fragment。
- * 参考<a href="https://developer.android.com/design/patterns/navigation-drawer.html#Interaction">
- * 设计向导</a>
- */
- public class NavigationDrawerFragment extends Fragment {
- /**
- * 存放选中item的位置
- */
- private static final String STATE_SELECTED_POSITION = "selected_navigation_drawer_position";
- /**
- * 存放用户是否需要默认开启drawer的key
- */
- private static final String PREF_USER_LEARNED_DRAWER = "navigation_drawer_learned";
- /**
- * 宿主activity实现的回调接口的引用
- */
- private NavigationDrawerCallbacks mCallbacks;
- /**
- * 将action bar和drawerlayout绑定的组件
- */
- private ActionBarDrawerToggle mDrawerToggle;
- private DrawerLayout mDrawerLayout;
- private ListView mDrawerListView;
- private View mFragmentContainerView;
- private int mCurrentSelectedPosition = 0;
- private boolean mFromSavedInstanceState;
- private boolean mUserLearnedDrawer;
- private List<DrawerListItem> mData = new ArrayList<DrawerListItem>();
- public NavigationDrawerFragment() {
- }
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- // 通过这个flag判断用户是否已经知道drawer了,第一次启动应用显示出drawer(抽屉),之后启动应用默认将其隐藏
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
- mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false);
- if (savedInstanceState != null) {
- mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION);
- mFromSavedInstanceState = true;
- }
- }
- @Override
- public void onActivityCreated (Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- // 设置该fragment拥有自己的actionbar action item
- setHasOptionsMenu(true);
- }
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- mDrawerListView = (ListView) inflater.inflate(R.layout.fragment_navigation_drawer, container, false);
- View headerView = inflater.inflate(R.layout.list_header, null);
- mDrawerListView.addHeaderView(headerView);
- mDrawerListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- selectItem(position);
- }
- });
- String[] itemTitle = getResources().getStringArray(R.array.item_title);
- int[] itemIconRes = {
- R.drawable.ic_drawer_home,
- R.drawable.ic_drawer_explore,
- R.drawable.ic_drawer_follow,
- R.drawable.ic_drawer_collect,
- R.drawable.ic_drawer_draft,
- R.drawable.ic_drawer_search,
- R.drawable.ic_drawer_question,
- R.drawable.ic_drawer_setting};
- for (int i = 0; i < itemTitle.length; i++) {
- DrawerListItem item = new DrawerListItem(getResources().getDrawable(itemIconRes[i]), itemTitle[i]);
- mData.add(item);
- }
- selectItem(mCurrentSelectedPosition);
- DrawerListAdapter adapter = new DrawerListAdapter(this.getActivity(), mData);
- mDrawerListView.setAdapter(adapter);
- mDrawerListView.setItemChecked(mCurrentSelectedPosition, true);
- return mDrawerListView;
- }
- public boolean isDrawerOpen() {
- return mDrawerLayout != null && mDrawerLayout.isDrawerOpen(mFragmentContainerView);
- }
- /**
- * 设置导航drawer
- *
- * @param fragmentId fragmentent的id
- * @param drawerLayout fragment的容器
- */
- public void setUp(int fragmentId, DrawerLayout drawerLayout) {
- mFragmentContainerView = getActivity().findViewById(fragmentId);
- mDrawerLayout = drawerLayout;
- mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
- ActionBar actionBar = getActionBar();
- actionBar.setDisplayHomeAsUpEnabled(true);
- actionBar.setHomeButtonEnabled(true);
- //隐藏Action bar上的app icon
- actionBar.setDisplayShowHomeEnabled(false);
- mDrawerToggle = new ActionBarDrawerToggle(
- getActivity(), /* 宿主 */
- mDrawerLayout, /* DrawerLayout 对象 */
- R.drawable.ic_drawer, /* 替换actionbar上的'Up'图标 */
- R.string.navigation_drawer_open,
- R.string.navigation_drawer_close
- ) {
- @Override
- public void onDrawerClosed(View drawerView) {
- super.onDrawerClosed(drawerView);
- if (!isAdded()) {
- return;
- }
- getActivity().supportInvalidateOptionsMenu(); // 调用 onPrepareOptionsMenu()
- }
- @Override
- public void onDrawerOpened(View drawerView) {
- super.onDrawerOpened(drawerView);
- if (!isAdded()) {
- return;
- }
- if (!mUserLearnedDrawer) {
- mUserLearnedDrawer = true;
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
- sp.edit().putBoolean(PREF_USER_LEARNED_DRAWER, true).commit();
- }
- getActivity().supportInvalidateOptionsMenu(); // 调用 onPrepareOptionsMenu()
- }
- };
- // 如果是第一次进入应用,显示抽屉
- if (!mUserLearnedDrawer && !mFromSavedInstanceState) {
- mDrawerLayout.openDrawer(mFragmentContainerView);
- }
- mDrawerLayout.post(new Runnable() {
- @Override
- public void run() {
- mDrawerToggle.syncState();
- }
- });
- mDrawerLayout.setDrawerListener(mDrawerToggle);
- }
- private void selectItem(int position) {
- mCurrentSelectedPosition = position;
- if (mDrawerListView != null) {
- mDrawerListView.setItemChecked(position, true);
- }
- if (mDrawerLayout != null) {
- mDrawerLayout.closeDrawer(mFragmentContainerView);
- }
- if (mCallbacks != null) {
- if(mCurrentSelectedPosition == 0) {
- mCallbacks.onNavigationDrawerItemSelected(getString(R.string.app_name));
- return;
- }
- mCallbacks.onNavigationDrawerItemSelected(mData.get(position - 1).getTitle());
- }
- }
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- try {
- mCallbacks = (NavigationDrawerCallbacks) activity;
- } catch (ClassCastException e) {
- throw new ClassCastException("Activity must implement NavigationDrawerCallbacks.");
- }
- }
- @Override
- public void onDetach() {
- super.onDetach();
- mCallbacks = null;
- }
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putInt(STATE_SELECTED_POSITION, mCurrentSelectedPosition);
- }
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- // 当系统配置改变时调用DrawerToggle的改变配置方法(例如横竖屏切换会回调此方法)
- mDrawerToggle.onConfigurationChanged(newConfig);
- }
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- //当抽屉打开时显示应用全局的actionbar设置
- if (mDrawerLayout != null && isDrawerOpen()) {
- inflater.inflate(R.menu.global, menu);
- showGlobalContextActionBar();
- }
- super.onCreateOptionsMenu(menu, inflater);
- }
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (mDrawerToggle.onOptionsItemSelected(item)) {
- return true;
- }
- if (item.getItemId() == R.id.action_example) {
- Toast.makeText(getActivity(), "Example action.", Toast.LENGTH_SHORT).show();
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
- /**
- * 当抽屉打开时显示应用全局的actionbar设置
- */
- private void showGlobalContextActionBar() {
- ActionBar actionBar = getActionBar();
- actionBar.setDisplayShowTitleEnabled(true);
- actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
- actionBar.setTitle(R.string.app_name);
- }
- private ActionBar getActionBar() {
- return ((ActionBarActivity) getActivity()).getSupportActionBar();
- }
- /**
- * 宿主activity要实现的回调接口
- * 用于activity与该fragment之间通讯
- */
- public static interface NavigationDrawerCallbacks {
- /**
- * 当drawer中的某个item被选择是调用该方法
- */
- void onNavigationDrawerItemSelected(String title);
- }
- }
怎么样,很像吧,Drawer是不是简直可以以假乱真了,哈哈。
demo地址:http://download.csdn.net/detail/sweetvvck/7794083
其实demo中还有写知识点没有讲到,比如drawer划开时和关闭时action bar上的action item其实是不一样的,这时如何实现的呢?怎么设置action bar不现实logo/icon?选择Drawer中listview的item切换fragment可以每选择一次都replace一次fragment,但是这样每次都得重新创建一个fragment,如果fragment初始化较复杂就更占资源,此时可以配合使用add,hide,show来实现切换同时将以加载过的fragment缓存起来......由于篇幅原因,这些问题都会在之后的博客中详细讲到的~