diff --git a/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/TabStrip.java b/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/TabStrip.java index be7970d42..557be52a1 100644 --- a/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/TabStrip.java +++ b/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/TabStrip.java @@ -152,6 +152,7 @@ class TabStrip extends LinearLayout { } } + // Used by TabLayout (the 'old' tab-view control) void onViewPagerPageChanged(int position, float positionOffset) { mSelectedPosition = position; mSelectionOffset = positionOffset; @@ -159,6 +160,13 @@ class TabStrip extends LinearLayout { updateTabsTextColor(); } + // Used by TabsBar + void onTabsViewPagerPageChanged(int position, float positionOffset) { + mSelectedPosition = position; + mSelectionOffset = positionOffset; + invalidate(); + } + int getSelectedPosition(){ return mSelectedPosition; } diff --git a/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/TabsBar.java b/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/TabsBar.java new file mode 100644 index 000000000..c53661cc7 --- /dev/null +++ b/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/TabsBar.java @@ -0,0 +1,441 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.nativescript.widgets; + +import android.content.Context; +import android.graphics.Typeface; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager.widget.ViewPager; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.HorizontalScrollView; +import android.widget.ImageView; +import android.widget.ImageView.ScaleType; +import android.widget.LinearLayout; +import android.widget.TextView; + +/** + * To be used with ViewPager to provide a tab indicator component which give + * constant feedback as to the user's scroll progress. + *

+ * To use the component, simply add it to your view hierarchy. Then in your + * {@link android.app.Activity} or {@link android.support.v4.app.Fragment} call + * {@link #setViewPager(ViewPager)} providing it the ViewPager this layout is + * being used for. + *

+ * The colors can be customized in two ways. The first and simplest is to + * provide an array of colors via {@link #setSelectedIndicatorColors(int...)}. + * The alternative is via the {@link TabColorizer} interface which provides you + * complete control over which color is used for any individual position. + *

+ */ +public class TabsBar extends HorizontalScrollView { + /** + * Allows complete control over the colors drawn in the tab layout. Set with + * {@link #setCustomTabColorizer(TabColorizer)}. + */ + public interface TabColorizer { + + /** + * @return return the color of the indicator used when {@code position} + * is selected. + */ + int getIndicatorColor(int position); + + } + + private static final int TITLE_OFFSET_DIPS = 24; + private static final int TAB_VIEW_PADDING_DIPS = 16; + private static final int TAB_VIEW_TEXT_SIZE_SP = 12; + private static final int TEXT_MAX_WIDTH = 180; + private static final int SMALL_MIN_HEIGHT = 48; + private static final int LARGE_MIN_HEIGHT = 72; + + private int mTitleOffset; + + private boolean mDistributeEvenly = true; + + private TabItemSpec[] mTabItems; + private ViewPager mViewPager; + private SparseArray mContentDescriptions = new SparseArray(); + private ViewPager.OnPageChangeListener mViewPagerPageChangeListener; + + private final TabStrip mTabStrip; + + public TabsBar(Context context) { + this(context, null); + } + + public TabsBar(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public TabsBar(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + // Disable the Scroll Bar + setHorizontalScrollBarEnabled(false); + // Make sure that the Tab Strips fills this View + setFillViewport(true); + + mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density); + + mTabStrip = new TabStrip(context); + addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + } + + /** + * Set the custom {@link TabColorizer} to be used. + * + * If you only require simple customisation then you can use + * {@link #setSelectedIndicatorColors(int...)} to achieve similar effects. + */ + public void setCustomTabColorizer(TabColorizer tabColorizer) { + //mTabStrip.setCustomTabColorizer(tabColorizer); + } + + public void setDistributeEvenly(boolean distributeEvenly) { + mDistributeEvenly = distributeEvenly; + } + + /** + * Sets the colors to be used for indicating the selected tab. These colors + * are treated as a circular array. Providing one color will mean that all + * tabs are indicated with the same color. + */ + public void setSelectedIndicatorColors(int... colors) { + mTabStrip.setSelectedIndicatorColors(colors); + this.mSelectedIndicatorColors = colors; + } + + private int[] mSelectedIndicatorColors; + public int[] getSelectedIndicatorColors() { + return this.mSelectedIndicatorColors; + } + + public void setTabTextColor(int color){ + mTabStrip.setTabTextColor(color); + } + + public int getTabTextColor(){ + return mTabStrip.getTabTextColor(); + } + + public void setSelectedTabTextColor(int color){ + mTabStrip.setSelectedTabTextColor(color); + } + + public int getSelectedTabTextColor(){ + return mTabStrip.getSelectedTabTextColor(); + } + + public void setTabTextFontSize(float fontSize){ + mTabStrip.setTabTextFontSize(fontSize); + } + + public float getTabTextFontSize(){ + return mTabStrip.getTabTextFontSize(); + } + + /** + * Set the {@link ViewPager.OnPageChangeListener}. When using + * {@link TabsBar} you are required to set any + * {@link ViewPager.OnPageChangeListener} through this method. This is so + * that the layout can update it's scroll position correctly. + * + * @see ViewPager#setOnPageChangeListener(ViewPager.OnPageChangeListener) + */ + public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { + mViewPagerPageChangeListener = listener; + } + + /** + * Sets the associated view pager. Note that the assumption here is that the + * pager content (number of tabs and tab titles) does not change after this + * call has been made. + */ + public void setViewPager(ViewPager viewPager) { + this.setItems(null, viewPager); + } + + public void setItems(TabItemSpec[] items, ViewPager viewPager) { + mTabStrip.removeAllViews(); + + mViewPager = viewPager; + mTabItems = items; + if (viewPager != null) { + viewPager.addOnPageChangeListener(new InternalViewPagerListener()); + populateTabStrip(); + } + } + + /** + * Updates the UI of an item at specified index + */ + public void updateItemAt(int position, TabItemSpec tabItem) { + LinearLayout ll = (LinearLayout)mTabStrip.getChildAt(position); + ImageView imgView = (ImageView)ll.getChildAt(0); + TextView textView = (TextView)ll.getChildAt(1); + this.setupItem(ll, textView, imgView, tabItem); + } + + /** + * Gets the TextView for tab item at index + */ + public TextView getTextViewForItemAt(int index){ + LinearLayout ll = this.getViewForItemAt(index); + return (ll != null) ? (TextView)ll.getChildAt(1) : null; + } + + /** + * Gets the LinearLayout container for tab item at index + */ + public LinearLayout getViewForItemAt(int index){ + LinearLayout result = null; + + if(this.mTabStrip.getChildCount() > index){ + result = (LinearLayout)this.mTabStrip.getChildAt(index); + } + + return result; + } + + /** + * Gets the number of realized tabs. + */ + public int getItemCount(){ + return this.mTabStrip.getChildCount(); + } + + /** + * Create a default view to be used for tabs. + */ + protected View createDefaultTabView(Context context, TabItemSpec tabItem) { + float density = getResources().getDisplayMetrics().density; + int padding = (int) (TAB_VIEW_PADDING_DIPS * density); + + LinearLayout ll = new LinearLayout(context); + ll.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)); + ll.setGravity(Gravity.CENTER); + ll.setOrientation(LinearLayout.VERTICAL); + TypedValue outValue = new TypedValue(); + getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true); + ll.setBackgroundResource(outValue.resourceId); + + ImageView imgView = new ImageView(context); + imgView.setScaleType(ScaleType.FIT_CENTER); + LinearLayout.LayoutParams imgLP = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + imgLP.gravity = Gravity.CENTER; + imgView.setLayoutParams(imgLP); + + TextView textView = new TextView(context); + textView.setGravity(Gravity.CENTER); + textView.setMaxWidth((int) (TEXT_MAX_WIDTH * density)); + textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP); + textView.setTypeface(Typeface.DEFAULT_BOLD); + textView.setEllipsize(TextUtils.TruncateAt.END); + textView.setAllCaps(true); + textView.setMaxLines(2); + textView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + textView.setPadding(padding, 0, padding, 0); + + this.setupItem(ll, textView, imgView, tabItem); + + ll.addView(imgView); + ll.addView(textView); + return ll; + } + + private void setupItem(LinearLayout ll, TextView textView,ImageView imgView, TabItemSpec tabItem){ + float density = getResources().getDisplayMetrics().density; + + if (tabItem.iconId != 0) { + imgView.setImageResource(tabItem.iconId); + imgView.setVisibility(VISIBLE); + } else if (tabItem.iconDrawable != null) { + imgView.setImageDrawable(tabItem.iconDrawable); + imgView.setVisibility(VISIBLE); + } else { + imgView.setVisibility(GONE); + } + + if (tabItem.title != null && !tabItem.title.isEmpty()) { + textView.setText(tabItem.title); + textView.setVisibility(VISIBLE); + } else { + textView.setVisibility(GONE); + } + + if (tabItem.backgroundColor != 0) { + ll.setBackgroundColor(tabItem.backgroundColor); + } + + if (imgView.getVisibility() == VISIBLE && textView.getVisibility() == VISIBLE) { + ll.setMinimumHeight((int) (LARGE_MIN_HEIGHT * density)); + } else { + ll.setMinimumHeight((int) (SMALL_MIN_HEIGHT * density)); + } + + if (mDistributeEvenly) { + LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ll.getLayoutParams(); + lp.width = 0; + lp.weight = 1; + } + } + + public boolean onTap(int position) { + // to be overridden in JS + return true; + } + + public void onSelectedPositionChange(int position, int prevPosition) { + // to be overridden in JS + } + + private void populateTabStrip() { + final PagerAdapter adapter = mViewPager.getAdapter(); + final OnClickListener tabClickListener = new TabClickListener(); + + for (int i = 0; i < adapter.getCount(); i++) { + View tabView = null; + + TabItemSpec tabItem; + if (this.mTabItems != null && this.mTabItems.length > i) { + tabItem = this.mTabItems[i]; + } else { + tabItem = new TabItemSpec(); + tabItem.title = adapter.getPageTitle(i).toString(); + } + + tabView = createDefaultTabView(getContext(), tabItem); + + tabView.setOnClickListener(tabClickListener); + String desc = mContentDescriptions.get(i, null); + if (desc != null) { + tabView.setContentDescription(desc); + } + + mTabStrip.addView(tabView); + if (i == mViewPager.getCurrentItem()) { + tabView.setSelected(true); + } + } + } + + public void setContentDescription(int i, String desc) { + mContentDescriptions.put(i, desc); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (mViewPager != null) { + scrollToTab(mViewPager.getCurrentItem(), 0); + } + } + + private void scrollToTab(int tabIndex, int positionOffset) { + final int tabStripChildCount = mTabStrip.getChildCount(); + if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) { + return; + } + + View selectedChild = mTabStrip.getChildAt(tabIndex); + if (selectedChild != null) { + int targetScrollX = selectedChild.getLeft() + positionOffset; + + if (tabIndex > 0 || positionOffset > 0) { + // If we're not at the first child and are mid-scroll, make sure + // we obey the offset + targetScrollX -= mTitleOffset; + } + + scrollTo(targetScrollX, 0); + } + } + + private class InternalViewPagerListener implements ViewPager.OnPageChangeListener { + private int mScrollState; + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + int tabStripChildCount = mTabStrip.getChildCount(); + if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) { + return; + } + + int prevPosition = mTabStrip.getSelectedPosition(); + + if (prevPosition != position) { + onSelectedPositionChange(position, prevPosition); + } + + mTabStrip.onTabsViewPagerPageChanged(position, positionOffset); + + View selectedTitle = mTabStrip.getChildAt(position); + int extraOffset = (selectedTitle != null) ? (int) (positionOffset * selectedTitle.getWidth()) : 0; + scrollToTab(position, extraOffset); + + if (mViewPagerPageChangeListener != null) { + mViewPagerPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels); + } + } + + @Override + public void onPageScrollStateChanged(int state) { + mScrollState = state; + + if (mViewPagerPageChangeListener != null) { + mViewPagerPageChangeListener.onPageScrollStateChanged(state); + } + } + + @Override + public void onPageSelected(int position) { + if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { + mTabStrip.onTabsViewPagerPageChanged(position, 0f); + scrollToTab(position, 0); + } + for (int i = 0; i < mTabStrip.getChildCount(); i++) { + mTabStrip.getChildAt(i).setSelected(position == i); + } + if (mViewPagerPageChangeListener != null) { + mViewPagerPageChangeListener.onPageSelected(position); + } + } + + } + + private class TabClickListener implements OnClickListener { + @Override + public void onClick(View v) { + for (int i = 0; i < mTabStrip.getChildCount(); i++) { + if (v == mTabStrip.getChildAt(i)) { + if (onTap(i)) { + mViewPager.setCurrentItem(i); + } + return; + } + } + } + } +} \ No newline at end of file diff --git a/tns-core-modules/ui/tabs/tabs.android.ts b/tns-core-modules/ui/tabs/tabs.android.ts index c7a5e2d55..2b4993203 100644 --- a/tns-core-modules/ui/tabs/tabs.android.ts +++ b/tns-core-modules/ui/tabs/tabs.android.ts @@ -28,7 +28,7 @@ interface PagerAdapter { const TABID = "_tabId"; const INDEX = "_index"; let PagerAdapter: PagerAdapter; -let TabLayout: any; +let TabsBar: any; function makeFragmentName(viewId: number, id: number): string { return "android:viewpager:" + viewId + ":" + id; @@ -234,7 +234,7 @@ function initializeNativeClasses() { } } - class TabLayoutImplementation extends org.nativescript.widgets.TabLayout { + class TabsBarImplementation extends org.nativescript.widgets.TabsBar { constructor(context: android.content.Context, public owner: Tabs) { super(context); @@ -280,7 +280,7 @@ function initializeNativeClasses() { } PagerAdapter = FragmentPagerAdapter; - TabLayout = TabLayoutImplementation; + TabsBar = TabsBarImplementation; } function createTabItemSpec(tabStripItem: TabStripItem): org.nativescript.widgets.TabItemSpec { @@ -337,12 +337,12 @@ function getDefaultAccentColor(context: android.content.Context): number { return defaultAccentColor; } -function setElevation(grid: org.nativescript.widgets.GridLayout, tabLayout: org.nativescript.widgets.TabLayout) { +function setElevation(grid: org.nativescript.widgets.GridLayout, tabsBar: org.nativescript.widgets.TabsBar) { const compat = androidx.core.view.ViewCompat; if (compat.setElevation) { const val = DEFAULT_ELEVATION * layout.getDisplayDensity(); compat.setElevation(grid, val); - compat.setElevation(tabLayout, val); + compat.setElevation(tabsBar, val); } } @@ -357,7 +357,7 @@ function iterateIndexRange(index: number, eps: number, lastIndex: number, callba } export class Tabs extends TabsBase { - private _tabLayout: org.nativescript.widgets.TabLayout; + private _tabsBar: org.nativescript.widgets.TabsBar; private _viewPager: androidx.viewpager.widget.ViewPager; private _pagerAdapter: androidx.viewpager.widget.PagerAdapter; private _androidViewId: number = -1; @@ -393,7 +393,7 @@ export class Tabs extends TabsBase { const context: android.content.Context = this._context; const nativeView = new org.nativescript.widgets.GridLayout(context); const viewPager = new org.nativescript.widgets.TabViewPager(context); - const tabLayout = new TabLayout(context, this); + const tabsBar = new TabsBar(context, this); const lp = new org.nativescript.widgets.CommonLayoutParams(); const primaryColor = ad.resources.getPaletteColor(PRIMARY_COLOR, context); let accentColor = getDefaultAccentColor(context); @@ -409,7 +409,7 @@ export class Tabs extends TabsBase { nativeView.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.star)); nativeView.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.auto)); - tabLayout.setLayoutParams(lp); + tabsBar.setLayoutParams(lp); } nativeView.addView(viewPager); @@ -419,17 +419,17 @@ export class Tabs extends TabsBase { viewPager.setAdapter(adapter); (viewPager).adapter = adapter; - nativeView.addView(tabLayout); - (nativeView).tabLayout = tabLayout; + nativeView.addView(tabsBar); + (nativeView).tabsBar = tabsBar; - setElevation(nativeView, tabLayout); + setElevation(nativeView, tabsBar); if (accentColor) { - tabLayout.setSelectedIndicatorColors([accentColor]); + tabsBar.setSelectedIndicatorColors([accentColor]); } if (primaryColor) { - tabLayout.setBackgroundColor(primaryColor); + tabsBar.setBackgroundColor(primaryColor); } return nativeView; @@ -442,7 +442,7 @@ export class Tabs extends TabsBase { } const nativeView: any = this.nativeViewProtected; - this._tabLayout = (nativeView).tabLayout; + this._tabsBar = (nativeView).tabsBar; const viewPager = (nativeView).viewPager; viewPager.setId(this._androidViewId); @@ -508,11 +508,11 @@ export class Tabs extends TabsBase { } public disposeNativeView() { - this._tabLayout.setItems(null, null); + this._tabsBar.setItems(null, null); (this._pagerAdapter).owner = null; this._pagerAdapter = null; - this._tabLayout = null; + this._tabsBar = null; this._viewPager = null; super.disposeNativeView(); } @@ -590,7 +590,7 @@ export class Tabs extends TabsBase { private setTabStripItems(items: Array) { const length = items ? items.length : 0; if (length === 0) { - this._tabLayout.setItems(null, null); + this._tabsBar.setItems(null, null); return; } @@ -603,11 +603,11 @@ export class Tabs extends TabsBase { tabItems.push(tabItemSpec); }); - const tabLayout = this._tabLayout; - tabLayout.setItems(tabItems, this._viewPager); - this.tabStrip.setNativeView(tabLayout); + const tabsBar = this._tabsBar; + tabsBar.setItems(tabItems, this._viewPager); + this.tabStrip.setNativeView(tabsBar); items.forEach((item, i, arr) => { - const tv = tabLayout.getTextViewForItemAt(i); + const tv = tabsBar.getTextViewForItemAt(i); item.setNativeView(tv); }); } @@ -643,32 +643,32 @@ export class Tabs extends TabsBase { // } public updateAndroidItemAt(index: number, spec: org.nativescript.widgets.TabItemSpec) { - this._tabLayout.updateItemAt(index, spec); + this._tabsBar.updateItemAt(index, spec); } public getTabBarBackgroundColor(): android.graphics.drawable.Drawable { - return this._tabLayout.getBackground(); + return this._tabsBar.getBackground(); } public setTabBarBackgroundColor(value: android.graphics.drawable.Drawable | Color): void { if (value instanceof Color) { - this._tabLayout.setBackgroundColor(value.android); + this._tabsBar.setBackgroundColor(value.android); } else { - this._tabLayout.setBackground(tryCloneDrawable(value, this.nativeViewProtected.getResources)); + this._tabsBar.setBackground(tryCloneDrawable(value, this.nativeViewProtected.getResources)); } } public getTabBarColor(): number { - return this._tabLayout.getTabTextColor(); + return this._tabsBar.getTabTextColor(); } public setTabBarColor(value: number | Color): void { if (value instanceof Color) { - this._tabLayout.setTabTextColor(value.android); - this._tabLayout.setSelectedTabTextColor(value.android); + this._tabsBar.setTabTextColor(value.android); + this._tabsBar.setSelectedTabTextColor(value.android); } else { - this._tabLayout.setTabTextColor(value); - this._tabLayout.setSelectedTabTextColor(value); + this._tabsBar.setTabTextColor(value); + this._tabsBar.setSelectedTabTextColor(value); } } @@ -678,7 +678,7 @@ export class Tabs extends TabsBase { public setTabBarHighlightColor(value: number | Color) { const color = value instanceof Color ? value.android : value; - this._tabLayout.setSelectedIndicatorColors([color]); + this._tabsBar.setSelectedIndicatorColors([color]); } public setTabBarItemBackgroundColor(tabStripItem: TabStripItem, value: android.graphics.drawable.Drawable | Color): void { diff --git a/tns-platform-declarations/android/org.nativescript.widgets.d.ts b/tns-platform-declarations/android/org.nativescript.widgets.d.ts index 33ab960a1..b78e57aa2 100644 --- a/tns-platform-declarations/android/org.nativescript.widgets.d.ts +++ b/tns-platform-declarations/android/org.nativescript.widgets.d.ts @@ -122,7 +122,7 @@ export class LinearGradientDefinition { constructor(startX: number, endX: number, startY: number, - endY: number, colors: number[], stops?: number[]); + endY: number, colors: number[], stops?: number[]); public getStartX(): number; public getStartY(): number; @@ -398,6 +398,28 @@ getItemCount(): number; } + export class TabsBar extends android.widget.HorizontalScrollView { + constructor(context: android.content.Context); + constructor(context: android.content.Context, attrs: android.util.AttributeSet); + constructor(context: android.content.Context, attrs: android.util.AttributeSet, defStyle: number); + + setSelectedIndicatorColors(color: Array): void; + getSelectedIndicatorColors(): Array; + setTabTextColor(color: number): void; + getTabTextColor(): number; + setSelectedTabTextColor(color: number): void; + getSelectedTabTextColor(): number; + setTabTextFontSize(fontSize: number): void; + getTabTextFontSize(): number; + + setItems(items: Array, viewPager: androidx.viewpager.widget.ViewPager): void; + updateItemAt(position: number, itemSpec: TabItemSpec): void; + + getTextViewForItemAt(index: number): android.widget.TextView; + getViewForItemAt(index: number): android.widget.LinearLayout; + getItemCount(): number; + } + export class BottomNavigationBar extends android.widget.LinearLayout { constructor(context: android.content.Context); constructor(context: android.content.Context, attrs: android.util.AttributeSet); @@ -411,7 +433,7 @@ getTabTextFontSize(): number; onTap(position: number): boolean; - onSelectedPositionChange(position: number, prevPosition: number): void ; + onSelectedPositionChange(position: number, prevPosition: number): void; setSelectedPosition(position: number): void; setItems(items: Array): void; updateItemAt(position: number, itemSpec: TabItemSpec): void;