TabHost的编写过程

TabHost 一个类似于苹果上TabBar的上东西。

开发原因
虽然不提倡在Android上使用这种方式,但是无奈要使用的场景太多了。
以前的解决方案是使用RadioGroup ,这个解决方案比较快,但是问题也有,就是无法显示小红点。
粗暴的解决方案是使用布局控件,LiearLayout 包裹着几个个RelativeLayout,然后就可以解决了。
以上的解决每次都要自己写一次(当然,很多人其实一年也只是写一次而已),但是我就是觉得很烦,而且上面的组合方式一定是用到了Fragment,还要管理Fragment的恢复和保存,不然就要等着回复Activity实例的时候Fragment的各种错位。

解决的问题
主要解决的问题的是Fragment的保存和恢复的问题,其次才是一下杂七杂八的问题

TabHost 架构

界面层
TabHost Tab 这些元素组成了,TabBar界面的现实
模型层
FragmentTabAdapter 完成了整个外部界面的显示和切换,在getFragment 中提供Fragment

当界面回收的时候,FramgmentTabAdapter会把Fragment装换到Bundle中,通过TabHost的outState 保存,
当恢复的时候通过TabHost的onRestoreInstance函数调用FragmentTabAdapter的onrestoreInstance 调用。

讲讲代码

  1. 创建CheckableLayout.java
    这是一个响应选中事件的RelativeLayout,用来当Tab的父类
package me.hangox.tabhost;


import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Checkable;
import android.widget.FrameLayout;


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




/**
 * @author 47
 *         继承于RelativeLayout,
 *         主要作用是使布局中其中一个View
 */
public class CheckableLayout extends FrameLayout implements Checkable {
    public static final String TAG = CheckableLayout.class.getSimpleName();


    private static final int[] LAYOUT_STATE = {
            android.R.attr.state_checked,
    };


    private List<Checkable> mCheckableList = new ArrayList<>();


    private boolean mChecked;


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


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


    public CheckableLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }




    @Override
    public void setChecked(boolean checked) {
        if (mChecked == checked) return;
        mChecked = checked;
        setChildClicked(checked);
        refreshDrawableState();


    }


    private void setChildClicked(boolean checked){
        for(Checkable checkable : mCheckableList){
            checkable.setChecked(checked);
            ((View)checkable).refreshDrawableState();
        }
    }


    public void addCheckableView(Checkable checkable){
        if(checkable instanceof View){
            mCheckableList.add(checkable);
        }
    }


    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
        if (isChecked()) {
            mergeDrawableStates(drawableState, LAYOUT_STATE);
        }
        return drawableState;
    }


    @Override
    public boolean performClick() {
        toggle();
        return super.performClick();
    }


    @Override
    public boolean isChecked() {
        return mChecked;
    }


    @Override
    public void toggle() {
        //防止双击的时候再次选中
        if (!isChecked()) setChecked(!mChecked);
    }




}

这里比较困难的地方在于,RelativeLayout 原本是不支持Checkable 这个状态的,需要添加代码来响应这个状态,具体代码参考onCreateDrawbleState().

  1. 创建Tab.java
    Tab是用来显示一个个的按钮的,外带小红点功能
package me.hangox.tabhost;


import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckedTextView;


import com.hangox.xlog.XLog;


import org.hangox.tabhost.R;




/**
 * Created With Android Studio
 * User @47
 * Date 2014-10-21
 * Time 22:29 5
 */
public class Tab extends CheckableLayout {
    public static final String TAG = Tab.class.getSimpleName();
    /**
     * 显示文字作用
     */
    private CheckedTextView mCheckText;


    /**
     * 自带背景红点功能
     */
    private TipView mTipView;
    private OnCheckedChangeListener mCheckChangedListener;
    private int mTipBackgroundColor = 0xFFFC5C3F;
    private int mTipCount = -1;




    public int getIndex() {
        return mIndex;
    }


    public void setIndex(int mIndex) {
        this.mIndex = mIndex;
    }


    private int mIndex;


    public Tab(Context context) {
        super(context);
        init(null, 0);
    }


    public Tab(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0);
    }


    public Tab(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs, defStyleAttr);
    }


    private void init(AttributeSet attrs, int defStyle) {
        final TypedArray a = getContext().obtainStyledAttributes(attrs,
                R.styleable.Tab, defStyle, 0);
        Drawable icon = a.getDrawable(R.styleable.Tab_tabIcon);
        String tabName = a.getString(R.styleable.Tab_tabName);
        float tabTextSize = a.getDimension(R.styleable.Tab_tabTextSize, TypedValue.applyDimension
                (TypedValue.COMPLEX_UNIT_SP, 10, getResources().getDisplayMetrics()));
        ColorStateList tabTextColor = a.getColorStateList(R.styleable.Tab_tabTextColor);


        mTipBackgroundColor = a.getColor(R.styleable.Tab_tipBackgroundColor, mTipBackgroundColor);
        int tipTextColor = a.getColor(R.styleable.Tab_tipTextColor, Color.WHITE);


        a.recycle();
        //initView
        //初始化文字View
        Context context = getContext();
        View rootView = View.inflate(context, R.layout.tabhost_tab, null);
        //icon
        CheckedImageView iconView = (CheckedImageView) rootView.findViewById(R.id.tabIcon);
        iconView.setImageDrawable(icon);
        addCheckableView(iconView);
        //tabName
        mCheckText = (CheckedTextView) rootView.findViewById(R.id.tabName);
        mCheckText.setText(tabName);
        if (tabTextColor != null) mCheckText.setTextColor(tabTextColor);
        mCheckText.setTextSize(TypedValue.COMPLEX_UNIT_PX, tabTextSize);
        mCheckText.setFocusable(false);
        addCheckableView(mCheckText);
        //添加小红点
        mTipView = (TipView) rootView.findViewById(R.id.tipView);
        //设置小红点背景颜色
        mTipView.setTipBackgroundColor(mTipBackgroundColor);
        mTipView.setTextColor(tipTextColor);
        //设置整个View
        addView(rootView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT));
        setClickable(true);
        setSaveEnabled(true);
    }




    @Override
    public boolean isInEditMode() {
        return true;
    }


    public void setTipText(String number) {
        mTipView.setText(number);
    }


    public int getTipCount() {
        return mTipCount;
    }


    public void setTipCount(int count) {
        mTipCount = count;
        if (count <= 0) mTipView.setVisibility(GONE);
        else mTipView.setVisibility(VISIBLE);
        if (count > 99) mTipView.setText("N");
        else mTipView.setText(count + "");
        XLog.i("tipCount"+getTipCount());
    }




    public interface OnCheckedChangeListener {
        void onCheckedChanged(Tab tab, boolean isChecked);
    }


    @Override
    public void setChecked(boolean checked) {
        super.setChecked(checked);
        if (mCheckChangedListener != null) {
            mCheckChangedListener.onCheckedChanged(this, checked);
        }


    }




    @Override
    public void onRestoreInstanceState(Parcelable state) {
        SaveState saveState = (SaveState) state;
        if (saveState.tipCount != -1) {
            setTipCount(saveState.tipCount);
        }
        super.onRestoreInstanceState(saveState.getSuperState());


    }


    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superSave = super.onSaveInstanceState();
        SaveState saveState = new SaveState(superSave);
        saveState.tipCount = mTipCount;
        return saveState;
    }


    public void setOnCheckChangedListener(OnCheckedChangeListener checkChangedListener) {
        mCheckChangedListener = checkChangedListener;
    }


    public CharSequence getText() {
        return mCheckText.getText();
    }


    static class SaveState extends BaseSavedState {
        int tipCount;


        public SaveState(Parcel source) {
            super(source);
            tipCount = source.readInt();
        }


        public SaveState(Parcelable superSave) {
            super(superSave);
        }




        @Override
        public void writeToParcel(Parcel dest, int flags) {
            super.writeToParcel(dest, flags);
            dest.writeInt(tipCount);
        }


        public static final Creator<SaveState> CREATOR = new Creator<SaveState>() {
            @Override
            public SaveState createFromParcel(Parcel source) {
                return new SaveState(source);
            }


            @Override
            public SaveState[] newArray(int size) {
                return new SaveState[size];
            }
        };




    }


}



  1. 创建TabHost.java
package me.hangox.tabhost;




import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.os.ParcelableCompat;
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;




/**
*
* @author 47
* Tab容器
*/
public class TabHost extends LinearLayout {
// holds the checked id; the selection is empty by default
private int mCheckedId = -1;
// tracks children radio buttons checked state


private CheckedStateTracker mChildOnCheckedChangeListener;
// when true, mFragmentTabAdapter discards events
private boolean mProtectFromCheckedChange = false;
private FragmentTabAdapter mFragmentTabAdapter;
private PassThroughHierarchyChangeListener mPassThroughListener;
//restore onAdapter
private Parcelable mAdapterSavedState;
private ClassLoader mAdapterClassLoader;






/**
* {@inheritDoc}
*/
public TabHost(Context context) {
super(context);
setOrientation(HORIZONTAL);
init();
}


@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public TabHost(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}


/**
* {@inheritDoc}
*/
public TabHost(Context context, AttributeSet attrs) {
super(context, attrs);
init();


}


@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public TabHost(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}


private void init() {
mChildOnCheckedChangeListener = new CheckedStateTracker();
mPassThroughListener = new PassThroughHierarchyChangeListener();
super.setOnHierarchyChangeListener(mPassThroughListener);
}




@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (child instanceof Tab) {
final Tab tab = (Tab)child;
if (tab.isChecked()) {
mProtectFromCheckedChange = true;
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false);
}
mProtectFromCheckedChange = false;
setCheckedId(child.getId());
}
}


super.addView(child, index, params);
}






/**
* <p>Sets the selection to the radio button whose identifier is passed in
* parameter. Using -1 as the selection identifier clears the selection;
*
* @param id the unique id of the radio button to select in this group
*
* @see #getCheckedTabId()
*/
public void check(int id) {
// don't even bother
if (id != -1  && (id == mCheckedId)) {
return;
}


if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false);
}


if (id != -1) {
setCheckedStateForView(id, true);
}


}


/**
* 设定点击位置
* @param position
*/
public void checkPosition(int position){
if(position < 0 || position >= getChildCount()) return;
check(getChildAt(position).getId());
}


private void setCheckedId(int id ){
mCheckedId = id;
Tab tab = findTabById(mCheckedId);
if (mFragmentTabAdapter != null) {
mFragmentTabAdapter.onCheckedChanged(this, mCheckedId, tab);
}
}


private void setCheckedStateForView(int viewId, boolean checked) {
//我不知道为什么findviewbyid 无法找到这个view
//        View checkedView = findViewById(viewId);
Tab checkedView = findTabById(viewId);
if (checkedView != null) {
checkedView.setChecked(checked);
}
}




/**
* 自己写的findviewbyid,不知道为何findviewbyid无法找到子view
* @param viewId
* @return
*/
private Tab findTabById(int viewId){
for(int i = getChildCount() - 1; i >= 0 ; i -- ){
View child = getChildAt(i);
if(child.getId() == viewId && child instanceof Tab) return (Tab) child;
}
return null;
}


/**
* <p>Returns the identifier of the selected radio button in this group.
* Upon empty selection, the returned value is -1.</p>
*
* @return the unique id of the selected radio button in this group
*
* @see #check(int)
*
* @attr ref android.R.styleable#RadioGroup_checkedButton
*/
public int getCheckedTabId() {
return mCheckedId;
}




/**
* <p>Register a callback to be invoked when the checked radio button
* changes in this group.</p>
*
* @param adapter the callback to call on checked state change
*/
public void setAdapter(FragmentTabAdapter adapter) {
mFragmentTabAdapter = adapter;
if(mCheckedId != - 1){
mFragmentTabAdapter.onRestoreInstanceState(mAdapterSavedState,mAdapterClassLoader);
setCheckedId(mCheckedId);
}
}






@Override
protected Parcelable onSaveInstanceState() {
SavedState savedState = new SavedState(super.onSaveInstanceState());
savedState.checkedId= mCheckedId;
if (mFragmentTabAdapter != null) savedState.adapterState = mFragmentTabAdapter
.onSavedState();
return savedState;
}


@Override
protected void onRestoreInstanceState(Parcelable state) {
if(!(state instanceof SavedState)){
super.onRestoreInstanceState(state);
return;
}
SavedState savedState  = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
if(mFragmentTabAdapter != null){
mFragmentTabAdapter.onRestoreInstanceState(savedState.adapterState,savedState.loader);
check(savedState.checkedId);
}else{
mAdapterSavedState = savedState.adapterState;
mCheckedId = savedState.checkedId;
mAdapterClassLoader = savedState.loader;
//            XLog.i(mCheckedId);
}
}




static class SavedState extends BaseSavedState{
Parcelable adapterState;
int checkedId = -1;
ClassLoader loader;


SavedState(Parcelable superState){
super(superState);
}


public SavedState(Parcel source,ClassLoader classLoader) {
super(source);
if(classLoader == null) classLoader = getClass().getClassLoader();
adapterState = source.readParcelable(classLoader);
checkedId = source.readInt();
loader = classLoader;
}


@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeParcelable(adapterState, 1);
dest.writeInt(checkedId);
}




public static final Creator<SavedState> CREATOR = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in, ClassLoader loader) {
return new SavedState(in,loader);
}


@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
});
}




private class CheckedStateTracker implements Tab.OnCheckedChangeListener {


@Override
public void onCheckedChanged(Tab tab, boolean isChecked) {
// prevents from infinite recursion
if (mProtectFromCheckedChange) {
return;
}


if(mCheckedId == tab.getId()) return;


mProtectFromCheckedChange = true;
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false);
}
mProtectFromCheckedChange = false;


int id = tab.getId();
setCheckedId(id);
}
}




public Tab getTabAt(int index){
return (Tab) getChildAt(index);
}


/**
* @author 47
* 一个添加和删除view的监听器
*/
private class PassThroughHierarchyChangeListener implements
OnHierarchyChangeListener {
private OnHierarchyChangeListener mOnHierarchyChangeListener;


/**
* {@inheritDoc}
*/
public void onChildViewAdded(View parent, View child) {
if (parent == TabHost.this && child instanceof Tab) {
int id = child.getId();
//如果没有分配ID就给一个ID控件
if (id == View.NO_ID) {
child.setId(((Tab) child).getText().hashCode());
}
//                XLog.i(child.getId());
//do something on Add
Tab tab = (Tab) child;
tab.setOnCheckChangedListener(mChildOnCheckedChangeListener);
//aleady add to parent;
tab.setIndex(TabHost.this.getChildCount() - 1);
//自动改变布局为均等大小
LayoutParams layoutParams  = (LayoutParams) tab.getLayoutParams();
if(layoutParams == null)
layoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.weight = 1;
layoutParams.width = 0;
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
tab.requestLayout();
}


if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewAdded(parent, child);
}
}


/**
* {@inheritDoc}
*/
public void onChildViewRemoved(View parent, View child) {
if (parent == TabHost.this && child instanceof Tab) {
//do something
Tab tab = (Tab) child;
tab.setOnCheckChangedListener(null);
}


if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
}
}
}
}

TabHost的设计其实很大一部分都是参考RadioGroup源码的