在移动应用上, 可水平切换Tab或者点击标签里上的某个标签直接跳到某个对应标签页, 这种交互很常见. 的确, 在开源上也有相当多不错的这类开源可用, 比如ViewPageIndicator, MaterialTab等等. 开源给予我们很多帮助, 提高我们的开发效率, 而研究别人优秀的代码也能提高自己的水平.
但另一方面, 我们去使用开源的时候, 往往会遇到一个问题, 而这个问题相信也是不少人考虑到的: 一个项目中如果大量引用开源, 会不会导致项目的体量增大很多? 甚至兼容旧版本时糟糕的碰到65535的方法数限制的问题? 答案是, 会增加这样的可能. 前提当然是大量的使用了不需要的方法代码. 所以, 项目中可以考虑只加入会用到的代码, 对于不会用的, 尽量不加入. 所以学习了别人优秀的代码后, 以后就可以按照具体需求实现某个功能模块, 而不是整个都 "搬" 进去. 同时也锻炼了自己的能力哦.
一段时间没写, 唠嗑多了. 下面是效果图:
实现思路
这种切换tab的效果, 可分为2部分: 顶部的可滑动可点击切换的switchTabBar; 底部是ViewPager容器, 装载着显示内容的的标签页, 比如Fragment.
底部ViewPager没什么好说的, switchTabBar的效果, 最直接想到的可能是horizontallistview, 的确horizontallistview能实现, 又能滑动又能点击, 但是DIY的空间不大, 因为tab的宽度可大可小, 并没有规律的. 于是我们选择更灵活的, Horizontalscrollview.
在Horizontalscrollview装载唯一一个容器LinearLayout, 利用addView()或removeView() 动态的添加或删减 tabView. 当然, 这些tabView是需要和底部的ViewPager标签页联动的. 在ViewPager上左右滑动切换标签页, 顶部的Horizontalscrollview也会随之滑动到相应的tabView; 或者点击顶部的tabView后, 底部的ViewPager也能跳到相应的标签页.
Horizontalscrollview的使用跟ScrollView是一样的, 充分利用 scrollTo() 或 smoothScrollTo() 实现滑动, 利用ViewPager 的setCurrentItem() 去控制标签页的位置, 当选择到某个标签页时, 我们或许还需要一个监听该事件的监听器, 这当然是由OnPageChangeListener 来完成. OK, 到了这里, 我们已经明确了具体的效果, 以及实现的重要思路. 那么下面直接看一个简单的例子, 应该是easy job 了.
具体代码
顶部的 tabView 的样式: text_tab.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/text_tab_title" android:layout_width="match_parent" android:layout_height="match_parent" android:textSize="16sp" android:gravity="center" /> <ImageView android:id="@+id/text_tab_divider" android:layout_width="match_parent" android:layout_height="2dp" android:layout_gravity="bottom" android:background="#FF00CC99" /> </FrameLayout>
tabView的对象类: SwitchTab
/** * Created by AlexTam on 2015/10/14. */ public class SwitchTab implements View.OnTouchListener { private View mView; private TextView mTvTitle; private ImageView mImgDivider; private int mPosition; private SwitchTabBar.OnSwitchTabListener listener; public SwitchTab(Context context) { mView = LayoutInflater.from(context).inflate(R.layout.text_tab, null); mTvTitle = (TextView) mView.findViewById(R.id.text_tab_title); mImgDivider = (ImageView) mView.findViewById(R.id.text_tab_divider); int mPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, context.getResources().getDisplayMetrics()); mTvTitle.setPadding(mPadding, 0, mPadding, 0); Log.e("SwitchTab(..)____", "创建TAG"); mView.setOnTouchListener(this); } protected void setTabTitle(String title) { mTvTitle.setText(title); } protected View getView() { if (mView != null) { return mView; } return null; } @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: return true; case MotionEvent.ACTION_MOVE: return true; case MotionEvent.ACTION_UP: //选中 setTabPress(); if (listener != null) { listener.onSelectTab(this); } return true; } return false; } /** * 设置tabView的正常效果 */ protected void setTabNormal() { mTvTitle.setTextColor(0xFF8B668B); mImgDivider.setBackgroundColor(0x00000000); } /** * 设置tabView的按下效果 */ protected void setTabPress() { mTvTitle.setTextColor(0xFFFFFFFF); mImgDivider.setBackgroundColor(0xFFF08080); } protected void setPosition(int position) { this.mPosition = position; } protected int getPosition() { return mPosition; } /** * 获取显示字符区域的宽度 * @return */ private int getTextLenght() { String textString = mTvTitle.getText().toString(); Rect bounds = new Rect(); Paint textPaint = mTvTitle.getPaint(); textPaint.getTextBounds(textString, 0, textString.length(), bounds); return bounds.width(); } protected int getTabWidth() { return getTextLenght(); } public void setSwitchTabListener(SwitchTabBar.OnSwitchTabListener listener) { if (listener != null) { this.listener = listener; } } }在这个类中, 对tabView添加onTouch() 的触摸监听, 当点击该tabView时, 就调用接口, 将底部的ViewPager 切换到对应的标签页. 监听器的实现放在主类Activity中.
下面是顶部装载tabView的 SwitchTabBar类:
public class SwitchTabBar extends RelativeLayout { private HorizontalScrollView mHzScrollView; private LinearLayout mLinearLayout; private List<SwitchTab> mTabs; //屏幕宽度, 高度 private int mScreenWidth; private int mScreenHeight; //是否可滑动 private boolean isScroll; //是否平板 private boolean isTablet; private int mPosition; public SwitchTabBar(Context context) { this(context, null); } public SwitchTabBar(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SwitchTabBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context) { mHzScrollView = new HorizontalScrollView(context); mHzScrollView.setOverScrollMode(HorizontalScrollView.OVER_SCROLL_NEVER); mHzScrollView.setHorizontalScrollBarEnabled(false); mLinearLayout = new LinearLayout(context); mLinearLayout.setOrientation(LinearLayout.HORIZONTAL); mHzScrollView.addView(mLinearLayout); mTabs = new ArrayList<SwitchTab>(); DisplayMetrics dm = new DisplayMetrics(); WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); windowManager.getDefaultDisplay().getMetrics(dm); //获取屏幕宽高 mScreenWidth = dm.widthPixels; mScreenHeight = dm.heightPixels; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (this.getWidth() != 0 && mTabs.size() != 0) { notifyLayoyut(); } } private void notifyLayoyut() { super.removeAllViews(); mLinearLayout.removeAllViews(); if (!isTablet) { //适配手机 int minWidth = mScreenWidth / 4; LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT); int tabTotalWidth = 0; for (int i = 0; i < mTabs.size(); i++) { View tabView = mTabs.get(i).getView(); tabView.setMinimumWidth(minWidth); mLinearLayout.addView(tabView, params); tabTotalWidth += mTabs.get(i).getTabWidth(); } if (tabTotalWidth > mScreenWidth) { isScroll = true; } } else { //适配平板,平板的屏幕比手机大 //... } RelativeLayout.LayoutParams paramsScroll = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); this.addView(mHzScrollView, paramsScroll); } protected void addTab(SwitchTab tab) { tab.setPosition(mTabs.size()); mTabs.add(tab); } /** * 设置选中Tab * @param position */ protected void setSelectItem(int position) { if (mTabs != null && mTabs.size() > 0) { if (position < mTabs.size()) { mTabs.get(position).setTabPress(); } for (int c = mTabs.size() - 1; c >= 0; c--) { if (c != position) { mTabs.get(c).setTabNormal(); } } } if (isScroll) { scrollTo(position); } } private void scrollTo(int position) { int totalWidth = 0; for (int i = 0; i < position; i++) { int width = mTabs.get(i).getTabWidth(); totalWidth += width; } mHzScrollView.smoothScrollTo(totalWidth, 0); } public interface OnSwitchTabListener { void onSelectTab(SwitchTab tab); } }这个类的作用是, 根据tabView的个数和宽度算出SwitchTabBar 的宽度, 同时实现点击tabView后的SwitchTabBar 如何滑动. 这样效果就出来了, 下面在Activity 中添加数据, 简单的效果就出来了.
public class MainActivity extends AppCompatActivity { private ViewPager mViewPager; private SwitchTabBar mSwitchTabBar; private ArrayList<SwitchTab> mTabs; private int mTabCount = 6; private ArrayList<Fragment> mFragments; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.viewpager_main); init(); } private void init() { mTabs = new ArrayList<SwitchTab>(); mViewPager = (ViewPager) findViewById(R.id.viewpager_main); mSwitchTabBar = (SwitchTabBar) findViewById(R.id.switchbar_main); final SwitchTab tab1 = new SwitchTab(this); tab1.setTabTitle("每日新闻"); mSwitchTabBar.addTab(tab1); tab1.setSwitchTabListener(new SwitchTabBar.OnSwitchTabListener() { @Override public void onSelectTab(SwitchTab tab) { mViewPager.setCurrentItem(tab.getPosition()); } }); SwitchTab tab2 = new SwitchTab(this); tab2.setTabTitle("经济新闻 - 财经焦点"); mSwitchTabBar.addTab(tab2); tab2.setSwitchTabListener(new SwitchTabBar.OnSwitchTabListener() { @Override public void onSelectTab(SwitchTab tab) { mViewPager.setCurrentItem(tab.getPosition()); } }); SwitchTab tab3 = new SwitchTab(this); tab3.setTabTitle("军事频道"); mSwitchTabBar.addTab(tab3); tab3.setSwitchTabListener(new SwitchTabBar.OnSwitchTabListener() { @Override public void onSelectTab(SwitchTab tab) { mViewPager.setCurrentItem(tab.getPosition()); } }); SwitchTab tab4 = new SwitchTab(this); tab4.setTabTitle("Tech News"); mSwitchTabBar.addTab(tab4); tab4.setSwitchTabListener(new SwitchTabBar.OnSwitchTabListener() { @Override public void onSelectTab(SwitchTab tab) { mViewPager.setCurrentItem(tab.getPosition()); } }); SwitchTab tab5 = new SwitchTab(this); tab5.setTabTitle("生活趣事"); mSwitchTabBar.addTab(tab5); tab5.setSwitchTabListener(new SwitchTabBar.OnSwitchTabListener() { @Override public void onSelectTab(SwitchTab tab) { mViewPager.setCurrentItem(tab.getPosition()); } }); SwitchTab tab6 = new SwitchTab(this); tab6.setTabTitle("艺术"); mSwitchTabBar.addTab(tab6); tab6.setSwitchTabListener(new SwitchTabBar.OnSwitchTabListener() { @Override public void onSelectTab(SwitchTab tab) { mViewPager.setCurrentItem(tab.getPosition()); } }); mFragments = new ArrayList<Fragment>(); for (int i = 0; i < mTabCount; i++) { TextFragment fragment = new TextFragment(); Bundle bundle = new Bundle(); bundle.putString("title", "" + i); fragment.setArguments(bundle); mFragments.add(fragment); } mViewPager.setAdapter(new MyViewPagerAdapter(getSupportFragmentManager())); mViewPager.addOnPageChangeListener(new MyPageChangeListener()); mSwitchTabBar.setSelectItem(0); } private class MyPageChangeListener extends ViewPager.SimpleOnPageChangeListener{ @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { // This space for rent } @Override public void onPageSelected(int position) { // This space for rent mSwitchTabBar.setSelectItem(position); } @Override public void onPageScrollStateChanged(int state) { // This space for rent } } private class MyViewPagerAdapter extends FragmentStatePagerAdapter { public MyViewPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { return mFragments.get(position); } @Override public int getCount() { return mFragments.size(); } } }
例子到此完成. 看完是不是觉得好简单? 在这个基础上, 可以优化更多东西, 以及添加更多内容, 比如tabView可以添加icon等等. 其实实现思路我也是之前看了几个开源后, 从MaterialTab后借鉴过来的, 但由于MaterialTab为了实现android新版本的ripple水纹效果又引入了android-ui这个库. 如果只为了标签页切换这一个功能而使用了2个库, 对项目而言, 达不到精简的目的. 所以, 在这个例子中, 去掉了UI库的依赖, 尽量用更少的代码重新实现这种思路. 并在文章开篇就写了那一段话. 我总是喜欢逻辑尽量简单而清晰的东西, 当然这不一定容易, 但是简单的东西都比较容易上手, 让你快速掌握一些思路. 就像打通你任督二脉, 先入了门, 再深入学习. 不然一开始就看无比复杂的东西, 花了很多时间看了一堆才发现不是自己要的, 或者不喜欢. 不是太好吧... 哟 又唠嗑了,,, 晚安~