Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android學習路線(二十四)ActionBar Fragment運用最佳實踐

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

編輯:關於Android編程

 

通過前面的幾篇博客,大家看到了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的,那麼我們的項目就來模仿知乎的主界面。首先看下效果圖:

data-cke-saved-src=https://www.android5.online/Android/UploadFiles_5356/201702/2017022316424135.png data-cke-saved-src=https://www.android5.online/Android/UploadFiles_5356/201702/2017022316424212.png

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

新建一個工程FakeZhihu:

data-cke-saved-src=https://www.android5.online/Android/UploadFiles_5356/201702/2017022316424342.png data-cke-saved-src=https://www.android5.online/Android/UploadFiles_5356/201702/2017022316424438.png

從上圖可以看到,使用最新的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:

 



    

    
這裡要注意的是無論是在自定義主題還是自定義style時,要根據情況加上parent屬性,如果沒有加上相應的parent屬性的話就不能使用父style中沒有被覆蓋的樣式。具體如何設置action bar的style可以參考 Android學習路線(九)為Action Bar添加Style。

 

完成自定義主題和style後要記得在manifest文件中應用:

 




    

    
        
            
                

                
            
        
    


這裡可以讓整個應用都使用自定義的主題,也可以指定單個activity使用,使用android:theme屬性來指定。

 

接下來要給app添加DrawerLayout了,修改MainActivity的布局文件,添加一個DrawerLayout,內容非常簡單,其中包含一個Drawer和內容布局的Container:

 



    <framelayout android:id="@+id/container" android:layout_height="match_parent" android:layout_width="match_parent">

    

</framelayout>
注意,下面那個fragment就是app的Drawer,其中的屬性android:layout_gravity在這裡表示Drawer從哪一側劃出,start代表左側,end代表右側;還可以定義兩個fragment,然後一個左側劃出一個右側劃出,DrawerLayout在之後會詳細講解,這裡先簡單了解如何使用。

 

創建完DrawerLayout布局後,我們來為Drawer定義一個fragment,我們可以看到知乎的Drawer中只是包含了一個ListView。這個ListView的第一項和其它項的布局不一樣,我們可以想到用ListView加上headerView來實現,知道這些後,我們來創建一個NavigationDrawerFragment繼承自Fragment,這個fragment的布局包含一個ListView:

 


使用一個ArrayList來存放ListView的數據,定義一個DrawerListItem對象來存放每個Item的title和icon的資源ID:

 

 


    首頁
    發現
    關注
    收藏
    草稿
    搜索
    提問
    設置
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的背景:

 



    
    
圖片的src,這裡以home為例:

 

 



    
    
文字的顏色:

 

 




    
    
    

這樣就能實現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。
 * 參考
 * 設計向導 
 */
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 mData = new ArrayList();

    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的開發啦,來看看效果如何:

 

 

data-cke-saved-src=https://www.android5.online/Android/UploadFiles_5356/201702/2017022316424487.png data-cke-saved-src=https://www.android5.online/Android/UploadFiles_5356/201702/2017022316424598.png data-cke-saved-src=https://www.android5.online/Android/UploadFiles_5356/201702/2017022316424623.png

怎麼樣,很像吧,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. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved