类似于手机版qq的底边栏Tab效果有很多种实现方法,比如TabActivity、自定义RadioGroup等。由于高版本下TabActivity已经被废弃,而且Activity比较重量级,所以一般不使用TabActivity。这里分享一种我写的自定义底部Tab的方法,顺带加上底部标签的角标显示效果。效果如下:
关于Demo需要交代几点:
1.这个Demo中并没有对尺寸做适配,在不同机型的手机上运行需要调整代码中的尺寸相关代码。
2.角标效果只是个演示效果,逻辑可能并不合理,具体显示或者改变、隐藏逻辑可以在实际应用中进行设置。
下面从代码的角度来展开说明:
首先看最主要的FragmentIndicator类,这个类继承自LinearLayout实现了OnClickListener接口:
/**
* @Instruction:
* @FragmentIndicator.java
* @com.example.tabindicator.views
* @Tab&Indicator
* @author donz 2015年8月26日 上午11:40:46
* @qq 457901706
*/
public class FragmentIndicator extends LinearLayout implements OnClickListener {
private int mDefaultIndicator = 0;
private static int mCurIndicator;
private Context context;
private static View[] mIndicators;
private OnIndicateListener mOnIndicateListener;
public static int[] imageResources = new int[]{R.drawable.ic_launcher,R.drawable.ic_launcher,R.drawable.ic_launcher,R.drawable.ic_launcher};
public static String[] nameResources = new String[]{"Fragment_A","Fragment_B","Fragment_C","Fragment_D"};
private FragmentIndicator(Context context) {
super(context);
this.context = context;
}
public FragmentIndicator(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
mCurIndicator = mDefaultIndicator;
setOrientation(LinearLayout.HORIZONTAL);
setBackgroundColor(0xfff3f6ed);
init(imageResources,nameResources);
}
重写的构造方法中完成一系列初始化操作,其中核心的操作是init(
imageResources,nameResources
)方法,在这个方法中完成了对要显示的底部Tab栏的图标和显示文字的初始化。来看这个方法都做了什么:
private void init(int[] imageResources, String[] nameResources) {
int length = 0;
length = Math.max(imageResources.length, nameResources.length);
mIndicators = new View[length];
for(int i = 0;i<length;i++){
mIndicators[i] = createIndicator(imageResources[i], nameResources[i], themeColor, "TAG_ICON_"+i, "TAG_TEXT_"+i);
mIndicators[i].setTag(i);
mIndicators[i].setOnClickListener(this);
addView(mIndicators[i]);
}
}
很简单,在
FragmentIndicator中有一个存储底部每一个标签和对应文字的View数组叫做
mIndicators,这里就是初始化了mIndicators为其赋值,由上面代码可以看到又涉及到一个核心方法createIndicator(int iconResID, String name, int stringColor, String iconTag, String textTag),这个方法是真正初始化每一个底部栏标签和文字的方法,来看他内部的实现:
private View createIndicator(int iconResID, String stringResID, int stringColor,
String iconTag, String textTag) {
LinearLayout view = new LinearLayout(getContext());
view.setPadding(0, 12, 0, 12);
view.setOrientation(LinearLayout.VERTICAL);
view.setLayoutParams(new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 1));
view.setGravity(Gravity.CENTER);
ImageView iconView = new ImageView(getContext());
iconView.setTag(iconTag);
LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
iconView.setColorFilter(themeColor);
iconView.setLayoutParams(iconParams);
iconView.setImageResource(iconResID);
TextView textView = new TextView(getContext());
textView.setFocusable(true);
textView.setGravity(Gravity.CENTER);
textView.setTag(textTag);
textView.setLayoutParams(new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
textView.setTextColor(stringColor);
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 11);
textView.setText(stringResID);
view.addView(iconView);
view.addView(textView);
return view;
}
一目了然,每一个底部栏标签其实是一个线性布局LinearLayout,然后根据传递进来的图标和文字分别构造ImageView和TextView及其一系列属性信息,最后通过addView(View view)方法将他们添加到这个线性布局中,返回这个线性布局。这就回到了init()方法中,init()中的循环里又调用了FragmentIndicator的addView方法将每个底部页签的线性布局添加到底部栏最外层的线性布局中,至此底部栏雏形完成了。
但是到这里还没有完,视图效果虽然出现了但是试着去点击底部页签是没有反应的,因为我们还没有为其绑定点击事件,下面代码:
public interface OnIndicateListener {
public void onIndicate(View v, int which);
}
public void setOnIndicateListener(OnIndicateListener listener) {
mOnIndicateListener = listener;
}
@Override
public void onClick(View v) {
if (mOnIndicateListener != null) {
int tag = (Integer) v.getTag();
if(mCurIndicator!=tag){
mOnIndicateListener.onIndicate(v, tag);
setIndicator(tag);
// 演示效果
if(null!=badgeView)
badgeView.toggle();
}
}
}
由于FragmentIndicator实现了OnClickListner所以需要实现onClick方法,在这个点击方法里我们需要调用一个自定义接口回调方法来实现我们的业务逻辑和视图效果。其中业务逻辑是由
mOnIndicateListener.onIndicate(v, tag)实现的,切换标签视图效果(字体加粗,图标颜色加重)是由setIndicator(int index)来实现的。来看setIndicator(int index)的逻辑:
public static void setIndicator(int which) {
// clear previous status.
ImageView prevIcon;
TextView prevText;
prevIcon =(ImageView) mIndicators[mCurIndicator].findViewWithTag("TAG_ICON_"+mCurIndicator);
prevIcon.setImageResource(FragmentIndicator.imageResources[mCurIndicator]);
prevIcon.setColorFilter(themeColor);
prevText = (TextView) mIndicators[mCurIndicator].findViewWithTag("TAG_TEXT_"+mCurIndicator);
prevText.setTextColor(themeColor);
TextPaint tpaint0 = prevText.getPaint();
tpaint0 .setFakeBoldText(false);
// update current status.
ImageView currIcon;
TextView currText;
currIcon =(ImageView) mIndicators[which].findViewWithTag("TAG_ICON_"+which);
currIcon.setImageResource(FragmentIndicator.imageResources[which]);
currIcon.setColorFilter(themeColorDarker);
currText = (TextView) mIndicators[which].findViewWithTag("TAG_TEXT_"+which);
currText.setTextColor(themeColorDarker);
TextPaint tpaint = currText.getPaint();
tpaint.setFakeBoldText(true);
mCurIndicator = which;
}
这个方法里对前一个底部栏页签的图标和文字效果进行了还原,还原成默认效果,对切换到的当前页签的图标和文字效果进行了选中效果的渲染,并对当前选中的页签下标
mCurIndicator进行了重新赋值。而OnIndicateListener接口的方法具体实现在MainActivity中:
public class MainActivity extends FragmentActivity {
private Fragment[] mFragments;
private FragmentIndicator mIndicator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setFragmentIndicator(0);
mIndicator.showBadge(2, "4");
}
private void setFragmentIndicator(int whichIsDefault) {
final FragmentManager manager = getSupportFragmentManager();
mFragments = new Fragment[]{manager.findFragmentById(
R.id.fragment_A),manager.findFragmentById(
R.id.fragment_B),manager.findFragmentById(
R.id.fragment_C),manager.findFragmentById(
R.id.fragment_D)};
for(int i = 0;i<mFragments.length;i++){
FragmentTransaction transaction = manager.beginTransaction();
transaction.hide(mFragments[i]).commit();
}
manager.beginTransaction().show(mFragments[0]).commit();
mIndicator = (FragmentIndicator) findViewById(R.id.indicator_main);
FragmentIndicator.setIndicator(whichIsDefault);
// 初始化
mIndicator.setOnIndicateListener(new OnIndicateListener() {
@Override
public void onIndicate(View v, int which) {
for(int i = 0;i<mFragments.length;i++){
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.hide(mFragments[i]).commit();
}
manager.beginTransaction().show(mFragments[which]).commit();
}
});
}
这里的业务逻辑仅仅是切换Fragment,MainActivity里初始化了四个Fragment,主要的逻辑是相应底部标签栏的点击事件而对这四个Fragment进行切换。红色角标的显示来自于GitHub的开源代码,其实逻辑也很简单,这里只看一下关键代码把:
BadgeView类中:
private void applyTo(View target) {
LayoutParams lp = target.getLayoutParams();
ViewParent parent = target.getParent();
FrameLayout container = new FrameLayout(context);
if (target instanceof TabWidget) {
// set target to the relevant tab child container
target = ((TabWidget) target).getChildTabViewAt(targetTabIndex);
this.target = target;
((ViewGroup) target).addView(container,
new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
this.setVisibility(View.GONE);
container.addView(this);
} else {
// TODO verify that parent is indeed a ViewGroup
ViewGroup group = (ViewGroup) parent;
int index = group.indexOfChild(target);
group.removeView(target);
group.addView(container, index, lp);
container.addView(target);
this.setVisibility(View.GONE);
container.addView(this);
group.invalidate();
}
}
其实角标是先把原视图remove然后add进去一个FrameLayout,然后再依次把原始图和角标add进去。至于其他的一些诸如设置角标位置的方法看代码即可。我在FragmentIndicator中定义了两个方法:
public void showBadge(int index,String content){
badgeView = new BadgeView(context, mIndicators[index].findViewWithTag("TAG_ICON_"+index));
badgeView.setBadgePosition(BadgeView.POSITION_TOP_RIGHT);
badgeView.setText(content);
badgeView.show();
}
public void hideBadge(){
if(null!=badgeView)
badgeView.hide();
}
方法showBadge(int index,String content)方法根据传递进来的两个参数index(角标所对应的标签下标),content(角标显示内容)来在特定页签的位置显示出角标,而hideBadge()则是隐藏角标。MainActivity的初始化代码中的mIndicator.showBadge(2, "4")就是在第三个页签显示内容为4的角标。
至此一个自定义带角标功能的Tab+Fragment功能就完成了,下面是代码Demo:
如果内容对您有帮助就请不吝点个赞吧~!