mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
Call native setters once when a page is instantiated. Fix background not being reset, Label's background is now set to background, not layer Make the nativeView field instead of property for performance reasons
450 lines
16 KiB
TypeScript
450 lines
16 KiB
TypeScript
import { Font } from "../styling/font";
|
|
|
|
import {
|
|
TabViewBase, TabViewItemBase, itemsProperty, selectedIndexProperty,
|
|
tabTextColorProperty, tabBackgroundColorProperty, selectedTabTextColorProperty,
|
|
androidSelectedTabHighlightColorProperty, androidOffscreenTabLimitProperty,
|
|
fontSizeProperty, fontInternalProperty, View, layout,
|
|
traceCategory, traceEnabled, traceWrite, Color
|
|
} from "./tab-view-common"
|
|
import { textTransformProperty, TextTransform, getTransformedText } from "../text-base";
|
|
import { fromFileOrResource } from "../../image-source";
|
|
import { RESOURCE_PREFIX, ad } from "../../utils/utils";
|
|
|
|
export * from "./tab-view-common";
|
|
|
|
const VIEWS_STATES = "_viewStates";
|
|
const ACCENT_COLOR = "colorAccent";
|
|
const PRIMARY_COLOR = "colorPrimary";
|
|
const DEFAULT_ELEVATION = 4;
|
|
|
|
interface PagerAdapter {
|
|
new (owner: TabView, items: Array<TabViewItem>): android.support.v4.view.PagerAdapter;
|
|
}
|
|
|
|
interface PageChangedListener {
|
|
new (owner: TabView): android.support.v4.view.ViewPager.SimpleOnPageChangeListener;
|
|
}
|
|
|
|
let PagerAdapter: PagerAdapter;
|
|
let PageChangedListener: PageChangedListener;
|
|
|
|
function initializeNativeClasses() {
|
|
if (PagerAdapter) {
|
|
return;
|
|
}
|
|
|
|
class PagerAdapterImpl extends android.support.v4.view.PagerAdapter {
|
|
constructor(public owner: TabView, public items: Array<TabViewItem>) {
|
|
super();
|
|
return global.__native(this);
|
|
}
|
|
|
|
getCount() {
|
|
return this.items ? this.items.length : 0;
|
|
}
|
|
|
|
getPageTitle(index: number) {
|
|
if (index < 0 || index >= this.items.length) {
|
|
return "";
|
|
}
|
|
|
|
return this.items[index].title;
|
|
}
|
|
|
|
instantiateItem(container: android.view.ViewGroup, index: number) {
|
|
if (traceEnabled()) {
|
|
traceWrite("TabView.PagerAdapter.instantiateItem; container: " + container + "; index: " + index, traceCategory);
|
|
}
|
|
|
|
let item = this.items[index];
|
|
// if (item.view.parent !== this.owner) {
|
|
// this.owner._addView(item.view);
|
|
// }
|
|
|
|
if (this[VIEWS_STATES]) {
|
|
if (traceEnabled()) {
|
|
traceWrite("TabView.PagerAdapter.instantiateItem; restoreHierarchyState: " + item.view, traceCategory);
|
|
}
|
|
item.view.nativeView.restoreHierarchyState(this[VIEWS_STATES]);
|
|
}
|
|
|
|
if (item.view.nativeView) {
|
|
container.addView(item.view.nativeView);
|
|
}
|
|
return item.view.nativeView;
|
|
}
|
|
|
|
destroyItem(container: android.view.ViewGroup, index: number, _object: any) {
|
|
if (traceEnabled()) {
|
|
traceWrite("TabView.PagerAdapter.destroyItem; container: " + container + "; index: " + index + "; _object: " + _object, traceCategory);
|
|
}
|
|
let item = this.items[index];
|
|
let nativeView = item.view.nativeView;
|
|
|
|
if (!nativeView || !_object) {
|
|
return;
|
|
}
|
|
|
|
if (nativeView.toString() !== _object.toString()) {
|
|
throw new Error("Expected " + nativeView.toString() + " to equal " + _object.toString());
|
|
}
|
|
|
|
container.removeView(nativeView);
|
|
|
|
// Note: this.owner._removeView will clear item.view.nativeView.
|
|
// So call this after the native instance is removed form the container.
|
|
// if (item.view.parent === this.owner) {
|
|
// this.owner._removeView(item.view);
|
|
// }
|
|
}
|
|
|
|
isViewFromObject(view: android.view.View, _object: any) {
|
|
return view === _object;
|
|
}
|
|
|
|
saveState(): android.os.Parcelable {
|
|
if (traceEnabled()) {
|
|
traceWrite("TabView.PagerAdapter.saveState", traceCategory);
|
|
}
|
|
|
|
const owner: TabView = this.owner;
|
|
if (owner._childrenCount === 0) {
|
|
return null;
|
|
}
|
|
|
|
if (!this[VIEWS_STATES]) {
|
|
this[VIEWS_STATES] = new android.util.SparseArray<android.os.Parcelable>();
|
|
}
|
|
let viewStates = this[VIEWS_STATES];
|
|
let childCallback = function (view: View): boolean {
|
|
let nativeView: android.view.View = view.nativeView;
|
|
if (nativeView && nativeView.isSaveFromParentEnabled && nativeView.isSaveFromParentEnabled()) {
|
|
nativeView.saveHierarchyState(viewStates);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
owner.eachChildView(childCallback);
|
|
|
|
let bundle = new android.os.Bundle();
|
|
bundle.putSparseParcelableArray(VIEWS_STATES, viewStates);
|
|
return bundle;
|
|
}
|
|
|
|
restoreState(state: android.os.Parcelable, loader: java.lang.ClassLoader) {
|
|
if (traceEnabled()) {
|
|
traceWrite("TabView.PagerAdapter.restoreState", traceCategory);
|
|
}
|
|
let bundle: android.os.Bundle = <android.os.Bundle>state;
|
|
bundle.setClassLoader(loader);
|
|
this[VIEWS_STATES] = bundle.getSparseParcelableArray(VIEWS_STATES);
|
|
}
|
|
};
|
|
|
|
class PageChangedListenerImpl extends android.support.v4.view.ViewPager.SimpleOnPageChangeListener {
|
|
private _owner: TabView;
|
|
constructor(owner: TabView) {
|
|
super();
|
|
this._owner = owner;
|
|
return global.__native(this);
|
|
}
|
|
|
|
public onPageSelected(position: number) {
|
|
this._owner.selectedIndex = position;
|
|
}
|
|
}
|
|
|
|
PagerAdapter = PagerAdapterImpl;
|
|
PageChangedListener = PageChangedListenerImpl;
|
|
}
|
|
|
|
function createTabItemSpec(item: TabViewItem): org.nativescript.widgets.TabItemSpec {
|
|
let 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));
|
|
} else {
|
|
let is = fromFileOrResource(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(is.android);
|
|
}
|
|
}
|
|
}
|
|
|
|
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.getPalleteColor(ACCENT_COLOR, context) || 0xFF33B5E5;
|
|
}
|
|
return defaultAccentColor;
|
|
}
|
|
|
|
export class TabViewItem extends TabViewItemBase {
|
|
nativeView: android.widget.TextView;
|
|
public tabItemSpec: org.nativescript.widgets.TabItemSpec;
|
|
public index: number;
|
|
private _defaultTransformationMethod: android.text.method.TransformationMethod;
|
|
|
|
public initNativeView(): void {
|
|
super.initNativeView();
|
|
if (this.nativeView) {
|
|
this._defaultTransformationMethod = this.nativeView.getTransformationMethod();
|
|
}
|
|
}
|
|
|
|
public resetNativeView(): void {
|
|
super.resetNativeView();
|
|
if (this.nativeView) {
|
|
// We reset it here too because this could be changed by multiple properties - whiteSpace, secure, textTransform
|
|
this.nativeView.setTransformationMethod(this._defaultTransformationMethod);
|
|
}
|
|
}
|
|
|
|
public createNativeView() {
|
|
return this.nativeView;
|
|
}
|
|
|
|
public _update(): void {
|
|
const tv = this.nativeView;
|
|
if (tv) {
|
|
const tabLayout = <org.nativescript.widgets.TabLayout>tv.getParent();
|
|
tabLayout.updateItemAt(this.index, this.tabItemSpec);
|
|
}
|
|
}
|
|
|
|
[fontSizeProperty.getDefault](): { nativeSize: number } {
|
|
return { nativeSize: this.nativeView.getTextSize() };
|
|
}
|
|
[fontSizeProperty.setNative](value: number | { nativeSize: number }) {
|
|
if (typeof value === "number") {
|
|
this.nativeView.setTextSize(value);
|
|
} else {
|
|
this.nativeView.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, value.nativeSize);
|
|
}
|
|
}
|
|
|
|
[fontInternalProperty.getDefault](): android.graphics.Typeface {
|
|
return this.nativeView.getTypeface();
|
|
}
|
|
[fontInternalProperty.setNative](value: Font | android.graphics.Typeface) {
|
|
this.nativeView.setTypeface(value instanceof Font ? value.getAndroidTypeface() : value);
|
|
}
|
|
|
|
[textTransformProperty.getDefault](): "default" {
|
|
return "default";
|
|
}
|
|
[textTransformProperty.setNative](value: TextTransform | "default") {
|
|
const tv = this.nativeView;
|
|
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>android.support.v4.view.ViewCompat;
|
|
if (compat.setElevation) {
|
|
const val = DEFAULT_ELEVATION * layout.getDisplayDensity();
|
|
compat.setElevation(grid, val);
|
|
compat.setElevation(tabLayout, val);
|
|
}
|
|
}
|
|
|
|
export class TabView extends TabViewBase {
|
|
|
|
private _tabLayout: org.nativescript.widgets.TabLayout;
|
|
private _viewPager: android.support.v4.view.ViewPager;
|
|
private _pagerAdapter: android.support.v4.view.PagerAdapter;
|
|
private _androidViewId: number = -1;
|
|
|
|
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 (traceEnabled()) {
|
|
traceWrite("TabView._createUI(" + this + ");", traceCategory);
|
|
}
|
|
|
|
const context: android.content.Context = this._context;
|
|
const nativeView = new org.nativescript.widgets.GridLayout(context);
|
|
nativeView.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.auto));
|
|
nativeView.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.star));
|
|
|
|
const tabLayout = new org.nativescript.widgets.TabLayout(context);
|
|
nativeView.addView(tabLayout);
|
|
(<any>nativeView).tabLayout = tabLayout;
|
|
|
|
setElevation(nativeView, tabLayout);
|
|
|
|
const accentColor = getDefaultAccentColor(context);
|
|
if (accentColor) {
|
|
tabLayout.setSelectedIndicatorColors([accentColor]);
|
|
}
|
|
|
|
const primaryColor = ad.resources.getPalleteColor(PRIMARY_COLOR, context);
|
|
if (primaryColor) {
|
|
tabLayout.setBackgroundColor(primaryColor);
|
|
}
|
|
|
|
const viewPager = new android.support.v4.view.ViewPager(context);
|
|
const lp = new org.nativescript.widgets.CommonLayoutParams();
|
|
lp.row = 1;
|
|
viewPager.setLayoutParams(lp);
|
|
nativeView.addView(viewPager);
|
|
(<any>nativeView).viewPager = viewPager;
|
|
|
|
const listener = new PageChangedListener(this);
|
|
(<any>viewPager).addOnPageChangeListener(listener);
|
|
(<any>viewPager).listener = listener;
|
|
|
|
const adapter = new PagerAdapter(this, null);
|
|
viewPager.setAdapter(adapter);
|
|
(<any>viewPager).adapter = adapter;
|
|
|
|
return nativeView;
|
|
}
|
|
|
|
public initNativeView(): void {
|
|
super.initNativeView();
|
|
if (this._androidViewId < 0) {
|
|
this._androidViewId = android.view.View.generateViewId();
|
|
}
|
|
|
|
const nativeView: any = this.nativeView;
|
|
this._tabLayout = (<any>nativeView).tabLayout;
|
|
|
|
const viewPager = (<any>nativeView).viewPager;
|
|
viewPager.setId(this._androidViewId);
|
|
this._viewPager = viewPager;
|
|
(<any>viewPager).listener.owner = this;
|
|
|
|
this._pagerAdapter = (<any>viewPager).adapter;
|
|
(<any>this._pagerAdapter).owner = this;
|
|
}
|
|
|
|
public disposeNativeView() {
|
|
this._pagerAdapter.notifyDataSetChanged();
|
|
(<any>this._pagerAdapter).owner = null;
|
|
this._pagerAdapter = null;
|
|
|
|
this._tabLayout = null;
|
|
(<any>this._viewPager).listener.owner = null;
|
|
this._viewPager = null;
|
|
super.disposeNativeView();
|
|
}
|
|
|
|
private setAdapterItems(items: Array<TabViewItem>) {
|
|
(<any>this._pagerAdapter).items = items;
|
|
|
|
const length = items ? items.length : 0;
|
|
if (length === 0) {
|
|
this._tabLayout.setItems(null, null);
|
|
return;
|
|
}
|
|
|
|
const tabItems = new Array<org.nativescript.widgets.TabItemSpec>();
|
|
items.forEach((item, i, arr) => {
|
|
const tabItemSpec = createTabItemSpec(item);
|
|
item.index = i;
|
|
item.tabItemSpec = tabItemSpec;
|
|
tabItems.push(tabItemSpec);
|
|
});
|
|
|
|
// // TODO: optimize by reusing the adapter and calling setAdapter(null) then the same adapter.
|
|
// this._pagerAdapter = new PagerAdapter(this, items);
|
|
// this._viewPager.setAdapter(this._pagerAdapter);
|
|
|
|
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();
|
|
}
|
|
|
|
[androidOffscreenTabLimitProperty.getDefault](): number {
|
|
return this._viewPager.getOffscreenPageLimit();
|
|
}
|
|
[androidOffscreenTabLimitProperty.setNative](value: number) {
|
|
this._viewPager.setOffscreenPageLimit(value);
|
|
}
|
|
|
|
[selectedIndexProperty.getDefault](): number {
|
|
return -1;
|
|
}
|
|
[selectedIndexProperty.setNative](value: number) {
|
|
if (traceEnabled()) {
|
|
traceWrite("TabView this._viewPager.setCurrentItem(" + value + ", true);", traceCategory);
|
|
}
|
|
this._viewPager.setCurrentItem(value, true);
|
|
}
|
|
|
|
[itemsProperty.getDefault](): TabViewItem[] {
|
|
return null;
|
|
}
|
|
[itemsProperty.setNative](value: TabViewItem[]) {
|
|
this.setAdapterItems(value);
|
|
selectedIndexProperty.coerce(this);
|
|
}
|
|
|
|
[tabBackgroundColorProperty.getDefault](): android.graphics.drawable.Drawable.ConstantState {
|
|
return this._tabLayout.getBackground().getConstantState();
|
|
}
|
|
[tabBackgroundColorProperty.setNative](value: android.graphics.drawable.Drawable.ConstantState | Color) {
|
|
if (value instanceof Color) {
|
|
this._tabLayout.setBackgroundColor(value.android);
|
|
} else {
|
|
this._tabLayout.setBackground(value ? value.newDrawable() : null);
|
|
}
|
|
}
|
|
|
|
[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) {
|
|
let tabLayout = this._tabLayout;
|
|
const color = value instanceof Color ? value.android : value;
|
|
tabLayout.setSelectedIndicatorColors([color]);
|
|
}
|
|
} |