zoukankan      html  css  js  c++  java
  • Android学习路线(二十四)ActionBar Fragment运用最佳实践

    转载请注明出处: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一下版本号的project中使用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。

    新建一个projectFakeZhihu:

      

          从上图能够看到,使用最新的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时。要依据情况加上parent属性,假设没有加上对应的parent属性的话就不能使用父style中没有被覆盖的样式。详细怎样设置action bar的style能够參考 Android学习路线(九)为Action Bar加入Style

    完毕自己定义主题和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>
    
    这里能够让整个应用都使用自己定义的主题。也能够指定单个activity使用,使用android:theme属性来指定。

    接下来要给app加入DrawerLayout了。改动MainActivity的布局文件,加入一个DrawerLayout。内容很easy,当中包括一个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>
    
    注意。以下那个fragment就是app的Drawer,当中的属性android:layout_gravity在这里表示Drawer从哪一側划出,start代表左側,end代表右側;还能够定义两个fragment,然后一个左側划出一个右側划出,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" />
    
    使用一个ArrayList来存放ListView的数据,定义一个DrawerListItem对象来存放每一个Item的title和icon的资源ID:

    <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设置Adapter,我们发现这个ListView是Single Choice模式的,而且每一个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>

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

    <?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>

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


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

    // 通过这个flag推断用户是否已经知道drawer了,第一次启动应用显示出drawer(抽屉)。之后启动应用默认将其
    SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
    mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false);
    接下来。要实现Drawer的fragment和宿主activity之间的通讯,须要定义一个回调接口。而且在宿主activity中实现:

    /**
    * 宿主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);
    }
    详细怎样来创建一个fragment以及怎样实现fragment和activity之间的通讯。能够參考:Android学习路线(二十一)运用Fragment构建动态UI——创建一个Fragment 和 Android学习路线(二十三)运用Fragment构建动态UI——Fragment间通讯 ;完整的NavigationDrawerFragment代码例如以下:

    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://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缓存起来......因为篇幅原因。这些问题都会在之后的博客中具体讲到的~



    
  • 相关阅读:
    音频文件转码工具文档 目录 1. 音频文件转码 1 1.1. 简介 1 1.2. 转换命令示例 2 1.3. wav 文件转 16k 16bits 位深的单声道pcm文件 2 1.4. mp3 文件转
    Atitit 调用百度语音识别 目录 1. 建立一个音频app项目,获得appid kersec 1 2. 直接使用JAR包步骤如下: 1 2.1. public class baiduAudio
    Atitit 传媒学院专业与课程表艾提拉总结 目录 1. 媒体分为感觉媒体、表示媒体、表现媒体、存储媒体和传输媒体 1 1.1. 1、感觉媒体 如文字、数据、声音、图形、图像等。 1 1.2. 表示
    Atitit 剪贴板数据类型 DataFlavor 目录 1. HtmlFlavor 1 1.1. allHtmlFlavor 1 1.2. selectionHtmlFlavor 1 1.3. fr
    Atitit 解析m4a文件的元数据标签音乐名,歌手 专辑 年代等信息 java版本 目录 1.1. 自己解析mp4 m4a结构 1 1.2. 格式返回 1 1.3. /bookmarksHtmlE
    Atitit 音频资料与音乐库管理系统功能 目录 1. 通用功能区 2 1.1. 批量处理功能文件夹遍历 2 1.2. Zip文件遍历与读取 2 1.3. Rar文件遍历与读取 2 1.4. She
    Atitit java播放mp3 目录 1.1. 不能直接支持mp3播放。。需要解码播放转化为pcm 1 1.2. 使用\javalayer类库播放 3 1.3. ,就是普通的java sound
    Atitit 加强学生就业的规划与艾提拉的治学理念 目录 1. 思路的转换 1 1.1. 发展内需为主模型 vs 外贸模式 1 1.2. 批发模式vs 零售模式vs 1 1.3. 天堂模式vs地狱模
    Atitit sql的执行功能 目录 1. 主要流程 1 1.1. 获取conn,执行sql取得结果, 1 1.2. Orm类的执行(hb mybatis为例 1 2. 常见sql执行框架与类库 1
    Atitit ffmpeg功能表 多媒体处理类库工具 音频视频 1.1.ffmpeg音视频合成  1.2.Atitit 视频音频分离 提取法 1.3.ffmpeg对视频封装和分离 使用ffmpeg对
  • 原文地址:https://www.cnblogs.com/bhlsheji/p/5182309.html
Copyright © 2011-2022 走看看