<Android 开源库> FlycoTabLayout 从头到脚
来源:互联网 发布:ug汽车大模具编程刀路 编辑:程序博客网 时间:2024/05/18 13:41
简介
FlycoTabLayout,是一个比Google原生TabLayout 功能更强大的TabLayout库。目前有3种TabLayout:
- SlidingTabLayout
- CommonTabLayout
- SegmentTabLayout
具体介绍和使用方法参考开源库的Wiki
官方示例:
源码分析
1. SlidingTabLayout
1.1 特有属性
1.2 类结构
1.2.1 构造方法
第一个调用第二个,第二个调用第三个,第三个获取自定义属性值。
public SlidingTabLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setFillViewport(true);//设置滚动视图是否可以伸缩其内容以填充视口 setWillNotDraw(false);//重写onDraw方法,需要调用这个方法来清除flag setClipChildren(false);//不限制child在其范围内绘制 setClipToPadding(false);//滚动时child可以绘制到padding区域 this.mContext = context; mTabsContainer = new LinearLayout(context);//tab容器 addView(mTabsContainer);//添加到HorizontalScrollView中 obtainAttributes(context, attrs);//获取自定义属性,常用的方法,TypedArray记得回收 //获取layout_height属性的值,这个方法比较溜,之前没见过 String height = attrs.getAttributeValue("http://schemas.android.com/apk/res/android", "layout_height"); //针对height做处理 if (height.equals(ViewGroup.LayoutParams.MATCH_PARENT + "")) { } else if (height.equals(ViewGroup.LayoutParams.WRAP_CONTENT + "")) { } else { int[] systemAttrs = {android.R.attr.layout_height}; TypedArray a = context.obtainStyledAttributes(attrs, systemAttrs); //获取高度 mHeight = a.getDimensionPixelSize(0, ViewGroup.LayoutParams.WRAP_CONTENT); a.recycle(); }}
1.2.2 ViewPager
/** 关联ViewPager */ public void setViewPager(ViewPager vp) { if (vp == null || vp.getAdapter() == null) { throw new IllegalStateException("ViewPager or ViewPager adapter can not be NULL !"); } /*本地赋值*/ this.mViewPager = vp; /*重新绑定OnPageChangeListener*/ this.mViewPager.removeOnPageChangeListener(this); this.mViewPager.addOnPageChangeListener(this); /*viewpager变化,tab页响应处理处理*/ notifyDataSetChanged(); }
/** 更新数据 */ public void notifyDataSetChanged() { mTabsContainer.removeAllViews();//清空tab this.mTabCount = mTitles == null ? mViewPager.getAdapter().getCount() : mTitles.size();//获取tab数量,优先级mTitles > ViewPager的默认标题 /*添加tab*/ View tabView; for (int i = 0; i < mTabCount; i++) { tabView = View.inflate(mContext, R.layout.layout_tab, null); CharSequence pageTitle = mTitles == null ? mViewPager.getAdapter().getPageTitle(i) : mTitles.get(i); addTab(i, pageTitle.toString(), tabView); } //更新选中未选中状态更新tab updateTabStyles(); }
/** 创建并添加tab */ private void addTab(final int position, String title, View tabView) { //设置标题 TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); if (tv_tab_title != null) { if (title != null) tv_tab_title.setText(title); } //绑定点击事件,与ViewPager联动 tabView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { int position = mTabsContainer.indexOfChild(v); if (position != -1) { if (mViewPager.getCurrentItem() != position) { if (mSnapOnTabClick) { // transition immediately mViewPager.setCurrentItem(position, false); } else { //smoothly scroll to mViewPager.setCurrentItem(position); } if (mListener != null) { //自定义tab点击事件处理 mListener.onTabSelect(position); } } else { if (mListener != null) { //自定义Reselect事件处理 mListener.onTabReselect(position); } } } } }); /** 每一个Tab的布局参数,mTabSpaceEqual 属性控制是否均分 */ LinearLayout.LayoutParams lp_tab = mTabSpaceEqual ? new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f) : new LinearLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); if (mTabWidth > 0) { lp_tab = new LinearLayout.LayoutParams((int) mTabWidth, LayoutParams.MATCH_PARENT); } //添加到Tab容器 mTabsContainer.addView(tabView, position, lp_tab); }
private void updateTabStyles() { //遍历设置标题选中颜色,未选中颜色,字体大小,大小写,粗体字 for (int i = 0; i < mTabCount; i++) { View v = mTabsContainer.getChildAt(i); TextView tv_tab_title = (TextView) v.findViewById(R.id.tv_tab_title); if (tv_tab_title != null) { tv_tab_title.setTextColor(i == mCurrentTab ? mTextSelectColor : mTextUnselectColor); tv_tab_title.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextsize); tv_tab_title.setPadding((int) mTabPadding, 0, (int) mTabPadding, 0); if (mTextAllCaps) { tv_tab_title.setText(tv_tab_title.getText().toString().toUpperCase()); } if (mTextBold == TEXT_BOLD_BOTH) { tv_tab_title.getPaint().setFakeBoldText(true); } else if (mTextBold == TEXT_BOLD_NONE) { tv_tab_title.getPaint().setFakeBoldText(false); } } } }
/** 关联ViewPager,用于连适配器都不想自己实例化的情况 */ public void setViewPager(ViewPager vp, String[] titles, FragmentActivity fa, ArrayList<Fragment> fragments) { if (vp == null) { throw new IllegalStateException("ViewPager can not be NULL !"); } if (titles == null || titles.length == 0) { throw new IllegalStateException("Titles can not be EMPTY !"); } this.mViewPager = vp; /*通过传入的参数构建FragmentPagerAdapter,设置到ViewPager*/ this.mViewPager.setAdapter(new InnerPagerAdapter(fa.getSupportFragmentManager(), fragments, titles)); this.mViewPager.removeOnPageChangeListener(this); this.mViewPager.addOnPageChangeListener(this); notifyDataSetChanged(); }
看下这个内部的InnerPagerAdapter,静态Fragment,不销毁重建,只更新数据内容。
class InnerPagerAdapter extends FragmentPagerAdapter { private ArrayList<Fragment> fragments = new ArrayList<>(); private String[] titles; public InnerPagerAdapter(FragmentManager fm, ArrayList<Fragment> fragments, String[] titles) { super(fm); this.fragments = fragments; this.titles = titles; } @Override public int getCount() { return fragments.size(); } @Override public CharSequence getPageTitle(int position) { return titles[position]; } @Override public Fragment getItem(int position) { return fragments.get(position); } @Override public void destroyItem(ViewGroup container, int position, Object object) { // 覆写destroyItem并且空实现,这样每个Fragment中的视图就不会被销毁 // super.destroyItem(container, position, object); } @Override public int getItemPosition(Object object) { return PagerAdapter.POSITION_NONE; } }
SCROLL_STATE_IDLE(pager处于空闲状态)
SCROLL_STATE_DRAGGING( pager处于正在拖拽中)
SCROLL_STATE_SETTLING(pager正在自动沉降,相当于松手后,pager恢复到一个完整pager的过程)
@Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { /** * position:当前View的位置 * mCurrentPositionOffset:当前View的偏移量比例.[0,1) */ this.mCurrentTab = position; this.mCurrentPositionOffset = positionOffset; /*标签栏根据ViewPager的滚动状态联动,滚动到对应位置*/ scrollToCurrentTab(); /*触发重绘*/ invalidate(); } @Override public void onPageSelected(int position) { /*根据ViewPager选中状态调整标签页的选中状态*/ updateTabSelection(position); } @Override public void onPageScrollStateChanged(int state) { }
/** HorizontalScrollView滚到当前tab,并且居中显示 */ private void scrollToCurrentTab() { if (mTabCount <= 0) { return; } int offset = (int) (mCurrentPositionOffset * mTabsContainer.getChildAt(mCurrentTab).getWidth()); /**当前Tab的left+当前Tab的Width乘以positionOffset*/ int newScrollX = mTabsContainer.getChildAt(mCurrentTab).getLeft() + offset; if (mCurrentTab > 0 || offset > 0) { /**HorizontalScrollView移动到当前tab,并居中*/ newScrollX -= getWidth() / 2 - getPaddingLeft(); calcIndicatorRect(); newScrollX += ((mTabRect.right - mTabRect.left) / 2); } if (newScrollX != mLastScrollX) { mLastScrollX = newScrollX; /** scrollTo(int x,int y):x,y代表的不是坐标点,而是偏移量 * x:表示离起始位置的x水平方向的偏移量 * y:表示离起始位置的y垂直方向的偏移量 */ scrollTo(newScrollX, 0); } }
/*根据是否选中设置字体颜色和粗体*/ private void updateTabSelection(int position) { for (int i = 0; i < mTabCount; ++i) { View tabView = mTabsContainer.getChildAt(i); final boolean isSelect = i == position; TextView tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); if (tab_title != null) { tab_title.setTextColor(isSelect ? mTextSelectColor : mTextUnselectColor); if (mTextBold == TEXT_BOLD_WHEN_SELECT) { tab_title.getPaint().setFakeBoldText(isSelect); } } } }
1.2.3 Tab相关
public void addNewTab(String title) { View tabView = View.inflate(mContext, R.layout.layout_tab, null); if (mTitles != null) { mTitles.add(title); } CharSequence pageTitle = mTitles == null ? mViewPager.getAdapter().getPageTitle(mTabCount) : mTitles.get(mTabCount); addTab(mTabCount, pageTitle.toString(), tabView); this.mTabCount = mTitles == null ? mViewPager.getAdapter().getCount() : mTitles.size(); updateTabStyles(); }
public void setCurrentTab(int currentTab) { this.mCurrentTab = currentTab; mViewPager.setCurrentItem(currentTab); } public void setCurrentTab(int currentTab, boolean smoothScroll) { this.mCurrentTab = currentTab; mViewPager.setCurrentItem(currentTab, smoothScroll); }
1.2.4 Getter和Setter
不做赘述
1.2.5 MsgView相关(未读信息)
2. CommonTabLayout
2.1 特有属性
2.2 区别于SlidingTabLayout
- 不依赖于ViewPager,可以与其他组件搭配
- 支持自定义Tab样式,主要体现在常用的图标+文字的形式。
- SlidingTabLayout继承HorizontalScrollView而CommonTabLayout继承FrameLayout
2.3 类结构
2.3.1 构造方法
与SlidingTabLayout实现类似,获取的属性值不太一样而已。多出一个动画的内容,点击某一个Tab后,indicator的移动动画效果
mValueAnimator = ValueAnimator.ofObject(new PointEvaluator(), mLastP, mCurrentP);mValueAnimator.addUpdateListener(this);
2.3.2 动画相关
class IndicatorPoint { public float left; public float right;}private IndicatorPoint mCurrentP = new IndicatorPoint();private IndicatorPoint mLastP = new IndicatorPoint();class PointEvaluator implements TypeEvaluator<IndicatorPoint> { @Override public IndicatorPoint evaluate(float fraction, IndicatorPoint startValue, IndicatorPoint endValue) { float left = startValue.left + fraction * (endValue.left - startValue.left); float right = startValue.right + fraction * (endValue.right - startValue.right); IndicatorPoint point = new IndicatorPoint(); point.left = left; point.right = right; return point; }}
@Overridepublic void onAnimationUpdate(ValueAnimator animation) { View currentTabView = mTabsContainer.getChildAt(this.mCurrentTab); IndicatorPoint p = (IndicatorPoint) animation.getAnimatedValue(); mIndicatorRect.left = (int) p.left; mIndicatorRect.right = (int) p.right; if (mIndicatorWidth < 0) { //indicatorWidth小于0时,原jpardogo's PagerSlidingTabStrip } else {//indicatorWidth大于0时,圆角矩形以及三角形 float indicatorLeft = p.left + (currentTabView.getWidth() - mIndicatorWidth) / 2; mIndicatorRect.left = (int) indicatorLeft; mIndicatorRect.right = (int) (mIndicatorRect.left + mIndicatorWidth); } invalidate();}
2.3.3 Tab相关
public void setTabData(ArrayList<CustomTabEntity> tabEntitys) { if (tabEntitys == null || tabEntitys.size() == 0) { throw new IllegalStateException("TabEntitys can not be NULL or EMPTY !"); } this.mTabEntitys.clear(); /*设置tab标签*/ this.mTabEntitys.addAll(tabEntitys); /*更新数据*/ notifyDataSetChanged();}
/** 更新数据 */public void notifyDataSetChanged() { /*清空容器中的tab*/ mTabsContainer.removeAllViews(); this.mTabCount = mTabEntitys.size(); View tabView; /*根据图标的gravity构建不同的tab样式,图标支持上下左右*/ for (int i = 0; i < mTabCount; i++) { if (mIconGravity == Gravity.LEFT) { tabView = View.inflate(mContext, R.layout.layout_tab_left, null); } else if (mIconGravity == Gravity.RIGHT) { tabView = View.inflate(mContext, R.layout.layout_tab_right, null); } else if (mIconGravity == Gravity.BOTTOM) { tabView = View.inflate(mContext, R.layout.layout_tab_bottom, null); } else { tabView = View.inflate(mContext, R.layout.layout_tab_top, null); } /*i添加到tag,但从这个类的方法上看,这一步没有什么必要,因为addTab会传入i,addTab中直接使用i就好,但是如果我们在外部拿到tabView,就可以直接指导它的position,不用循环遍历,还是挺方便的*/ tabView.setTag(i); /*添加tab*/ addTab(i, tabView); } /*根据选中未选中状态更新tab显示效果*/ updateTabStyles();}
/** 创建并添加tab */private void addTab(final int position, View tabView) { /*设置文本内容,title*/ TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); tv_tab_title.setText(mTabEntitys.get(position).getTabTitle()); /*设置图标内容,添加未选中内容,后面根据选中未选中重新刷一次图片*/ ImageView iv_tab_icon = (ImageView) tabView.findViewById(R.id.iv_tab_icon); iv_tab_icon.setImageResource(mTabEntitys.get(position).getTabUnselectedIcon()); /*设置tabView的点击事件*/ tabView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { /*前面设置的tab,也就是position*/ int position = (Integer) v.getTag(); if (mCurrentTab != position) { /*设置当前tab*/ setCurrentTab(position); //有OnTabSelectListener则执行对应的处理 if (mListener != null) { mListener.onTabSelect(position); } } else { if (mListener != null) { mListener.onTabReselect(position); } } } }); /** 每一个Tab的布局参数 ,根据是否均分设置不同的布局,若宽度不为0,则设置宽度*/ LinearLayout.LayoutParams lp_tab = mTabSpaceEqual ? new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f) : new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); if (mTabWidth > 0) { lp_tab = new LinearLayout.LayoutParams((int) mTabWidth, LayoutParams.MATCH_PARENT); } mTabsContainer.addView(tabView, position, lp_tab);}
/*和SlidingTabLayout相似,多了一个图标的处理*/private void updateTabStyles() { for (int i = 0; i < mTabCount; i++) { View tabView = mTabsContainer.getChildAt(i); tabView.setPadding((int) mTabPadding, 0, (int) mTabPadding, 0); TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); tv_tab_title.setTextColor(i == mCurrentTab ? mTextSelectColor : mTextUnselectColor); tv_tab_title.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextsize);// tv_tab_title.setPadding((int) mTabPadding, 0, (int) mTabPadding, 0); if (mTextAllCaps) { tv_tab_title.setText(tv_tab_title.getText().toString().toUpperCase()); } if (mTextBold == TEXT_BOLD_BOTH) { tv_tab_title.getPaint().setFakeBoldText(true); } else if (mTextBold == TEXT_BOLD_NONE) { tv_tab_title.getPaint().setFakeBoldText(false); } ImageView iv_tab_icon = (ImageView) tabView.findViewById(R.id.iv_tab_icon); if (mIconVisible) { iv_tab_icon.setVisibility(View.VISIBLE); CustomTabEntity tabEntity = mTabEntitys.get(i); iv_tab_icon.setImageResource(i == mCurrentTab ? tabEntity.getTabSelectedIcon() : tabEntity.getTabUnselectedIcon()); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( mIconWidth <= 0 ? LinearLayout.LayoutParams.WRAP_CONTENT : (int) mIconWidth, mIconHeight <= 0 ? LinearLayout.LayoutParams.WRAP_CONTENT : (int) mIconHeight); if (mIconGravity == Gravity.LEFT) { lp.rightMargin = (int) mIconMargin; } else if (mIconGravity == Gravity.RIGHT) { lp.leftMargin = (int) mIconMargin; } else if (mIconGravity == Gravity.BOTTOM) { lp.topMargin = (int) mIconMargin; } else { lp.bottomMargin = (int) mIconMargin; } iv_tab_icon.setLayoutParams(lp); } else { iv_tab_icon.setVisibility(View.GONE); } }}
另外一个setTabData
/** 关联数据支持同时切换fragments */public void setTabData(ArrayList<CustomTabEntity> tabEntitys, FragmentActivity fa, int containerViewId, ArrayList<Fragment> fragments) { /*拿到一个mFragmentChangeManager ,后面setCurrentTab的时候 ,如果这个值不为空,根据这个来切换fragment,实现一种类似FragmentPagerAdapter的效果*/ mFragmentChangeManager = new FragmentChangeManager(fa.getSupportFragmentManager(), containerViewId, fragments); setTabData(tabEntitys);}
public void setCurrentTab(int currentTab) { mLastTab = this.mCurrentTab; this.mCurrentTab = currentTab; /*遍历更新tab选中未选中状态*/ updateTabSelection(currentTab); /*如果mFragmentChangeManager 不为空,就根据当前选中的tab显示对应的Fragment*/ if (mFragmentChangeManager != null) { mFragmentChangeManager.setFragments(currentTab); } /*indicator动画效果,计算后重绘*/ if (mIndicatorAnimEnable) { calcOffset(); } else { invalidate(); }}
2.3.4 FragmentChangeManager
public class FragmentChangeManager { private FragmentManager mFragmentManager; private int mContainerViewId; /** Fragment切换数组 */ private ArrayList<Fragment> mFragments; /** 当前选中的Tab */ private int mCurrentTab; /*构造方法,setTabData的时候看到过*/ public FragmentChangeManager(FragmentManager fm, int containerViewId, ArrayList<Fragment> fragments) { this.mFragmentManager = fm; this.mContainerViewId = containerViewId; this.mFragments = fragments; initFragments(); } /** 初始化fragments */ private void initFragments() { for (Fragment fragment : mFragments) { mFragmentManager.beginTransaction().add(mContainerViewId, fragment).hide(fragment).commit(); } setFragments(0); } /** 界面切换控制,CommonTabLayout中的setCurrentTab方法可以控制*/ public void setFragments(int index) { for (int i = 0; i < mFragments.size(); i++) { FragmentTransaction ft = mFragmentManager.beginTransaction(); Fragment fragment = mFragments.get(i); if (i == index) { ft.show(fragment); } else { ft.hide(fragment); } ft.commit(); } mCurrentTab = index; } public int getCurrentTab() { return mCurrentTab; } public Fragment getCurrentFragment() { return mFragments.get(mCurrentTab); }}
2.3.5 Getter,Setter以及MsgView先关
Getter和Setter方法是属性值的获取和设置,MsgView相关方法和SlidingTabLayout比较相似。
3. SegmentTabLayout
3.1 特有属性
3.2 区别于CommonTabLayout
- 不支持图标,但是可以看做是一个特殊的CommonTabLayout.
3.3 类结构
整体来说,内容基本上和CommonTabLayout,只是少了Icon的对应处理,多出的是Segment样式的处理。
4. MsgView
4.1 自定义属性
4.2 类结构
项目使用
个人项目Gank.io Android 客户端中使用效果,底部使用的CommonTabLayout,顶部使用的是SlidingTabLayout。整体而言,日常开发过程中,FlycoTabLayout还是很实用的。
最后
个人微信公众号:Learning_Of_ALL,欢迎大家扫码关注,Android技术交流。
4 0
- <Android 开源库> FlycoTabLayout 从头到脚
- <Android开源库> PagerSlidingTabStrip从头到脚
- <Android 开源库> PhotoPicker 从头到脚
- Android TabLayout 库:FlycoTabLayout
- FlycoTabLayout
- <Android 基础(三十四)> TabLayout 从头到脚
- FlycoTabLayout使用
- FlycoTabLayout使用
- FlycoTabLayout使用
- 面试从头到脚
- 开源项目: FlycoTabLayout
- FlycoTabLayout的用法
- FlycoTabLayout 的使用
- 开源项目: FlycoTabLayout
- 开源项目: FlycoTabLayout——CommonTabLayout
- 开源项目: FlycoTabLayout (滑动指示器)
- FlycoTabLayout 开源项目制作app首页
- 从头到脚跟你解释什么是Hibernate
- Linux 查看端口占用
- 在进行USB CDC类开发时,无法发送64整数倍的数据(续)
- 学习HTML语法的过程——第一章
- 动态规划:采药
- 单调队列 POJ 2823+单调栈 HDU 1506
- <Android 开源库> FlycoTabLayout 从头到脚
- poj 3009 Curling 2.0
- hihocoder 1142 三分法求极值
- 如何控制android中ImageView的位置
- python 将对象设置为可迭代有两种实现方式
- Google数据分析
- 小白训练Day3
- 私人笔记
- 病毒 LCIS