底边栏Tab切换Fragment,带角标显示效果

类似于手机版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();
	}
<span style="white-space:pre">	</span>showBadge(int index,String content)方法根据传递进来的两个参数index(角标所对应的标签下标),content(角标显示内容)来在特定页签的位置显示出角标,而<span style="font-family: Arial, Helvetica, sans-serif;">hideBadge()则是隐藏角标。MainActivity的初始化代码中的</span><span style="font-family: Arial, Helvetica, sans-serif;">mIndicator.showBadge(2, "4")就是在第三个页签显示内容为4的角标。</span>

至此一个自定义带角标功能的Tab+Fragment功能就完成了,下面是代码Demo:

底部Tab+Fragment切换+标签角标显示效果

如果内容对您有帮助就请不吝点个赞吧~!

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-26 09:44:29

底边栏Tab切换Fragment,带角标显示效果的相关文章

谈谈一些有趣的CSS题目(八)-- 纯CSS的导航栏Tab切换方案

开本系列,谈谈一些有趣的 CSS 题目,题目类型天马行空,想到什么说什么,不仅为了拓宽一下解决问题的思路,更涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题中有你感觉到生僻的 CSS 属性,赶紧去补习一下吧. 不断更新,不断更新,不断更新,重要的事情说三遍. 谈谈一些有趣的CSS题目(一)-- 左边竖条的实现方法 谈谈一些有趣的CSS题目(二)-- 从条纹边框的实现谈盒子模型 谈谈一些有趣的CSS题目(三)-- 层叠顺序与堆栈上下文知多少 谈谈一些有趣

Android典型界面设计——FragmentTabHost+Fragment实现底部tab切换

Android典型界面设计——FragmentTabHost+Fragment实现底部tab切换 Android学习笔记:TabHost 和 FragmentTabHost

原生javascript实现Tab切换

tab切换在各大主流页面有广泛的应用,今天来分享一个用原生javascript来实现类似京东购物边栏的TAB.对于正在使用web前端开发(http://www.maiziedu.com/course/web/)网站的朋友,是非常有用的哦. 首先以下是一段边栏HTML框架代码 <body> <div class="wrap"> <div id="left"> <ul id="leftList"> &l

CSS3 :target伪类实现Tab切换效果

用:target伪类实现Tab切换效果真的非常简单!简单到什么程度呢?它只需要下面这些代码. style.css: .song-info { position: absolute; background: #fff; } #song-info:target, #song-lyricCN:target, #song-lyricEN:target { z-index: 1; } html代码: <div class="song-nav"> <ul class="

切换Fragment时实现数据保持

摘要 Fragment设计初衷是为了简化不同屏幕分辨率的开发难度,他将代表一个功能的UI及其相关数据看做一个模块,以便达到复用.可以将Fragment看作是一个可以嵌入布局中的activity,有自己的生命周期. Fragment设计初衷是为了简化不同屏幕分辨率的开发难度,他将代表一个功能的UI及其相关数据看做一个模块,以便达到复用.可以将Fragment看作是一个可以嵌入布局中的activity,有自己的生命周期.比如我现在在手机上有activityA和activityB,但是在平板上有更大的

Android典型界面设计-访网易新闻实现双导航tab切换

一.问题描述 双导航tab切换(底部区块+区域内头部导航),实现方案底部区域使用FragmentTabHost+Fragment, 区域内头部导航使用ViewPager+Fragment,可在之前博客Android典型界面设计2(FragmentTabHost+Fragment实现底部tab切换)基础之上和Android典型界面设计1(ViewPage+Fragment实现区域顶部tab滑动切换)整合应用实现.查看两篇博客请点击:http://www.cnblogs.com/jerehedu/p

Android 底部导航栏的使用 fragment

一.初始化各项组件 private void initViews() { viewPager = (ViewPager) findViewById(R.id.view_pager); imgDynamic = (ImageView) findViewById(R.id.tab_dynamic_get); imgMsg = (ImageView) findViewById(R.id.tab_msg_get); imgExam = (ImageView) findViewById(R.id.tab_

JS控制下的双层Tab切换

如题,JS控制下的双层Tab切换,其实只要想通了原理,实现起来很简单! 首先,附上CSS <style type="text/css"> img{border:none;padding:0px; margin:0 auto;} ul,li{list-style-type:none; margin:0px; padding:0px;} body {font-family:ì,Dì,oúì,Arial,Helvetica,sans-serif;font-size:12px;co

使用ViewPager切换Fragment时,防止频繁调用OnCreatView

使用ViewPager切换Fragment,我原先使用系统自带的适配器FragmentPagerAdapter. 切换fragment时,频繁调用oncreatview(). 查看FragmentPagerAdapter的源码,发现两个关键的地方 1 @Override 2 public Object instantiateItem(ViewGroup container, int position) { 3 if (mCurTransaction == null) { 4 mCurTransa