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 调用。
讲讲代码
- 创建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().
- 创建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];
}
};
}
}
- 创建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源码的