Android學習路線(二十四)ActionBar Fragment運用最佳實踐

轉載請註明出處:http://blog.csdn.net/sweetvvck/article/details/38645297html

經過前面的幾篇博客。你們看到了Google是怎樣解釋action bar和fragment以及推薦的使用方法。俗話說沒有demo的博客不是好博客,如下我會介紹一下action bar和fragment在實戰中的應用,以及相關demo源代碼,但願和你們相互交流。java

瞭解過fragment的同窗們應該都知道,fragment是android 3.0版本號纔出現的的,所以假設要在支持android 3.0一下版本號的project中使用fragment的話是需要加入Support Library的。詳細怎樣加入我就再也不贅述。可以看我前面的博客Android學習路線(二十一)運用Fragment構建動態UI——建立一個Fragment,如下的項目支持到API Level最低爲8,因此項目中也會使用到Support Library。
android

做爲一個有上進心的Android開發人員,咱們是但願項目的設計符合Android Design的。Android Design是Google官方推薦的應用設計原則,不瞭解Android Design的同窗可以去了解下。我這裏有官方翻譯文檔緩存

我發現「知乎」的App設計是符合Android Design的。那麼咱們的項目就來模仿知乎的主界面。首先看下效果圖:app

       

咱們來分析一下這種界面應該怎麼實現。從上圖可以看出「知乎」android端使用了action bar和drawerlayout,同一時候drawer中item切換主界面應該是fragment。ide

新建一個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。post


      首先咱們打開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緩存起來......由於篇幅緣由。這些問題都會在以後的博客中具體講到的~




相關文章
相關標籤/搜索