mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-17 04:41:36 +08:00
596 lines
20 KiB
TypeScript
596 lines
20 KiB
TypeScript
import { PercentLength, Point, CustomLayoutView as CustomLayoutViewDefinition } from "ui/core/view";
|
|
import { ad as androidBackground } from "ui/styling/background";
|
|
import {
|
|
ViewCommon, layout, isEnabledProperty, originXProperty, originYProperty, automationTextProperty, isUserInteractionEnabledProperty, visibilityProperty, opacityProperty,
|
|
minWidthProperty, minHeightProperty, Length,
|
|
widthProperty, heightProperty, marginLeftProperty, marginTopProperty,
|
|
marginRightProperty, marginBottomProperty, horizontalAlignmentProperty, verticalAlignmentProperty,
|
|
rotateProperty, scaleXProperty, scaleYProperty,
|
|
translateXProperty, translateYProperty, zIndexProperty, backgroundInternalProperty,
|
|
Background, GestureTypes, GestureEventData,
|
|
traceEnabled, traceWrite, traceCategories, traceNotifyEvent, Visibility, HorizontalAlignment, VerticalAlignment
|
|
} from "./view-common";
|
|
|
|
export * from "./view-common";
|
|
|
|
const ANDROID = "_android";
|
|
const NATIVE_VIEW = "_nativeView";
|
|
const VIEW_GROUP = "_viewGroup";
|
|
|
|
// TODO: Move this class into widgets.
|
|
@Interfaces([android.view.View.OnTouchListener])
|
|
class DisableUserInteractionListener extends java.lang.Object implements android.view.View.OnTouchListener {
|
|
constructor() {
|
|
super();
|
|
return global.__native(this);
|
|
}
|
|
|
|
onTouch(view: android.view.View, event: android.view.MotionEvent): boolean {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
@Interfaces([android.view.View.OnTouchListener])
|
|
class TouchListener extends java.lang.Object implements android.view.View.OnTouchListener {
|
|
constructor(private owner: WeakRef<View>) {
|
|
super();
|
|
return global.__native(this);
|
|
}
|
|
|
|
onTouch(view: android.view.View, event: android.view.MotionEvent): boolean {
|
|
let owner = this.owner.get();
|
|
if (!owner) {
|
|
return false;
|
|
}
|
|
|
|
for (let type in owner._gestureObservers) {
|
|
let list = owner._gestureObservers[type];
|
|
for (let i = 0; i < list.length; i++) {
|
|
list[i].androidOnTouchEvent(event);
|
|
}
|
|
}
|
|
|
|
let nativeView = owner._nativeView;
|
|
if (!nativeView || !nativeView.onTouchEvent) {
|
|
return false;
|
|
}
|
|
|
|
return nativeView.onTouchEvent(event);
|
|
}
|
|
}
|
|
|
|
const disableUserInteractionListener = new DisableUserInteractionListener();
|
|
|
|
export class View extends ViewCommon {
|
|
private touchListenerIsSet: boolean;
|
|
private touchListener: android.view.View.OnTouchListener;
|
|
|
|
public nativeView: android.view.View;
|
|
|
|
// TODO: Implement unobserve that detach the touchListener.
|
|
observe(type: GestureTypes, callback: (args: GestureEventData) => void, thisArg?: any): void {
|
|
super.observe(type, callback, thisArg);
|
|
if (this.isLoaded && !this.touchListenerIsSet) {
|
|
this.setOnTouchListener();
|
|
}
|
|
}
|
|
|
|
public onLoaded() {
|
|
super.onLoaded();
|
|
this.setOnTouchListener();
|
|
}
|
|
|
|
public onUnloaded() {
|
|
if (this.touchListenerIsSet) {
|
|
this._nativeView.setOnTouchListener(null);
|
|
this.touchListenerIsSet = false;
|
|
}
|
|
|
|
this._cancelAllAnimations();
|
|
super.onUnloaded();
|
|
}
|
|
|
|
private hasGestureObservers() {
|
|
return this._gestureObservers && Object.keys(this._gestureObservers).length > 0
|
|
}
|
|
|
|
private setOnTouchListener() {
|
|
if (this._nativeView && this.hasGestureObservers()) {
|
|
this.touchListenerIsSet = true;
|
|
if (this._nativeView.setClickable) {
|
|
this._nativeView.setClickable(true);
|
|
}
|
|
|
|
this.touchListener = this.touchListener || new TouchListener(new WeakRef(this));
|
|
this._nativeView.setOnTouchListener(this.touchListener);
|
|
}
|
|
}
|
|
|
|
// TODO: revise this method
|
|
public _disposeNativeView() {
|
|
|
|
// Widgets like buttons and such have reference to their native view in both properties.
|
|
if (this[NATIVE_VIEW] === this[ANDROID]) {
|
|
(<any>this)[NATIVE_VIEW] = undefined;
|
|
}
|
|
|
|
// Handle layout and content view
|
|
if (this[VIEW_GROUP] === this[ANDROID]) {
|
|
this[VIEW_GROUP] = undefined;
|
|
}
|
|
|
|
this[ANDROID] = undefined;
|
|
}
|
|
|
|
get _nativeView(): android.view.View {
|
|
return this.android;
|
|
}
|
|
|
|
get isLayoutRequired(): boolean {
|
|
return !this.isLayoutValid;
|
|
}
|
|
|
|
get isLayoutValid(): boolean {
|
|
if (this._nativeView) {
|
|
return !this._nativeView.isLayoutRequested();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public layoutNativeView(left: number, top: number, right: number, bottom: number): void {
|
|
if (this._nativeView) {
|
|
this._nativeView.layout(left, top, right, bottom);
|
|
}
|
|
}
|
|
|
|
public requestLayout(): void {
|
|
super.requestLayout();
|
|
if (this._nativeView) {
|
|
return this._nativeView.requestLayout();
|
|
}
|
|
}
|
|
|
|
public measure(widthMeasureSpec: number, heightMeasureSpec: number): void {
|
|
super.measure(widthMeasureSpec, heightMeasureSpec);
|
|
this.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
}
|
|
|
|
public layout(left: number, top: number, right: number, bottom: number): void {
|
|
super.layout(left, top, right, bottom);
|
|
this.onLayout(left, top, right, bottom);
|
|
}
|
|
|
|
public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void {
|
|
let view = this._nativeView;
|
|
if (view) {
|
|
view.measure(widthMeasureSpec, heightMeasureSpec);
|
|
this.setMeasuredDimension(view.getMeasuredWidth(), view.getMeasuredHeight());
|
|
}
|
|
}
|
|
|
|
public onLayout(left: number, top: number, right: number, bottom: number): void {
|
|
let view = this._nativeView;
|
|
if (view) {
|
|
this.layoutNativeView(left, top, right, bottom);
|
|
}
|
|
}
|
|
|
|
_getCurrentLayoutBounds(): { left: number; top: number; right: number; bottom: number } {
|
|
if (this._nativeView) {
|
|
return {
|
|
left: this._nativeView.getLeft(),
|
|
top: this._nativeView.getTop(),
|
|
right: this._nativeView.getRight(),
|
|
bottom: this._nativeView.getBottom()
|
|
};
|
|
}
|
|
|
|
return super._getCurrentLayoutBounds();
|
|
}
|
|
|
|
public getMeasuredWidth(): number {
|
|
if (this._nativeView) {
|
|
return this._nativeView.getMeasuredWidth();
|
|
}
|
|
|
|
return super.getMeasuredWidth();
|
|
}
|
|
|
|
public getMeasuredHeight(): number {
|
|
if (this._nativeView) {
|
|
return this._nativeView.getMeasuredHeight();
|
|
}
|
|
|
|
return super.getMeasuredHeight();
|
|
}
|
|
|
|
public focus(): boolean {
|
|
if (this._nativeView) {
|
|
return this._nativeView.requestFocus();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public getLocationInWindow(): Point {
|
|
if (!this._nativeView || !this._nativeView.getWindowToken()) {
|
|
return undefined;
|
|
}
|
|
|
|
let nativeArray = (<any>Array).create("int", 2);
|
|
this._nativeView.getLocationInWindow(nativeArray);
|
|
return {
|
|
x: layout.toDeviceIndependentPixels(nativeArray[0]),
|
|
y: layout.toDeviceIndependentPixels(nativeArray[1]),
|
|
}
|
|
}
|
|
|
|
public getLocationOnScreen(): Point {
|
|
if (!this._nativeView || !this._nativeView.getWindowToken()) {
|
|
return undefined;
|
|
}
|
|
|
|
let nativeArray = (<any>Array).create("int", 2);
|
|
this._nativeView.getLocationOnScreen(nativeArray);
|
|
return {
|
|
x: layout.toDeviceIndependentPixels(nativeArray[0]),
|
|
y: layout.toDeviceIndependentPixels(nativeArray[1]),
|
|
}
|
|
}
|
|
|
|
public getLocationRelativeTo(otherView: ViewCommon): Point {
|
|
if (!this._nativeView || !this._nativeView.getWindowToken() ||
|
|
!otherView._nativeView || !otherView._nativeView.getWindowToken() ||
|
|
this._nativeView.getWindowToken() !== otherView._nativeView.getWindowToken()) {
|
|
return undefined;
|
|
}
|
|
|
|
let myArray = (<any>Array).create("int", 2);
|
|
this._nativeView.getLocationOnScreen(myArray);
|
|
let otherArray = (<any>Array).create("int", 2);
|
|
otherView._nativeView.getLocationOnScreen(otherArray);
|
|
return {
|
|
x: layout.toDeviceIndependentPixels(myArray[0] - otherArray[0]),
|
|
y: layout.toDeviceIndependentPixels(myArray[1] - otherArray[1]),
|
|
}
|
|
}
|
|
|
|
public static resolveSizeAndState(size: number, specSize: number, specMode: number, childMeasuredState: number): number {
|
|
let result = size;
|
|
switch (specMode) {
|
|
case layout.UNSPECIFIED:
|
|
result = size;
|
|
break;
|
|
|
|
case layout.AT_MOST:
|
|
if (specSize < size) {
|
|
result = specSize | layout.MEASURED_STATE_TOO_SMALL;
|
|
}
|
|
break;
|
|
|
|
case layout.EXACTLY:
|
|
result = specSize;
|
|
break;
|
|
}
|
|
|
|
return result | (childMeasuredState & layout.MEASURED_STATE_MASK);
|
|
}
|
|
|
|
get [isEnabledProperty.native](): boolean {
|
|
return this.nativeView.isEnabled();
|
|
}
|
|
set [isEnabledProperty.native](value: boolean) {
|
|
this.nativeView.setEnabled(value);
|
|
}
|
|
|
|
get [originXProperty.native](): number {
|
|
return this.nativeView.getPivotX();
|
|
}
|
|
set [originXProperty.native](value: number) {
|
|
org.nativescript.widgets.OriginPoint.setX(this.nativeView, value);
|
|
}
|
|
|
|
get [originYProperty.native](): number {
|
|
return this.nativeView.getPivotY();
|
|
}
|
|
set [originYProperty.native](value: number) {
|
|
org.nativescript.widgets.OriginPoint.setY(this.nativeView, value);
|
|
}
|
|
|
|
get [automationTextProperty.native](): string {
|
|
return this.nativeView.getContentDescription();
|
|
}
|
|
set [automationTextProperty.native](value: string) {
|
|
this.nativeView.setContentDescription(value);
|
|
}
|
|
|
|
get [isUserInteractionEnabledProperty.native](): boolean {
|
|
return true;
|
|
}
|
|
set [isUserInteractionEnabledProperty.native](value: boolean) {
|
|
if (!value) {
|
|
// User interaction is disabled -- we stop it and we do not care whether someone wants to listen for gestures.
|
|
this._nativeView.setOnTouchListener(disableUserInteractionListener);
|
|
} else {
|
|
this.setOnTouchListener();
|
|
}
|
|
}
|
|
|
|
get [visibilityProperty.native](): Visibility {
|
|
let nativeVisibility = this.nativeView.getVisibility();
|
|
switch (nativeVisibility) {
|
|
case android.view.View.VISIBLE:
|
|
return Visibility.VISIBLE;
|
|
case android.view.View.INVISIBLE:
|
|
return Visibility.HIDDEN;
|
|
case android.view.View.GONE:
|
|
return Visibility.COLLAPSE;
|
|
default:
|
|
throw new Error(`Unsupported android.view.View visibility: ${nativeVisibility}. Currently supported values are android.view.View.VISIBLE, android.view.View.INVISIBLE, android.view.View.GONE.`);
|
|
}
|
|
}
|
|
set [visibilityProperty.native](value: Visibility) {
|
|
switch (value) {
|
|
case Visibility.VISIBLE:
|
|
this.nativeView.setVisibility(android.view.View.VISIBLE);
|
|
break;
|
|
case Visibility.HIDDEN:
|
|
this.nativeView.setVisibility(android.view.View.INVISIBLE);
|
|
break;
|
|
case Visibility.COLLAPSE:
|
|
this.nativeView.setVisibility(android.view.View.GONE);
|
|
break;
|
|
default:
|
|
throw new Error(`Invalid visibility value: ${value}. Valid values are: "${Visibility.VISIBLE}", "${Visibility.HIDDEN}", "${Visibility.COLLAPSE}".`);
|
|
}
|
|
}
|
|
|
|
get [opacityProperty.native](): number {
|
|
return this.nativeView.getAlpha();
|
|
}
|
|
set [opacityProperty.native](value: number) {
|
|
this.nativeView.setAlpha(value);
|
|
}
|
|
|
|
get [horizontalAlignmentProperty.native](): HorizontalAlignment {
|
|
return <HorizontalAlignment>org.nativescript.widgets.ViewHelper.getHorizontalAlignment(this.nativeView);
|
|
}
|
|
set [horizontalAlignmentProperty.native](value: HorizontalAlignment) {
|
|
org.nativescript.widgets.ViewHelper.setHorizontalAlignment(this.nativeView, value);
|
|
}
|
|
|
|
get [verticalAlignmentProperty.native](): VerticalAlignment {
|
|
return <VerticalAlignment>org.nativescript.widgets.ViewHelper.getVerticalAlignment(this.nativeView);
|
|
}
|
|
set [verticalAlignmentProperty.native](value: VerticalAlignment) {
|
|
org.nativescript.widgets.ViewHelper.setVerticalAlignment(this.nativeView, value);
|
|
}
|
|
|
|
get [rotateProperty.native](): number {
|
|
return org.nativescript.widgets.ViewHelper.getRotate(this.nativeView);
|
|
}
|
|
set [rotateProperty.native](value: number) {
|
|
org.nativescript.widgets.ViewHelper.setRotate(this.nativeView, float(value));
|
|
}
|
|
|
|
get [scaleXProperty.native](): number {
|
|
return org.nativescript.widgets.ViewHelper.getScaleX(this.nativeView);
|
|
}
|
|
set [scaleXProperty.native](value: number) {
|
|
org.nativescript.widgets.ViewHelper.setScaleX(this.nativeView, float(value));
|
|
}
|
|
|
|
get [scaleYProperty.native](): number {
|
|
return org.nativescript.widgets.ViewHelper.getScaleY(this.nativeView);
|
|
}
|
|
set [scaleYProperty.native](value: number) {
|
|
org.nativescript.widgets.ViewHelper.setScaleY(this.nativeView, float(value));
|
|
}
|
|
|
|
get [translateXProperty.native](): number {
|
|
return org.nativescript.widgets.ViewHelper.getTranslateX(this.nativeView);
|
|
}
|
|
set [translateXProperty.native](value: number) {
|
|
org.nativescript.widgets.ViewHelper.setTranslateX(this.nativeView, float(value));
|
|
}
|
|
|
|
get [translateYProperty.native](): number {
|
|
return org.nativescript.widgets.ViewHelper.getTranslateY(this.nativeView);
|
|
}
|
|
set [translateYProperty.native](value: number) {
|
|
org.nativescript.widgets.ViewHelper.setTranslateY(this.nativeView, float(value));
|
|
}
|
|
|
|
get [zIndexProperty.native](): number {
|
|
return org.nativescript.widgets.ViewHelper.getZIndex(this.nativeView);
|
|
}
|
|
set [zIndexProperty.native](value: number) {
|
|
org.nativescript.widgets.ViewHelper.setZIndex(this.nativeView, value);
|
|
// let nativeView = this.nativeView;
|
|
// if (nativeView instanceof android.widget.Button) {
|
|
// nativeView.setStateListAnimator(null);
|
|
// }
|
|
}
|
|
|
|
get [backgroundInternalProperty.native](): android.graphics.drawable.Drawable {
|
|
return this.nativeView.getBackground();
|
|
}
|
|
set [backgroundInternalProperty.native](value: android.graphics.drawable.Drawable | Background) {
|
|
if (value instanceof android.graphics.drawable.Drawable) {
|
|
this.nativeView.setBackground(value);
|
|
} else {
|
|
androidBackground.onBackgroundOrBorderPropertyChanged(this);
|
|
}
|
|
}
|
|
|
|
set [minWidthProperty.native](value: Length) {
|
|
if (this.parent instanceof CustomLayoutView && this.parent.nativeView) {
|
|
this.parent._setChildMinWidthNative(this);
|
|
} else {
|
|
this._minWidthNative = this.minWidth;
|
|
}
|
|
}
|
|
|
|
set [minHeightProperty.native](value: Length) {
|
|
if (this.parent instanceof CustomLayoutView && this.parent.nativeView) {
|
|
this.parent._setChildMinHeightNative(this);
|
|
} else {
|
|
this._minHeightNative = this.minHeight;
|
|
}
|
|
}
|
|
}
|
|
|
|
type NativeSetter = { (view: android.view.View, value: number): void };
|
|
type NativeGetter = { (view: android.view.View): number };
|
|
const percentNotSupported = (view: android.view.View, value: number) => { throw new Error("PercentLength is not supported."); };
|
|
interface NativePercentLengthPropertyOptions {
|
|
key: string | symbol;
|
|
auto?: number;
|
|
getPixels: NativeGetter;
|
|
setPixels: NativeSetter;
|
|
setPercent?: NativeSetter
|
|
}
|
|
function createNativePercentLengthProperty({key, auto = 0, getPixels, setPixels, setPercent = percentNotSupported}: NativePercentLengthPropertyOptions) {
|
|
Object.defineProperty(View.prototype, key, {
|
|
get: function (this: View): PercentLength {
|
|
const value = getPixels(this.nativeView);
|
|
if (value == auto) { // tslint:disable-line
|
|
return "auto";
|
|
} else {
|
|
return { value, unit: "px" };
|
|
}
|
|
},
|
|
set: function (this: View, length: PercentLength) {
|
|
if (length == "auto") { // tslint:disable-line
|
|
setPixels(this.nativeView, auto);
|
|
} else if (typeof length === "number") {
|
|
setPixels(this.nativeView, length * layout.getDisplayDensity());
|
|
} else if (length.unit == "dip") { // tslint:disable-line
|
|
setPixels(this.nativeView, length.value * layout.getDisplayDensity());
|
|
} else if (length.unit == "px") { // tslint:disable-line
|
|
setPixels(this.nativeView, length.value);
|
|
} else if (length.unit == "%") { // tslint:disable-line
|
|
setPercent(this.nativeView, length.value);
|
|
} else {
|
|
throw new Error(`Unsupported PercentLength ${length}`);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
const ViewHelper = org.nativescript.widgets.ViewHelper;
|
|
|
|
createNativePercentLengthProperty({
|
|
key: marginTopProperty.native,
|
|
getPixels: ViewHelper.getMarginTop,
|
|
setPixels: ViewHelper.setMarginTop,
|
|
setPercent: ViewHelper.setMarginTopPercent
|
|
});
|
|
|
|
createNativePercentLengthProperty({
|
|
key: marginRightProperty.native,
|
|
getPixels: ViewHelper.getMarginRight,
|
|
setPixels: ViewHelper.setMarginRight,
|
|
setPercent: ViewHelper.setMarginRightPercent
|
|
});
|
|
|
|
createNativePercentLengthProperty({
|
|
key: marginBottomProperty.native,
|
|
getPixels: ViewHelper.getMarginBottom,
|
|
setPixels: ViewHelper.setMarginBottom,
|
|
setPercent: ViewHelper.setMarginBottomPercent
|
|
});
|
|
|
|
createNativePercentLengthProperty({
|
|
key: marginLeftProperty.native,
|
|
getPixels: ViewHelper.getMarginLeft,
|
|
setPixels: ViewHelper.setMarginLeft,
|
|
setPercent: ViewHelper.setMarginLeftPercent
|
|
});
|
|
|
|
createNativePercentLengthProperty({
|
|
key: widthProperty.native,
|
|
auto: android.view.ViewGroup.LayoutParams.MATCH_PARENT,
|
|
getPixels: ViewHelper.getWidth,
|
|
setPixels: ViewHelper.setWidth,
|
|
setPercent: ViewHelper.setWidthPercent
|
|
});
|
|
|
|
createNativePercentLengthProperty({
|
|
key: heightProperty.native,
|
|
auto: android.view.ViewGroup.LayoutParams.MATCH_PARENT,
|
|
getPixels: ViewHelper.getHeight,
|
|
setPixels: ViewHelper.setHeight,
|
|
setPercent: ViewHelper.setHeightPercent
|
|
});
|
|
|
|
createNativePercentLengthProperty({
|
|
key: "_minWidthNative",
|
|
getPixels: ViewHelper.getMinWidth,
|
|
setPixels: ViewHelper.setMinWidth
|
|
});
|
|
|
|
createNativePercentLengthProperty({
|
|
key: "_minHeightNative",
|
|
getPixels: ViewHelper.getMinHeight,
|
|
setPixels: ViewHelper.setMinHeight
|
|
});
|
|
|
|
export class CustomLayoutView extends View implements CustomLayoutViewDefinition {
|
|
private _viewGroup: android.view.ViewGroup;
|
|
|
|
get android(): android.view.ViewGroup {
|
|
return this._viewGroup;
|
|
}
|
|
|
|
get _nativeView(): android.view.ViewGroup {
|
|
return this._viewGroup;
|
|
}
|
|
|
|
public _createNativeView() {
|
|
this._viewGroup = new org.nativescript.widgets.ContentLayout(this._context);
|
|
}
|
|
|
|
public _addViewToNativeVisualTree(child: ViewCommon, atIndex: number = -1): boolean {
|
|
super._addViewToNativeVisualTree(child);
|
|
|
|
if (this._nativeView && child.nativeView) {
|
|
if (traceEnabled) {
|
|
traceWrite(`${this}.nativeView.addView(${child}.nativeView, ${atIndex})`, traceCategories.VisualTreeEvents);
|
|
}
|
|
this._nativeView.addView(child.nativeView, atIndex);
|
|
if (child instanceof View) {
|
|
this._updateNativeLayoutParams(child);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public _updateNativeLayoutParams(child: View): void {
|
|
this._setChildMinWidthNative(child);
|
|
this._setChildMinHeightNative(child);
|
|
}
|
|
|
|
public _setChildMinWidthNative(child: View): void {
|
|
child._minWidthNative = child.minWidth;
|
|
}
|
|
|
|
public _setChildMinHeightNative(child: View): void {
|
|
child._minHeightNative = child.minHeight;
|
|
}
|
|
|
|
public _removeViewFromNativeVisualTree(child: ViewCommon): void {
|
|
super._removeViewFromNativeVisualTree(child);
|
|
|
|
if (this._nativeView && child._nativeView) {
|
|
this._nativeView.removeView(child._nativeView);
|
|
if (traceEnabled) {
|
|
traceWrite(`${this}._nativeView.removeView(${child}._nativeView)`, traceCategories.VisualTreeEvents);
|
|
traceNotifyEvent(child, "childInLayoutRemovedFromNativeVisualTree");
|
|
}
|
|
}
|
|
}
|
|
} |