chore: addimg tabbable component android

This commit is contained in:
Vasko
2020-03-31 20:51:30 +03:00
parent 35bed137f4
commit ecb844f9f2
18 changed files with 757 additions and 952 deletions

View File

@@ -0,0 +1,41 @@
// Types
import { Tabs as TabsDefinition } from ".";
// Requires
import { CSSType, booleanConverter } from "../core/view";
import { Property } from "../core/properties";
import { TabbableComponent } from "../tabbable-component/tabbable-component.android";
export const traceCategory = "TabView";
export module knownCollections {
export const items = "items";
}
@CSSType("Tabs")
export abstract class TabsBase extends TabbableComponent implements TabsDefinition {
public swipeEnabled: boolean;
public offscreenTabLimit: number;
public tabsPosition: "top" | "bottom";
public iOSTabBarItemsAlignment: IOSTabBarItemsAlignment;
}
// TODO: Add Unit tests
export const swipeEnabledProperty = new Property<TabsBase, boolean>({
name: "swipeEnabled", defaultValue: true, valueConverter: booleanConverter
});
swipeEnabledProperty.register(TabsBase);
// TODO: Add Unit tests
// TODO: Coerce to max number of items?
export const offscreenTabLimitProperty = new Property<TabsBase, number>({
name: "offscreenTabLimit", defaultValue: 1, valueConverter: (v) => parseInt(v)
});
offscreenTabLimitProperty.register(TabsBase);
export const tabsPositionProperty = new Property<TabsBase, "top" | "bottom">({ name: "tabsPosition", defaultValue: "top" });
tabsPositionProperty.register(TabsBase);
export type IOSTabBarItemsAlignment = "leading" | "justified" | "center" | "centerSelected";
export const iOSTabBarItemsAlignmentProperty = new Property<TabsBase, IOSTabBarItemsAlignment>({ name: "iOSTabBarItemsAlignment", defaultValue: "justified" });
iOSTabBarItemsAlignmentProperty.register(TabsBase);

View File

@@ -0,0 +1,33 @@
// Types
import { Tabs as TabsDefinition } from ".";
// Requires
import { CSSType, booleanConverter } from "../core/view";
import { Property } from "../core/properties";
import { TabbableComponent } from "../tabbable-component";
export * from "../tab-navigation-base/tab-content-item";
export * from "../tab-navigation-base/tab-navigation-base";
export * from "../tab-navigation-base/tab-strip";
export * from "../tab-navigation-base/tab-strip-item";
export const traceCategory = "TabView";
export module knownCollections {
export const items = "items";
}
@CSSType("Tabs")
export abstract class TabsBase extends TabbableComponent implements TabsDefinition {
public swipeEnabled: boolean;
public offscreenTabLimit: number;
public tabsPosition: "top" | "bottom";
public iOSTabBarItemsAlignment: IOSTabBarItemsAlignment;
}
export const swipeEnabledProperty: Property<TabsBase, boolean>;
export const offscreenTabLimitProperty: Property<TabsBase, number>;
export const tabsPositionProperty: Property<TabsBase, "top" | "bottom">;
export type IOSTabBarItemsAlignment = "leading" | "justified" | "center" | "centerSelected";
export const iOSTabBarItemsAlignmentProperty: Property<TabsBase, IOSTabBarItemsAlignment>;

View File

@@ -2,9 +2,9 @@
import { Tabs as TabsDefinition } from ".";
// Requires
import { TabNavigationBase } from "../tab-navigation-base/tab-navigation-base";
import { CSSType, booleanConverter } from "../core/view";
import { Property } from "../core/properties";
import { TabbableComponent } from "../tabbable-component";
export * from "../tab-navigation-base/tab-content-item";
export * from "../tab-navigation-base/tab-navigation-base";
@@ -18,7 +18,7 @@ export module knownCollections {
}
@CSSType("Tabs")
export class TabsBase extends TabNavigationBase implements TabsDefinition {
export abstract class TabsBase extends TabbableComponent implements TabsDefinition {
public swipeEnabled: boolean;
public offscreenTabLimit: number;
public tabsPosition: "top" | "bottom";

View File

@@ -2,21 +2,12 @@
import { TabContentItem } from "../tab-navigation-base/tab-content-item";
import { TabStrip } from "../tab-navigation-base/tab-strip";
import { TabStripItem } from "../tab-navigation-base/tab-strip-item";
import { TextTransform } from "../text-base";
// Requires
import * as application from "../../application";
import { ImageSource } from "../../image-source";
import { ad, isFontIconURI, layout, RESOURCE_PREFIX } from "../../utils/utils";
import { ad, layout } from "../../utils/utils";
import { Color } from "../core/view";
import { Frame } from "../frame";
import { Font } from "../styling/font";
import {
getIconSpecSize, itemsProperty, selectedIndexProperty, tabStripProperty
} from "../tab-navigation-base/tab-navigation-base";
import { getTransformedText } from "../text-base";
import { offscreenTabLimitProperty, swipeEnabledProperty, TabsBase } from "./tabs-common";
import { offscreenTabLimitProperty, swipeEnabledProperty, TabsBase } from "./tabs-common.android";
export * from "./tabs-common";
const ACCENT_COLOR = "colorAccent";
@@ -361,23 +352,9 @@ function setElevation(grid: org.nativescript.widgets.GridLayout, tabsBar: org.na
export const tabs = new Array<WeakRef<Tabs>>();
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 Tabs extends TabsBase {
private _tabsBar: org.nativescript.widgets.TabsBar;
private _viewPager: androidx.viewpager.widget.ViewPager;
private _pagerAdapter: androidx.viewpager.widget.PagerAdapter;
private _androidViewId: number = -1;
public _originalBackground: any;
private _textTransform: TextTransform = "uppercase";
private _selectedItemColor: Color;
private _unSelectedItemColor: Color;
constructor() {
super();
@@ -388,16 +365,12 @@ export class Tabs extends TabsBase {
return true;
}
public onItemsChanged(oldItems: TabContentItem[], newItems: TabContentItem[]): void {
super.onItemsChanged(oldItems, newItems);
public getTabsBar(): org.nativescript.widgets.TabsBar {
return <org.nativescript.widgets.TabsBar>(<any>this).nativeBar;
}
if (oldItems) {
oldItems.forEach((item: TabContentItem, i, arr) => {
(<any>item).index = 0;
(<any>item).tabItemSpec = null;
item.setNativeView(null);
});
}
protected setNativeItems(items: Array<org.nativescript.widgets.TabItemSpec>) {
this.getTabsBar().setItems(items, items ? this._viewPager : null);
}
public createNativeView() {
@@ -454,71 +427,21 @@ export class Tabs extends TabsBase {
public initNativeView(): void {
super.initNativeView();
if (this._androidViewId < 0) {
this._androidViewId = android.view.View.generateViewId();
}
const nativeView: any = this.nativeViewProtected;
this._tabsBar = (<any>nativeView).tabsBar;
const viewPager = (<any>nativeView).viewPager;
viewPager.setId(this._androidViewId);
const viewPager = (<any>this.nativeViewProtected).viewPager;
viewPager.setId(this._contentViewId);
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.offscreenTabLimit;
let toUnload = [];
let 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.content);
}
});
const newItem = items[newIndex];
const selectedView = newItem && newItem.content;
if (selectedView instanceof Frame) {
(<Frame>selectedView)._pushInFrameStackRecursive();
}
toLoad.forEach(index => {
const item = items[index];
if (this.isLoaded && items[index]) {
item.loadView(item.content);
}
});
protected getOffscreenTabLimit(): number {
return this.offscreenTabLimit;
}
public onLoaded(): void {
super.onLoaded();
if (this._originalBackground) {
this.backgroundColor = null;
this.backgroundColor = this._originalBackground;
this._originalBackground = null;
}
this.setItems((<any>this.items));
if (this.tabStrip) {
@@ -528,44 +451,16 @@ export class Tabs extends TabsBase {
// this.setAdapterItems(this.items);
}
public onUnloaded(): void {
super.onUnloaded();
this.setItems(null);
this.setTabStripItems(null);
// this.setAdapterItems(null);
}
public disposeNativeView() {
this._tabsBar.setItems(null, null);
this.setNativeItems(null);
(<any>this._pagerAdapter).owner = null;
this._pagerAdapter = null;
this._tabsBar = null;
this.nativeBar = 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();
for (let fragment of (<Array<any>>fragmentManager.getFragments().toArray())) {
transaction.remove(fragment);
}
transaction.commitNowAllowingStateLoss();
}
private shouldUpdateAdapter(items: Array<TabContentItem>) {
if (!this._pagerAdapter) {
return false;
@@ -603,7 +498,7 @@ export class Tabs extends TabsBase {
return false;
}
private setItems(items: Array<TabContentItem>) {
protected setItems(items: Array<TabContentItem>) {
if (this.shouldUpdateAdapter(items)) {
(<any>this._pagerAdapter).items = items;
@@ -617,335 +512,16 @@ export class Tabs extends TabsBase {
}
}
private setTabStripItems(items: Array<TabStripItem>) {
const length = items ? items.length : 0;
if (length === 0) {
this._tabsBar.setItems(null, null);
return;
}
const tabItems = new Array<org.nativescript.widgets.TabItemSpec>();
items.forEach((tabStripItem: TabStripItem, i, arr) => {
tabStripItem._index = i;
const tabItemSpec = this.createTabItemSpec(tabStripItem);
(<any>tabStripItem).tabItemSpec = tabItemSpec;
tabItems.push(tabItemSpec);
});
const tabsBar = this._tabsBar;
tabsBar.setItems(tabItems, this._viewPager);
this.tabStrip.setNativeView(tabsBar);
items.forEach((item, i, arr) => {
const tv = tabsBar.getTextViewForItemAt(i);
item.setNativeView(tv);
this._setItemColor(item);
});
}
private getItemLabelTextTransform(tabStripItem: TabStripItem): TextTransform {
const nestedLabel = tabStripItem.label;
let textTransform: TextTransform = null;
if (nestedLabel && nestedLabel.style.textTransform !== "initial") {
textTransform = nestedLabel.style.textTransform;
} else if (tabStripItem.style.textTransform !== "initial") {
textTransform = tabStripItem.style.textTransform;
}
return textTransform || this._textTransform;
}
private createTabItemSpec(tabStripItem: TabStripItem): org.nativescript.widgets.TabItemSpec {
const tabItemSpec = new org.nativescript.widgets.TabItemSpec();
if (tabStripItem.isLoaded) {
const nestedLabel = tabStripItem.label;
let title = nestedLabel.text;
// TEXT-TRANSFORM
const textTransform = this.getItemLabelTextTransform(tabStripItem);
title = getTransformedText(title, textTransform);
tabItemSpec.title = title;
// BACKGROUND-COLOR
const backgroundColor = tabStripItem.style.backgroundColor;
tabItemSpec.backgroundColor = backgroundColor ? backgroundColor.android : this.getTabBarBackgroundArgbColor();
// COLOR
let itemColor = this.selectedIndex === tabStripItem._index ? this._selectedItemColor : this._unSelectedItemColor;
const color = itemColor || nestedLabel.style.color;
tabItemSpec.color = color && color.android;
// FONT
const fontInternal = nestedLabel.style.fontInternal;
if (fontInternal) {
tabItemSpec.fontSize = fontInternal.fontSize;
tabItemSpec.typeFace = fontInternal.getAndroidTypeface();
}
// ICON
const iconSource = tabStripItem.image && tabStripItem.image.src;
if (iconSource) {
const icon = this.getIcon(tabStripItem, itemColor);
if (icon) {
// TODO: Make this native call that accepts string so that we don't load Bitmap in JS.
// tslint:disable-next-line:deprecation
tabItemSpec.iconDrawable = icon;
} else {
// TODO:
// traceMissingIcon(iconSource);
}
}
}
return tabItemSpec;
}
private getIcon(tabStripItem: TabStripItem, color?: Color): android.graphics.drawable.BitmapDrawable {
const iconSource = tabStripItem.image && tabStripItem.image.src;
if (!iconSource) {
return null;
}
let is: ImageSource;
if (isFontIconURI(iconSource)) {
const fontIconCode = iconSource.split("//")[1];
const target = tabStripItem.image ? tabStripItem.image : tabStripItem;
const font = target.style.fontInternal;
if (!color) {
color = target.style.color;
}
is = ImageSource.fromFontIconCodeSync(fontIconCode, font, color);
} else {
is = ImageSource.fromFileOrResourceSync(iconSource);
}
let imageDrawable: android.graphics.drawable.BitmapDrawable;
if (is && is.android) {
let image = is.android;
if (this.tabStrip && this.tabStrip.isIconSizeFixed) {
image = this.getFixedSizeIcon(image);
}
imageDrawable = new android.graphics.drawable.BitmapDrawable(appResources, image);
} else {
// TODO
// traceMissingIcon(iconSource);
}
return imageDrawable;
}
private getFixedSizeIcon(image: android.graphics.Bitmap): android.graphics.Bitmap {
const inWidth = image.getWidth();
const inHeight = image.getHeight();
const iconSpecSize = getIconSpecSize({ width: inWidth, height: inHeight });
const widthPixels = iconSpecSize.width * layout.getDisplayDensity();
const heightPixels = iconSpecSize.height * layout.getDisplayDensity();
const scaledImage = android.graphics.Bitmap.createScaledBitmap(image, widthPixels, heightPixels, true);
return scaledImage;
}
// private setAdapterItems(items: Array<TabStripItem>) {
// 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: TabStripItem, i, arr) => {
// const tabItemSpec = createTabItemSpec(item);
// (<any>item).index = i;
// (<any>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();
// }
// }
public updateAndroidItemAt(index: number, spec: org.nativescript.widgets.TabItemSpec) {
this._tabsBar.updateItemAt(index, spec);
}
public getTabBarBackgroundColor(): android.graphics.drawable.Drawable {
return this._tabsBar.getBackground();
}
public setTabBarBackgroundColor(value: android.graphics.drawable.Drawable | Color): void {
if (value instanceof Color) {
this._tabsBar.setBackgroundColor(value.android);
} else {
this._tabsBar.setBackground(tryCloneDrawable(value, this.nativeViewProtected.getResources()));
}
this.updateTabStripItems();
}
private updateTabStripItems(): void {
this.tabStrip.items.forEach((tabStripItem: TabStripItem) => {
if (tabStripItem.nativeView) {
const tabItemSpec = this.createTabItemSpec(tabStripItem);
this.updateAndroidItemAt(tabStripItem._index, tabItemSpec);
}
});
}
public getTabBarHighlightColor(): number {
return getDefaultAccentColor(this._context);
}
public setTabBarHighlightColor(value: number | Color) {
const color = value instanceof Color ? value.android : value;
this._tabsBar.setSelectedIndicatorColors([color]);
this.getTabsBar().setSelectedIndicatorColors([color]);
}
private setItemsColors(items: Array<TabStripItem>): void {
items.forEach((item) => {
if (item.nativeView) {
this._setItemColor(item);
}
});
}
public getTabBarSelectedItemColor(): Color {
return this._selectedItemColor;
}
public setTabBarSelectedItemColor(value: Color) {
this._selectedItemColor = value;
this.setItemsColors(this.tabStrip.items);
}
public getTabBarUnSelectedItemColor(): Color {
return this._unSelectedItemColor;
}
public setTabBarUnSelectedItemColor(value: Color) {
this._unSelectedItemColor = value;
this.setItemsColors(this.tabStrip.items);
}
private updateItem(tabStripItem: TabStripItem): void {
// TODO: Should figure out a way to do it directly with the the nativeView
const tabStripItemIndex = this.tabStrip.items.indexOf(tabStripItem);
const tabItemSpec = this.createTabItemSpec(tabStripItem);
this.updateAndroidItemAt(tabStripItemIndex, tabItemSpec);
}
public setTabBarItemTitle(tabStripItem: TabStripItem, value: string): void {
this.updateItem(tabStripItem);
}
public setTabBarItemBackgroundColor(tabStripItem: TabStripItem, value: android.graphics.drawable.Drawable | Color): void {
this.updateItem(tabStripItem);
}
public _setItemColor(tabStripItem: TabStripItem) {
const itemColor = (tabStripItem._index === this.selectedIndex) ? this._selectedItemColor : this._unSelectedItemColor;
if (!itemColor) {
return;
}
// set label color
tabStripItem.nativeViewProtected.setTextColor(itemColor.android);
// set icon color
this.setIconColor(tabStripItem, itemColor);
}
private setIconColor(tabStripItem: TabStripItem, color?: Color) {
const tabBarItem = this._tabsBar.getViewForItemAt(tabStripItem._index);
const drawable = this.getIcon(tabStripItem, color);
const imgView = <android.widget.ImageView>tabBarItem.getChildAt(0);
imgView.setImageDrawable(drawable);
if (color) {
imgView.setColorFilter(color.android);
}
}
public setTabBarItemColor(tabStripItem: TabStripItem, value: number | Color): void {
const itemColor = (tabStripItem._index === this.selectedIndex) ? this._selectedItemColor : this._unSelectedItemColor;
if (itemColor) {
// the itemColor is set through the selectedItemColor and unSelectedItemColor properties
// so it does not respect the css color
return;
}
const androidColor = value instanceof Color ? value.android : value;
tabStripItem.nativeViewProtected.setTextColor(androidColor);
}
public setTabBarIconColor(tabStripItem: TabStripItem, value: number | Color): void {
const itemColor = (tabStripItem._index === this.selectedIndex) ? this._selectedItemColor : this._unSelectedItemColor;
if (itemColor) {
// the itemColor is set through the selectedItemColor and unSelectedItemColor properties
// so it does not respect the css color
return;
}
this.setIconColor(tabStripItem);
}
public setTabBarIconSource(tabStripItem: TabStripItem, value: number | Color): void {
this.updateItem(tabStripItem);
}
public setTabBarItemFontInternal(tabStripItem: TabStripItem, value: Font): void {
if (value.fontSize) {
tabStripItem.nativeViewProtected.setTextSize(value.fontSize);
}
tabStripItem.nativeViewProtected.setTypeface(value.getAndroidTypeface());
}
public getTabBarItemTextTransform(tabStripItem: TabStripItem): TextTransform {
return this.getItemLabelTextTransform(tabStripItem);
}
public setTabBarItemTextTransform(tabStripItem: TabStripItem, value: TextTransform): void {
const nestedLabel = tabStripItem.label;
const title = getTransformedText(nestedLabel.text, value);
tabStripItem.nativeViewProtected.setText(title);
}
public getTabBarTextTransform(): TextTransform {
return this._textTransform;
}
public setTabBarTextTransform(value: TextTransform): void {
let items = this.tabStrip && this.tabStrip.items;
if (items) {
items.forEach((tabStripItem) => {
if (tabStripItem.label && tabStripItem.nativeViewProtected) {
const nestedLabel = tabStripItem.label;
const title = getTransformedText(nestedLabel.text, value);
tabStripItem.nativeViewProtected.setText(title);
}
});
}
this._textTransform = value;
}
[selectedIndexProperty.setNative](value: number) {
protected setSelectedItem(value: number) {
const smoothScroll = true;
// TODO
@@ -956,21 +532,6 @@ export class Tabs extends TabsBase {
this._viewPager.setCurrentItem(value, smoothScroll);
}
[itemsProperty.getDefault](): TabContentItem[] {
return null;
}
[itemsProperty.setNative](value: TabContentItem[]) {
this.setItems(value);
selectedIndexProperty.coerce(this);
}
[tabStripProperty.getDefault](): TabStrip {
return null;
}
[tabStripProperty.setNative](value: TabStrip) {
this.setTabStripItems(value.items);
}
[swipeEnabledProperty.getDefault](): boolean {
// TODO: create native method and get native?
return true;
@@ -985,15 +546,4 @@ export class Tabs extends TabsBase {
[offscreenTabLimitProperty.setNative](value: number) {
this._viewPager.setOffscreenPageLimit(value);
}
}
function tryCloneDrawable(value: android.graphics.drawable.Drawable, resources: android.content.res.Resources): android.graphics.drawable.Drawable {
if (value) {
const constantState = value.getConstantState();
if (constantState) {
return constantState.newDrawable(resources);
}
}
return value;
}
}

View File

@@ -5,9 +5,8 @@
import { EventData, Property } from "../core/view";
import { TabContentItem } from "../tab-navigation-base/tab-content-item";
import {
SelectedIndexChangedEventData, TabNavigationBase
} from "../tab-navigation-base/tab-navigation-base";
import { SelectedIndexChangedEventData } from "../tab-navigation-base/tab-navigation-base";
import { TabbableComponent } from "../tabbable-component";
import { TabStrip } from "../tab-navigation-base/tab-strip";
export * from "../tab-navigation-base/tab-content-item";
@@ -18,7 +17,7 @@ export * from "../tab-navigation-base/tab-strip-item";
/**
* Represents a swipeable tabs view.
*/
export class Tabs extends TabNavigationBase {
export class Tabs extends TabbableComponent {
/**
* Gets or sets the items of the Tabs.
*/