mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-14 18:12:09 +08:00
807 lines
24 KiB
TypeScript
807 lines
24 KiB
TypeScript
import { TabViewItem as TabViewItemDefinition } from '.';
|
|
import { Font } from '../styling/font';
|
|
|
|
import { TabViewBase, TabViewItemBase, itemsProperty, selectedIndexProperty, tabTextColorProperty, tabBackgroundColorProperty, tabTextFontSizeProperty, selectedTabTextColorProperty, androidSelectedTabHighlightColorProperty, androidOffscreenTabLimitProperty, traceCategory, traceMissingIcon, androidIconRenderingModeProperty } from './tab-view-common';
|
|
import { textTransformProperty, getTransformedText } from '../text-base';
|
|
import { CoreTypes } from '../../core-types';
|
|
import { ImageSource } from '../../image-source';
|
|
import { Trace } from '../../trace';
|
|
import { Color } from '../../color';
|
|
import { fontSizeProperty, fontInternalProperty } from '../styling/style-properties';
|
|
import { RESOURCE_PREFIX, ad, layout } from '../../utils';
|
|
import { Frame } from '../frame';
|
|
import { Application } from '../../application';
|
|
import { AndroidHelper } from '../core/view';
|
|
|
|
export * from './tab-view-common';
|
|
|
|
const ACCENT_COLOR = 'colorAccent';
|
|
const PRIMARY_COLOR = 'colorPrimary';
|
|
const DEFAULT_ELEVATION = 4;
|
|
|
|
interface PagerAdapter {
|
|
new (owner: TabView): androidx.viewpager.widget.PagerAdapter;
|
|
}
|
|
|
|
const TABID = '_tabId';
|
|
const INDEX = '_index';
|
|
let PagerAdapter: PagerAdapter;
|
|
let appResources: android.content.res.Resources;
|
|
|
|
function makeFragmentName(viewId: number, id: number): string {
|
|
return 'android:viewpager:' + viewId + ':' + id;
|
|
}
|
|
|
|
function getTabById(id: number): TabView {
|
|
const ref = tabs.find((ref) => {
|
|
const tab = ref.get();
|
|
|
|
return tab && tab._domId === id;
|
|
});
|
|
|
|
return ref && ref.get();
|
|
}
|
|
|
|
function initializeNativeClasses() {
|
|
if (PagerAdapter) {
|
|
return;
|
|
}
|
|
|
|
@NativeClass
|
|
class TabFragmentImplementation extends org.nativescript.widgets.FragmentBase {
|
|
private owner: TabView;
|
|
private index: number;
|
|
private backgroundBitmap: android.graphics.Bitmap = null;
|
|
|
|
constructor() {
|
|
super();
|
|
|
|
return global.__native(this);
|
|
}
|
|
|
|
static newInstance(tabId: number, index: number): TabFragmentImplementation {
|
|
const args = new android.os.Bundle();
|
|
args.putInt(TABID, tabId);
|
|
args.putInt(INDEX, index);
|
|
const fragment = new TabFragmentImplementation();
|
|
fragment.setArguments(args);
|
|
|
|
return fragment;
|
|
}
|
|
|
|
public onCreate(savedInstanceState: android.os.Bundle): void {
|
|
super.onCreate(savedInstanceState);
|
|
const args = this.getArguments();
|
|
this.owner = getTabById(args.getInt(TABID));
|
|
this.index = args.getInt(INDEX);
|
|
if (!this.owner) {
|
|
throw new Error(`Cannot find TabView`);
|
|
}
|
|
}
|
|
|
|
public onCreateView(inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle): android.view.View {
|
|
const tabItem = this.owner.items[this.index];
|
|
|
|
return tabItem.view.nativeViewProtected;
|
|
}
|
|
|
|
public onDestroyView() {
|
|
const hasRemovingParent = this.getRemovingParentFragment();
|
|
|
|
// Get view as bitmap and set it as background. This is workaround for the disapearing nested fragments.
|
|
// TODO: Consider removing it when update to androidx.fragment:1.2.0
|
|
if (hasRemovingParent && this.owner.selectedIndex === this.index) {
|
|
const bitmapDrawable = new android.graphics.drawable.BitmapDrawable(appResources, this.backgroundBitmap);
|
|
this.owner._originalBackground = this.owner.backgroundColor || new Color('White');
|
|
this.owner.nativeViewProtected.setBackground(bitmapDrawable);
|
|
this.backgroundBitmap = null;
|
|
}
|
|
|
|
super.onDestroyView();
|
|
}
|
|
|
|
public onPause(): void {
|
|
const hasRemovingParent = this.getRemovingParentFragment();
|
|
|
|
// Get view as bitmap and set it as background. This is workaround for the disapearing nested fragments.
|
|
// TODO: Consider removing it when update to androidx.fragment:1.2.0
|
|
if (hasRemovingParent && this.owner.selectedIndex === this.index) {
|
|
this.backgroundBitmap = this.loadBitmapFromView(this.owner.nativeViewProtected);
|
|
}
|
|
|
|
super.onPause();
|
|
}
|
|
|
|
private loadBitmapFromView(view: android.view.View): android.graphics.Bitmap {
|
|
// Another way to get view bitmap. Test performance vs setDrawingCacheEnabled
|
|
// const width = view.getWidth();
|
|
// const height = view.getHeight();
|
|
// const bitmap = android.graphics.Bitmap.createBitmap(width, height, android.graphics.Bitmap.Config.ARGB_8888);
|
|
// const canvas = new android.graphics.Canvas(bitmap);
|
|
// view.layout(0, 0, width, height);
|
|
// view.draw(canvas);
|
|
|
|
view.setDrawingCacheEnabled(true);
|
|
const bitmap = android.graphics.Bitmap.createBitmap(view.getDrawingCache());
|
|
view.setDrawingCacheEnabled(false);
|
|
|
|
return bitmap;
|
|
}
|
|
}
|
|
|
|
const POSITION_UNCHANGED = -1;
|
|
const POSITION_NONE = -2;
|
|
|
|
@NativeClass
|
|
class FragmentPagerAdapter extends androidx.viewpager.widget.PagerAdapter {
|
|
public items: Array<TabViewItemDefinition>;
|
|
private mCurTransaction: androidx.fragment.app.FragmentTransaction;
|
|
private mCurrentPrimaryItem: androidx.fragment.app.Fragment;
|
|
// in fragments 1.3+, committing a transaction may call the adapter's methods and trigger another commit
|
|
// we prevent that here.
|
|
private transactionRunning = false;
|
|
|
|
constructor(public owner: TabView) {
|
|
super();
|
|
|
|
return global.__native(this);
|
|
}
|
|
|
|
getCount() {
|
|
const items = this.items;
|
|
|
|
return items ? items.length : 0;
|
|
}
|
|
|
|
getPageTitle(index: number) {
|
|
const items = this.items;
|
|
if (index < 0 || index >= items.length) {
|
|
return '';
|
|
}
|
|
|
|
return items[index].title;
|
|
}
|
|
|
|
startUpdate(container: android.view.ViewGroup): void {
|
|
if (container.getId() === android.view.View.NO_ID) {
|
|
throw new Error(`ViewPager with adapter ${this} requires a view containerId`);
|
|
}
|
|
}
|
|
|
|
instantiateItem(container: android.view.ViewGroup, position: number): java.lang.Object {
|
|
const fragmentManager = this.owner._getFragmentManager();
|
|
if (!this.mCurTransaction) {
|
|
this.mCurTransaction = fragmentManager.beginTransaction();
|
|
}
|
|
|
|
const itemId = this.getItemId(position);
|
|
const name = makeFragmentName(container.getId(), itemId);
|
|
|
|
let fragment: androidx.fragment.app.Fragment = fragmentManager.findFragmentByTag(name);
|
|
if (fragment != null) {
|
|
this.mCurTransaction.attach(fragment);
|
|
} else {
|
|
fragment = TabFragmentImplementation.newInstance(this.owner._domId, position);
|
|
this.mCurTransaction.add(container.getId(), fragment, name);
|
|
}
|
|
|
|
if (fragment !== this.mCurrentPrimaryItem) {
|
|
fragment.setMenuVisibility(false);
|
|
fragment.setUserVisibleHint(false);
|
|
}
|
|
|
|
const tabItems = this.owner.items;
|
|
const tabItem = tabItems ? tabItems[position] : null;
|
|
if (tabItem) {
|
|
tabItem.canBeLoaded = true;
|
|
}
|
|
|
|
return fragment;
|
|
}
|
|
|
|
getItemPosition(object: java.lang.Object): number {
|
|
return this.items ? POSITION_UNCHANGED : POSITION_NONE;
|
|
}
|
|
|
|
destroyItem(container: android.view.ViewGroup, position: number, object: java.lang.Object): void {
|
|
if (!this.mCurTransaction) {
|
|
const fragmentManager = this.owner._getFragmentManager();
|
|
this.mCurTransaction = fragmentManager.beginTransaction();
|
|
}
|
|
|
|
const fragment: androidx.fragment.app.Fragment = <androidx.fragment.app.Fragment>object;
|
|
this.mCurTransaction.detach(fragment);
|
|
|
|
if (this.mCurrentPrimaryItem === fragment) {
|
|
this.mCurrentPrimaryItem = null;
|
|
}
|
|
|
|
const tabItems = this.owner.items;
|
|
const tabItem = tabItems ? tabItems[position] : null;
|
|
if (tabItem) {
|
|
tabItem.canBeLoaded = false;
|
|
}
|
|
}
|
|
|
|
setPrimaryItem(container: android.view.ViewGroup, position: number, object: java.lang.Object): void {
|
|
const fragment = <androidx.fragment.app.Fragment>object;
|
|
if (fragment !== this.mCurrentPrimaryItem) {
|
|
if (this.mCurrentPrimaryItem != null) {
|
|
this.mCurrentPrimaryItem.setMenuVisibility(false);
|
|
this.mCurrentPrimaryItem.setUserVisibleHint(false);
|
|
}
|
|
|
|
if (fragment != null) {
|
|
fragment.setMenuVisibility(true);
|
|
fragment.setUserVisibleHint(true);
|
|
}
|
|
|
|
this.mCurrentPrimaryItem = fragment;
|
|
this.owner.selectedIndex = position;
|
|
|
|
const tab = this.owner;
|
|
const tabItems = tab.items;
|
|
const newTabItem = tabItems ? tabItems[position] : null;
|
|
|
|
if (newTabItem) {
|
|
tab._loadUnloadTabItems(tab.selectedIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
finishUpdate(container: android.view.ViewGroup): void {
|
|
this._commitCurrentTransaction();
|
|
}
|
|
|
|
isViewFromObject(view: android.view.View, object: java.lang.Object): boolean {
|
|
return (<androidx.fragment.app.Fragment>object).getView() === view;
|
|
}
|
|
|
|
saveState(): android.os.Parcelable {
|
|
// Commit the current transaction on save to prevent "No view found for id 0xa" exception on restore.
|
|
// Related to: https://github.com/NativeScript/NativeScript/issues/6466
|
|
this._commitCurrentTransaction();
|
|
|
|
return null;
|
|
}
|
|
|
|
restoreState(state: android.os.Parcelable, loader: java.lang.ClassLoader): void {
|
|
//
|
|
}
|
|
|
|
getItemId(position: number): number {
|
|
return position;
|
|
}
|
|
|
|
private _commitCurrentTransaction() {
|
|
if (this.mCurTransaction != null && !this.transactionRunning) {
|
|
this.transactionRunning = true;
|
|
this.mCurTransaction.commitNowAllowingStateLoss();
|
|
this.transactionRunning = false;
|
|
this.mCurTransaction = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
PagerAdapter = FragmentPagerAdapter;
|
|
appResources = Application.android.context.getResources();
|
|
}
|
|
|
|
function createTabItemSpec(item: TabViewItem): org.nativescript.widgets.TabItemSpec {
|
|
const result = new org.nativescript.widgets.TabItemSpec();
|
|
result.title = item.title;
|
|
|
|
if (item.iconSource) {
|
|
if (item.iconSource.indexOf(RESOURCE_PREFIX) === 0) {
|
|
result.iconId = ad.resources.getDrawableId(item.iconSource.substr(RESOURCE_PREFIX.length));
|
|
if (result.iconId === 0) {
|
|
traceMissingIcon(item.iconSource);
|
|
}
|
|
} else {
|
|
const is = ImageSource.fromFileOrResourceSync(item.iconSource);
|
|
if (is) {
|
|
// TODO: Make this native call that accepts string so that we don't load Bitmap in JS.
|
|
result.iconDrawable = new android.graphics.drawable.BitmapDrawable(appResources, is.android);
|
|
} else {
|
|
traceMissingIcon(item.iconSource);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
let defaultAccentColor: number = undefined;
|
|
function getDefaultAccentColor(context: android.content.Context): number {
|
|
if (defaultAccentColor === undefined) {
|
|
//Fallback color: https://developer.android.com/samples/SlidingTabsColors/src/com.example.android.common/view/SlidingTabStrip.html
|
|
defaultAccentColor = ad.resources.getPaletteColor(ACCENT_COLOR, context) || 0xff33b5e5;
|
|
}
|
|
|
|
return defaultAccentColor;
|
|
}
|
|
|
|
export class TabViewItem extends TabViewItemBase {
|
|
nativeViewProtected: android.widget.TextView;
|
|
public tabItemSpec: org.nativescript.widgets.TabItemSpec;
|
|
public index: number;
|
|
private _defaultTransformationMethod: android.text.method.TransformationMethod;
|
|
|
|
get _hasFragments(): boolean {
|
|
return true;
|
|
}
|
|
|
|
public initNativeView(): void {
|
|
super.initNativeView();
|
|
if (this.nativeViewProtected) {
|
|
this._defaultTransformationMethod = this.nativeViewProtected.getTransformationMethod();
|
|
}
|
|
}
|
|
|
|
public onLoaded(): void {
|
|
super.onLoaded();
|
|
}
|
|
|
|
public resetNativeView(): void {
|
|
super.resetNativeView();
|
|
if (this.nativeViewProtected) {
|
|
// We reset it here too because this could be changed by multiple properties - whiteSpace, secure, textTransform
|
|
this.nativeViewProtected.setTransformationMethod(this._defaultTransformationMethod);
|
|
}
|
|
}
|
|
|
|
public disposeNativeView(): void {
|
|
(<TabViewItemDefinition>this).canBeLoaded = false;
|
|
super.disposeNativeView();
|
|
}
|
|
|
|
public createNativeView() {
|
|
return this.nativeViewProtected;
|
|
}
|
|
|
|
public _update(): void {
|
|
const tv = this.nativeViewProtected;
|
|
const tabView = this.parent as TabView;
|
|
if (tv && tabView) {
|
|
this.tabItemSpec = createTabItemSpec(this);
|
|
tabView.updateAndroidItemAt(this.index, this.tabItemSpec);
|
|
}
|
|
}
|
|
|
|
public _getChildFragmentManager(): androidx.fragment.app.FragmentManager {
|
|
const tabView = this.parent as TabView;
|
|
let tabFragment = null;
|
|
const fragmentManager = tabView._getFragmentManager();
|
|
const fragments = fragmentManager.getFragments().toArray();
|
|
for (let i = 0; i < fragments.length; i++) {
|
|
if (fragments[i].index === this.index) {
|
|
tabFragment = fragments[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
// TODO: can happen in a modal tabview scenario when the modal dialog fragment is already removed
|
|
if (!tabFragment) {
|
|
if (Trace.isEnabled()) {
|
|
Trace.write(`Could not get child fragment manager for tab item with index ${this.index}`, traceCategory);
|
|
}
|
|
|
|
return (<any>tabView)._getRootFragmentManager();
|
|
}
|
|
|
|
return tabFragment.getChildFragmentManager();
|
|
}
|
|
|
|
[fontSizeProperty.getDefault](): { nativeSize: number } {
|
|
return { nativeSize: this.nativeViewProtected.getTextSize() };
|
|
}
|
|
[fontSizeProperty.setNative](value: number | { nativeSize: number }) {
|
|
if (typeof value === 'number') {
|
|
this.nativeViewProtected.setTextSize(value);
|
|
} else {
|
|
this.nativeViewProtected.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, value.nativeSize);
|
|
}
|
|
}
|
|
|
|
[fontInternalProperty.getDefault](): android.graphics.Typeface {
|
|
return this.nativeViewProtected.getTypeface();
|
|
}
|
|
[fontInternalProperty.setNative](value: Font | android.graphics.Typeface) {
|
|
this.nativeViewProtected.setTypeface(value instanceof Font ? value.getAndroidTypeface() : value);
|
|
}
|
|
|
|
[textTransformProperty.getDefault](): 'default' {
|
|
return 'default';
|
|
}
|
|
[textTransformProperty.setNative](value: CoreTypes.TextTransformType | 'default') {
|
|
const tv = this.nativeViewProtected;
|
|
if (value === 'default') {
|
|
tv.setTransformationMethod(this._defaultTransformationMethod);
|
|
tv.setText(this.title);
|
|
} else {
|
|
const result = getTransformedText(this.title, value);
|
|
tv.setText(result);
|
|
tv.setTransformationMethod(null);
|
|
}
|
|
}
|
|
}
|
|
|
|
function setElevation(grid: org.nativescript.widgets.GridLayout, tabLayout: org.nativescript.widgets.TabLayout) {
|
|
const compat = <any>androidx.core.view.ViewCompat;
|
|
if (compat.setElevation) {
|
|
const val = DEFAULT_ELEVATION * layout.getDisplayDensity();
|
|
compat.setElevation(grid, val);
|
|
compat.setElevation(tabLayout, val);
|
|
}
|
|
}
|
|
|
|
export const tabs = new Array<WeakRef<TabView>>();
|
|
|
|
function iterateIndexRange(index: number, eps: number, lastIndex: number, callback: (i) => void) {
|
|
const rangeStart = Math.max(0, index - eps);
|
|
const rangeEnd = Math.min(index + eps, lastIndex);
|
|
for (let i = rangeStart; i <= rangeEnd; i++) {
|
|
callback(i);
|
|
}
|
|
}
|
|
|
|
export class TabView extends TabViewBase {
|
|
private _tabLayout: org.nativescript.widgets.TabLayout;
|
|
private _viewPager: androidx.viewpager.widget.ViewPager;
|
|
private _pagerAdapter: androidx.viewpager.widget.PagerAdapter;
|
|
private _androidViewId = -1;
|
|
public _originalBackground: any;
|
|
|
|
constructor() {
|
|
super();
|
|
tabs.push(new WeakRef(this));
|
|
}
|
|
|
|
get _hasFragments(): boolean {
|
|
return true;
|
|
}
|
|
|
|
public onItemsChanged(oldItems: TabViewItem[], newItems: TabViewItem[]): void {
|
|
super.onItemsChanged(oldItems, newItems);
|
|
|
|
if (oldItems) {
|
|
oldItems.forEach((item: TabViewItem, i, arr) => {
|
|
item.index = 0;
|
|
item.tabItemSpec = null;
|
|
item.setNativeView(null);
|
|
});
|
|
}
|
|
}
|
|
|
|
public createNativeView() {
|
|
initializeNativeClasses();
|
|
if (Trace.isEnabled()) {
|
|
Trace.write('TabView._createUI(' + this + ');', traceCategory);
|
|
}
|
|
|
|
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 org.nativescript.widgets.TabLayout(context);
|
|
const lp = new org.nativescript.widgets.CommonLayoutParams();
|
|
const primaryColor = ad.resources.getPaletteColor(PRIMARY_COLOR, context);
|
|
let accentColor = getDefaultAccentColor(context);
|
|
|
|
lp.row = 1;
|
|
|
|
if (this.androidTabsPosition === 'top') {
|
|
nativeView.addRowsFromJSON(
|
|
JSON.stringify([
|
|
{ value: 1, type: 0 /* org.nativescript.widgets.GridUnitType.auto */ },
|
|
{ value: 1, type: 2 /* org.nativescript.widgets.GridUnitType.star */ },
|
|
])
|
|
);
|
|
viewPager.setLayoutParams(lp);
|
|
|
|
if (!this.androidSwipeEnabled) {
|
|
viewPager.setSwipePageEnabled(false);
|
|
}
|
|
} else {
|
|
nativeView.addRowsFromJSON(
|
|
JSON.stringify([
|
|
{ value: 1, type: 2 /* org.nativescript.widgets.GridUnitType.star */ },
|
|
{ value: 1, type: 0 /* org.nativescript.widgets.GridUnitType.auto */ },
|
|
])
|
|
);
|
|
tabLayout.setLayoutParams(lp);
|
|
viewPager.setSwipePageEnabled(false);
|
|
// set completely transparent accent color for tab selected indicator.
|
|
accentColor = 0x00ffffff;
|
|
}
|
|
|
|
nativeView.addView(viewPager);
|
|
(<any>nativeView).viewPager = viewPager;
|
|
|
|
const adapter = new PagerAdapter(this);
|
|
viewPager.setAdapter(adapter);
|
|
(<any>viewPager).adapter = adapter;
|
|
|
|
nativeView.addView(tabLayout);
|
|
(<any>nativeView).tabLayout = tabLayout;
|
|
|
|
setElevation(nativeView, tabLayout);
|
|
|
|
if (accentColor) {
|
|
tabLayout.setSelectedIndicatorColors([accentColor]);
|
|
}
|
|
|
|
if (primaryColor) {
|
|
tabLayout.setBackgroundColor(primaryColor);
|
|
}
|
|
|
|
return nativeView;
|
|
}
|
|
|
|
public initNativeView(): void {
|
|
super.initNativeView();
|
|
if (this._androidViewId < 0) {
|
|
this._androidViewId = android.view.View.generateViewId();
|
|
}
|
|
|
|
const nativeView: any = this.nativeViewProtected;
|
|
this._tabLayout = (<any>nativeView).tabLayout;
|
|
|
|
const viewPager = (<any>nativeView).viewPager;
|
|
viewPager.setId(this._androidViewId);
|
|
this._viewPager = viewPager;
|
|
this._pagerAdapter = (<any>viewPager).adapter;
|
|
(<any>this._pagerAdapter).owner = this;
|
|
}
|
|
|
|
public _loadUnloadTabItems(newIndex: number) {
|
|
const items = this.items;
|
|
if (!items) {
|
|
return;
|
|
}
|
|
|
|
const lastIndex = items.length - 1;
|
|
const offsideItems = this.androidTabsPosition === 'top' ? this.androidOffscreenTabLimit : 1;
|
|
|
|
const toUnload = [];
|
|
const toLoad = [];
|
|
|
|
iterateIndexRange(newIndex, offsideItems, lastIndex, (i) => toLoad.push(i));
|
|
|
|
items.forEach((item, i) => {
|
|
const indexOfI = toLoad.indexOf(i);
|
|
if (indexOfI < 0) {
|
|
toUnload.push(i);
|
|
}
|
|
});
|
|
|
|
toUnload.forEach((index) => {
|
|
const item = items[index];
|
|
if (items[index]) {
|
|
item.unloadView(item.view);
|
|
}
|
|
});
|
|
|
|
const newItem = items[newIndex];
|
|
const selectedView = newItem && newItem.view;
|
|
if (selectedView instanceof Frame) {
|
|
selectedView._pushInFrameStackRecursive();
|
|
}
|
|
|
|
toLoad.forEach((index) => {
|
|
const item = items[index];
|
|
if (this.isLoaded && items[index]) {
|
|
item.loadView(item.view);
|
|
}
|
|
});
|
|
}
|
|
|
|
public onLoaded(): void {
|
|
super.onLoaded();
|
|
|
|
if (this._originalBackground) {
|
|
this.backgroundColor = null;
|
|
this.backgroundColor = this._originalBackground;
|
|
this._originalBackground = null;
|
|
}
|
|
|
|
this.setAdapterItems(this.items);
|
|
}
|
|
|
|
public onUnloaded(): void {
|
|
super.onUnloaded();
|
|
|
|
this.setAdapterItems(null);
|
|
}
|
|
|
|
public disposeNativeView() {
|
|
this._tabLayout.setItems(null, null);
|
|
(<any>this._pagerAdapter).owner = null;
|
|
this._pagerAdapter = null;
|
|
|
|
this._tabLayout = null;
|
|
this._viewPager = null;
|
|
super.disposeNativeView();
|
|
}
|
|
|
|
public _onRootViewReset(): void {
|
|
super._onRootViewReset();
|
|
|
|
// call this AFTER the super call to ensure descendants apply their rootview-reset logic first
|
|
// i.e. in a scenario with tab frames let the frames cleanup their fragments first, and then
|
|
// cleanup the tab fragments to avoid
|
|
// android.content.res.Resources$NotFoundException: Unable to find resource ID #0xfffffff6
|
|
this.disposeCurrentFragments();
|
|
}
|
|
|
|
private disposeCurrentFragments(): void {
|
|
const fragmentManager = this._getFragmentManager();
|
|
const transaction = fragmentManager.beginTransaction();
|
|
const fragments = <Array<any>>fragmentManager.getFragments().toArray();
|
|
for (let i = 0; i < fragments.length; i++) {
|
|
transaction.remove(fragments[i]);
|
|
}
|
|
transaction.commitNowAllowingStateLoss();
|
|
}
|
|
|
|
private shouldUpdateAdapter(items: Array<TabViewItemDefinition>) {
|
|
if (!this._pagerAdapter) {
|
|
return false;
|
|
}
|
|
|
|
const currentPagerAdapterItems = (<any>this._pagerAdapter).items;
|
|
|
|
// if both values are null, should not update
|
|
if (!items && !currentPagerAdapterItems) {
|
|
return false;
|
|
}
|
|
|
|
// if one value is null, should update
|
|
if (!items || !currentPagerAdapterItems) {
|
|
return true;
|
|
}
|
|
|
|
// if both are Arrays but length doesn't match, should update
|
|
if (items.length !== currentPagerAdapterItems.length) {
|
|
return true;
|
|
}
|
|
|
|
const matchingItems = currentPagerAdapterItems.filter((currentItem) => {
|
|
return !!items.filter((item) => {
|
|
return item._domId === currentItem._domId;
|
|
})[0];
|
|
});
|
|
|
|
// if both are Arrays and length matches, but not all items are the same, should update
|
|
if (matchingItems.length !== items.length) {
|
|
return true;
|
|
}
|
|
|
|
// if both are Arrays and length matches and all items are the same, should not update
|
|
return false;
|
|
}
|
|
|
|
private setAdapterItems(items: Array<TabViewItemDefinition>) {
|
|
if (this.shouldUpdateAdapter(items)) {
|
|
(<any>this._pagerAdapter).items = items;
|
|
|
|
const length = items ? items.length : 0;
|
|
if (length === 0) {
|
|
this._tabLayout.setItems(null, null);
|
|
this._pagerAdapter.notifyDataSetChanged();
|
|
|
|
return;
|
|
}
|
|
|
|
const tabItems = new Array<org.nativescript.widgets.TabItemSpec>();
|
|
items.forEach((item: TabViewItem, i, arr) => {
|
|
const tabItemSpec = createTabItemSpec(item);
|
|
item.index = i;
|
|
item.tabItemSpec = tabItemSpec;
|
|
tabItems.push(tabItemSpec);
|
|
});
|
|
|
|
const tabLayout = this._tabLayout;
|
|
tabLayout.setItems(tabItems, this._viewPager);
|
|
items.forEach((item, i, arr) => {
|
|
const tv = tabLayout.getTextViewForItemAt(i);
|
|
item.setNativeView(tv);
|
|
});
|
|
|
|
this._pagerAdapter.notifyDataSetChanged();
|
|
}
|
|
}
|
|
|
|
private getNativeRenderingMode(mode: 'alwaysOriginal' | 'alwaysTemplate'): number {
|
|
switch (mode) {
|
|
case 'alwaysTemplate':
|
|
return org.nativescript.widgets.TabIconRenderingMode.template;
|
|
default:
|
|
case 'alwaysOriginal':
|
|
return org.nativescript.widgets.TabIconRenderingMode.original;
|
|
}
|
|
}
|
|
|
|
public updateAndroidItemAt(index: number, spec: org.nativescript.widgets.TabItemSpec) {
|
|
this._tabLayout.updateItemAt(index, spec);
|
|
}
|
|
|
|
[androidOffscreenTabLimitProperty.getDefault](): number {
|
|
return this._viewPager.getOffscreenPageLimit();
|
|
}
|
|
[androidOffscreenTabLimitProperty.setNative](value: number) {
|
|
this._viewPager.setOffscreenPageLimit(value);
|
|
}
|
|
|
|
[androidIconRenderingModeProperty.getDefault](): 'alwaysOriginal' | 'alwaysTemplate' {
|
|
return 'alwaysOriginal';
|
|
}
|
|
[androidIconRenderingModeProperty.setNative](value: 'alwaysOriginal' | 'alwaysTemplate') {
|
|
this._tabLayout.setIconRenderingMode(this.getNativeRenderingMode(value));
|
|
}
|
|
|
|
[selectedIndexProperty.setNative](value: number) {
|
|
const smoothScroll = this.androidTabsPosition === 'top';
|
|
|
|
if (Trace.isEnabled()) {
|
|
Trace.write('TabView this._viewPager.setCurrentItem(' + value + ', ' + smoothScroll + ');', traceCategory);
|
|
}
|
|
|
|
this._viewPager.setCurrentItem(value, smoothScroll);
|
|
}
|
|
|
|
[itemsProperty.getDefault](): TabViewItem[] {
|
|
return null;
|
|
}
|
|
[itemsProperty.setNative](value: TabViewItem[]) {
|
|
this.setAdapterItems(value);
|
|
selectedIndexProperty.coerce(this);
|
|
}
|
|
|
|
[tabBackgroundColorProperty.getDefault](): android.graphics.drawable.Drawable {
|
|
return this._tabLayout.getBackground();
|
|
}
|
|
[tabBackgroundColorProperty.setNative](value: android.graphics.drawable.Drawable | Color) {
|
|
if (value instanceof Color) {
|
|
this._tabLayout.setBackgroundColor(value.android);
|
|
} else {
|
|
this._tabLayout.setBackground(AndroidHelper.getCopyOrDrawable(value, this.nativeViewProtected.getResources()));
|
|
}
|
|
}
|
|
|
|
[tabTextFontSizeProperty.getDefault](): number {
|
|
return this._tabLayout.getTabTextFontSize();
|
|
}
|
|
[tabTextFontSizeProperty.setNative](value: number | { nativeSize: number }) {
|
|
if (typeof value === 'number') {
|
|
this._tabLayout.setTabTextFontSize(value);
|
|
} else {
|
|
this._tabLayout.setTabTextFontSize(value.nativeSize);
|
|
}
|
|
}
|
|
|
|
[tabTextColorProperty.getDefault](): number {
|
|
return this._tabLayout.getTabTextColor();
|
|
}
|
|
[tabTextColorProperty.setNative](value: number | Color) {
|
|
const color = value instanceof Color ? value.android : value;
|
|
this._tabLayout.setTabTextColor(color);
|
|
}
|
|
|
|
[selectedTabTextColorProperty.getDefault](): number {
|
|
return this._tabLayout.getSelectedTabTextColor();
|
|
}
|
|
[selectedTabTextColorProperty.setNative](value: number | Color) {
|
|
const color = value instanceof Color ? value.android : value;
|
|
this._tabLayout.setSelectedTabTextColor(color);
|
|
}
|
|
|
|
[androidSelectedTabHighlightColorProperty.getDefault](): number {
|
|
return getDefaultAccentColor(this._context);
|
|
}
|
|
[androidSelectedTabHighlightColorProperty.setNative](value: number | Color) {
|
|
const tabLayout = this._tabLayout;
|
|
const color = value instanceof Color ? value.android : value;
|
|
tabLayout.setSelectedIndicatorColors([color]);
|
|
}
|
|
}
|