决定写这个系列,是因为大部分的国内博客都是拿来主义——照搬,一点也不考察博客内容的正确性,
希望这个系列能对国内的这种风气起到一点作用。
首先就拿一个常见的FragmentTabHost和ViewPager实现手势切换底部工具栏来开开刀吧
一个实现普遍的实现如博客使用FragmentTabHost和ViewPager实现仿微信主界面侧滑 (无意冒犯,Google搜索的排名靠前,如需删除请告知)
这种实现方法确实能够实现底部工具栏的切换,但是却增加了一倍Fragment的数量。
文中调用FragmentTabHost的addTab将fragmentArray中Fragments加入FragmentTabHost中,
(注:fragment是以class的形式传入,在FragmentTabHost的内部会将这些fragment的类实例化)
而在initPager函数中又新建了一遍所有的fragment,并加入到ViewPager中。
因此文中存在8个fragment的实例,而这个界面往往是放在应用启动后的第一个界面,这种资源的浪费是很严重的。
那么如何解决呢?
一种方法是自己写一个底部的工具栏,然后配合ViewPager进行Tab的切换。
虽然方法简单,但略显繁琐,有点重复造轮子的感脚。
另外一种方法是重用FragmentTabHost中的TabWidget类,具体的xml代码如下:
TabWidget的具体用法资料比较少,可直接看其源代码TabWidget.java
简单说下,通过TabWidget.addView()来加入Tab的图标,
通过TabWidget.setCurrentTab()来设置当前focus在哪个tab上。
但是这种方法存在两个问题,一个是Tab的点击响应实现不方便,一个是2.3及以下的系统不兼容。
可以看到源码中OnTabSelectionChanged并不是public,因此只有和TabWidget在一个package中的才能访问这个interface。
而不兼容问题导致在应用中TabWidget显示成一个白条。。。
因此小星最后的解决方法是实现自己的一个TabWidget,但并不是完全自己写,而是有选择的拷贝TabWidget里的代码。
最终修改后的TabWidget的源码如下,小星将其命名为MartianTabWidget:
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.LinearLayout;
/**
* Created by xuzb on 14-12-29.
*/
public class MartianTabWidget extends LinearLayout implements View.OnFocusChangeListener {
private OnTabSelectionChanged mSelectionChangedListener;
// This value will be set to 0 as soon as the first tab is added to TabHost.
private int mSelectedTab = -1;
public MartianTabWidget(Context context) {
super(context);
initTabWidget();
}
public MartianTabWidget(Context context, AttributeSet attrs) {
super(context, attrs);
initTabWidget();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public MartianTabWidget(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initTabWidget();
}
private void initTabWidget() {
setChildrenDrawingOrderEnabled(true);
// Deal with focus, as we don't want the focus to go by default
// to a tab other than the current tab
setFocusable(true);
setOnFocusChangeListener(this);
}
/**
* Returns the tab indicator view at the given index.
*
* @param index the zero-based index of the tab indicator view to return
* @return the tab indicator view at the given index
*/
public View getChildTabViewAt(int index) {
return getChildAt(index);
}
/**
* Returns the number of tab indicator views.
* @return the number of tab indicator views.
*/
public int getTabCount() {
return getChildCount();
}
/**
* Sets the current tab.
* This method is used to bring a tab to the front of the Widget,
* and is used to post to the rest of the UI that a different tab
* has been brought to the foreground.
*
* Note, this is separate from the traditional "focus" that is
* employed from the view logic.
*
* For instance, if we have a list in a tabbed view, a user may be
* navigating up and down the list, moving the UI focus (orange
* highlighting) through the list items. The cursor movement does
* not effect the "selected" tab though, because what is being
* scrolled through is all on the same tab. The selected tab only
* changes when we navigate between tabs (moving from the list view
* to the next tabbed view, in this example).
*
* To move both the focus AND the selected tab at once, please use
* {@link #setCurrentTab}. Normally, the view logic takes care of
* adjusting the focus, so unless you're circumventing the UI,
* you'll probably just focus your interest here.
*
* @param index The tab that you want to indicate as the selected
* tab (tab brought to the front of the widget)
*
*/
public void setCurrentTab(int index) {
if (index < 0 || index >= getTabCount() || index == mSelectedTab) {
return;
}
if (mSelectedTab != -1) {
getChildTabViewAt(mSelectedTab).setSelected(false);
}
mSelectedTab = index;
getChildTabViewAt(mSelectedTab).setSelected(true);
if (isShown()) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
}
}
@Override
protected int getChildDrawingOrder(int childCount, int i) {
if (mSelectedTab == -1) {
return i;
} else {
// Always draw the selected tab last, so that drop shadows are drawn
// in the correct z-order.
if (i == childCount - 1) {
return mSelectedTab;
} else if (i >= mSelectedTab) {
return i + 1;
} else {
return i;
}
}
}
@Override
public void addView(View child) {
if (child.getLayoutParams() == null) {
final LinearLayout.LayoutParams lp = new LayoutParams(
0,
ViewGroup.LayoutParams.MATCH_PARENT, 1.0f);
lp.setMargins(0, 0, 0, 0);
child.setLayoutParams(lp);
}
// Ensure you can navigate to the tab with the keyboard, and you can touch it
child.setFocusable(true);
child.setClickable(true);
super.addView(child);
// TODO: detect this via geometry with a tabwidget listener rather
// than potentially interfere with the view's listener
child.setOnClickListener(new TabClickListener(getTabCount() - 1));
child.setOnFocusChangeListener(this);
}
@Override
public void removeAllViews() {
super.removeAllViews();
mSelectedTab = -1;
}
/**
* Provides a way for {@link android.widget.TabHost} to be notified that the user clicked on a tab indicator.
*/
public void setTabSelectionListener(OnTabSelectionChanged listener) {
mSelectionChangedListener = listener;
}
/** {@inheritDoc} */
public void onFocusChange(View v, boolean hasFocus) {
if (v == this && hasFocus && getTabCount() > 0) {
getChildTabViewAt(mSelectedTab).requestFocus();
return;
}
if (hasFocus) {
int i = 0;
int numTabs = getTabCount();
while (i < numTabs) {
if (getChildTabViewAt(i) == v) {
setCurrentTab(i);
mSelectionChangedListener.onTabSelectionChanged(i, false);
if (isShown()) {
// a tab is focused so send an event to announce the tab widget state
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
}
break;
}
i++;
}
}
}
// registered with each tab indicator so we can notify tab host
private class TabClickListener implements View.OnClickListener {
private final int mTabIndex;
private TabClickListener(int tabIndex) {
mTabIndex = tabIndex;
}
public void onClick(View v) {
mSelectionChangedListener.onTabSelectionChanged(mTabIndex, true);
}
}
/**
* Let {@link android.widget.TabHost} know that the user clicked on a tab indicator.
*/
public static interface OnTabSelectionChanged {
/**
* Informs the TabHost which tab was selected. It also indicates
* if the tab was clicked/pressed or just focused into.
*
* @param tabIndex index of the tab that was selected
* @param clicked whether the selection changed due to a touch/click
* or due to focus entering the tab through navigation. Pass true
* if it was due to a press/click and false otherwise.
*/
public void onTabSelectionChanged(int tabIndex, boolean clicked);
}
}
其中将OnTabSelectionChanged这个interface设置为public,
这样实现MartianTabWidget的tab点击响应事件就可以简单的调用setTabSelectionListener来实现了~
大家如有问题可留言讨论哈~
该方法已经在我们的小说阅读软件淘小说中实现,
大家多多支持哈~后续会有更多干货出来哦~