TvTabLayout——封装一个Android TV端易用的TabLayout
概述Tab + ViewPager是我们常用的一种Android端UI架构,Android系统提供了TabLayout等控件用于实现与ViewPager配套使用问题,但是原生的TabLayout使用在Android TV端并不友好,需要添加复杂的逻辑解决按键与焦点问题,本文旨在封装一个简单的TvTabLayout控件,方便实现Android TV端的常用功能。TvTabLayout简介...
概述
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的双向绑定
添加TabLayoutOnPageChangeListener
和ViewPagerOnTabSelectedListener
两个回调监听,实现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控件是从现有项目中剥离出来的,未做过多的功能性封装。相较于其他类似开源项目略显简陋,但是也希望能为你解决类似问题提供思路。
更多推荐
所有评论(0)