mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-26 03:01:51 +08:00
feat(bottom-navigation-android): add tabstripitem css support (#7458)
* wip: add background color placeholders for tabstripitem * feat: add css for tabstripitem for bottom navigation android * chore: update example * fix: revert native default index * clean up tabcontentitem * update setTabBarItemTextTransform * textTransform inherited css property now * fix(android-bottom-navigation): fragment detach logic * chore: fix tests * fix(android-bottom-navigation): fragment lifecycle logic * fix: revert text-transform inherited css property
This commit is contained in:

committed by
Manol Donev

parent
6e1e0e843a
commit
fab9c90007
@ -2,11 +2,14 @@
|
||||
import { TabStrip } from "../tab-navigation-base/tab-strip";
|
||||
import { TabStripItem } from "../tab-navigation-base/tab-strip-item";
|
||||
import { TabContentItem } from "../tab-navigation-base/tab-content-item";
|
||||
import { TextTransform } from "../text-base";
|
||||
|
||||
// Requires
|
||||
import { TabNavigationBase, itemsProperty, selectedIndexProperty, tabStripProperty } from "../tab-navigation-base/tab-navigation-base";
|
||||
import { Font } from "../styling/font";
|
||||
import { getTransformedText } from "../text-base";
|
||||
import { CSSType, Color } from "../core/view";
|
||||
import { Frame } from "../frame";
|
||||
import { Frame, View } from "../frame";
|
||||
import { RESOURCE_PREFIX, ad, layout } from "../../utils/utils";
|
||||
import { fromFileOrResource } from "../../image-source";
|
||||
// TODO: Impl trace
|
||||
@ -22,8 +25,11 @@ const DEFAULT_ELEVATION = 8;
|
||||
|
||||
const TABID = "_tabId";
|
||||
const INDEX = "_index";
|
||||
const ownerSymbol = Symbol("_owner");
|
||||
|
||||
let TabFragment: any;
|
||||
let BottomNavigationBar: any;
|
||||
let AttachStateChangeListener: any;
|
||||
|
||||
function makeFragmentName(viewId: number, id: number): string {
|
||||
return "android:bottomnavigation:" + viewId + ":" + id;
|
||||
@ -89,20 +95,77 @@ function initializeNativeClasses() {
|
||||
return global.__native(this);
|
||||
}
|
||||
|
||||
public onSelectedPositionChange(position: number): void {
|
||||
this.owner.changeTab(position);
|
||||
this.owner.selectedIndex = position;
|
||||
public onSelectedPositionChange(position: number, prevPosition: number): void {
|
||||
const owner = this.owner;
|
||||
if (!owner) {
|
||||
return;
|
||||
}
|
||||
|
||||
owner.changeTab(position);
|
||||
|
||||
const tabStripItems = owner.tabStrip && owner.tabStrip.items;
|
||||
|
||||
if (position >= 0 && tabStripItems && tabStripItems[position]) {
|
||||
tabStripItems[position]._emit(TabStripItem.selectEvent);
|
||||
}
|
||||
|
||||
if (prevPosition >= 0 && tabStripItems && tabStripItems[prevPosition]) {
|
||||
tabStripItems[prevPosition]._emit(TabStripItem.unselectEvent);
|
||||
}
|
||||
|
||||
owner.selectedIndex = position;
|
||||
}
|
||||
|
||||
public onTap(position: number): void {
|
||||
const owner = this.owner;
|
||||
if (!owner) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tabStripItems = owner.tabStrip && owner.tabStrip.items;
|
||||
|
||||
if (position >= 0 && tabStripItems[position]) {
|
||||
tabStripItems[position]._emit(TabStripItem.tapEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Interfaces([android.view.View.OnAttachStateChangeListener])
|
||||
class AttachListener extends java.lang.Object implements android.view.View.OnAttachStateChangeListener {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
return global.__native(this);
|
||||
}
|
||||
|
||||
onViewAttachedToWindow(view: android.view.View): void {
|
||||
const owner: View = view[ownerSymbol];
|
||||
if (owner) {
|
||||
owner._onAttachedToWindow();
|
||||
}
|
||||
}
|
||||
|
||||
onViewDetachedFromWindow(view: android.view.View): void {
|
||||
const owner: View = view[ownerSymbol];
|
||||
if (owner) {
|
||||
owner._onDetachedFromWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TabFragment = TabFragmentImplementation;
|
||||
BottomNavigationBar = BottomNavigationBarImplementation;
|
||||
AttachStateChangeListener = new AttachListener();
|
||||
}
|
||||
|
||||
function createTabItemSpec(tabStripItem: TabStripItem): org.nativescript.widgets.TabItemSpec {
|
||||
const result = new org.nativescript.widgets.TabItemSpec();
|
||||
result.title = tabStripItem.title;
|
||||
|
||||
if (tabStripItem.backgroundColor instanceof Color) {
|
||||
result.backgroundColor = tabStripItem.backgroundColor.android;
|
||||
}
|
||||
|
||||
if (tabStripItem.iconSource) {
|
||||
if (tabStripItem.iconSource.indexOf(RESOURCE_PREFIX) === 0) {
|
||||
result.iconId = ad.resources.getDrawableId(tabStripItem.iconSource.substr(RESOURCE_PREFIX.length));
|
||||
@ -151,6 +214,8 @@ export class BottomNavigation extends TabNavigationBase {
|
||||
private _contentViewId: number = -1;
|
||||
private _bottomNavigationBar: org.nativescript.widgets.BottomNavigationBar;
|
||||
private _currentFragment: androidx.fragment.app.Fragment;
|
||||
private _currentTransaction: androidx.fragment.app.FragmentTransaction;
|
||||
private _attachedToWindow = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@ -187,17 +252,17 @@ export class BottomNavigation extends TabNavigationBase {
|
||||
|
||||
// CONTENT VIEW
|
||||
const contentView = new org.nativescript.widgets.ContentLayout(this._context);
|
||||
const contentViewLP = new org.nativescript.widgets.CommonLayoutParams();
|
||||
contentViewLP.row = 0;
|
||||
contentView.setLayoutParams(contentViewLP);
|
||||
const contentViewLayoutParams = new org.nativescript.widgets.CommonLayoutParams();
|
||||
contentViewLayoutParams.row = 0;
|
||||
contentView.setLayoutParams(contentViewLayoutParams);
|
||||
nativeView.addView(contentView);
|
||||
(<any>nativeView).contentView = contentView;
|
||||
|
||||
// TABSTRIP
|
||||
const bottomNavigationBar = new BottomNavigationBar(context, this);
|
||||
const bottomNavigationBarLP = new org.nativescript.widgets.CommonLayoutParams();
|
||||
bottomNavigationBarLP.row = 1;
|
||||
bottomNavigationBar.setLayoutParams(bottomNavigationBarLP);
|
||||
const bottomNavigationBarLayoutParams = new org.nativescript.widgets.CommonLayoutParams();
|
||||
bottomNavigationBarLayoutParams.row = 1;
|
||||
bottomNavigationBar.setLayoutParams(bottomNavigationBarLayoutParams);
|
||||
nativeView.addView(bottomNavigationBar);
|
||||
(<any>nativeView).bottomNavigationBar = bottomNavigationBar;
|
||||
|
||||
@ -213,15 +278,25 @@ export class BottomNavigation extends TabNavigationBase {
|
||||
|
||||
public initNativeView(): void {
|
||||
super.initNativeView();
|
||||
|
||||
if (this._contentViewId < 0) {
|
||||
this._contentViewId = android.view.View.generateViewId();
|
||||
}
|
||||
|
||||
const nativeView: any = this.nativeViewProtected;
|
||||
|
||||
nativeView.addOnAttachStateChangeListener(AttachStateChangeListener);
|
||||
nativeView[ownerSymbol] = this;
|
||||
|
||||
this._contentView = (<any>nativeView).contentView;
|
||||
this._contentView.setId(this._contentViewId);
|
||||
|
||||
this._bottomNavigationBar = (<any>nativeView).bottomNavigationBar;
|
||||
(<any>this._bottomNavigationBar).owner = this;
|
||||
|
||||
if (this.tabStrip) {
|
||||
this.tabStrip.setNativeView(this._bottomNavigationBar);
|
||||
}
|
||||
}
|
||||
|
||||
public _loadUnloadTabItems(newIndex: number) {
|
||||
@ -265,19 +340,46 @@ export class BottomNavigation extends TabNavigationBase {
|
||||
public onLoaded(): void {
|
||||
super.onLoaded();
|
||||
|
||||
this.setTabStripItems();
|
||||
const items = this.tabStrip ? this.tabStrip.items : null;
|
||||
this.setTabStripItems(items);
|
||||
|
||||
if (this._attachedToWindow) {
|
||||
this.changeTab(this.selectedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
_onAttachedToWindow(): void {
|
||||
super._onAttachedToWindow();
|
||||
|
||||
this._attachedToWindow = true;
|
||||
this.changeTab(this.selectedIndex);
|
||||
}
|
||||
|
||||
_onDetachedFromWindow(): void {
|
||||
super._onDetachedFromWindow();
|
||||
|
||||
this._attachedToWindow = false;
|
||||
}
|
||||
|
||||
public onUnloaded(): void {
|
||||
super.onUnloaded();
|
||||
|
||||
this.setTabStripItems();
|
||||
this.setTabStripItems(null);
|
||||
|
||||
const fragmentToDetach = this._currentFragment;
|
||||
if (fragmentToDetach) {
|
||||
this.destroyItem((<any>fragmentToDetach).index, fragmentToDetach);
|
||||
this.commitCurrentTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public disposeNativeView() {
|
||||
this._bottomNavigationBar.setItems(null);
|
||||
this._bottomNavigationBar = null;
|
||||
|
||||
this.nativeViewProtected.removeOnAttachStateChangeListener(AttachStateChangeListener);
|
||||
this.nativeViewProtected[ownerSymbol] = null;
|
||||
|
||||
super.disposeNativeView();
|
||||
}
|
||||
|
||||
@ -288,78 +390,132 @@ export class BottomNavigation extends TabNavigationBase {
|
||||
// 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();
|
||||
this.disposeTabFragments();
|
||||
}
|
||||
|
||||
private disposeCurrentFragments(): void {
|
||||
private disposeTabFragments(): void {
|
||||
const fragmentManager = this._getFragmentManager();
|
||||
const transaction = fragmentManager.beginTransaction();
|
||||
for (let fragment of (<Array<any>>fragmentManager.getFragments().toArray())) {
|
||||
transaction.remove(fragment);
|
||||
}
|
||||
|
||||
transaction.commitNowAllowingStateLoss();
|
||||
}
|
||||
|
||||
private get currentTransaction(): androidx.fragment.app.FragmentTransaction {
|
||||
if (!this._currentTransaction) {
|
||||
const fragmentManager = this._getFragmentManager();
|
||||
this._currentTransaction = fragmentManager.beginTransaction();
|
||||
}
|
||||
|
||||
return this._currentTransaction;
|
||||
}
|
||||
|
||||
private commitCurrentTransaction(): void {
|
||||
if (this._currentTransaction) {
|
||||
this._currentTransaction.commitNowAllowingStateLoss();
|
||||
this._currentTransaction = null;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Should we extract adapter-like class?
|
||||
// TODO: Rename this?
|
||||
public changeTab(index: number) {
|
||||
// this is the case when there are no items
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const containerView = this._contentView;
|
||||
const fragmentToDetach = this._currentFragment;
|
||||
if (fragmentToDetach) {
|
||||
this.destroyItem((<any>fragmentToDetach).index, fragmentToDetach);
|
||||
}
|
||||
|
||||
const fragment = this.instantiateItem(this._contentView, index);
|
||||
this.setPrimaryItem(index, fragment);
|
||||
|
||||
this.commitCurrentTransaction();
|
||||
}
|
||||
|
||||
private instantiateItem(container: android.view.ViewGroup, position: number): androidx.fragment.app.Fragment {
|
||||
const name = makeFragmentName(container.getId(), position);
|
||||
|
||||
const fragmentManager = this._getFragmentManager();
|
||||
const transaction = fragmentManager.beginTransaction();
|
||||
let fragment: androidx.fragment.app.Fragment = fragmentManager.findFragmentByTag(name);
|
||||
if (fragment != null) {
|
||||
this.currentTransaction.attach(fragment);
|
||||
} else {
|
||||
fragment = TabFragment.newInstance(this._domId, position);
|
||||
this.currentTransaction.add(container.getId(), fragment, name);
|
||||
}
|
||||
|
||||
if (this._currentFragment) {
|
||||
const fragment = this._currentFragment;
|
||||
transaction.detach(fragment);
|
||||
if (fragment !== this._currentFragment) {
|
||||
fragment.setMenuVisibility(false);
|
||||
fragment.setUserVisibleHint(false);
|
||||
}
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
private setPrimaryItem(position: number, fragment: androidx.fragment.app.Fragment): void {
|
||||
if (fragment !== this._currentFragment) {
|
||||
if (this._currentFragment != null) {
|
||||
this._currentFragment.setMenuVisibility(false);
|
||||
this._currentFragment.setUserVisibleHint(false);
|
||||
}
|
||||
|
||||
if (fragment != null) {
|
||||
fragment.setMenuVisibility(true);
|
||||
fragment.setUserVisibleHint(true);
|
||||
}
|
||||
|
||||
this._currentFragment = fragment;
|
||||
|
||||
const tabItems = this.items;
|
||||
const tabItem = tabItems ? tabItems[position] : null;
|
||||
if (tabItem) {
|
||||
tabItem.canBeLoaded = true;
|
||||
this._loadUnloadTabItems(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private destroyItem(position: number, fragment: androidx.fragment.app.Fragment): void {
|
||||
if (fragment) {
|
||||
this.currentTransaction.detach(fragment);
|
||||
if (this._currentFragment === fragment) {
|
||||
this._currentFragment = null;
|
||||
}
|
||||
}
|
||||
|
||||
const name = makeFragmentName(containerView.getId(), index);
|
||||
|
||||
let fragment: androidx.fragment.app.Fragment = fragmentManager.findFragmentByTag(name);
|
||||
if (fragment != null) {
|
||||
transaction.attach(fragment);
|
||||
} else {
|
||||
fragment = TabFragment.newInstance(this._domId, index);
|
||||
transaction.add(containerView.getId(), fragment, name);
|
||||
if (this.items && this.items[position]) {
|
||||
this.items[position].canBeLoaded = false;
|
||||
}
|
||||
|
||||
this._currentFragment = fragment;
|
||||
|
||||
const tabItems = this.items;
|
||||
const tabItem = tabItems ? tabItems[index] : null;
|
||||
if (tabItem) {
|
||||
tabItem.canBeLoaded = true;
|
||||
this._loadUnloadTabItems(index);
|
||||
}
|
||||
|
||||
transaction.commitNowAllowingStateLoss();
|
||||
}
|
||||
|
||||
private setTabStripItems() {
|
||||
if (this.tabStrip && this.tabStrip.items) {
|
||||
const tabItems = new Array<org.nativescript.widgets.TabItemSpec>();
|
||||
this.tabStrip.items.forEach((item, i, arr) => {
|
||||
if (this.tabStrip.items[i]) {
|
||||
const tabItemSpec = createTabItemSpec(this.tabStrip.items[i]);
|
||||
tabItems.push(tabItemSpec);
|
||||
}
|
||||
});
|
||||
|
||||
this._bottomNavigationBar.setItems(tabItems);
|
||||
this.tabStrip.setNativeView(this._bottomNavigationBar);
|
||||
this.tabStrip.items.forEach((item, i, arr) => {
|
||||
const tv = this._bottomNavigationBar.getTextViewForItemAt(i);
|
||||
item.setNativeView(tv);
|
||||
});
|
||||
} else {
|
||||
private setTabStripItems(items: Array<TabStripItem>) {
|
||||
if (!this.tabStrip || !items) {
|
||||
this._bottomNavigationBar.setItems(null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const tabItems = new Array<org.nativescript.widgets.TabItemSpec>();
|
||||
items.forEach((item, i, arr) => {
|
||||
(<any>item).index = i;
|
||||
if (items[i]) {
|
||||
const tabItemSpec = createTabItemSpec(items[i]);
|
||||
tabItems.push(tabItemSpec);
|
||||
}
|
||||
});
|
||||
|
||||
this._bottomNavigationBar.setItems(tabItems);
|
||||
|
||||
items.forEach((item, i, arr) => {
|
||||
const textView = this._bottomNavigationBar.getTextViewForItemAt(i);
|
||||
item.setNativeView(textView);
|
||||
});
|
||||
}
|
||||
|
||||
public updateAndroidItemAt(index: number, spec: org.nativescript.widgets.TabItemSpec) {
|
||||
@ -378,6 +534,80 @@ export class BottomNavigation extends TabNavigationBase {
|
||||
}
|
||||
}
|
||||
|
||||
public getTabBarColor(): number {
|
||||
return this._bottomNavigationBar.getTabTextColor();
|
||||
}
|
||||
|
||||
public setTabBarColor(value: number | Color): void {
|
||||
if (value instanceof Color) {
|
||||
this._bottomNavigationBar.setTabTextColor(value.android);
|
||||
this._bottomNavigationBar.setSelectedTabTextColor(value.android);
|
||||
} else {
|
||||
this._bottomNavigationBar.setTabTextColor(value);
|
||||
this._bottomNavigationBar.setSelectedTabTextColor(value);
|
||||
}
|
||||
}
|
||||
|
||||
public setTabBarItemBackgroundColor(tabStripItem: TabStripItem, value: android.graphics.drawable.Drawable | Color): void {
|
||||
// TODO: Should figure out a way to do it directly with the the nativeView
|
||||
const tabStripItemIndex = this.tabStrip.items.indexOf(tabStripItem);
|
||||
const tabItemSpec = createTabItemSpec(tabStripItem);
|
||||
this.updateAndroidItemAt(tabStripItemIndex, tabItemSpec);
|
||||
}
|
||||
|
||||
public getTabBarItemColor(tabStripItem: TabStripItem): number {
|
||||
return tabStripItem.nativeViewProtected.getCurrentTextColor();
|
||||
}
|
||||
|
||||
public setTabBarItemColor(tabStripItem: TabStripItem, value: number | Color): void {
|
||||
if (typeof value === "number") {
|
||||
tabStripItem.nativeViewProtected.setTextColor(value);
|
||||
} else {
|
||||
tabStripItem.nativeViewProtected.setTextColor(value.android);
|
||||
}
|
||||
}
|
||||
|
||||
public getTabBarItemFontSize(tabStripItem: TabStripItem): { nativeSize: number } {
|
||||
return { nativeSize: tabStripItem.nativeViewProtected.getTextSize() };
|
||||
}
|
||||
|
||||
public setTabBarItemFontSize(tabStripItem: TabStripItem, value: number | { nativeSize: number }): void {
|
||||
if (typeof value === "number") {
|
||||
tabStripItem.nativeViewProtected.setTextSize(value);
|
||||
} else {
|
||||
tabStripItem.nativeViewProtected.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, value.nativeSize);
|
||||
}
|
||||
}
|
||||
|
||||
public getTabBarItemFontInternal(tabStripItem: TabStripItem): android.graphics.Typeface {
|
||||
return tabStripItem.nativeViewProtected.getTypeface();
|
||||
}
|
||||
|
||||
public setTabBarItemFontInternal(tabStripItem: TabStripItem, value: Font | android.graphics.Typeface): void {
|
||||
tabStripItem.nativeViewProtected.setTypeface(value instanceof Font ? value.getAndroidTypeface() : value);
|
||||
}
|
||||
|
||||
private _defaultTransformationMethod: android.text.method.TransformationMethod;
|
||||
|
||||
public getTabBarItemTextTransform(tabStripItem: TabStripItem): "default" {
|
||||
return "default";
|
||||
}
|
||||
|
||||
public setTabBarItemTextTransform(tabStripItem: TabStripItem, value: TextTransform | "default"): void {
|
||||
const tv = tabStripItem.nativeViewProtected;
|
||||
|
||||
this._defaultTransformationMethod = this._defaultTransformationMethod || tv.getTransformationMethod();
|
||||
|
||||
if (value === "default") {
|
||||
tv.setTransformationMethod(this._defaultTransformationMethod);
|
||||
tv.setText(tabStripItem.title);
|
||||
} else {
|
||||
const result = getTransformedText(tabStripItem.title, value);
|
||||
tv.setText(result);
|
||||
tv.setTransformationMethod(null);
|
||||
}
|
||||
}
|
||||
|
||||
[selectedIndexProperty.setNative](value: number) {
|
||||
// const smoothScroll = false;
|
||||
|
||||
@ -405,7 +635,8 @@ export class BottomNavigation extends TabNavigationBase {
|
||||
return null;
|
||||
}
|
||||
[tabStripProperty.setNative](value: TabStrip) {
|
||||
this.setTabStripItems();
|
||||
const items = this.tabStrip ? this.tabStrip.items : null;
|
||||
this.setTabStripItems(items);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user