Android基础教程(一)开发环境搭建
Android开发基础教程(二)创建Android项目
Android开发基础教程(三)运行你的Android应用
Android开发基础教程(四)构建一个简单的UI
Android开发基础教程(五)开启另一个Activity
Android开发基础教程(六)为Android应用添加ActionBar
Android开发基础教程(七)设置Action Bar
Android开发基础教程(八)为Action bar添加action按钮
Android开发基础教程(九)为Action Bar添加Style
Android开发基础教程(十)如何将Action Bar叠放在你的布局上
Android开发基础教程(十一)管理Activity的生命周期
Android开发基础教程(十二)Activity生命周期——启动一个Activity
Android开发基础教程(十三)Activity生命周期——暂停和恢复(Pausing and Resuming )一个Activity
Android开发基础教程(十四)Activity生命周期——停止和重启(Stopping and Restarting)一个Activity
Android开发基础教程(十五)Activity生命周期——重新创建(Recreating)一个Activity
Android开发基础教程(十六)支持不同设备
Android开发基础教程(十七)支持不同设备——支持不同的语言
Android开发基础教程(十八)支持不同设备——支持不同的屏幕
Android开发基础教程(十九)支持不同设备——支持不同(Android)平台版本
Android开发基础教程(二十)运用Fragment构建动态UI
Android开发基础教程(二十一)运用Fragment构建动态UI——创建一个Fragment
Android开发基础教程(二十二)运用Fragment构建动态UI——构建一个灵活的UI
Android开发基础教程(二十三)运用Fragment构建动态UI——Fragment间通讯
Android开发基础教程(二十四)ActionBar Fragment运用最佳实践

通过前面的几篇博客,大家看到了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:

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?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时,要根据情况加上parent属性,如果没有加上相应的parent属性的话就不能使用父style中没有被覆盖的样式。具体如何设置action bar的style可以参考 Android学习路线(九)为Action Bar添加Style。

完成自定义主题和style后要记得在manifest文件中应用:

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?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>

这里可以让整个应用都使用自定义的主题,也可以指定单个activity使用,使用android:theme属性来指定。

接下来要给app添加DrawerLayout了,修改MainActivity的布局文件,添加一个DrawerLayout,内容非常简单,其中包含一个Drawer和内容布局的Container:

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <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>

注意,下面那个fragment就是app的Drawer,其中的属性android:layout_gravity在这里表示Drawer从哪一侧划出,start代表左侧,end代表右侧;还可以定义两个fragment,然后一个左侧划出一个右侧划出,DrawerLayout在之后会详细讲解,这里先简单了解如何使用。

创建完DrawerLayout布局后,我们来为Drawer定义一个 fragment,我们可以看到知乎的Drawer中只是包含了一个ListView。这个ListView的第一项和其它项的布局不一样,我们可以想到 用ListView加上headerView来实现,知道这些后,我们来创建一个NavigationDrawerFragment继承自 Fragment,这个fragment的布局包含一个ListView:

?

1 2 3 4 5 6 7 8 9 <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" />

使用一个ArrayList来存放ListView的数据,定义一个DrawerListItem对象来存放每个Item的title和icon的资源ID:

?

1 2 3 4 5 6 7 8 9 10 <string-array name= "item_title" > <item>首页</item> <item>发现</item> <item>关注</item> <item>收藏</item> <item>草稿</item> <item>搜索</item> <item>提问</item> <item>设置</item> </string-array>

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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设置Adapter,我们发现这个ListView是Single Choice模式的,并且每个Item被选中后会高亮。那么如何来实现这个功能呢?

实现这样的效果有两个步骤:

第一:在ListView中指定android:choiceMode="singleChoice";

第二:给ListView的Item的布局设置一个特殊的背景drawable,这个drawable包含当状态为activated时的背景和常态下的背景;同时这个item布局中的图片src和文字颜色也要坐相应的设置;

item的背景:

?

1 2 3 4 5 <?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>

图片的src,这里以home为例:

?

1 2 3 4 5 <?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>

文字的颜色:

?

1 2 3 4 5 6 7 8 9 10 <?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>

这样就能实现ListView点击Item高亮的效果了。

考虑到用户在第一次使用app的时候可能不知道有Drawer的存在,我们可以在app第一次被启动时让Drawer处于打开状态,之后再默认隐藏,这是实际项目中常用的手段,这里我们用sharedpreference来实现:

?

1 2 3 4 // 通过这个flag判断用户是否已经知道drawer了,第一次启动应用显示出drawer(抽屉),之后启动应用默认将其 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity()); mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false );

接下来,要实现Drawer的fragment和宿主activity之间的通讯,需要定义一个回调接口,并且在宿主activity中实现:

?

1 2 3 4 5 6 7 8 9 10 /** * 宿主activity要实现的回调接口 * 用于activity与该fragment之间通讯 */ public static interface NavigationDrawerCallbacks { /** * 当drawer中的某个item被选择是调用该方法 */ void onNavigationDrawerItemSelected(String title); }

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @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); }

具体如何来创建一个fragment以及如何实现fragment和activity之间的通讯,可以参考:Android学习路线(二十一)运用Fragment构建动态UI——创建一个Fragment和Android学习路线(二十三)运用Fragment构建动态UI——Fragment间通讯;完整的NavigationDrawerFragment代码如下:

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 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); } }

这样就完成模仿“知乎”主界面的demo的开发啦,来看看效果如何:

怎么样,很像吧,Drawer是不是简直可以以假乱真了,哈哈。

demo地址:http://www.kwstu.com/ResourcesView/kwstu_201408211204286424

其实demo中还有写知识点没有讲到,比如drawer划开时和关闭时action bar上的action item其实是不一样的,这时如何实现的呢?怎么设置action bar不现实logo/icon?选择Drawer中listview的item切换fragment可以每选择一次都replace一次 fragment,但是这样每次都得重新创建一个fragment,如果fragment初始化较复杂就更占资源,此时可以配合使用 add,hide,show来实现切换同时将以加载过的fragment缓存起来......由于篇幅原因,这些问题都会在之后的博客中详细讲到的~


更多相关文章

  1. Android的Dialog开发和运用
  2. Android(安卓)程式开发:(十一)选择控件 —— 11.1 TimePicker
  3. Android开发指南(42) —— Adding Custom Suggestions
  4. 转:iPhone Android(安卓)Web开发(概要)
  5. 转 搭建android 开发环境(ubuntu10。04LTS)
  6. Qt For Android(安卓)开发环境配置
  7. 【Android(安卓)开发教程】编码实现获取与修改预设信息中的值
  8. XE5 Android(安卓)开发数据访问server端
  9. Android开发手记——登陆界面

随机推荐

  1. Android错误之控件FloatingActionButton
  2. android:clipChildren 子元素超出父元素
  3. android布局layout中的一些属性
  4. Android中的人脸检测的示例代码(静态和动
  5. Android 设置控件是否可见
  6. 【从源码看Android】03Android(安卓)Mess
  7. android root权限相关bin程序rageagainst
  8. Android TextView文字链接4中方法
  9. Android 导入Flutter模块
  10. android ARM 汇编学习 —— hello world