在这里插入图片描述

概述

Tab + ViewPager是我们常用的一种Android端UI架构,Android系统提供了TabLayout等控件用于实现与ViewPager配套使用问题,但是原生的TabLayout使用在Android TV端并不友好,需要添加复杂的逻辑解决按键与焦点问题,本文旨在封装一个简单的TvTabLayout控件,方便实现Android TV端的常用功能。

TvTabLayout

简介

  • 参考TabLayout抽取了核心的切换逻辑
  • 添加了按键处理
  • 添加焦点效果处理逻辑
  • 修改了TabLayout TabView高度封装的特性,开放TabView可定制UI效果
  • 支持三种Tab切换滚动模式

本控件只实现了控制ViewPager和切换Tab的核心逻辑,功能性、稳定性和在代码封装上远不及TabLayout和其他开源框架,但是用于学习参考和小项目的使用也足够。

使用

布局

    <com.zhong.demo.fragment.widget.TVTabLayout
        android:id="@+id/tv_tab_layout"
        android:layout_width="300dp"
        android:layout_height="@dimen/tab_height"
        android:layout_centerHorizontal="true"/>

    <android.support.v4.view.ViewPager
        android:id="@+id/man_view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/tv_tab_layout"/>

代码

    private void initTabLayout(){
        for(int i = 0; i < fragmentList.size(); i++){
            TextView tabView = (TextView) getTabView("TAG" + i);
            tabViewList.add(tabView);
            TVTabLayout.TabView tab = tabLayout.newTabView().setCustomView(tabView);
            tabLayout.addTab(tab);
        }
        //绑定ViewPager
        tabLayout.setupWithViewPager(viewPager);
        //设置Tab焦点框
        tabLayout.setTabFocusedBackground(R.drawable.focused_bg);
        //设置tab间距
        tabLayout.setTabsMargin(10);
        //设置模式:选中Tab居中模式
        tabLayout.setMode(TVTabLayout.MODE_SCROLLABLE_INDICATOR_CENTER);
        //添加监听,处理TabView的UI效果
        tabLayout.addOnTabSelectedListener(new TVTabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TVTabLayout.TabView view, int index) {
                tabViewList.get(index).setTextColor(Color.RED);
            }

            @Override
            public void onTabUnSelected(TVTabLayout.TabView view, int index) {
                tabViewList.get(index).setTextColor(Color.BLACK);
            }
        });
    }

    /**
     * 获取TabView
     * @param title
     * @return
     */
    private View getTabView(String title){
        TextView customView = new TextView(TVTabLayoutActivity.this);
        customView.setPadding(15,7,15,7);
        customView.setGravity(Gravity.CENTER);
        customView.setTextColor(Color.BLACK);
        customView.setText(title);
        return customView;
    }

实现思路

Tab相关逻辑封装

封装一个TabView,并提供了setCustomView(View)方法实现自定义样式。

    public static final class TabView extends FrameLayout {
        private View mCustomView;

        //省略部分无关代码

        @NonNull
        public TabView setCustomView(@Nullable View view) {
            mCustomView = view;
            updateView();
            return this;
        }

        void updateView() {
            if (mCustomView != null) {
                LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);
                addView(mCustomView,layoutParams);
            }
        }
    }

使用时通过TvTabLayout.newTabView()来实例化一个TabView,通过addTab(TabView)添加TabView。

    public void addTab(@NonNull TabView view, int tabsMargin){
        view.setFocusable(false);
        view.setFocusableInTouchMode(false);
        mTabs.add(view);
        tabViewLinearLayout.addView(view, createLayoutParamsForTabs());

        if(mSelectedIndex == mTabs.indexOf(view)){
            setSelectTabView(mSelectedIndex);
        }
    }

    private LinearLayout.LayoutParams createLayoutParamsForTabs() {
        final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
        //设置Tab间距
        lp.leftMargin = tabsMargin;
        updateTabViewLayoutParams(lp);
        return lp;
    }

    private void updateTabViewLayoutParams(LinearLayout.LayoutParams lp) {
        //根据模式设置Tab尺寸
        if (mMode == MODE_FIXED_NON_SCROLLABLE) {
            lp.width = 0;
            lp.weight = 1;
        } else {
            lp.width = LinearLayout.LayoutParams.WRAP_CONTENT;
            lp.weight = 0;
        }
    }

处理按键事件重写dispatchKeyEvent(KeyEvent)方法添加左右按键处理逻辑,并添加了选中边界Tab可循环滚动逻辑

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if(event.getAction() != KeyEvent.ACTION_DOWN){
            return super.dispatchKeyEvent(event);
        }

        switch (event.getKeyCode()){
            case KeyEvent.KEYCODE_DPAD_LEFT:
                int currentTabIndexLeft = mSelectedIndex;
                if(isLoop){
                    int nextLeftPosition = currentTabIndexLeft > 0 ? --currentTabIndexLeft : getTabCount() -1;
                    setSelectTabView(nextLeftPosition);
                } else{
                    if(currentTabIndexLeft > 0){
                        int nextLeftPosition =  --currentTabIndexLeft;
                        setSelectTabView(nextLeftPosition);
                    } else{
                        Log.d(TAG, "index == 0, 不循环");
                    }
                }
                return true;
            case KeyEvent.KEYCODE_DPAD_RIGHT:
                int currentTabIndexRight = mSelectedIndex;
                if(isLoop){
                    int nextRightPosition = currentTabIndexRight < getTabCount() - 1 ? ++currentTabIndexRight : 0;
                    setSelectTabView(nextRightPosition);
                } else{
                    if(currentTabIndexRight < getTabCount() - 1){
                        int nextRightPosition = ++currentTabIndexRight;
                        setSelectTabView(nextRightPosition);
                    } else{
                        Log.d(TAG, "index == count, 不循环");
                    }
                }
                return true;
        }

        return super.dispatchKeyEvent(event);
    }
TvTabLayout与ViewPager的双向绑定

添加TabLayoutOnPageChangeListenerViewPagerOnTabSelectedListener两个回调监听,实现TvTabLayout和ViewPager的双向绑定。

    public static class TabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener {
        private final WeakReference<TVTabLayout> mTabLayoutRef;
        private int mPreviousScrollState;
        private int mScrollState;

        //省略部分代码

        @Override
        public void onPageSelected(final int position) {
            //监听ViewPager的Item选中事件,切换Tab
            final TVTabLayout tabLayout = mTabLayoutRef.get();
            if (tabLayout != null && tabLayout.getSelectedTabPosition() != position
                    && position < tabLayout.getTabCount()) {
                // Select the tab, only updating the indicator if we're not being dragged/settled
                // (since onPageScrolled will handle that).
                tabLayout.setSelectTabView(position);
            }
        }
    }

    public interface OnTabSelectedListener{
        void onTabSelected(TabView view, int index);

        void onTabUnSelected(TabView view, int index);
    }

    public static class ViewPagerOnTabSelectedListener implements OnTabSelectedListener {
        private final ViewPager mViewPager;

        //省略部分代码

        @Override
        public void onTabSelected(TabView view, int index) {
            //监听Tab选中事件,切换ViewPager
            if(mViewPager != null && mViewPager.getCurrentItem() != index){
                try{
                    //部分型号上抛出异常:
                    //IllegalStateException: FragmentManager is already executing transactions
                    //简单捕获处理下,后续完善
                    mViewPager.setCurrentItem(index);
                } catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }

提供setupWithViewPager(ViewPager)方法用于绑定ViewPager。

    public void setupWithViewPager(ViewPager viewPager){
        if(this.viewPager != null){
            //移除所有监听
            if(onPageChangeListener != null){
                this.viewPager.removeOnPageChangeListener(onPageChangeListener);
            }

            if(onTabSelectedListener != null){
                this.removeOnTabSelectedListener(onTabSelectedListener);
            }
        }

        if(viewPager != null){
            this.viewPager = viewPager;
            //绑定,设置监听
            onPageChangeListener = new TabLayoutOnPageChangeListener(this);
            viewPager.addOnPageChangeListener(onPageChangeListener);

            onTabSelectedListener = new ViewPagerOnTabSelectedListener(viewPager);
            this.addOnTabSelectedListener(onTabSelectedListener);
        } else{
            this.viewPager = null;
            this.onPageChangeListener = null;
            this.onTabSelectedListener = null;
        }
    }

OnTabSelectedListener事件使用了观察者模式,除了绑定ViewPagerOnTabSelectedListener监听外,还可以通过实现OnTabSelectedListener接口实现自定义事件监听,为TvTabLayout单独使用(不搭配ViewPager)提供了条件。用户可以通过监听OnTabSelectedListener的onTabSelected(TabView,int)事件,来实现自定义的TabView的选中效果(UI效果、动画等)或者TvTabLayout单独使用的需求。

Tab滚动效果

定义了三种Tab滚动模式。

    /**
     * Tab填充满显示区域,不可滚动
     */
    public static final int MODE_FIXED_NON_SCROLLABLE = 1;
    /**
     * Tab可滚动且选中栏目一直居中
     */
    public static final int MODE_SCROLLABLE_INDICATOR_CENTER = 2;
    /**
     * Tab可滚动
     */
    public static final int MODE_SCROLLABLE = 3;

    @IntDef(value = {MODE_FIXED_NON_SCROLLABLE, MODE_SCROLLABLE, MODE_SCROLLABLE_INDICATOR_CENTER})
    public @interface Mode{}
    //TabView Mode
    private int mMode = MODE_FIXED_NON_SCROLLABLE;

重写onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法根据Mode修改TabView尺寸。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if(getChildCount() == 1){
            View view = getChildAt(0);
            int childViewWidth = view.getMeasuredWidth();

            switch (mMode){
                case MODE_FIXED_NON_SCROLLABLE:
                    if(childViewWidth >= getMeasuredWidth()){
                        //更新Tab的尺寸
                        int tabViewCount = tabViewLinearLayout.getChildCount();
                        for(int i = 0; i < tabViewCount; i++){
                            ViewGroup.LayoutParams lp = tabViewLinearLayout.getChildAt(i).getLayoutParams();
                            lp.width = getMeasuredWidth() / tabViewCount;
                        }
                    }
                    break;
                //其他模式的修改逻辑
            }
        }
    }

当TabView被选中时,根据Mode设置滚动效果。

   //TabView选中处理
   void setSelectTabView(TabView tabView, int position){
        final TabView currentTab = mSelectedTabView;
        final int currentTabIndex = mSelectedIndex;

        if (currentTab == tabView) {
            if (currentTab != null) {
                dispatchTabSelected(currentTab, currentTabIndex);

                if(hasFocus()){
                    setFocusedTabView(currentTabIndex);
                }
            }
        } else {
            if (currentTab != null) {
                dispatchTabUnselected(currentTab, currentTabIndex);
                setUnFocusedTabView(currentTabIndex);
            }
            mSelectedTabView = tabView;
            mSelectedIndex = position;
            if (tabView != null) {
                dispatchTabSelected(tabView, position);

                if(hasFocus()){
                    setFocusedTabView(position);
                }
            }

            setScrollPosition();
        }
    }


    //设置滚动效果
    private void setScrollPosition(){
        View tabView = mSelectedTabView;
        int index = mSelectedIndex;
        if(mMode == MODE_SCROLLABLE || mMode == MODE_SCROLLABLE_INDICATOR_CENTER){
            int scrollViewWidth = getMeasuredWidth();
            int scrolled = getScrollX();
            int tabViewX = (int) tabView.getX();
            int tabViewWidth = tabView.getMeasuredWidth();
            int tabViewLinearLayoutWidth = tabViewLinearLayout.getMeasuredWidth();
            Log.d(TAG, "index = " + index + " --- scrollViewWidth = " + scrollViewWidth + " --- scrolled = " + scrolled + " --- tabViewX = " + tabViewX + " --- tabViewWidth" + tabViewWidth);

            switch (mMode){
                case MODE_SCROLLABLE:
                    if(tabViewX + tabViewWidth > scrollViewWidth + scrolled){
                        int scrollTo = tabViewX + tabViewWidth - scrollViewWidth;
                        smoothScrollTo(scrollTo, 0);
                    } else if(scrolled > tabViewX){
                        int scrollTo = tabViewX;
                        smoothScrollTo(scrollTo, 0);
                    }
                    break;
                case MODE_SCROLLABLE_INDICATOR_CENTER:
                    if(tabViewX + tabViewWidth / 2 < scrollViewWidth / 2 ||
                            tabViewX + tabViewWidth / 2 > tabViewLinearLayoutWidth - scrollViewWidth / 2){
                        break;
                    }

                    if((tabViewX + tabViewWidth / 2) != scrolled + scrollViewWidth / 2){
                        int scrollTo = tabViewX + tabViewWidth / 2 - scrollViewWidth / 2;
                        smoothScrollTo(scrollTo, 0);
                    }
                    break;
            }
        }
    }

实现思路大量借鉴了系统的TabLayout,整体实现逻辑并不复杂,实现细节可以阅读下面的源码。

源码

/**
 * <pre>
 *     author: Zhong
 *     time  : 2019/9/24
 *     desc  : 封装了一个电视端易用的TabLayout
 * </pre>
 */
public class TVTabLayout extends HorizontalScrollView {
    private static final String TAG = TVTabLayout.class.getSimpleName();

    //所有的TabView
    private final ArrayList<TabView> mTabs = new ArrayList<>();
    //当前选中的TabView
    private TabView mSelectedTabView = null;
    //当前选中的Tab index
    private int mSelectedIndex = -1;

    //tab之间的间距
    private int tabsMargin = 0;
    //是否循环,焦点移动边界时,是否循环
    private boolean isLoop = false;

    private LinearLayout tabViewLinearLayout;
    /**
     * Tab填充满显示区域,不可滚动
     */
    public static final int MODE_FIXED_NON_SCROLLABLE = 1;
    /**
     * Tab可滚动且选中栏目一直居中
     */
    public static final int MODE_SCROLLABLE_INDICATOR_CENTER = 2;
    /**
     * Tab可滚动
     */
    public static final int MODE_SCROLLABLE = 3;

    @IntDef(value = {MODE_FIXED_NON_SCROLLABLE, MODE_SCROLLABLE, MODE_SCROLLABLE_INDICATOR_CENTER})
    public @interface Mode{}
    //TabView Mode
    private int mMode = MODE_FIXED_NON_SCROLLABLE;

    //Tab焦点背景
    private Drawable mTabFocusedBackground = null;
    //TabView的选中监听,观察者模式
    private ArrayList<OnTabSelectedListener> mSelectedListeners = new ArrayList<>();
    //绑定的ViewPager
    private ViewPager viewPager;
    //ViewPager页面切换监听
    private ViewPager.OnPageChangeListener onPageChangeListener;
    //TabLayout切换监听
    private OnTabSelectedListener onTabSelectedListener;

    public TVTabLayout(Context context) {
        this(context, null);
    }

    public TVTabLayout(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TVTabLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

//        setOrientation(HORIZONTAL);
        setFocusable(true);
        setFocusableInTouchMode(true);
        // Disable the Scroll Bar
        setHorizontalScrollBarEnabled(false);
        setFillViewport(true);

        // Add the TabStrip
        tabViewLinearLayout = new LinearLayout(context);
        tabViewLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
        tabViewLinearLayout.setFocusable(false);
        tabViewLinearLayout.setFocusableInTouchMode(false);

        applyMode();
        addView(tabViewLinearLayout, new HorizontalScrollView.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
    }


    private void applyMode(){
        switch (mMode) {
            case MODE_FIXED_NON_SCROLLABLE:
                tabViewLinearLayout.setGravity(Gravity.CENTER);
                break;
            case MODE_SCROLLABLE:
            case MODE_SCROLLABLE_INDICATOR_CENTER:
                tabViewLinearLayout.setGravity(Gravity.CENTER_VERTICAL| START);
                break;
        }
    }

    public void setMode(@Mode int mode){
        if(mode != mMode){
            this.mMode = mode;

            applyMode();
        }
    }

    /**
     * Return the current mode
     * @see #setMode(int)
     */
    @Mode
    public int getMode(){
        return mMode;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if(getChildCount() == 1){
            View view = getChildAt(0);
            int childViewWidth = view.getMeasuredWidth();

            switch (mMode){
                case MODE_FIXED_NON_SCROLLABLE:
                    if(childViewWidth >= getMeasuredWidth()){
                        //更新Tab的尺寸
                        int tabViewCount = tabViewLinearLayout.getChildCount();
                        for(int i = 0; i < tabViewCount; i++){
                            ViewGroup.LayoutParams lp = tabViewLinearLayout.getChildAt(i).getLayoutParams();
                            lp.width = getMeasuredWidth() / tabViewCount;
                        }
                    }
                    break;
                //其他模式的修改逻辑
            }
        }
    }

    @Override
    protected void onFocusChanged(boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect) {
        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
        if(gainFocus){
            if(mSelectedIndex >= 0){
                setSelectTabView(mSelectedIndex);
            } else{
                setSelectTabView(0);
            }
        } else{
            setUnFocusedTabView(mSelectedIndex);
        }
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if(event.getAction() != KeyEvent.ACTION_DOWN){
            return super.dispatchKeyEvent(event);
        }

        switch (event.getKeyCode()){
            case KeyEvent.KEYCODE_DPAD_LEFT:
                int currentTabIndexLeft = mSelectedIndex;
                if(isLoop){
                    int nextLeftPosition = currentTabIndexLeft > 0 ? --currentTabIndexLeft : getTabCount() -1;
                    setSelectTabView(nextLeftPosition);
                } else{
                    if(currentTabIndexLeft > 0){
                        int nextLeftPosition =  --currentTabIndexLeft;
                        setSelectTabView(nextLeftPosition);
                    } else{
                        Log.d(TAG, "index == 0, 不循环");
                    }
                }
                return true;
            case KeyEvent.KEYCODE_DPAD_RIGHT:
                int currentTabIndexRight = mSelectedIndex;
                if(isLoop){
                    int nextRightPosition = currentTabIndexRight < getTabCount() - 1 ? ++currentTabIndexRight : 0;
                    setSelectTabView(nextRightPosition);
                } else{
                    if(currentTabIndexRight < getTabCount() - 1){
                        int nextRightPosition = ++currentTabIndexRight;
                        setSelectTabView(nextRightPosition);
                    } else{
                        Log.d(TAG, "index == count, 不循环");
                    }
                }
                return true;
        }

        return super.dispatchKeyEvent(event);
    }

    @Override
    public void setOnKeyListener(OnKeyListener l) {
        super.setOnKeyListener(l);
    }

    /**
     * 设置Tab间距
     * @param margin 间距
     */
    public void setTabsMargin(int margin){
        this.tabsMargin = margin;
    }

    /**
     * 添加tab
     * @param view tabView
     */
    public void addTab(@NonNull TabView view){
        addTab(view, tabsMargin);
    }

    /**
     * 添加tab
     * @param view tabView
     */
    public void addTab(@NonNull TabView view, int tabsMargin){
        view.setFocusable(false);
        view.setFocusableInTouchMode(false);
        mTabs.add(view);
        tabViewLinearLayout.addView(view, createLayoutParamsForTabs());

        if(mSelectedIndex == mTabs.indexOf(view)){
            setSelectTabView(mSelectedIndex);
        }
    }

    private LinearLayout.LayoutParams createLayoutParamsForTabs() {
        final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
        //设置Tab间距
        lp.leftMargin = tabsMargin;
        updateTabViewLayoutParams(lp);
        return lp;
    }

    private void updateTabViewLayoutParams(LinearLayout.LayoutParams lp) {
        //根据模式设置Tab尺寸
        if (mMode == MODE_FIXED_NON_SCROLLABLE) {
            lp.width = 0;
            lp.weight = 1;
        } else {
            lp.width = LinearLayout.LayoutParams.WRAP_CONTENT;
            lp.weight = 0;
        }
    }

    /**
     * 获取当前选中的Tab index
     * @return select index
     */
    public int getSelectedTabPosition() {
        return mSelectedIndex;
    }

    /**
     * 根据Index 获取TabView
     * @param index index
     * @return tabView
     */
    public TabView getTab(int index){
        return (index < 0 || index >= getTabCount()) ? null : mTabs.get(index);
    }

    /**
     * 获取TabView个数
     * @return TabView个数
     */
    public int getTabCount() {
        return mTabs.size();
    }

    /**
     * 设置获取焦点的TavView
     * @param position
     */
    protected void setFocusedTabView(int position){
        final int tabCount = getTabCount();
        if (position < tabCount) {
            if(mTabFocusedBackground != null && getTab(position) != null){
                getTab(position).setBackground(mTabFocusedBackground);
            }
        }
    }

    /**
     * 设置失去焦点的TabView
     * @param position
     */
    protected void setUnFocusedTabView(int position){
        final int tabCount = getTabCount();
        if (position < tabCount) {
            if(mTabFocusedBackground != null && getTab(position) != null){
                getTab(position).setBackground(null);
            }
        }
    }

    /**
     * 设置Tab选中状态
     * @param position
     */
    public void setSelectTabView(int position) throws IndexOutOfBoundsException{
        final int tabCount = getTabCount();
        if (position < tabCount) {
            setSelectTabView(getTab(position), position);
        } else{
            throw new IndexOutOfBoundsException("index out of TabView count");
        }
    }

    /**
     * 设置Tab选中状态
     * @param tabView
     */
    void setSelectTabView(TabView tabView, int position){
        final TabView currentTab = mSelectedTabView;
        final int currentTabIndex = mSelectedIndex;

        if (currentTab == tabView) {
            if (currentTab != null) {
                dispatchTabSelected(currentTab, currentTabIndex);

                if(hasFocus()){
                    setFocusedTabView(currentTabIndex);
                }
            }
        } else {
            if (currentTab != null) {
                dispatchTabUnselected(currentTab, currentTabIndex);
                setUnFocusedTabView(currentTabIndex);
            }
            mSelectedTabView = tabView;
            mSelectedIndex = position;
            if (tabView != null) {
                dispatchTabSelected(tabView, position);

                if(hasFocus()){
                    setFocusedTabView(position);
                }
            }

            setScrollPosition();
        }
    }

    /**
     * 分发Tab选中状态
     * @param tab selected TabView
     * @param position selected TabView`s index
     */
    private void dispatchTabSelected(@NonNull TabView tab, int position) {
        for (int i = mSelectedListeners.size() - 1; i >= 0; i--) {
            mSelectedListeners.get(i).onTabSelected(tab,position);
        }
    }

    private void dispatchTabUnselected(@NonNull TabView tab, int position) {
        for (int i = mSelectedListeners.size() - 1; i >= 0; i--) {
            mSelectedListeners.get(i).onTabUnSelected(tab, position);
        }
    }

    public void setTabSelectedListener(OnTabSelectedListener onTabSelectedListener){
        if (onTabSelectedListener != null) {
            addOnTabSelectedListener(onTabSelectedListener);
        }
    }

    public void addOnTabSelectedListener(@NonNull OnTabSelectedListener listener) {
        if (!mSelectedListeners.contains(listener)) {
            mSelectedListeners.add(listener);
        }
    }

    public void removeOnTabSelectedListener(@NonNull OnTabSelectedListener listener){
        if(mSelectedListeners != null){
            mSelectedListeners.remove(listener);
        }
    }

    public void setTabFocusedBackground(Drawable drawable){
        mTabFocusedBackground = drawable;
    }

    public void setTabFocusedBackground(int resource){
        Drawable drawable = getResources().getDrawable(resource);
        mTabFocusedBackground = drawable;
    }

    /**
     * 滚动处理
     */
    private void setScrollPosition(){
        View tabView = mSelectedTabView;
        int index = mSelectedIndex;
        if(mMode == MODE_SCROLLABLE || mMode == MODE_SCROLLABLE_INDICATOR_CENTER){
            int scrollViewWidth = getMeasuredWidth();
            int scrolled = getScrollX();
            int tabViewX = (int) tabView.getX();
            int tabViewWidth = tabView.getMeasuredWidth();
            int tabViewLinearLayoutWidth = tabViewLinearLayout.getMeasuredWidth();
            Log.d(TAG, "index = " + index + " --- scrollViewWidth = " + scrollViewWidth + " --- scrolled = " + scrolled + " --- tabViewX = " + tabViewX + " --- tabViewWidth" + tabViewWidth);

            switch (mMode){
                case MODE_SCROLLABLE:
                    if(tabViewX + tabViewWidth > scrollViewWidth + scrolled){
                        int scrollTo = tabViewX + tabViewWidth - scrollViewWidth;
                        smoothScrollTo(scrollTo, 0);
                    } else if(scrolled > tabViewX){
                        int scrollTo = tabViewX;
                        smoothScrollTo(scrollTo, 0);
                    }
                    break;
                case MODE_SCROLLABLE_INDICATOR_CENTER:
                    if(tabViewX + tabViewWidth / 2 < scrollViewWidth / 2 ||
                            tabViewX + tabViewWidth / 2 > tabViewLinearLayoutWidth - scrollViewWidth / 2){
                        break;
                    }

                    if((tabViewX + tabViewWidth / 2) != scrolled + scrollViewWidth / 2){
                        int scrollTo = tabViewX + tabViewWidth / 2 - scrollViewWidth / 2;
                        smoothScrollTo(scrollTo, 0);
                    }
                    break;
            }
        }
    }

    @NonNull
    public TabView newTabView() {
        TabView tabView = new TabView(getContext());
        tabView.setFocusable(false);
        return tabView;
    }

    /**
     * 绑定ViewPager
     * @param viewPager ViewPager
     */
    public void setupWithViewPager(ViewPager viewPager){
        if(this.viewPager != null){
            //移除所有监听
            if(onPageChangeListener != null){
                this.viewPager.removeOnPageChangeListener(onPageChangeListener);
            }

            if(onTabSelectedListener != null){
                this.removeOnTabSelectedListener(onTabSelectedListener);
            }
        }

        if(viewPager != null){
            this.viewPager = viewPager;
            //绑定,设置监听
            onPageChangeListener = new TabLayoutOnPageChangeListener(this);
            viewPager.addOnPageChangeListener(onPageChangeListener);

            onTabSelectedListener = new ViewPagerOnTabSelectedListener(viewPager);
            this.addOnTabSelectedListener(onTabSelectedListener);
        } else{
            this.viewPager = null;
            this.onPageChangeListener = null;
            this.onTabSelectedListener = null;
        }
    }

    /**
     public interface OnTabFocusListener{
     public void onTabFocused(View view, int index);

     public void onTabUnFocus(View view, int index);
     }
     **/


    public static final class TabView extends FrameLayout {
        private View mCustomView;

        public TabView(@NonNull Context context) {
            super(context);
        }

        public TabView(@NonNull Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }

        public TabView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }

        @Nullable
        public View getCustomView() {
            return mCustomView;
        }

        @NonNull
        public TabView setCustomView(@Nullable View view) {
            mCustomView = view;
            updateView();
            return this;
        }

        void updateView() {
            if (mCustomView != null) {
                LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);
                setPadding(4,4,4,4);
                addView(mCustomView,layoutParams);
            }
        }
    }

    public static class TabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener {
        private final WeakReference<TVTabLayout> mTabLayoutRef;
        private int mPreviousScrollState;
        private int mScrollState;

        public TabLayoutOnPageChangeListener(TVTabLayout tabLayout) {
            mTabLayoutRef = new WeakReference<>(tabLayout);
        }

        @Override
        public void onPageScrollStateChanged(final int state) {
            mPreviousScrollState = mScrollState;
            mScrollState = state;
        }

        @Override
        public void onPageScrolled(final int position, final float positionOffset,
                                   final int positionOffsetPixels) {
        }

        @Override
        public void onPageSelected(final int position) {
            //监听ViewPager的Item选中事件,切换Tab
            final TVTabLayout tabLayout = mTabLayoutRef.get();
            if (tabLayout != null && tabLayout.getSelectedTabPosition() != position
                    && position < tabLayout.getTabCount()) {
                // Select the tab, only updating the indicator if we're not being dragged/settled
                // (since onPageScrolled will handle that).
                tabLayout.setSelectTabView(position);
            }
        }
    }

    public interface OnTabSelectedListener{
        void onTabSelected(TabView view, int index);

        void onTabUnSelected(TabView view, int index);
    }

    public static class ViewPagerOnTabSelectedListener implements OnTabSelectedListener {
        private final ViewPager mViewPager;

        public ViewPagerOnTabSelectedListener(ViewPager viewPager) {
            mViewPager = viewPager;
        }

        @Override
        public void onTabSelected(TabView view, int index) {
            //监听Tab选中事件,切换ViewPager
            if(mViewPager != null && mViewPager.getCurrentItem() != index){
                try{
                    //部分型号上抛出异常:
                    //IllegalStateException: FragmentManager is already executing transactions
                    //简单捕获处理下,后续完善
                    mViewPager.setCurrentItem(index);
                } catch (Exception e){
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onTabUnSelected(TabView view, int index) {

        }
    }
}

总结

TvTabLayout控件是从现有项目中剥离出来的,未做过多的功能性封装。相较于其他类似开源项目略显简陋,但是也希望能为你解决类似问题提供思路。

Logo

智屏生态联盟致力于大屏生态发展,利用大屏快应用技术降低开发者开发、发布大屏应用门槛

更多推荐