Android—頂部滑動(dòng)導(dǎo)航

一路克、概述

在 Android 開(kāi)發(fā)中,經(jīng)常需要使用頂部或者底部的導(dǎo)航來(lái)切換當(dāng)前顯示的 Fragment养交。
在很多應(yīng)用中還添加了滑動(dòng)切換的效果衷戈,大體效果如下:

Pager滑動(dòng)導(dǎo)航.gif

這類程序分為兩個(gè)部分。
下方使用 ViewPager 實(shí)現(xiàn)多頁(yè)滑動(dòng)顯示层坠。滑動(dòng)時(shí)刁笙,ViewPager 顯示不同的 Fragment破花,我們可以為 ViewPager 設(shè)置適配器來(lái)實(shí)現(xiàn)這樣的效果谦趣。
上方的四個(gè) TextView 的顯示需要我們自己實(shí)現(xiàn),主要是在 ViewPager 切換的時(shí)候進(jìn)行文字顏色的設(shè)置以及下方橫線的滑動(dòng)座每。

程序源碼:PagerSlide

二前鹅、Fragment

ViewPager 本身是一個(gè)可以滑動(dòng)的對(duì)象,我們可以在其中添加滑動(dòng)的廣告峭梳,或者是這里說(shuō)的 Fragment 的切換舰绘。
如果只是添加圖片之類的控件,我們只需要設(shè)置相應(yīng)的布局文件即可葱椭,但是添加 Fragment 卻不是這么簡(jiǎn)單的捂寿。下面我們從 Fragment 生命周期開(kāi)始講起。

1. Fragment 生命周期

Fragment 生命周期.png

Fragment 的生命周期很復(fù)雜孵运,我們只看重點(diǎn)秦陋,F(xiàn)ragment 在 onCreateView() 中加載視圖。經(jīng)過(guò) onActivityCreate() --> onStart() --> onResume() 后才真正顯示治笨。
而在 Fragment 顯示前驳概,還有一個(gè) onActivityCreate() 函數(shù),我們可以在這里加載 Fragment 所需要的數(shù)據(jù)(這個(gè)例子沒(méi)有數(shù)據(jù)旷赖,但在真正的項(xiàng)目里顺又,這里一般加載聯(lián)網(wǎng)數(shù)據(jù))。

2. BaseFragment

我們創(chuàng)建一個(gè)繼承自 Fragment(support.v4 包) 的抽象類 BaseFragment等孵,在里面實(shí)現(xiàn)一些公共的方法稚照。我們所有的自定義 Fragment 都將繼承自 BaseFragment。

BaseFragment 的子類必須都重寫(xiě) initView() 方法(因?yàn)槊總€(gè) Fragment 都需要加載布局)流济,這個(gè)方法返回當(dāng)前 Fragment 的 View 對(duì)象锐锣。
而在 onActivityCreated() 方法中我們通過(guò) initData() 加載數(shù)據(jù),如果子類需要加載數(shù)據(jù)并重寫(xiě)了此方法绳瘟,那么根據(jù)上面講的生命周期雕憔,數(shù)據(jù)就會(huì)在 Fragment 顯示前加載完畢。

public abstract class BaseFragment extends Fragment {

    // 上下文對(duì)象
    protected Context mContext;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = getActivity();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return initView();
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        initData();
    }

    // 繼承此類的子類必須重寫(xiě)此方法加載布局
    public abstract View initView();

    // 加載數(shù)據(jù)的方法
    public void initData() { }
}

3. 子 Fragment

有了 BaseFragment糖声,我們就可以自定義需要顯示的 Fragment 了斤彼。Fragment 的布局文件隨你樂(lè)意,這里我只加了一張圖片蘸泻。
我們?cè)?initView() 中加載并返回了 View 視圖對(duì)象琉苇,在 initData() 中加載數(shù)據(jù)。這兩個(gè)方法里都有 Log 日志打印悦施,這個(gè)待會(huì)有用并扇。

public class Fragment1 extends BaseFragment {

    @Override
    public View initView() {
        Log.e("TAG", "Fragment1 --> initView");
        View view = View.inflate(mContext, R.layout.fragment1, null);
        return view;
    }

    @Override
    public void initData() {
        super.initData();
        // ......加載數(shù)據(jù)
        Log.e("TAG", "Fragment1 --> initData");
    }
}

之后再定義三個(gè)相似的 Fragment 即可。

三抡诞、布局文件

定義四個(gè)橫向的 Textview 用于頂部導(dǎo)航穷蛹。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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:orientation="vertical"
    tools:context="com.learn.lister.pagerslide.activity.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:padding="10dp"
            android:gravity="center">
            <TextView
                android:id="@+id/page_0"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="首頁(yè)"
                android:textSize="16sp"
                android:textColor="@android:color/black"/>
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:padding="10dp"
            android:gravity="center">
            <TextView
                android:id="@+id/page_1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="朋友"
                android:textSize="16sp"
                android:textColor="@android:color/black"/>
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:padding="10dp"
            android:gravity="center">
            <TextView
                android:id="@+id/page_2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="動(dòng)態(tài)"
                android:textSize="16sp"
                android:textColor="@android:color/black"/>
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:padding="10dp"
            android:gravity="center">
            <TextView
                android:id="@+id/page_3"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="附近"
                android:textSize="16sp"
                android:textColor="@android:color/black"/>
        </LinearLayout>

    </LinearLayout>

    <View
        android:layout_width="match_parent"
        android:layout_height="2dp"
        android:background="@android:color/darker_gray"/>

    <ImageView
        android:id="@+id/main_tab_line"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/slider"/>

    <android.support.v4.view.ViewPager
        android:id="@+id/main_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </android.support.v4.view.ViewPager>

</LinearLayout>

四土陪、主要代碼

1. 適配器

為了支持在 ViewPager 滑動(dòng)時(shí)向其中添加不同的 Fragment,我們需要為 ViewPager 設(shè)置一個(gè)適配器肴熏。我們可以自定義一個(gè)繼承于 FragmentPagerAdapter 的適配器鬼雀。

官方文檔對(duì) FragmentPagerAdapter 的解釋大致如下:

FragmentPagerAdapter 派生自 PagerAdapter,它是用來(lái)呈現(xiàn)Fragment頁(yè)面的蛙吏,這些Fragment頁(yè)面會(huì)一直保存在fragment manager中源哩,以便用戶可以隨時(shí)取用。
這個(gè)適配器適用于有限個(gè)靜態(tài)fragment頁(yè)面的管理鸦做。盡管不可見(jiàn)的視圖有時(shí)會(huì)被銷毀励烦,但用戶所有訪問(wèn)過(guò)的fragment都會(huì)被保存在內(nèi)存中。

而繼承自 FragmentPagerAdapter 的適配器也只需要重寫(xiě) getCount() 和 getItem(int position) 兩個(gè)方法馁龟。

/**
 * Fragment 滑動(dòng)適配器
 * BaseFragment 為自定義的 Fragment 基類崩侠。
 */
public class PagerSlideAdapter extends FragmentPagerAdapter {

    private List<BaseFragment> mFragmentList;

    public PagerSlideAdapter(FragmentManager fm, List<BaseFragment> fragmentList) {
        super(fm);
        this.mFragmentList = fragmentList;
    }

    @Override
    public Fragment getItem(int position) {
        return mFragmentList.get(position);
    }

    @Override
    public int getCount() {
        return mFragmentList.size();
    }
}

從代碼中我們可以看出,在構(gòu)造函數(shù)中需要傳入一個(gè) Fragment 的合集并初始化坷檩,這些就是 ViewPager 中滑動(dòng)的對(duì)象却音。

2. MainActivity

ViewPager 的滑動(dòng)是設(shè)置適配器的效果,而滑動(dòng)頁(yè)面時(shí)文字的變化以及橫條的移動(dòng)就需要我們自己動(dòng)手了矢炼。

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    @BindView(R.id.page_0) TextView text0;
    @BindView(R.id.page_1) TextView text1;
    @BindView(R.id.page_2) TextView text2;
    @BindView(R.id.page_3) TextView text3;
    @BindView(R.id.main_tab_line) ImageView tab_line;
    @BindView(R.id.main_pager) ViewPager mViewPager;

    private int screenWidth;
    private List<BaseFragment> mFragmentList = new ArrayList<>();
    private PagerSlideAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        initData(); // 初始化數(shù)據(jù)
        initWidth(); // 初始化滑動(dòng)橫條的寬度
        setListener(); // 設(shè)置監(jiān)聽(tīng)器
    }

    private void initData() {
        // 將我們自定義 Fragment 的對(duì)象添加到 List<BaseFragment> 中系瓢。
        mFragmentList.add(new Fragment1());
        mFragmentList.add(new Fragment2());
        mFragmentList.add(new Fragment3());
        mFragmentList.add(new Fragment4());

        // 新建適配器
        adapter = new PagerSlideAdapter(getSupportFragmentManager(), mFragmentList);
        // 為 ViewPager 設(shè)置適配器
        mViewPager.setAdapter(adapter);

        // 打開(kāi)應(yīng)用時(shí) ViewPager 顯示第一個(gè) Fragment
        mViewPager.setCurrentItem(0);
        text0.setTextColor(Color.BLUE);
    }

    private void setListener() {

        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            /**
             * This method will be invoked when the current page is scrolled, either as part
             * of a programmatically initiated smooth scroll or a user initiated touch scroll.
             *
             * @param position Position index of the first page currently being displayed.
             *                 Page position+1 will be visible if positionOffset is nonzero.
             * @param positionOffset Value from [0, 1) indicating the offset from the page at position.
             * @param positionOffsetPixels Value in pixels indicating the offset from position.
             *                             這個(gè)參數(shù)的使用是為了在滑動(dòng)頁(yè)面時(shí)有文字下方橫條的滑動(dòng)效果
             */
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) tab_line.getLayoutParams();
                lp.leftMargin = screenWidth/4*position + positionOffsetPixels/4;
                tab_line.setLayoutParams(lp);
            }

            @Override
            public void onPageSelected(int position) {
                // 在每次切換頁(yè)面時(shí)重置 TextView 的顏色
                resetTextView();
                switch (position) {
                    case 0:
                        text0.setTextColor(Color.BLUE);
                        break;
                    case 1:
                        text1.setTextColor(Color.BLUE);
                        break;
                    case 2:
                        text2.setTextColor(Color.BLUE);
                        break;
                    case 3:
                        text3.setTextColor(Color.BLUE);
                        break;
                }
            }

            @Override
            public void onPageScrollStateChanged(int state) {
            }
        });
        text0.setOnClickListener(this);
        text1.setOnClickListener(this);
        text2.setOnClickListener(this);
        text3.setOnClickListener(this);

    }

    private void resetTextView() {
        text0.setTextColor(Color.BLACK);
        text1.setTextColor(Color.BLACK);
        text2.setTextColor(Color.BLACK);
        text3.setTextColor(Color.BLACK);
    }

    // 初始化滑動(dòng)橫條的寬度
    private void initWidth() {
        DisplayMetrics dpMetrics = new DisplayMetrics();
        getWindow().getWindowManager().getDefaultDisplay().getMetrics(dpMetrics);
        screenWidth = dpMetrics.widthPixels;
        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) tab_line.getLayoutParams();
        lp.width = screenWidth / 4;
        tab_line.setLayoutParams(lp);
    }

    // 設(shè)置文字的點(diǎn)擊事件,點(diǎn)擊某個(gè) TextView 就跳到相應(yīng)頁(yè)面
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.page_0:
                mViewPager.setCurrentItem(0);
                break;
            case R.id.page_1:
                mViewPager.setCurrentItem(1);
                break;
            case R.id.page_2:
                mViewPager.setCurrentItem(2);
                break;
            case R.id.page_3:
                mViewPager.setCurrentItem(3);
                break;
        }
    }
}

五句灌、Fragment 的緩存

到這里我們的程序已經(jīng)可以運(yùn)行了夷陋,但還記得我們之前在自定義 Fragment 類中的 Log 日志嗎?運(yùn)行程序胰锌,讓我們看一下這個(gè)日志骗绕。
程序剛運(yùn)行時(shí)日志:

E/TAG: Fragment1 --> initView
E/TAG: Fragment1 --> initData
E/TAG: Fragment2 --> initView
E/TAG: Fragment2 --> initData

程序剛打開(kāi)時(shí)不是只顯示一個(gè) Fragment 嗎?為什么會(huì)加載兩個(gè) Fragment 的資源资昧?這時(shí)滑動(dòng)到第二個(gè) Fragment酬土,你會(huì)發(fā)現(xiàn)日志是這樣的:

E/TAG: Fragment3 --> initView
E/TAG: Fragment3 --> initData

看起來(lái)適配器總是會(huì)預(yù)先加載一個(gè)頁(yè)面,但是當(dāng)你滑動(dòng)到最后一個(gè)頁(yè)面格带,再往前滑動(dòng)時(shí)撤缴,日志是這樣的:

E/TAG: Fragment2 --> initView
E/TAG: Fragment2 --> initData

Fragment2 之前不是加載過(guò)了嗎?怎么又來(lái)叽唱?
其實(shí)是這樣屈呕,適配器為你保存在內(nèi)存中的 Fragment 時(shí)當(dāng)前所顯示的 Fragmen以及當(dāng)前 Fragment 的前一個(gè)和后一個(gè)。在內(nèi)存中最多只會(huì)緩存三個(gè) Fragment棺亭。(剛打開(kāi)時(shí)只緩存了兩個(gè))

六虎眨、總結(jié)

這里講到了滑動(dòng) ViewPager 顯示不同 Fragment,但是這里的 Fragment 都是靜態(tài)的,如果要處理大量的頁(yè)面切換专甩,F(xiàn)ragmentStatePagerAdapter 會(huì)更優(yōu)秀钟鸵,有興趣的話就去學(xué)習(xí)一下吧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末涤躲,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子贡未,更是在濱河造成了極大的恐慌种樱,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件俊卤,死亡現(xiàn)場(chǎng)離奇詭異嫩挤,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)消恍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門岂昭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人狠怨,你說(shuō)我怎么就攤上這事约啊。” “怎么了佣赖?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵恰矩,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我憎蛤,道長(zhǎng)外傅,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任俩檬,我火速辦了婚禮萎胰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘棚辽。我一直安慰自己技竟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布晚胡。 她就那樣靜靜地躺著灵奖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪估盘。 梳的紋絲不亂的頭發(fā)上瓷患,一...
    開(kāi)封第一講書(shū)人閱讀 51,604評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音遣妥,去河邊找鬼擅编。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的爱态。 我是一名探鬼主播谭贪,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼锦担!你這毒婦竟也來(lái)了俭识?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤洞渔,失蹤者是張志新(化名)和其女友劉穎套媚,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體磁椒,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡堤瘤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了浆熔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片本辐。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖医增,靈堂內(nèi)的尸體忽然破棺而出慎皱,到底是詐尸還是另有隱情,我是刑警寧澤调窍,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布宝冕,位于F島的核電站,受9級(jí)特大地震影響邓萨,放射性物質(zhì)發(fā)生泄漏地梨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一缔恳、第九天 我趴在偏房一處隱蔽的房頂上張望宝剖。 院中可真熱鬧,春花似錦歉甚、人聲如沸万细。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)赖钞。三九已至,卻和暖如春聘裁,著一層夾襖步出監(jiān)牢的瞬間雪营,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工衡便, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留献起,地道東北人洋访。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像谴餐,于是被迫代替她去往敵國(guó)和親姻政。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,152評(píng)論 25 707
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,762評(píng)論 22 665
  • 郭芳艷 焦點(diǎn)網(wǎng)絡(luò)初級(jí)五期 堅(jiān)持原創(chuàng)分享第131天 2017.9.30 昨天還熱鬧異常的國(guó)培樓岂嗓,今天一...
    冰山藍(lán)鷹閱讀 191評(píng)論 0 0
  • 老公和兒子都愛(ài)吃V埂!厌殉! (芝士應(yīng)該用馬拉里蘇芝士善镰,家里只有安佳的,不拉絲了年枕,但絲毫不影響味道!乎完!中西結(jié)合的一道晚餐...
    與民同樂(lè)閱讀 595評(píng)論 0 0
  • 概念:就是說(shuō)兩個(gè)人之間的合作熏兄,一定要雙方都獲得價(jià)值,如果一方賺錢树姨,是建立在另一方的損失之上摩桶,那我就不干。"雙贏思維...
    小芭蕾_7ed2閱讀 172評(píng)論 0 0