mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-14 10:01:08 +08:00
329 lines
10 KiB
TypeScript
329 lines
10 KiB
TypeScript
import { Font } from '../styling/font';
|
|
import { SegmentedBarItemBase, SegmentedBarBase, selectedIndexProperty, itemsProperty, selectedBackgroundColorProperty, selectedTextColorProperty } from './segmented-bar-common';
|
|
import { AndroidHelper, isEnabledProperty } from '../core/view';
|
|
import { colorProperty, fontInternalProperty, fontSizeProperty } from '../styling/style-properties';
|
|
import { Color } from '../../color';
|
|
import { layout } from '../../utils';
|
|
import { SDK_VERSION } from '../../utils/constants';
|
|
import { Trace } from '../../trace';
|
|
|
|
export * from './segmented-bar-common';
|
|
|
|
const R_ID_TABS = 0x01020013;
|
|
const R_ID_TABCONTENT = 0x01020011;
|
|
const R_ATTR_STATE_SELECTED = 0x010100a1;
|
|
const TITLE_TEXT_VIEW_ID = 16908310; // http://developer.android.com/reference/android/R.id.html#title
|
|
|
|
interface TabChangeListener {
|
|
new (owner: SegmentedBar): android.widget.TabHost.OnTabChangeListener;
|
|
}
|
|
|
|
interface TabContentFactory {
|
|
new (owner: SegmentedBar): android.widget.TabHost.TabContentFactory;
|
|
}
|
|
|
|
interface TabHost {
|
|
new (context: android.content.Context, attrs: android.util.AttributeSet): android.widget.TabHost;
|
|
}
|
|
|
|
let selectedIndicatorThickness: number;
|
|
|
|
let TabHost: TabHost;
|
|
let TabChangeListener: TabChangeListener;
|
|
let TabContentFactory: TabContentFactory;
|
|
|
|
// TODO: All TabHost public methods become deprecated in API 30.
|
|
function initializeNativeClasses(): void {
|
|
if (TabChangeListener) {
|
|
return;
|
|
}
|
|
|
|
// Indicator thickness for material - 2dip. For pre-material - 5dip.
|
|
selectedIndicatorThickness = layout.toDevicePixels(SDK_VERSION >= 21 ? 2 : 5);
|
|
|
|
@NativeClass
|
|
@Interfaces([android.widget.TabHost.OnTabChangeListener])
|
|
class TabChangeListenerImpl extends java.lang.Object implements android.widget.TabHost.OnTabChangeListener {
|
|
constructor(public owner: SegmentedBar) {
|
|
super();
|
|
|
|
return global.__native(this);
|
|
}
|
|
|
|
onTabChanged(id: string): void {
|
|
const owner = this.owner;
|
|
if (owner) {
|
|
setTimeout(() => {
|
|
owner.setTabColor(id);
|
|
});
|
|
if (owner.shouldChangeSelectedIndex()) {
|
|
owner.selectedIndex = parseInt(id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@NativeClass
|
|
@Interfaces([android.widget.TabHost.TabContentFactory])
|
|
class TabContentFactoryImpl extends java.lang.Object implements android.widget.TabHost.TabContentFactory {
|
|
constructor(public owner: SegmentedBar) {
|
|
super();
|
|
|
|
return global.__native(this);
|
|
}
|
|
|
|
createTabContent(tag: string): android.view.View {
|
|
const tv = new android.widget.TextView(this.owner._context);
|
|
// This is collapsed by default and made visible
|
|
// by android when TabItem becomes visible/selected.
|
|
// TODO: Try commenting visibility change.
|
|
tv.setVisibility(android.view.View.GONE);
|
|
tv.setMaxLines(1);
|
|
tv.setEllipsize(android.text.TextUtils.TruncateAt.END);
|
|
|
|
return tv;
|
|
}
|
|
}
|
|
|
|
@NativeClass
|
|
class TabHostImpl extends android.widget.TabHost {
|
|
constructor(context: android.content.Context, attrs: android.util.AttributeSet) {
|
|
super(context, attrs);
|
|
|
|
return global.__native(this);
|
|
}
|
|
|
|
public onAttachedToWindow(): void {
|
|
// overriden to remove the code that will steal the focus from edit fields.
|
|
}
|
|
}
|
|
|
|
TabHost = TabHostImpl;
|
|
TabChangeListener = TabChangeListenerImpl;
|
|
TabContentFactory = TabContentFactoryImpl;
|
|
}
|
|
|
|
export class SegmentedBarItem extends SegmentedBarItemBase {
|
|
nativeViewProtected: android.widget.TextView;
|
|
|
|
public setupNativeView(tabIndex: number): void {
|
|
// TabHost.TabSpec.setIndicator DOES NOT WORK once the title has been set.
|
|
// http://stackoverflow.com/questions/2935781/modify-tab-indicator-dynamically-in-android
|
|
const titleTextView = <android.widget.TextView>this.parent.nativeViewProtected.getTabWidget().getChildAt(tabIndex).findViewById(TITLE_TEXT_VIEW_ID);
|
|
|
|
this.setNativeView(titleTextView);
|
|
if (titleTextView) {
|
|
if (this.titleDirty) {
|
|
this._update();
|
|
}
|
|
}
|
|
}
|
|
|
|
private titleDirty: boolean;
|
|
public _update(): void {
|
|
const tv = this.nativeViewProtected;
|
|
if (tv) {
|
|
let title = this.title;
|
|
title = title === null || title === undefined ? '' : title;
|
|
tv.setText(title);
|
|
this.titleDirty = false;
|
|
} else {
|
|
this.titleDirty = true;
|
|
}
|
|
}
|
|
|
|
[colorProperty.getDefault](): number {
|
|
return this.nativeViewProtected.getCurrentTextColor();
|
|
}
|
|
[colorProperty.setNative](value: Color | number) {
|
|
const color = value instanceof Color ? value.android : value;
|
|
this.nativeViewProtected.setTextColor(color);
|
|
}
|
|
|
|
[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);
|
|
}
|
|
|
|
[selectedBackgroundColorProperty.getDefault](): android.graphics.drawable.Drawable {
|
|
const viewGroup = <android.view.ViewGroup>this.nativeViewProtected.getParent();
|
|
|
|
return viewGroup.getBackground();
|
|
}
|
|
[selectedBackgroundColorProperty.setNative](value: Color | android.graphics.drawable.Drawable) {
|
|
const nativeView = this.nativeViewProtected;
|
|
const viewGroup = <android.view.ViewGroup>nativeView.getParent();
|
|
if (value instanceof Color) {
|
|
const color = value.android;
|
|
const backgroundDrawable = viewGroup.getBackground();
|
|
if (SDK_VERSION > 21 && backgroundDrawable) {
|
|
const newDrawable = AndroidHelper.getCopyOrDrawable(backgroundDrawable, nativeView.getResources());
|
|
AndroidHelper.setDrawableColor(color, newDrawable);
|
|
viewGroup.setBackground(newDrawable);
|
|
} else {
|
|
const stateDrawable = new android.graphics.drawable.StateListDrawable();
|
|
const colorDrawable: android.graphics.drawable.ColorDrawable = new org.nativescript.widgets.SegmentedBarColorDrawable(color, selectedIndicatorThickness);
|
|
const arr = Array.create('int', 1);
|
|
arr[0] = R_ATTR_STATE_SELECTED;
|
|
stateDrawable.addState(arr, colorDrawable);
|
|
stateDrawable.setBounds(0, 15, viewGroup.getRight(), viewGroup.getBottom());
|
|
viewGroup.setBackground(stateDrawable);
|
|
}
|
|
} else {
|
|
const backgroundDrawable = AndroidHelper.getCopyOrDrawable(value, nativeView.getResources());
|
|
viewGroup.setBackground(backgroundDrawable);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class SegmentedBar extends SegmentedBarBase {
|
|
nativeViewProtected: android.widget.TabHost;
|
|
private _tabContentFactory: android.widget.TabHost.TabContentFactory;
|
|
private _addingTab: boolean;
|
|
|
|
public shouldChangeSelectedIndex(): boolean {
|
|
return !this._addingTab;
|
|
}
|
|
|
|
public createNativeView() {
|
|
initializeNativeClasses();
|
|
|
|
const context: android.content.Context = this._context;
|
|
const nativeView = new TabHost(context, null);
|
|
|
|
const tabHostLayout = new android.widget.LinearLayout(context);
|
|
tabHostLayout.setOrientation(android.widget.LinearLayout.VERTICAL);
|
|
|
|
const tabWidget = new android.widget.TabWidget(context);
|
|
tabWidget.setId(R_ID_TABS);
|
|
tabHostLayout.addView(tabWidget);
|
|
|
|
const frame = new android.widget.FrameLayout(context);
|
|
frame.setId(R_ID_TABCONTENT);
|
|
frame.setVisibility(android.view.View.GONE);
|
|
tabHostLayout.addView(frame);
|
|
|
|
nativeView.addView(tabHostLayout);
|
|
|
|
return nativeView;
|
|
}
|
|
|
|
public initNativeView(): void {
|
|
super.initNativeView();
|
|
const nativeView = this.nativeViewProtected;
|
|
const listener = new TabChangeListener(this);
|
|
nativeView.setOnTabChangedListener(listener);
|
|
(<any>nativeView).listener = listener;
|
|
nativeView.setup();
|
|
this._tabContentFactory = this._tabContentFactory || new TabContentFactory(this);
|
|
}
|
|
|
|
public disposeNativeView() {
|
|
const nativeView: any = this.nativeViewProtected;
|
|
if (nativeView?.listener) {
|
|
nativeView.listener.owner = null;
|
|
}
|
|
super.disposeNativeView();
|
|
}
|
|
|
|
public onLoaded() {
|
|
super.onLoaded();
|
|
|
|
// Can only be applied after view is loaded
|
|
const tabWidget = this.nativeViewProtected.getTabWidget();
|
|
if (tabWidget) {
|
|
tabWidget.setEnabled(tabWidget.isEnabled());
|
|
}
|
|
}
|
|
|
|
private insertTab(tabItem: SegmentedBarItem, index: number): void {
|
|
const tabHost = this.nativeViewProtected;
|
|
const tab = tabHost.newTabSpec(index + '');
|
|
tab.setIndicator(tabItem.title + '');
|
|
tab.setContent(this._tabContentFactory);
|
|
|
|
this._addingTab = true;
|
|
tabHost.addTab(tab);
|
|
tabItem.setupNativeView(index);
|
|
this._addingTab = false;
|
|
}
|
|
|
|
[selectedIndexProperty.getDefault](): number {
|
|
return -1;
|
|
}
|
|
[selectedIndexProperty.setNative](value: number) {
|
|
this.nativeViewProtected.setCurrentTab(value);
|
|
}
|
|
|
|
[itemsProperty.getDefault](): SegmentedBarItem[] {
|
|
return null;
|
|
}
|
|
[itemsProperty.setNative](value: SegmentedBarItem[]) {
|
|
this.nativeViewProtected.clearAllTabs();
|
|
|
|
const newItems = value;
|
|
if (newItems) {
|
|
newItems.forEach((item, i, arr) => this.insertTab(item, i));
|
|
}
|
|
|
|
selectedIndexProperty.coerce(this);
|
|
}
|
|
[isEnabledProperty.setNative](value: boolean) {
|
|
const tabWidget = this.nativeViewProtected.getTabWidget();
|
|
if (tabWidget) {
|
|
tabWidget.setEnabled(value);
|
|
}
|
|
}
|
|
public setTabColor(index) {
|
|
try {
|
|
const tabWidget = this.nativeViewProtected?.getTabWidget();
|
|
if (tabWidget) {
|
|
const unselectedTextColor = this.getColorForAndroid(this.color ?? '#6e6e6e');
|
|
const selectedTextColor = this.getColorForAndroid(this?.selectedTextColor ?? '#000000');
|
|
const unselectedBackgroundColor = this.getColorForAndroid(this?.backgroundColor ?? '#dbdbdb');
|
|
const selectedBackgroundColor = this.getColorForAndroid(this?.selectedBackgroundColor ?? this?.backgroundColor ?? 'blue');
|
|
if (tabWidget) {
|
|
for (let i = 0; i < tabWidget.getTabCount(); i++) {
|
|
const view = tabWidget.getChildTabViewAt(i);
|
|
const item = this.items[i];
|
|
const textView = item?.nativeViewProtected;
|
|
view.setBackgroundColor(unselectedBackgroundColor);
|
|
if (textView) {
|
|
textView.setTextColor(unselectedTextColor);
|
|
}
|
|
if (index == i) {
|
|
view.setBackgroundColor(selectedBackgroundColor);
|
|
if (textView) {
|
|
textView.setTextColor(selectedTextColor);
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
Trace.error(e);
|
|
}
|
|
}
|
|
private getColorForAndroid(color: string | Color): number {
|
|
if (typeof color === 'string') {
|
|
return new Color(color).android;
|
|
} else if (color instanceof Color) {
|
|
return color.android;
|
|
}
|
|
}
|
|
}
|