[Android] 快速实现一个可切换Tab标签页的组件

时间:2022-11-30 12:33:41

在移动应用上, 可水平切换Tab或者点击标签里上的某个标签直接跳到某个对应标签页, 这种交互很常见. 的确, 在开源上也有相当多不错的这类开源可用, 比如ViewPageIndicator, MaterialTab等等.  开源给予我们很多帮助, 提高我们的开发效率, 而研究别人优秀的代码也能提高自己的水平.


但另一方面, 我们去使用开源的时候, 往往会遇到一个问题, 而这个问题相信也是不少人考虑到的: 一个项目中如果大量引用开源, 会不会导致项目的体量增大很多? 甚至兼容旧版本时糟糕的碰到65535的方法数限制的问题?  答案是, 会增加这样的可能. 前提当然是大量的使用了不需要的方法代码. 所以, 项目中可以考虑只加入会用到的代码, 对于不会用的, 尽量不加入. 所以学习了别人优秀的代码后, 以后就可以按照具体需求实现某个功能模块, 而不是整个都 "搬" 进去. 同时也锻炼了自己的能力哦. 


一段时间没写, 唠嗑多了.  下面是效果图:

[Android] 快速实现一个可切换Tab标签页的组件


实现思路

这种切换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库的依赖, 尽量用更少的代码重新实现这种思路. 并在文章开篇就写了那一段话.  我总是喜欢逻辑尽量简单而清晰的东西, 当然这不一定容易, 但是简单的东西都比较容易上手, 让你快速掌握一些思路. 就像打通你任督二脉, 先入了门, 再深入学习. 不然一开始就看无比复杂的东西, 花了很多时间看了一堆才发现不是自己要的, 或者不喜欢. 不是太好吧... 哟 又唠嗑了,,, 晚安~