概述
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控件是從現有項目中剝離出來的,未做過多的功能性封裝。相較於其他類似開源項目略顯簡陋,但是也希望能爲你解決類似問題提供思路。