TabLayoutViewPagerDemo【TabLayout+ViewPager可滑動】

版權聲明:本文爲HaiyuKing原創文章,轉載請註明出處!html

前言

使用TabLayout搭配ViewPager實現可滑動的頂部選項卡效果。java

效果圖

代碼分析

一、演示常規的設置。android

二、經過自定義ViewPager(MyCustomViewPager)解決解決切換須要通過中間頁的問題、實現控制viewpager是否可滑動的功能;git

3.一、經過在Fragment中的OnCreateView中判斷rootView是否爲空來解決viewpager+fragment來回滑動fragment從新加載的問題;
github

3.二、還有一個方案是在自定義的viewpager適配器類中重寫destroyItem方法,來解決從新加載的問題;【核心就是不銷燬fragment】web

四、經過自定義Fragment基類(BaseLazyFragment)來實現配合viewpager使用時禁止懶加載的功能;api

存在一個問題:那就是當選項卡比較多的時候,從首頁切換到尾頁,而後切換回來首頁的時候,會從新請求數據,由於首頁已經銷燬了,執行了onDestroyView方法。緩存

解決方案:網絡

1、使用setOffscreenPageLimit()方法, 設置數字越大越好(能夠設置總數目);app

2、採用3.2方案;

使用步驟

1、項目組織結構圖

注意事項:

一、 導入類文件後須要change包名以及從新import R文件路徑

二、 Values目錄下的文件(strings.xml、dimens.xml、colors.xml等),若是項目中存在,則複製裏面的內容,不要整個覆蓋

2、導入步驟

引入依賴庫

在APP的build.gradle文件中添加如下代碼【注意:版本號和com.android.support:appcompat-v7保持一致

注意:TabLayout爲5.0以後的新控件,因此styles.xml中的主題應該使用Theme.AppCompat.Light.NoActionBar或者Theme.AppCompat.Light等Theme.AppCompat.XXX的主題

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.3"
    defaultConfig {
        applicationId "com.why.project.tablayoutviewpagerdemo"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.3.1'
    testCompile 'junit:junit:4.12'
    //TabLayout compile 'com.android.support:design:25.3.1'
}

在colors.xml文件中添加如下代碼:【後續可根據實際狀況更改背景顏色、文字顏色值

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>

    <!-- *********************************頂部選項卡區域********************************* -->
    <!-- 頂部選項卡下劃線背景色 -->
    <color name="tab_auto_normal_top">#00ffffff</color>
    <color name="tab_auto_selected_top">#3090d9</color>
    <!-- 頂部選項卡文本顏色 -->
    <color name="tab_text_normal_top">#191919</color>
    <color name="tab_text_selected_top">#3090d9</color>
</resources>

在dimens.xml文件中添加如下代碼:【後續可根據實際狀況更改底部選項卡區域的高度值、文字大小值

<resources>
    <!-- Default screen margins, per the Android Design guidelines. -->
    <dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">16dp</dimen>

    <!-- *********************************頂部選項卡區域********************************* -->
    <!-- 選項卡的內邊距 -->
    <dimen name="tab_top_auto_padding">10dp</dimen>
    <!-- 選項卡標題的文字大小 -->
    <dimen name="tab_top_auto_title_size">18sp</dimen>
    <!-- 選項卡標題的下劃線高度 -->
    <dimen name="tab_top_auto_height">3dp</dimen>
</resources>

在styles.xml文件中添加如下代碼:

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <!-- TabLayout的文字大小 -->
    <style name="TabLayoutTextStyle">
        <item name="android:textSize">@dimen/tab_top_auto_title_size</item>
    </style>

</resources>

在AndroidManifest.xml文件中添加網絡請求的權限【demo中用到的

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.why.project.tablayoutviewpagerdemo">

    <!-- ======================受權訪問網絡(HttpUtil)========================== -->
    <!-- 容許程序打開網絡套接字 -->
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

3、使用方法

在Activity佈局文件中引用TabLayout【注意更改MyViewpager的完整路徑

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.why.project.tablayoutviewpagerdemo.MainActivity">

    <!-- 選項卡區域 -->
    <!--設置TabLayout的模式 app:tabMode 默認是fixed:固定的,標籤不少時候會被擠壓,不能滑動。-->
    <!--設置整個TabLayout的顏色 app:tabBackground-->
    <!--設置選中字體的顏色 app:tabSelectedTextColor-->
    <!--設置未選中字體的顏色 app:tabTextColor-->
    <!--設置指示器下標的顏色 app:tabIndicatorColor-->
    <!--設置指示器下標的高度 app:tabIndicatorHeight,若是設置的是0.0dp,則表明沒有下劃線-->
    <!--設置內容的顯示模式 app:tabGravity,center : 居中,若是是fill,則是充滿-->
    <android.support.design.widget.TabLayout android:id="@+id/tl_top" android:layout_width="match_parent" android:layout_height="wrap_content" app:tabBackground="@android:color/transparent" app:tabMode="scrollable" app:tabSelectedTextColor="@color/tab_text_selected_top" app:tabTextColor="@color/tab_text_normal_top" app:tabTextAppearance="@style/TabLayoutTextStyle" app:tabIndicatorColor="@color/tab_auto_selected_top" app:tabIndicatorHeight="@dimen/tab_top_auto_height" app:tabGravity="center"
        />

    <!-- viewpager區域 -->
    <com.why.project.tablayoutviewpagerdemo.viewpager.MyCustomViewPager
        android:id="@+id/vp_tab"
        android:layout_width="match_parent"
        android:layout_height="0.0dp"
        android:layout_weight="1"/>

</LinearLayout>

建立須要用到的fragment類和佈局文件【後續可根據實際狀況更改命名,而且須要從新import R文件

fragment_web.xml文件佈局以下

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

    <!-- webview -->
    <WebView
        android:id="@+id/web_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></WebView>

</LinearLayout>

WebViewFragment

package com.why.project.tablayoutviewpagerdemo.fragment;

import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import com.why.project.tablayoutviewpagerdemo.R;


/**
 * @Created HaiyuKing
 * @Used  首頁界面——碎片界面
 */
public class WebViewFragment extends BaseLazyFragment{
    
    private static final String TAG = "WebViewFragment";
    /**View實例*/
    private View myView;

    private WebView web_view;

    /**傳遞過來的參數*/
    private String bundle_param;

    //重寫
    public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {

        //使用FragmentTabHost時,Fragment之間切換時每次都會調用onCreateView方法,致使每次Fragment的佈局都重繪,沒法保持Fragment原有狀態。 //http://www.cnblogs.com/changkai244/p/4110173.html
        if(myView == null){ myView = inflater.inflate(R.layout.fragment_web, container, false); //接收傳參
            Bundle bundle = this.getArguments(); bundle_param = bundle.getString("param"); } //緩存的rootView須要判斷是否已經被加過parent, 若是有parent須要從parent刪除,要否則會發生這個rootview已經有parent的錯誤。
        ViewGroup parent = (ViewGroup) myView.getParent(); if (parent != null) { parent.removeView(myView); } return myView;
    }
    
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        //初始化控件以及設置
 initView(); //初始化控件的點擊事件
 initEvent(); // TODO Auto-generated method stub
        super.onActivityCreated(savedInstanceState);
    }

    //實現禁止預加載的功能
 @Override public void fetchData() { //初始化數據
 initData(); } //實現禁止預加載的功能
 @Override public void onInvisible() { }

    @Override
    public void onResume() {
        super.onResume();
    }
    
    @Override
    public void onPause() {
        super.onPause();
    }
    
    @Override
    public void onDestroy() {
        super.onDestroy();
    }
    
    /**
     * 初始化控件
     */
    private void initView() {
        web_view = (WebView) myView.findViewById(R.id.web_view);
        //設置支持js腳本
//        web_view.getSettings().setJavaScriptEnabled(true);
        web_view.setWebViewClient(new WebViewClient() {
            /**
             * 重寫此方法代表點擊網頁內的連接由本身處理,而不是新開Android的系統browser中響應該連接。
             */
            @Override
            public boolean shouldOverrideUrlLoading(WebView webView, String url) {
                //webView.loadUrl(url);
                return false;
            }
        });
    }
    
    /**
     * 初始化數據
     */
    public void initData() {
        Log.e("tag","{initData}bundle_param="+bundle_param);
        web_view.loadUrl(bundle_param);//加載網頁
    }

    /**
     * 初始化點擊事件
     * */
    private void initEvent(){
    }
    
}

BaseLazyFragment

package com.why.project.tablayoutviewpagerdemo.fragment;

import android.os.Bundle;
import android.util.Log;

/**
 * Used 主要實現配合viewpager使用時禁止懶加載
 * 咱們通常在onCreateView方法初始化視圖,onActivityCreated方法初始化數據,
 * 經過setUserVisibleHint和getUserVisibleHint方法來設置和獲取Fragment的顯示狀態,當顯示了纔去加載數據。
 *
 * setUserVisibleHint: isVisibleToUser = false
 onAttach
 onCreate
 setUserVisibleHint: isVisibleToUser = true
 onCreateView
 onActivityCreated
 onStart
 onResume
 onPause
 onStop
 onDestroyView
 onDestroy
 onDetach
 參考資料:https://blog.csdn.net/aiynmimi/article/details/73277836

 */

public abstract class BaseLazyFragment extends BaseFragment{

    private static final String TAG = BaseLazyFragment.class.getSimpleName();

    /**
     * rootView是否初始化標誌,防止回調函數在rootView爲空的時候觸發
     */
    protected boolean isViewInitiated;//view是否初始化
    /**
     * 當前Fragment是否處於可見狀態標誌,防止因ViewPager的緩存機制而致使回調函數的觸發
     */
    protected boolean isVisibleToUser;//是否可見
    protected boolean isDataInitiated;//數據是否加載完成

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        Log.e(TAG,"{setUserVisibleHint}isVisibleToUser="+isVisibleToUser);
        super.setUserVisibleHint(isVisibleToUser);
        this.isVisibleToUser = isVisibleToUser;
        if(getUserVisibleHint()) {
            prepareFetchData();
        } else {
            if(isViewInitiated){
                onInvisible();
            }
        }
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        isViewInitiated = true;
        prepareFetchData();
    }

    /**
     * 視圖銷燬的時候 Fragment是否初始化的狀態變爲false
     */
    @Override
    public void onDestroyView() {
        Log.e(TAG,"{onDestroyView}");
        super.onDestroyView();
        isViewInitiated = false;
        isVisibleToUser = false;
        isDataInitiated = false;
    }

    //初始化數據
    public abstract void fetchData();
    //當隱藏的時候執行的方法
    public abstract void onInvisible();

    public boolean prepareFetchData() {
        return prepareFetchData(false);
    }

    /**
     * 請求數據
     * @param forceUpdate : 是否強制請求數據*/
    public boolean prepareFetchData(boolean forceUpdate) {
        if (isVisibleToUser && isViewInitiated && (!isDataInitiated || forceUpdate)) {
            fetchData();
            isDataInitiated = true;
            return true;
        }
        return false;
    }
}
BaseLazyFragment.java

建立選項卡子項model類TabItemModel

package com.why.project.tablayoutviewpagerdemo.model;

/**
 * Created by HaiyuKing
 * Used
 */

public class TabItemModel {
    private String tabTitle;
    private String tabUrl;

    public TabItemModel(String tabTitle, String tabUrl){
        this.tabTitle = tabTitle;
        this.tabUrl = tabUrl;
    }

    public String getTabTitle() {
        return tabTitle;
    }

    public void setTabTitle(String tabTitle) {
        this.tabTitle = tabTitle;
    }

    public String getTabUrl() {
        return tabUrl;
    }

    public void setTabUrl(String tabUrl) {
        this.tabUrl = tabUrl;
    }
}

建立viewpager的適配器

package com.why.project.tablayoutviewpagerdemo.adapter;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.view.ViewGroup;

import com.why.project.tablayoutviewpagerdemo.model.TabItemModel;

import java.util.List;

/**
 * Created by HaiyuKing
 * Used 當viewpager中fragment數量多的時候用FragmentStatePagerAdapter,反之則用FragmentPagerAdapter。
 */

public class ContentPagerAdapter extends FragmentStatePagerAdapter {

    private List<TabItemModel> tabIndicators;
    /**碎片集合*/
    private List<Fragment> fragmentList;

    public ContentPagerAdapter(FragmentManager fm) {
        super(fm);
        // TODO Auto-generated constructor stub
    }

    /**
     * 自定義構造函數:用於傳遞碎片集合過來
     * 通常都寫上*/
    public ContentPagerAdapter(FragmentManager fm, List<TabItemModel> tabIndicators, List<Fragment> fragmentList) {
        super(fm);
        this.tabIndicators = tabIndicators;
        this.fragmentList = fragmentList;
    }

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

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

    @Override
    public CharSequence getPageTitle(int position) {
        return tabIndicators.get(position).getTabTitle();
    }

    @Override public void destroyItem(ViewGroup container, int position, Object object) { //viewpager+fragment來回滑動fragment從新加載的簡單解決辦法:註釋下面的代碼 //不建議使用,由於當選項卡過多的時候,若是不銷燬的是,擔憂內存溢出 //http://blog.csdn.net/qq_28058443/article/details/51519663
        super.destroyItem(container, position, object); }
}

MyCustomViewPager

package com.why.project.tablayoutviewpagerdemo.viewpager;

import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

/**
 * Created by HaiyuKing
 * Used 自定義的viewpager
 * https://www.cnblogs.com/tangs/articles/5933233.html
 * 解決切換須要通過中間頁的問題;
 * 實現控制viewpager是否可滑動;
 * 解決視頻播放器和viewpager滑動衝突問題【可擴展到任何view】;
 */

public class MyCustomViewPager extends ViewPager {

    /**是否能夠滑動:默承認以滑動*/
    private boolean isCanScroll = true;

    public MyCustomViewPager(Context context) {
        super(context);
    }

    public MyCustomViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * 解決切換須要通過中間頁
     */
    @Override
    public void setCurrentItem(int item) {
        //super.setCurrentItem(item);源碼
        super.setCurrentItem(item,false);//smoothScroll false表示切換的時候,不通過兩個頁面的中間頁
    }

    /**
     * 讓ViewPager不能左右滑動
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if(isCanScroll){
            return super.onTouchEvent(ev);
        }else{
            return false;
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if(isCanScroll){
            return super.onInterceptTouchEvent(ev);
        }else{
            return false;
        }
    }

    /**
     * 暴露出去的方法,屏蔽ViewPager的滑動,默認不可滑動
     * @param isCanScroll 爲true能夠左右滑動,爲false不可滑動
     */
    public void setIsCanScroll(boolean isCanScroll){
        this.isCanScroll = isCanScroll;
    }

    @Override
    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {

        /*if (v instanceof IjkVideoView) {//解決視頻播放器和viewpager滑動衝突問題
            return true;
        }*/
        return super.canScroll(v, checkV, dx, x, y);
    }
}
MyCustomViewPager.java

在Activity中使用以下【繼承FragmentActivity或者其子類

package com.why.project.tablayoutviewpagerdemo;

import android.os.Bundle;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;

import com.why.project.tablayoutviewpagerdemo.adapter.ContentPagerAdapter;
import com.why.project.tablayoutviewpagerdemo.fragment.WebViewFragment;
import com.why.project.tablayoutviewpagerdemo.model.TabItemModel;
import com.why.project.tablayoutviewpagerdemo.viewpager.MyCustomViewPager;

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

public class MainActivity extends AppCompatActivity {

    private TabLayout mTabLayout;
    private MyCustomViewPager mTabViewPager; private List<TabItemModel> tabIndicators;
    private List<Fragment> tabFragments;

    private ContentPagerAdapter contentAdapter;

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

        initViews();
        initDatas();
        initEvents();

    }

    private void initViews() {
        mTabLayout = (TabLayout) findViewById(R.id.tl_top);
        mTabViewPager = (MyCustomViewPager) findViewById(R.id.vp_tab);
        mTabViewPager.setOffscreenPageLimit(3);//禁止預加載【若是想要延遲首個選項卡的銷燬時間,那麼就須要設置這個數值高點】
    }

    private void initDatas() {
        //初始化選項卡子項的文本、超連接model集合
        tabIndicators = new ArrayList<TabItemModel>();
        tabIndicators.add(new TabItemModel("百度","http://www.baidu.com"));
        tabIndicators.add(new TabItemModel("CSDN","http://blog.csdn.net"));
        tabIndicators.add(new TabItemModel("博客園","http://www.cnblogs.com"));
        tabIndicators.add(new TabItemModel("極客頭條","http://geek.csdn.net/mobile"));
        tabIndicators.add(new TabItemModel("優設","http://www.uisdc.com/"));
        tabIndicators.add(new TabItemModel("玩Android","http://www.wanandroid.com/index"));
        tabIndicators.add(new TabItemModel("掘金","https://juejin.im/"));

        //初始化碎片集合
        tabFragments = new ArrayList<>();

        for(int i=0;i<tabIndicators.size();i++){
            TabItemModel tabItemModel = tabIndicators.get(i);

            Bundle bundle = new Bundle();
            bundle.putString("param", tabItemModel.getTabUrl());
            tabFragments.add(WebViewFragment.getInstance(WebViewFragment.class,bundle));
        }
        //實例化Adapter
        contentAdapter = new ContentPagerAdapter(getSupportFragmentManager(),tabIndicators,tabFragments);
        mTabViewPager.setAdapter(contentAdapter); //TabLayout和ViewPager相關聯
 mTabLayout.setupWithViewPager(mTabViewPager);
    }

    private void initEvents() {
        mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                //選中了tab的邏輯
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {
                //未選中了tab的邏輯
            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {
                //再次選中了tab的邏輯
            }
        });
    }
}

混淆配置

參考資料

Android TabLayout 分分鐘打造一個滑動標籤頁

Design庫-TabLayout屬性詳解

Design庫-TabLayout仿京東商品詳情Tab

TabLayout

Fragment 懶加載實戰

【Android】Fragment懶加載和ViewPager的坑

TabLayout-ViewPager填充Fragment和懶加載

ViewPager+Fragment LazyLoad懶加載最優解

Android中ViewPager+Fragment取消(禁止)預加載延遲加載(懶加載)問題解決方案

viewPaper+Fragment的佈局,在初始化時會致使fragment的佈局加載和網絡請求數據(懶加載)

項目demo下載地址

https://github.com/haiyuKing/TabLayoutViewPagerDemo

相關文章
相關標籤/搜索