FragmentTabHost实现App主界面


  • Android重写FragmentTabHost源码来实现状态保存
    FragmentTabHost在切换Fragment的时候是使用detach()和attach()来实现Fragment的隐藏和显示的,这样的话每次切换肯定要重新加载布局,除了使用detach()和attach(),我们还可以使用show()和hide()来实现显示和隐藏,这样可以保存状态,方案出来了就是修改FragmentTabHost源码将切换Fragment的方式detach()和attach()改为hide()和show()。
/* 
 * Copyright (C) 2012 The Android Open Source Project 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */  
  
import java.util.ArrayList;  
  
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;  
  
/** 
 * Special TabHost that allows the use of {@link Fragment} objects for its tab 
 * content. When placing this in a view hierarchy, after inflating the hierarchy 
 * you must call {@link #setup(Context, FragmentManager, int)} to complete the 
 * initialization of the tab host. 
 *  
 * <p> 
 * Here is a simple example of using a FragmentTabHost in an Activity: 
 *  
 * {@sample 
 * development/samples/Support4Demos/src/com/example/android/supportv4/app/ 
 * FragmentTabs.java complete} 
 *  
 * <p> 
 * This can also be used inside of a fragment through fragment nesting: 
 *  
 * {@sample 
 * development/samples/Support4Demos/src/com/example/android/supportv4/app/ 
 * FragmentTabsFragmentSupport.java complete} 
 */  
public class FragmentTabHost extends TabHost implements  
        TabHost.OnTabChangeListener {  
    private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();  
    private FrameLayout mRealTabContent;  
    private Context mContext;  
    private FragmentManager mFragmentManager;  
    private int mContainerId;  
    private TabHost.OnTabChangeListener mOnTabChangeListener;  
    private TabInfo mLastTab;  
    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;  
        }  
    }  
  
    static class DummyTabFactory implements TabHost.TabContentFactory {  
        private final Context mContext;  
  
        public DummyTabFactory(Context context) {  
            mContext = context;  
        }  
  
        @Override  
        public View createTabContent(String tag) {  
            View v = new View(mContext);  
            v.setMinimumWidth(0);  
            v.setMinimumHeight(0);  
            return v;  
        }  
    }  
  
    static class SavedState extends BaseSavedState {  
        String curTab;  
  
        SavedState(Parcelable superState) {  
            super(superState);  
        }  
  
        private SavedState(Parcel in) {  
            super(in);  
            curTab = in.readString();  
        }  
  
        @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 + "}";  
        }  
  
        public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.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);  
    }  
  
    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);  
    }  
  
    private void ensureHierarchy(Context context) {  
        // If owner hasn't 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 FrameLayout.LayoutParams(  
                    ViewGroup.LayoutParams.MATCH_PARENT,  
                    ViewGroup.LayoutParams.MATCH_PARENT));  
  
            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));  
  
            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't 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");  
    }  
  
    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't set one at this point, we will set it ourself.  
        if (getId() == View.NO_ID) {  
            setId(android.R.id.tabhost);  
        }  
    }  
  
    private void ensureContent() {  
        if (mRealTabContent == null) {  
            mRealTabContent = (FrameLayout) findViewById(mContainerId);  
            if (mRealTabContent == null) {  
                throw new IllegalStateException(  
                        "No tab content FrameLayout found for id "  
                                + mContainerId);  
            }  
        }  
    }  
  
    @Override  
    public void setOnTabChangedListener(OnTabChangeListener l) {  
        mOnTabChangeListener = l;  
    }  
  
    public void addTab(TabHost.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't  
            // 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();  
  
        // 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;  
                } 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);  
                }  
            }  
        }  
  
        // We are now ready to go. Make sure we are switched to the  
        // correct tab.  
        mAttached = true;  
        ft = doTabChanged(currentTab, ft);  
        if (ft != null) {  
            ft.commit();  
            mFragmentManager.executePendingTransactions();  
        }  
    }  
  
    @Override  
    protected void onDetachedFromWindow() {  
        super.onDetachedFromWindow();  
        mAttached = false;  
    }  
  
    @Override  
    protected Parcelable onSaveInstanceState() {  
        Parcelable superState = super.onSaveInstanceState();  
        SavedState ss = new SavedState(superState);  
        ss.curTab = getCurrentTabTag();  
        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);  
        }  
    }  
  
    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);  
                }  
            }  
            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;  
        }  
        return ft;  
    }  
}  
FragmentTabHost.png
  • 在主界面布局activity_main.xml上添加FragmentTabHost:
<?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:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="news.huoren.com.cnshop.MainActivity">

    <!--此FrameLayout是真正装载Fragment的控件-->
   <FrameLayout
       android:id="@+id/realtabcontent"
       android:layout_width="match_parent"
       android:layout_height="0dp"
       android:layout_weight="1"
       android:background="@color/bg_color"
       ></FrameLayout>

    <news.huoren.com.cnshop.widget.FragmentTabHost
        android:id="@android:id/tabhost" <!--id必须为@android:id/tabhost-->
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <!--官方要求此FrameLayout这样设置,id为@android:id/tabcontent-->
        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="0"></FrameLayout>
    </news.huoren.com.cnshop.widget.FragmentTabHost>
</LinearLayout>
  • 然后编写MainActivity:
package news.huoren.com.cnshop;


import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TabHost;
import android.widget.TextView;

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

import news.huoren.com.cnshop.bean.Tab;
import news.huoren.com.cnshop.fragment.CartFragment;
import news.huoren.com.cnshop.fragment.CategoryFragment;
import news.huoren.com.cnshop.fragment.HomeFragment;
import news.huoren.com.cnshop.fragment.HotFragment;
import news.huoren.com.cnshop.fragment.MineFragment;
import news.huoren.com.cnshop.widget.FragmentTabHost;

public class MainActivity extends AppCompatActivity {

    private LayoutInflater inflater;
    private FragmentTabHost mTabHost;
    private List<Tab> mTabList = new ArrayList<>(5);

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

        initTab();
    }

    private void initTab() {

        Tab tab_home = new Tab(R.drawable.selector_icon_home, R.string.home, HomeFragment.class);
        Tab tab_hot = new Tab(R.drawable.selector_icon_hot, R.string.hot, HotFragment.class);
        Tab tab_category = new Tab(R.drawable.selector_icon_category, R.string.category, CategoryFragment.class);
        Tab tab_cart = new Tab(R.drawable.selector_icon_cart, R.string.cart, CartFragment.class);
        Tab tab_mine = new Tab(R.drawable.selector_icon_mine, R.string.mine, MineFragment.class);

        mTabList.add(tab_home);
        mTabList.add(tab_hot);
        mTabList.add(tab_category);
        mTabList.add(tab_cart);
        mTabList.add(tab_mine);


        //初始化inflater
        inflater = LayoutInflater.from(this);
        mTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
        mTabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent);


        for (Tab tab : mTabList){
            TabHost.TabSpec tabspec = mTabHost.newTabSpec(getString(tab.getTitle()));
            tabspec.setIndicator(buildIndicator(tab));
            mTabHost.addTab(tabspec, tab.getFragment(), null);
        }

        mTabHost.getTabWidget().setShowDividers(LinearLayout.SHOW_DIVIDER_NONE);
        mTabHost.setCurrentTab(0);


    }

    private View buildIndicator(Tab tab){
        View view = inflater.inflate(R.layout.tab_indicator, null);
        <!--view的findViewById-->
        ImageView tab_img = (ImageView) view.findViewById(R.id.icon_tab);
        TextView tab_title = (TextView) view.findViewById(R.id.txt_indicator);


        tab_img.setBackgroundResource(tab.getIcon());
        tab_title.setText(tab.getTitle());

        return view;
    }
}
  • 上述代码使用了一个Tab的bean类,用来存放底部栏Tab的图标和文字,以及五个Fragment:
package news.huoren.com.cnshop.bean;

public class Tab {
    private int title;
    private int icon;
    private Class fragment;

    public Tab(int icon, int title, Class fragment) {
        this.title = title;
        this.icon = icon;
        this.fragment = fragment;
    }

    public int getTitle() {
        return title;
    }

    public void setTitle(int title) {
        this.title = title;
    }

    public int getIcon() {
        return icon;
    }

    public void setIcon(int icon) {
        this.icon = icon;
    }

    public Class getFragment() {
        return fragment;
    }

    public void setFragment(Class fragment) {
        this.fragment = fragment;
    }
}
package news.huoren.com.cnshop.bean;

public class Tab {
    private int title;
    private int icon;
    private Class fragment;

    public Tab(int icon, int title, Class fragment) {
        this.title = title;
        this.icon = icon;
        this.fragment = fragment;
    }

    public int getTitle() {
        return title;
    }

    public void setTitle(int title) {
        this.title = title;
    }

    public int getIcon() {
        return icon;
    }

    public void setIcon(int icon) {
        this.icon = icon;
    }

    public Class getFragment() {
        return fragment;
    }

    public void setFragment(Class fragment) {
        this.fragment = fragment;
    }
}
  • 还用到了selector设置图标和文字的切换变色:
    其中的一个图标的切换selector_icon_home.xml:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- 没获取到焦点状态 -->
    <item android:state_focused="false" android:state_selected="false" android:state_pressed="false" android:drawable="@mipmap/icon_home" />
    <item android:state_focused="false" android:state_selected="true" android:state_pressed="false" android:drawable="@mipmap/icon_home_press" />
    <!-- 获取到焦点状态 -->
    <item android:state_focused="true" android:state_selected="false" android:state_pressed="false" android:drawable="@mipmap/icon_home_press" />
    <item android:state_focused="true" android:state_selected="true" android:state_pressed="false" android:drawable="@mipmap/icon_home_press" />
    <!-- 按压状态 -->
    <item android:state_selected="true" android:state_pressed="true" android:drawable="@mipmap/icon_home_press" />
    <item android:state_pressed="true" android:drawable="@mipmap/icon_home_press" />

</selector>

Tab的文字颜色变换:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_selected="true" android:color="#eb4f38" />
    <item android:state_active="true" android:color="#eb4f38"/>
    <item android:state_selected="false" android:color="#a9b7b7" />
    <item android:state_active="false" android:color="#a9b7b7"/>
</selector>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容