Android 高仿新浪微博底部導航欄,實現雙擊首頁Tab,頁面的ListView滾動、刷新

 現在很多APP,如微信、QQ、微博等等,它們的主頁面都無一例外的選擇使用底部Tab導航, 通過這種方式,可以很好的把頁面層級分化,很好的提高用戶體驗。相信,很多Android開發者,都使用到過這種經典的設計,可是您你能保證您的設計真的沒問題麼?

 爲啥我會有這個疑問呢? 因爲我日前就遇到了這麼一個情況,發現我做的APP導航頁有問題。 具體可以參考這篇博客:【Android】保存Fragment切換狀態 , 首先說明的是,我的項目是從之前就沿用下來的框架,頁面底部tab的實現,就是採用前面博客提到的方式, 可是在測試的時候,竟然發現,使用這種方式來實現,經常會發生tab重疊情況: 比如,此刻選中的事“首頁”tab,可是內容確實“活動”tab,尤其是在你的app在二級頁面發生崩潰返回到一級頁面時,這種情況經常發生! 當然,這篇博客的評論裏面,也提到了這個問題,所以,最後大家建議大家採用的是;"推薦直接使用ViewPager,通過自定義ViewPager禁用掉左右滑動和自動銷燬即可"

 

  在我接觸的APP中,我覺得新浪微博的設計當然是最經典的,爲啥呢?就因爲它多了一個功能,“底部tab的雙擊,來實現列表滾動到最上方並刷新博客列表”,要知道,這樣的設計,可以極大提高用戶體驗的(避免了用戶手動滾動到最上方,然後下拉刷新...),接下來,將帶着大家學習如何去實現吧。


先看效果圖(尤其是日誌):


1. 直接定義tab頁面,一個ViewPager,四個RadioButton:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.lnyp.vf.ContainerViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="60dp" />

    <RadioGroup
        android:id="@+id/radiogroup"
        android:layout_width="fill_parent"
        android:layout_height="60dp"
        android:background="#ececec"
        android:layout_alignParentBottom="true"
        android:orientation="horizontal">

        <RadioButton
            android:id="@+id/radio_main"
            style="@style/navigation_style"
            android:checked="true"
            android:drawableTop="@drawable/selector_main_bottom_tab_first"
            android:paddingLeft="0dp"
            android:text="首頁" />

        <RadioButton
            android:id="@+id/radio_projects"
            style="@style/navigation_style"
            android:checked="false"
            android:drawableTop="@drawable/selector_main_bottom_tab_second"
            android:paddingLeft="0dp"
            android:text="活動" />

        <RadioButton
            android:id="@+id/radio_studys"
            style="@style/navigation_style"
            android:checked="false"
            android:drawableTop="@drawable/selector_main_bottom_tab_third"
            android:paddingLeft="0dp"
            android:text="社區" />

        <RadioButton
            android:id="@+id/radio_user_center"
            style="@style/navigation_style"
            android:checked="false"
            android:drawableTop="@drawable/selector_main_bottom_tab_forth"
            android:paddingLeft="0dp"
            android:text="我的" />
    </RadioGroup>

</RelativeLayout>

2. 自定義PagerAdapter,爲ViewPager添加布局(Fragment),要求可以實現Fragment切換,狀態的保存:

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.PagerAdapter;
import android.view.View;
import android.view.ViewGroup;

import java.util.List;

/**
 * 爲ViewPager添加布局(Fragment),綁定和處理fragments和viewpager之間的邏輯關係
 * 可保持Fragment切換狀態
 */
public class FragmentViewPagerAdapter extends PagerAdapter implements MyViewPager.OnPageChangeListener {
    private List<Fragment> fragments; // 每個Fragment對應一個Page
    private FragmentManager fragmentManager;
    private ContainerViewPager viewPager; // viewPager對象
    private int currentPageIndex = 0; // 當前page索引(切換之前)

    private OnExtraPageChangeListener onExtraPageChangeListener; // ViewPager切換頁面時的額外功能添加接口

    public FragmentViewPagerAdapter(FragmentManager fragmentManager, ContainerViewPager viewPager, List<Fragment> fragments) {
        this.fragments = fragments;
        this.fragmentManager = fragmentManager;
        this.viewPager = viewPager;
        this.viewPager.setAdapter(this);

        this.viewPager.setOnPageChangeListener(this);
    }

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

    @Override
    public boolean isViewFromObject(View view, Object o) {
        return view == o;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView(fragments.get(position).getView()); // 移出viewpager兩邊之外的page佈局
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = fragments.get(position);
        if (!fragment.isAdded()) { // 如果fragment還沒有added
            FragmentTransaction ft = fragmentManager.beginTransaction();
            ft.add(fragment, fragment.getClass().getSimpleName());
            ft.commit();
            /**
             * 在用FragmentTransaction.commit()方法提交FragmentTransaction對象後
             * 會在進程的主線程中,用異步的方式來執行。
             * 如果想要立即執行這個等待中的操作,就要調用這個方法(只能在主線程中調用)。
             * 要注意的是,所有的回調和相關的行爲都會在這個調用中被執行完成,因此要仔細確認這個方法的調用位置。
             */
            fragmentManager.executePendingTransactions();
        }

        if (fragment.getView().getParent() == null) {
            container.addView(fragment.getView()); // 爲viewpager增加布局
        }

        return fragment.getView();
    }

    /**
     * 當前page索引(切換之前)
     *
     * @return
     */
    public int getCurrentPageIndex() {
        return currentPageIndex;
    }

    public OnExtraPageChangeListener getOnExtraPageChangeListener() {
        return onExtraPageChangeListener;
    }

    /**
     * 設置頁面切換額外功能監聽器
     *
     * @param onExtraPageChangeListener
     */
    public void setOnExtraPageChangeListener(OnExtraPageChangeListener onExtraPageChangeListener) {
        this.onExtraPageChangeListener = onExtraPageChangeListener;
    }

    @Override
    public void onPageScrolled(int i, float v, int i2) {
        if (null != onExtraPageChangeListener) { // 如果設置了額外功能接口
            onExtraPageChangeListener.onExtraPageScrolled(i, v, i2);
        }
    }

    @Override
    public void onPageSelected(int i) {
        fragments.get(currentPageIndex).onPause(); // 調用切換前Fargment的onPause()
        if (fragments.get(i).isAdded()) {
            fragments.get(i).onResume(); // 調用切換後Fargment的onResume()
        }
        currentPageIndex = i;

        if (null != onExtraPageChangeListener) { // 如果設置了額外功能接口
            onExtraPageChangeListener.onExtraPageSelected(i);
        }
    }

    @Override
    public void onPageScrollStateChanged(int i) {
        if (null != onExtraPageChangeListener) { // 如果設置了額外功能接口
            onExtraPageChangeListener.onExtraPageScrollStateChanged(i);
        }
    }

    /**
     * page切換額外功能接口
     */
    public static class OnExtraPageChangeListener {
        public void onExtraPageScrolled(int i, float v, int i2) {
        }

        public void onExtraPageSelected(int i) {
        }

        public void onExtraPageScrollStateChanged(int i) {
        }
    }
}


  注: 上述代碼中,有個ContainerViewPager,該ContainerViewPager是繼承ViewPager,主要是爲了去除ViewPager左右滑動功能,大家可以再源碼裏直接看到。


3. 自定義四個(隨便幾個)Fragment, 每個Fragment就是ViewPager裏要承載的View,負責顯示每個Tab頁面


4. 實現MainActivity.java,爲ViewPager設置PageAdapter, 並且,在MainActivity中實現雙擊tab功能:

import android.os.Bundle;
import android.os.SystemClock;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.widget.RadioButton;

import java.util.ArrayList;
import java.util.List;

import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class MainActivity extends FragmentActivity {

    public static final int TAB_HOME = 0;
    public static final int TAB_PROJECTS = 1;
    public static final int TAB_STUDYS = 2;
    public static final int TAB_USER_CENTER = 3;

    @Bind(R.id.viewpager)
    public ContainerViewPager viewPager;

    @Bind(R.id.radio_main)
    public RadioButton radioMain;

    @Bind(R.id.radio_projects)
    public RadioButton radioProjects;

    @Bind(R.id.radio_studys)
    public RadioButton radioStudys;

    @Bind(R.id.radio_user_center)
    public RadioButton radioUserCenter;

    FragmentMain fragmentMain;

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

        ButterKnife.bind(this);

        initView();
        addPageChangeListener();
    }

    private void initView() {

        List<Fragment> fragments = new ArrayList<Fragment>();

        fragmentMain = new FragmentMain();

        FragmentHuodong fragmentHuodong = new FragmentHuodong();

        FragmentShequ fragmentShequ = new FragmentShequ();

        FragmentMy fragmentMy = new FragmentMy();

        fragments.add(fragmentMain);
        fragments.add(fragmentHuodong);
        fragments.add(fragmentShequ);
        fragments.add(fragmentMy);

        this.viewPager.setOffscreenPageLimit(0);

        FragmentViewPagerAdapter adapter = new FragmentViewPagerAdapter(this.getSupportFragmentManager(), viewPager, fragments);

    }

    private void addPageChangeListener() {
        viewPager.setOnPageChangeListener(new MyViewPager.OnPageChangeListener() {

            @Override
            public void onPageSelected(int id) {
                switch (id) {
                    case TAB_HOME:
                        radioMain.setChecked(true);
                        break;
                    case TAB_PROJECTS:
                        radioProjects.setChecked(true);
                        break;
                    case TAB_STUDYS:
                        radioStudys.setChecked(true);
                        break;
                    case TAB_USER_CENTER:
                        radioUserCenter.setChecked(true);
                        break;
                }
            }

            @Override
            public void onPageScrolled(int arg0, float arg1, int arg2) {

            }

            @Override
            public void onPageScrollStateChanged(int arg0) {

            }
        });
    }

    @OnClick({R.id.radio_main, R.id.radio_projects, R.id.radio_studys, R.id.radio_user_center})
    public void onClick(View v) {
        switch (v.getId()) {

            case R.id.radio_main:
                viewPager.setCurrentItem(TAB_HOME, false);
                doubleClick(v);
                break;
            case R.id.radio_projects:
                viewPager.setCurrentItem(TAB_PROJECTS, false);
                break;
            case R.id.radio_studys:
                viewPager.setCurrentItem(TAB_STUDYS, false);
                break;
            case R.id.radio_user_center:
                viewPager.setCurrentItem(TAB_USER_CENTER, false);
                break;
        }
    }

    long firstClickTime = 0;
    long secondClickTime = 0;

    public void doubleClick(View view) {

        if (firstClickTime > 0) {
            secondClickTime = SystemClock.uptimeMillis();
            if (secondClickTime - firstClickTime < 500) {
                fragmentMain.ScrollToTop();
            }
            firstClickTime = 0;
            return;
        }

        firstClickTime = SystemClock.uptimeMillis();

        new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    Thread.sleep(500);
                    firstClickTime = 0;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

 注:在處首頁Tab的點擊事件時,除了要設置viewPager.setCurrentItem(TAB_HOME, false);外,還需要設置doubleClick(v);它主要是處理了雙擊事件。


  通過以上四個步驟,已經可以實現tab導航,雙擊tab調用Fragment中方法了。接下來,讓我們看下日誌:仔細看下ViewPager+Fragment的生命週期:(我設置ViewPager取消了預加載功能)

1. 第一次進入到主頁面:加載FragmentMain,執行生命週期方法



2. 分別點擊其他的Tab:



3. 之後再點擊FragmentMain,我們發現,並未在執行任何生命週期的方法;

4. 點擊FragmentMain頁面中的Button,進入新的Activity:


  我們發現,剛剛啓動的幾個Fragment(首頁、活動、社區),都執行了onPause和onStop方法;


5. 返回到上一級頁面,也就是首頁:


  剛剛停止的幾個Fragment(首頁、活動、社區),執行了onStart和onResume方法。


6. 接着,測試下首頁tab的雙擊事件:


  會調用我們在FragmentMain中定義ScrollToTop方法,在該方法中,我們可以處理一些相應的邏輯。


7. 退出APP,看下:




看到這裏,不知道大家是否明白瞭如何定義使用Tab了,如果有疑問,可以再多看看源碼,也歡迎一起討論。


github源碼地址:https://github.com/zuiwuyuan/ViewpagerFragmentTab


  如此這般,就OK啦!歡迎指正!
  如有疑問,歡迎進QQ羣:487786925( Android研發村 )

發佈了118 篇原創文章 · 獲贊 209 · 訪問量 53萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章