FragmentTabHost结合ViewPager使用

来源:互联网 发布:武汉java 开发前景 编辑:程序博客网 时间:2024/05/22 15:28

FragmentTabHost结合ViewPager实现侧滑功能,最近刚学习完,写篇博客整理一下思路,望各位指正点评。

FragmentTabHost实现原理。
如下图整个是一个FragmentTabHost,而每一个item是TabSpec,图中有5个TabSpec,TabSpec中主要的是Indicator,Indicator里面包含自定义的view(上面ImageView,下面是TextView)

这里写图片描述

这里对FragmentTabHost类进行了修改,保存Fragment实例不销毁。

import android.content.Context;import android.content.res.TypedArray;import android.os.Bundle;import android.os.Parcel;import android.os.Parcelable;import android.support.v4.app.Fragment;import android.support.v4.app.FragmentManager;import android.support.v4.app.FragmentTransaction;import android.util.AttributeSet;import android.view.View;import android.view.ViewGroup;import android.widget.FrameLayout;import android.widget.LinearLayout;import android.widget.TabHost;import android.widget.TabWidget;import java.util.ArrayList;public class FragmentTabHost extends TabHost implements        TabHost.OnTabChangeListener {    private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();    private FrameLayout mRealTabContent;//用来设置TabContent,用android.R.id.tabcontent来标识    private Context mContext;    private FragmentManager mFragmentManager;//fragment管理器    private int mContainerId;//TabContent的id    private OnTabChangeListener mOnTabChangeListener;//tab切换监听    private TabInfo mLastTab;//tab类的封装    private boolean mAttached;//选项卡是否被选中    static final class TabInfo {        private final String tag;        private final Class<?> clss;        private final Bundle args;        private Fragment fragment;        TabInfo(String _tag, Class<?> _class, Bundle _args) {            tag = _tag;            clss = _class;            args = _args;        }    }    /**     * TabContent数据初始化,当选项卡被选择时调用     */    static class DummyTabFactory implements TabContentFactory {        private final Context mContext;        public DummyTabFactory(Context context) {            mContext = context;        }        /**         * 用tag来标识一个context,在实现类里可以使用LayoutInflater填充出来。         *         * @param tag         * @return         */        @Override        public View createTabContent(String tag) {            View v = new View(mContext);            v.setMinimumWidth(0);            v.setMinimumHeight(0);            return v;        }    }    /**     * BaseSavedState是View的一个静态内部类,把控件的属性打包到parcel容器,     * Activity的onSaveInstanceState、onRestoreInstanceState最终也会调用到控件的这两个同名方法。     */    static class SavedState extends BaseSavedState {        String curTab;        /**         * 当类创建实例的时候调用         */        SavedState(Parcelable superState) {            super(superState);        }        /**         * 当读取parcel时调用,Parcel是一个容器,Android系统中的binder进程间通信(IPC)就使用了Parcel类来进行         *         * @param in         */        private SavedState(Parcel in) {            super(in);            curTab = in.readString();        }        /**         * 写入接口函数,打包         *         * @param out         * @param flags         */        @Override        public void writeToParcel(Parcel out, int flags) {            super.writeToParcel(out, flags);            out.writeString(curTab);        }        @Override        public String toString() {            return "FragmentTabHost.SavedState{"                    + Integer.toHexString(System.identityHashCode(this))                    + " curTab=" + curTab + "}";        }        /**         * 读取接口,目的是要从Parcel中构造一个实现了Parcelable的类的实例出来。         * 因为实现类在这里还是不可知的,所以需要用到模板的方法名通过模板参数传入         * 为了实现模板参数的传入,这里定义了Creator嵌入接口,内含两个接口函数分别返回单个和多个继承类实例         */        public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {            public SavedState createFromParcel(Parcel in) {                return new SavedState(in);            }            public SavedState[] newArray(int size) {                return new SavedState[size];            }        };    }    public FragmentTabHost(Context context) {        // Note that we call through to the version that takes an AttributeSet,        // because the simple Context construct can result in a broken object!        super(context, null);        initFragmentTabHost(context, null);    }    public FragmentTabHost(Context context, AttributeSet attrs) {        super(context, attrs);        initFragmentTabHost(context, attrs);    }    /**     * obtainStyledAttributes:返回一个设计样式属性包含了set里面的attrs参数     * set:现在检索的属性值;     * attrs:制定的检索的属性值     * defStyleAttr:指向当前theme某个item描述的style 该style指定了一些默认值为这个TypedArray;     * defStyleRes:defStyleRes找不到或者为0,可以直接指定某个style     *     * @param context     * @param attrs     */    private void initFragmentTabHost(Context context, AttributeSet attrs) {        TypedArray a = context.obtainStyledAttributes(attrs,                new int[]{android.R.attr.inflatedId}, 0, 0);        mContainerId = a.getResourceId(0, 0);        a.recycle();        super.setOnTabChangedListener(this);    }    /**     * 判断是否设置tab显示的布局     *     * @param context     */    private void ensureHierarchy(Context context) {        // If owner hasn'mDatas made its own view hierarchy, then as a convenience        // we will construct a standard one here.        if (findViewById(android.R.id.tabs) == null) {            //设置布局            LinearLayout ll = new LinearLayout(context);            ll.setOrientation(LinearLayout.VERTICAL);            addView(ll, new LayoutParams(                    ViewGroup.LayoutParams.MATCH_PARENT,                    ViewGroup.LayoutParams.MATCH_PARENT));            //添加tab样式            TabWidget tw = new TabWidget(context);            tw.setId(android.R.id.tabs);            tw.setOrientation(TabWidget.HORIZONTAL);            ll.addView(tw, new LinearLayout.LayoutParams(                    ViewGroup.LayoutParams.MATCH_PARENT,                    ViewGroup.LayoutParams.WRAP_CONTENT, 0));            //设置fragmentLayout样式            FrameLayout fl = new FrameLayout(context);            fl.setId(android.R.id.tabcontent);            ll.addView(fl, new LinearLayout.LayoutParams(0, 0, 0));            mRealTabContent = fl = new FrameLayout(context);            mRealTabContent.setId(mContainerId);            ll.addView(fl, new LinearLayout.LayoutParams(                    LinearLayout.LayoutParams.MATCH_PARENT, 0, 1));        }    }    /**     * @deprecated Don'mDatas call the original TabHost setup, you must instead call     * {@link #setup(Context, FragmentManager)} or     * {@link #setup(Context, FragmentManager, int)}.     */    @Override    @Deprecated    public void setup() {        throw new IllegalStateException(                "Must call setup() that takes a Context and FragmentManager");    }    /**     * 加载tab布局     *     * @param context     * @param manager     */    public void setup(Context context, FragmentManager manager) {        ensureHierarchy(context); // Ensure views required by super.setup()        super.setup();        mContext = context;        mFragmentManager = manager;        ensureContent();    }    public void setup(Context context, FragmentManager manager, int containerId) {        ensureHierarchy(context); // Ensure views required by super.setup()        super.setup();        mContext = context;        mFragmentManager = manager;        mContainerId = containerId;        ensureContent();        mRealTabContent.setId(containerId);        // We must have an ID to be able to save/restore our state. If        // the owner hasn'mDatas set one at this point, we will set it ourself.        if (getId() == View.NO_ID) {            setId(android.R.id.tabhost);        }    }    /**     * 判断是否设置tab显示的样式     */    private void ensureContent() {        if (mRealTabContent == null) {            mRealTabContent = (FrameLayout) findViewById(mContainerId);            if (mRealTabContent == null) {                throw new IllegalStateException(                        "No tab content FrameLayout found for id "                                + mContainerId);            }        }    }    /**     * tab切换监听     *     * @param l     */    @Override    public void setOnTabChangedListener(OnTabChangeListener l) {        mOnTabChangeListener = l;    }    /**     * 添加tab选项卡     *     * @param tabSpec 选项卡的indicator,content,tag数据封装,用来跟踪选项卡     * @param clss    选项卡显示的类     * @param args    数据传递     */    public void addTab(TabSpec tabSpec, Class<?> clss, Bundle args) {        tabSpec.setContent(new DummyTabFactory(mContext));        String tag = tabSpec.getTag();        TabInfo info = new TabInfo(tag, clss, args);        if (mAttached) {            // If we are already attached to the window, then check to make            // sure this tab's fragment is inactive if it exists. This shouldn'mDatas            // normally happen.            info.fragment = mFragmentManager.findFragmentByTag(tag);            if (info.fragment != null && !info.fragment.isDetached()) {                FragmentTransaction ft = mFragmentManager.beginTransaction();//               ft.detach(info.fragment);                ft.hide(info.fragment);                ft.commit();            }        }        mTabs.add(info);        addTab(tabSpec);    }    @Override    protected void onAttachedToWindow() {        super.onAttachedToWindow();        String currentTab = getCurrentTabTag();//获取当前tab        // Go through all tabs and make sure their fragments match        // the correct state.        FragmentTransaction ft = null;        for (int i = 0; i < mTabs.size(); i++) {            TabInfo tab = mTabs.get(i);            tab.fragment = mFragmentManager.findFragmentByTag(tab.tag);//          if (tab.fragment != null && !tab.fragment.isDetached()) {            if (tab.fragment != null) {                if (tab.tag.equals(currentTab)) {                    // The fragment for this tab is already there and                    // active, and it is what we really want to have                    // as the current tab. Nothing to do.                    mLastTab = tab;//获取选中的tab                } else {                    // This fragment was restored in the active state,                    // but is not the current tab. Deactivate it.                    if (ft == null) {                        ft = mFragmentManager.beginTransaction();                    }//                    ft.detach(tab.fragment);                    ft.hide(tab.fragment);//隐藏fragment                }            }        }        // We are now ready to go. Make sure we are switched to the        // correct tab.        mAttached = true;        ft = doTabChanged(currentTab, ft);//当tab被选中,提交事务        if (ft != null) {            ft.commitAllowingStateLoss();            mFragmentManager.executePendingTransactions();        }    }    /**     * 当view从窗体分离时调用     */    @Override    protected void onDetachedFromWindow() {        super.onDetachedFromWindow();        mAttached = false;    }    @Override    protected Parcelable onSaveInstanceState() {        Parcelable superState = super.onSaveInstanceState();        SavedState ss = new SavedState(superState);//将控件属性打包到Parcel容器        ss.curTab = getCurrentTabTag();//初始化控件当前tab        return ss;    }    @Override    protected void onRestoreInstanceState(Parcelable state) {        SavedState ss = (SavedState) state;        super.onRestoreInstanceState(ss.getSuperState());        setCurrentTabByTag(ss.curTab);    }    @Override    public void onTabChanged(String tabId) {        if (mAttached) {            FragmentTransaction ft = doTabChanged(tabId, null);            if (ft != null) {                ft.commit();            }        }        if (mOnTabChangeListener != null) {            mOnTabChangeListener.onTabChanged(tabId);        }    }    /**     * tab选中事务处理     *     * @param tabId     * @param ft     * @return     */    private FragmentTransaction doTabChanged(String tabId,                                             FragmentTransaction ft) {        TabInfo newTab = null;        for (int i = 0; i < mTabs.size(); i++) {            TabInfo tab = mTabs.get(i);            if (tab.tag.equals(tabId)) {                newTab = tab;            }        }        if (newTab == null) {            throw new IllegalStateException("No tab known for tag " + tabId);        }        if (mLastTab != newTab) {            if (ft == null) {                ft = mFragmentManager.beginTransaction();            }            if (mLastTab != null) {                if (mLastTab.fragment != null) {//                    ft.detach(mLastTab.fragment);                    ft.hide(mLastTab.fragment);                }            }            //如果tab不为空,则显示,否则获取tab数据            if (newTab != null) {                if (newTab.fragment == null) {                    newTab.fragment = Fragment.instantiate(mContext,                            newTab.clss.getName(), newTab.args);                    ft.add(mContainerId, newTab.fragment, newTab.tag);                } else {//                    ft.attach(newTab.fragment);                    ft.show(newTab.fragment);                }            }            mLastTab = newTab;//获取选中的tab        }        return ft;    }}

接下来讲解如何使用FragmentTabHost。
附上官方文档FragmentTabHost官方文档
不使用ViewPager的情况,先在MainActivity.java的activity_main.xml布局文件中引用FragmentTabHost。

<FrameLayout        android:id="@+id/realtabcontent"  //这个才是真正显示tab内容的控件        android:layout_width="fill_parent"        android:background="@color/bg_color"        android:layout_weight="1"        android:layout_height="0dp">    </FrameLayout>   <com.dali.dalishop.widget.FragmentTabHost       android:id="@android:id/tabhost" //id是引用系统定义的       android:background="@color/white"       android:layout_width="fill_parent"       android:layout_height="?attr/actionBarSize">       <FrameLayout           android:id="@android:id/tabcontent"//这个是必须要写的,id引用的是系统的           android:layout_weight="0"           android:layout_width="0dp"           android:layout_height="0dp">       </FrameLayout>   </com.dali.dalishop.widget.FragmentTabHost>

在MainActivity中进行使用,在onCreate()方法中调用

private void bindWidget() {        tabs = new ArrayList<>(5);        inflater = LayoutInflater.from(this);        tabHost = (FragmentTabHost)this.findViewById(android.R.id.tabhost);        tabHost.setup(this,getSupportFragmentManager(),R.id.realtabcontent);}

既然要用到Fragment,就必须创建Fragment并添加其布局。创建Fragment比较简单,这里只贴出一个就好,其他类似。

public class HomeFragment extends Fragment {    public HomeFragment() {        // Required empty public constructor    }    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container,                             Bundle savedInstanceState) {        // Inflate the layout for this fragment        return inflater.inflate(R.layout.fragment_home, container, false);    }}

为了方便显示TabSpec,写一个bean来获取图片、文字以及Fragment。

public class Tab {    private int title;    private int image;    private Class fragment;    public Tab(int title, int image, Class fragment) {        this.title = title;        this.image = image;        this.fragment = fragment;    }    public int getTitle() {        return title;    }    public void setTitle(int title) {        this.title = title;    }    public int getImage() {        return image;    }    public void setImage(int image) {        this.image = image;    }    public Class getFragment() {        return fragment;    }    public void setFragment(Class fragment) {        this.fragment = fragment;    }}
// 添加tab显示的文字和图片,绑定fragmentprivate void initTabHost() {        //添加tab显示的文字和图片,绑定fragment        Tab tab_home = new Tab(R.string.home, R.drawable.selector_icon_home, HomeFragment.class);        Tab tab_hot = new Tab(R.string.hot, R.drawable.selector_icon_hot, HotFragment.class);        Tab tab_category = new Tab(R.string.category, R.drawable.selector_icon_category, CategoryFragment.class);        Tab tab_cart = new Tab(R.string.cart, R.drawable.selector_icon_cart, CartFragment.class);        Tab tab_mine = new Tab(R.string.mine, R.drawable.selector_icon_mine, MineFragment.class);        tabs.add(tab_home);        tabs.add(tab_hot);        tabs.add(tab_category);        tabs.add(tab_cart);        tabs.add(tab_mine);        for (Tab tab : tabs)            //实例化TabSpec对象 {            TabHost.TabSpec tabSpec = tabHost.newTabSpec(getString(tab.getTitle()));            //设置indicator            tabSpec.setIndicator(buildIndicator(tab));            //添加tabSpec            tabHost.addTab(tabSpec,tab.getFragment(),null);        }        //默认选择第一个        tabHost.setCurrentTab(0);    }

为方便显示,将view写成一个布局文件方便后续读写。

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:orientation="vertical"    android:gravity="center"    android:padding="10px"    android:layout_width="wrap_content"    android:layout_height="wrap_content">    <ImageView        android:id="@+id/img_tab"        android:src="@mipmap/ic_launcher"        android:layout_gravity="center"        android:layout_width="60px"        android:layout_height="60px" />    <TextView        android:id="@+id/tv_title"        android:text="text"        android:textColor="@color/selector_tab_text"        android:layout_gravity="center"        android:layout_width="wrap_content"        android:layout_height="wrap_content" /></LinearLayout>

读取布局文件。

    /**     * indicator包括ImageView和TextView     * @param tab     * @return     */private View buildIndicator(Tab tab) {        View view = inflater.inflate(R.layout.tab_indicator, null);        ImageView img = (ImageView) view.findViewById(R.id.img_tab);        TextView title = (TextView) view.findViewById(R.id.tv_title);        img.setImageResource(tab.getImage());        title.setText(tab.getTitle());        return view;    }

代码写到此,运行之后会看到中间有分割线,看上去不是很美观,因此需要将其去掉。

这里写图片描述

只需要在initTabHost()方法后面加上一句代码就搞定了。

//去掉分割线tabHost.getTabWidget().setShowDividers(LinearLayout.SHOW_DIVIDER_NONE);

接下来讲解如何将ViewPager和FragmentTabHost结合使用。
只需要在activity_main.xml中添加ViewPager控件就好。

<android.support.v4.view.ViewPager        android:id="@+id/view_pager"        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="1"        android:background="@color/bg_color" />//由于这个不再使用,将其注释掉。    <!--<FrameLayout-->        <!--android:id="@+id/realtabcontent"-->        <!--android:layout_width="fill_parent"-->        <!--android:background="@color/bg_color"-->        <!--android:layout_weight="1"-->        <!--android:layout_height="0dp">-->    <!--</FrameLayout>-->   <com.dali.dalishop.widget.FragmentTabHost       android:id="@android:id/tabhost"       android:background="@color/white"       android:layout_width="fill_parent"       android:layout_height="?attr/actionBarSize">       <FrameLayout           android:id="@android:id/tabcontent"           android:layout_weight="0"           android:layout_width="0dp"           android:layout_height="0dp">       </FrameLayout>   </com.dali.dalishop.widget.FragmentTabHost>

在MainActivity的bindWidget()方法中,将

 tabHost.setup(this,getSupportFragmentManager(),R.id.realtabcontent);

替换成

tabHost.setup(this, getSupportFragmentManager(), R.id.view_pager);

即可。

ViewPager要显示布局,需要为其定义一个适配器。并且必须继承FragmentPagerAdapter。

public class ViewPagerAdapter extends FragmentPagerAdapter {    List<Fragment> datas;    public ViewPagerAdapter(FragmentManager fm, List<Fragment> datas) {        super(fm);        this.datas = datas;    }    @Override    public Fragment getItem(int position) {        return datas == null ? null : datas.get(position);    }    @Override    public int getCount() {        return datas == null ? 0 : datas.size();    }}

在MainActivity中创建ViewPagerAdapter适配器实例,并添加相应数据。

private void initPager() {  //在onCreat()方法中调用        HomeFragment homeFragment = new HomeFragment();        HotFragment hotFragment = new HotFragment();        CartFragment cartFragment = new CartFragment();        CategoryFragment categoryFragment = new CategoryFragment();        MineFragment mineFragment = new MineFragment();        list.add(homeFragment);        list.add(hotFragment);        list.add(cartFragment);        list.add(categoryFragment);        list.add(mineFragment);        //绑定Fragment适配器        viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager(), list));    }

要想Fragment和ViewPager实现同步滑动,必须使用监听。
ViewPager.OnPageChangeListener, TabHost.OnTabChangeListener。

/*实现setOnTabChangedListener接口,目的是为监听界面切换,然后实现TabHost里面图片文字的选中状态切换*/        /*简单来说,是为了当点击下面菜单时,上面的ViewPager能滑动到对应的Fragment*/        tabHost.setOnTabChangedListener(this);        /*实现OnPageChangeListener接口,目的是监听Tab选项卡的变化,然后通知ViewPager适配器切换界面*/        /*简单来说,是为了让ViewPager滑动的时候能够带着底部菜单联动*/        viewPager.setOnPageChangeListener(this);
    @Override    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {    }    @Override    public void onPageSelected(int position) {//当前选中的页面位置Postion,页面跳转完毕的时候调用。        TabWidget widget = tabHost.getTabWidget();        int oldFocusability = widget.getDescendantFocusability();        widget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);//设置View覆盖子类控件而直接获得焦点        tabHost.setCurrentTab(position);//根据位置Postion设置当前的Tab        widget.setDescendantFocusability(oldFocusability);//设置取消分割线    }    @Override    public void onPageScrollStateChanged(int state) {    }    @Override    public void onTabChanged(String tabId) {        int position = tabHost.getCurrentTab();        viewPager.setCurrentItem(position);//把选中的Tab的位置赋给适配器,让它控制页面切换    }

接下来运行就可以实现同步滑动了。

0 0