From d2ba9ed20d121275bfbc17655c9f0cf573afcbdf Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Wed, 29 Nov 2023 23:06:42 +0000 Subject: [PATCH 1/4] feat(android): Background handling improvements --- packages/core/ui/action-bar/index.android.ts | 29 +---- .../ui/activity-indicator/index.android.ts | 5 +- packages/core/ui/button/index.android.ts | 30 +---- packages/core/ui/core/view/index.android.ts | 122 ++++++++++-------- packages/core/ui/core/view/view-common.ts | 2 +- .../ui/core/view/view-helper/index.android.ts | 37 +++++- .../core/ui/core/view/view-helper/index.d.ts | 9 ++ packages/core/ui/list-picker/index.android.ts | 1 - packages/core/ui/progress/index.android.ts | 9 +- .../core/ui/segmented-bar/index.android.ts | 19 +-- packages/core/ui/slider/index.android.ts | 11 +- packages/core/ui/switch/index.android.ts | 13 +- packages/core/ui/tab-view/index.android.ts | 14 +- packages/core/ui/utils.d.ts | 6 +- packages/core/ui/utils.ios.ts | 13 +- 15 files changed, 149 insertions(+), 171 deletions(-) diff --git a/packages/core/ui/action-bar/index.android.ts b/packages/core/ui/action-bar/index.android.ts index 994f87f852..f9b16ff340 100644 --- a/packages/core/ui/action-bar/index.android.ts +++ b/packages/core/ui/action-bar/index.android.ts @@ -1,6 +1,6 @@ import { AndroidActionItemSettings, AndroidActionBarSettings as AndroidActionBarSettingsDefinition, ActionItem as ActionItemDefinition } from '.'; import { ActionItemBase, ActionBarBase, isVisible, flatProperty, traceMissingIcon, androidContentInsetLeftProperty, androidContentInsetRightProperty } from './action-bar-common'; -import { View } from '../core/view'; +import { AndroidHelper, View } from '../core/view'; import { Color } from '../../color'; import { layout, RESOURCE_PREFIX, isFontIconURI } from '../../utils'; import { colorProperty } from '../styling/style-properties'; @@ -9,6 +9,7 @@ import { Application } from '../../application'; import { isAccessibilityServiceEnabled, updateContentDescription } from '../../accessibility'; import type { Background } from '../styling/background'; import { SDK_VERSION } from '../../utils/constants'; +import { NativeScriptAndroidView } from '../utils'; export * from './action-bar-common'; @@ -216,32 +217,6 @@ export class ActionBar extends ActionBarBase { this._updateNavigationButton(); } - public _applyBackground(background: Background, isBorderDrawable, onlyColor: boolean, backgroundDrawable: any) { - const nativeView = this.nativeViewProtected; - if (backgroundDrawable && onlyColor && SDK_VERSION >= 21) { - if (isBorderDrawable && (nativeView)._cachedDrawable) { - backgroundDrawable = (nativeView)._cachedDrawable; - // we need to duplicate the drawable or we lose the "default" cached drawable - const constantState = backgroundDrawable.getConstantState(); - if (constantState) { - try { - backgroundDrawable = constantState.newDrawable(nativeView.getResources()); - // eslint-disable-next-line no-empty - } catch {} - } - nativeView.setBackground(backgroundDrawable); - } - - const backgroundColor = ((backgroundDrawable).backgroundColor = background.color.android); - backgroundDrawable.mutate(); - backgroundDrawable.setColorFilter(backgroundColor, android.graphics.PorterDuff.Mode.SRC_IN); - backgroundDrawable.invalidateSelf(); // Make sure the drawable is invalidated. Android forgets to invalidate it in some cases: toolbar - (backgroundDrawable).backgroundColor = backgroundColor; - } else { - super._applyBackground(background, isBorderDrawable, onlyColor, backgroundDrawable); - } - } - public _onAndroidItemSelected(itemId: number): boolean { // Handle home button if (this.navigationButton && itemId === R_ID_HOME) { diff --git a/packages/core/ui/activity-indicator/index.android.ts b/packages/core/ui/activity-indicator/index.android.ts index 7d283d4ab1..ba3160cd9f 100644 --- a/packages/core/ui/activity-indicator/index.android.ts +++ b/packages/core/ui/activity-indicator/index.android.ts @@ -2,6 +2,7 @@ import { ActivityIndicatorBase, busyProperty } from './activity-indicator-common import { CoreTypes } from '../../core-types'; import { Color } from '../../color'; import { colorProperty, visibilityProperty } from '../styling/style-properties'; +import { AndroidHelper } from '../core/view'; export * from './activity-indicator-common'; @@ -51,9 +52,9 @@ export class ActivityIndicator extends ActivityIndicatorBase { const color = value instanceof Color ? value.android : value; const drawable = this.nativeViewProtected.getIndeterminateDrawable().mutate(); if (color) { - drawable.setColorFilter(color, android.graphics.PorterDuff.Mode.SRC_IN); + AndroidHelper.setDrawableColor(color, drawable); } else { - drawable.clearColorFilter(); + AndroidHelper.clearDrawableColor(drawable); } } } diff --git a/packages/core/ui/button/index.android.ts b/packages/core/ui/button/index.android.ts index 869d472368..4cc22f0715 100644 --- a/packages/core/ui/button/index.android.ts +++ b/packages/core/ui/button/index.android.ts @@ -1,5 +1,5 @@ import { ButtonBase } from './button-common'; -import { PseudoClassHandler } from '../core/view'; +import { AndroidHelper, PseudoClassHandler } from '../core/view'; import { paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, Length, zIndexProperty, minWidthProperty, minHeightProperty } from '../styling/style-properties'; import { textAlignmentProperty } from '../text-base'; import { CoreTypes } from '../../core-types'; @@ -7,8 +7,8 @@ import { profile } from '../../profiling'; import { TouchGestureEventData, GestureTypes, TouchAction } from '../gestures'; import { Device } from '../../platform'; import { SDK_VERSION } from '../../utils/constants'; -import lazy from '../../utils/lazy'; import type { Background } from '../styling/background'; +import { NativeScriptAndroidView } from '../utils'; export * from './button-common'; @@ -50,32 +50,6 @@ export class Button extends ButtonBase { private _stateListAnimator: any; private _highlightedHandler: (args: TouchGestureEventData) => void; - public _applyBackground(background: Background, isBorderDrawable, onlyColor: boolean, backgroundDrawable: any) { - const nativeView = this.nativeViewProtected; - if (backgroundDrawable && onlyColor) { - if (isBorderDrawable && (nativeView)._cachedDrawable) { - backgroundDrawable = (nativeView)._cachedDrawable; - // we need to duplicate the drawable or we lose the "default" cached drawable - const constantState = backgroundDrawable.getConstantState(); - if (constantState) { - try { - backgroundDrawable = constantState.newDrawable(nativeView.getResources()); - // eslint-disable-next-line no-empty - } catch {} - } - nativeView.setBackground(backgroundDrawable); - } - - const backgroundColor = ((backgroundDrawable).backgroundColor = background.color.android); - backgroundDrawable.mutate(); - backgroundDrawable.setColorFilter(backgroundColor, android.graphics.PorterDuff.Mode.SRC_IN); - backgroundDrawable.invalidateSelf(); // Make sure the drawable is invalidated. Android forgets to invalidate it in some cases: toolbar - (backgroundDrawable).backgroundColor = backgroundColor; - } else { - super._applyBackground(background, isBorderDrawable, onlyColor, backgroundDrawable); - } - } - @profile public createNativeView() { if (!AndroidButton) { diff --git a/packages/core/ui/core/view/index.android.ts b/packages/core/ui/core/view/index.android.ts index 3054ef729d..f37eaedcb2 100644 --- a/packages/core/ui/core/view/index.android.ts +++ b/packages/core/ui/core/view/index.android.ts @@ -3,7 +3,7 @@ import type { Point, CustomLayoutView as CustomLayoutViewDefinition } from '.'; import type { GestureTypes, GestureEventData } from '../../gestures'; // Types. -import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty } from './view-common'; +import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty, AndroidHelper } from './view-common'; import { paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, Length } from '../../styling/style-properties'; import { layout } from '../../../utils'; import { Trace } from '../../../trace'; @@ -19,13 +19,13 @@ import { topmost } from '../../frame/frame-stack'; import { Screen } from '../../../platform'; import { AndroidActivityBackPressedEventData, Application } from '../../../application'; import { Device } from '../../../platform'; -import lazy from '../../../utils/lazy'; import { accessibilityEnabledProperty, accessibilityHiddenProperty, accessibilityHintProperty, accessibilityIdentifierProperty, accessibilityLabelProperty, accessibilityLanguageProperty, accessibilityLiveRegionProperty, accessibilityMediaSessionProperty, accessibilityRoleProperty, accessibilityStateProperty, accessibilityValueProperty } from '../../../accessibility/accessibility-properties'; import { AccessibilityLiveRegion, AccessibilityRole, AndroidAccessibilityEvent, isAccessibilityServiceEnabled, sendAccessibilityEvent, updateAccessibilityProperties, updateContentDescription, AccessibilityState } from '../../../accessibility'; import * as Utils from '../../../utils'; import { SDK_VERSION } from '../../../utils/constants'; import { BoxShadow } from '../../styling/box-shadow'; import { _setAndroidFragmentTransitions, _getAnimatedEntries, _updateTransitions, _reverseTransitions, _clearEntry, _clearFragment, addNativeTransitionListener } from '../../frame/fragment.transitions'; +import { NativeScriptAndroidView } from '../../utils'; export * from './view-common'; // helpers (these are okay re-exported here) @@ -57,10 +57,6 @@ const modalMap = new Map(); let TouchListener: TouchListener; let DialogFragment: DialogFragment; -interface AndroidView { - _cachedDrawable: android.graphics.drawable.Drawable.ConstantState | android.graphics.drawable.Drawable; -} - interface DialogOptions { owner: View; fullscreen: boolean; @@ -175,8 +171,10 @@ function initializeDialogFragment() { } public onCreate(savedInstanceState: android.os.Bundle) { super.onCreate(savedInstanceState); - var ownerId = this.getArguments()?.getInt(DOMID); - var options = getModalOptions(ownerId); + + const ownerId = this.getArguments()?.getInt(DOMID); + const options = getModalOptions(ownerId); + // The teardown when the activity is destroyed happens after the state is saved, but is not recoverable, // Cancel the native dialog in this case or the app will crash with subsequent errors. if (savedInstanceState != null && options === undefined) { @@ -461,7 +459,11 @@ export class View extends ViewCommon { public initNativeView(): void { super.initNativeView(); + + const nativeView = this.nativeViewProtected; + this._isClickable = this.nativeViewProtected.isClickable(); + if (this.needsOnLayoutChangeListener()) { this.setOnLayoutChangeListener(); } @@ -1088,21 +1090,7 @@ export class View extends ViewCommon { [backgroundInternalProperty.getDefault](): android.graphics.drawable.Drawable { const nativeView = this.nativeViewProtected; - const drawable = nativeView.getBackground(); - if (drawable) { - const constantState = drawable.getConstantState(); - if (constantState) { - try { - return constantState.newDrawable(nativeView.getResources()); - } catch (e) { - return drawable; - } - } else { - return drawable; - } - } - - return null; + return AndroidHelper.getCopyOrDrawable(nativeView.getBackground(), nativeView.getResources()); } [backgroundInternalProperty.setNative](value: android.graphics.drawable.Drawable | Background) { this._redrawNativeBackground(value); @@ -1124,29 +1112,54 @@ export class View extends ViewCommon { } } - public _applyBackground(background: Background, isBorderDrawable: boolean, onlyColor: boolean, backgroundDrawable: any) { - const nativeView = this.nativeViewProtected; - if (!isBorderDrawable && onlyColor) { - if (backgroundDrawable && backgroundDrawable.setColor) { - // android.graphics.drawable.ColorDrawable - backgroundDrawable.setColor(background.color.android); + public _applyBackground(background: Background, isBorderDrawable: boolean, onlyColor: boolean, backgroundDrawable: android.graphics.drawable.Drawable) { + const nativeView = this.nativeViewProtected; + + if (onlyColor) { + const backgroundColor = background.color.android; + + if (isBorderDrawable) { + // We need to duplicate the drawable or we lose the "default" cached drawable + backgroundDrawable = nativeView._cachedDrawable != null ? AndroidHelper.getCopyOrDrawable(nativeView._cachedDrawable, nativeView.getResources()) : null; + nativeView.setBackground(backgroundDrawable); + } + + if (backgroundDrawable) { + backgroundDrawable.mutate(); + + AndroidHelper.setDrawableColor(backgroundColor, backgroundDrawable); backgroundDrawable.invalidateSelf(); } else { - nativeView.setBackgroundColor(background.color.android); + nativeView.setBackgroundColor(backgroundColor); } - } else if (!background.isEmpty()) { - if (isBorderDrawable) { - // org.nativescript.widgets.BorderDrawable - refreshBorderDrawable(this, backgroundDrawable); + } else { + if (background.clearFlags & BackgroundClearFlags.CLEAR_BACKGROUND_COLOR) { + if (backgroundDrawable) { + backgroundDrawable.mutate(); + + AndroidHelper.clearDrawableColor(backgroundDrawable); + backgroundDrawable.invalidateSelf(); + } else { + nativeView.setBackgroundColor(-1); + } + } + + if (background.isEmpty()) { + // Reset background to default if not already set + const defaultDrawable = nativeView._cachedDrawable ?? null; + if (backgroundDrawable !== defaultDrawable) { + nativeView.setBackground(defaultDrawable); + } } else { - backgroundDrawable = new org.nativescript.widgets.BorderDrawable(layout.getDisplayDensity(), this.toString()); - refreshBorderDrawable(this, backgroundDrawable); - nativeView.setBackground(backgroundDrawable); + if (isBorderDrawable) { + // org.nativescript.widgets.BorderDrawable + refreshBorderDrawable(this, backgroundDrawable); + } else { + const borderDrawable = new org.nativescript.widgets.BorderDrawable(layout.getDisplayDensity(), this.toString()); + refreshBorderDrawable(this, borderDrawable); + nativeView.setBackground(borderDrawable); + } } - } else { - //empty background let's reset - const cachedDrawable = (nativeView)._cachedDrawable; - nativeView.setBackground(cachedDrawable); } } @@ -1185,31 +1198,26 @@ export class View extends ViewCommon { } protected onBackgroundOrBorderPropertyChanged() { - const nativeView = < - android.view.View & { - _cachedDrawable: android.graphics.drawable.Drawable.ConstantState | android.graphics.drawable.Drawable; - } - >this.nativeViewProtected; + const nativeView = this.nativeViewProtected; if (!nativeView) { return; } const background = this.style.backgroundInternal; + const drawable = nativeView.getBackground(); + const isBorderDrawable = drawable instanceof org.nativescript.widgets.BorderDrawable; - if (background.clearFlags & BackgroundClearFlags.CLEAR_BOX_SHADOW || background.clearFlags & BackgroundClearFlags.CLEAR_BACKGROUND_COLOR) { - // clear background if we're clearing the box shadow - // or the background has been removed - nativeView.setBackground(null); + // Use undefined as not set. getBackground will never return undefined only Drawable or null; + if (nativeView._cachedDrawable === undefined) { + nativeView._cachedDrawable = drawable; } - const drawable = nativeView.getBackground(); - const androidView = (this) as AndroidView; - // use undefined as not set. getBackground will never return undefined only Drawable or null; - if (androidView._cachedDrawable === undefined && drawable) { - const constantState = drawable.getConstantState(); - androidView._cachedDrawable = constantState || drawable; + if (background.clearFlags & BackgroundClearFlags.CLEAR_BOX_SHADOW) { + // Clear background if we're clearing the box shadow + if (drawable instanceof org.nativescript.widgets.BoxShadowDrawable) { + nativeView.setBackground(nativeView._cachedDrawable ?? null); + } } - const isBorderDrawable = drawable instanceof org.nativescript.widgets.BorderDrawable; // prettier-ignore const onlyColor = !background.hasBorderWidth() diff --git a/packages/core/ui/core/view/view-common.ts b/packages/core/ui/core/view/view-common.ts index 021ff67273..809df37166 100644 --- a/packages/core/ui/core/view/view-common.ts +++ b/packages/core/ui/core/view/view-common.ts @@ -1139,7 +1139,7 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { public _redrawNativeBackground(value: any): void { // } - public _applyBackground(background, isBorderDrawable: boolean, onlyColor: boolean, backgroundDrawable: any) { + public _applyBackground(background, isBorderDrawable: boolean, onlyColor: boolean, backgroundDrawable: android.graphics.drawable.Drawable) { // } diff --git a/packages/core/ui/core/view/view-helper/index.android.ts b/packages/core/ui/core/view/view-helper/index.android.ts index 396f529cc3..7e3c582cf6 100644 --- a/packages/core/ui/core/view/view-helper/index.android.ts +++ b/packages/core/ui/core/view/view-helper/index.android.ts @@ -1,2 +1,37 @@ +import { Trace } from '../../../../trace'; +import { SDK_VERSION } from '../../../../utils/constants'; + export * from './view-helper-common'; -export const IOSHelper = 0; + +const androidxGraphics = androidx.core.graphics; + +export class AndroidHelper { + static setDrawableColor(color: number, drawable: android.graphics.drawable.Drawable, blendMode?: androidx.core.graphics.BlendModeCompat): void { + // ColorDrawable is an old layer that had support for setColorFilter on API 21 + if (SDK_VERSION < 21 && drawable instanceof android.graphics.drawable.ColorDrawable) { + drawable.setColor(color); + } else { + drawable.setColorFilter(androidxGraphics.BlendModeColorFilterCompat.createBlendModeColorFilterCompat(color, blendMode ?? androidxGraphics.BlendModeCompat.SRC_IN)); + } + } + + static clearDrawableColor(drawable: android.graphics.drawable.Drawable): void { + // ColorDrawable is an old layer that had support for setColorFilter on API 21 + if (SDK_VERSION < 21 && drawable instanceof android.graphics.drawable.ColorDrawable) { + drawable.setColor(-1); + } else { + drawable.clearColorFilter(); + } + } + + static getCopyOrDrawable(drawable: android.graphics.drawable.Drawable, resources?: android.content.res.Resources): android.graphics.drawable.Drawable { + if (drawable) { + const constantState = drawable.getConstantState(); + if (constantState) { + return resources ? constantState.newDrawable(resources) : constantState.newDrawable(); + } + } + + return drawable; + } +} diff --git a/packages/core/ui/core/view/view-helper/index.d.ts b/packages/core/ui/core/view/view-helper/index.d.ts index 0635b7415e..303e451108 100644 --- a/packages/core/ui/core/view/view-helper/index.d.ts +++ b/packages/core/ui/core/view/view-helper/index.d.ts @@ -33,6 +33,15 @@ export class ViewHelper { public static combineMeasuredStates(curState: number, newState): number; } +/** + * Various Android view helper methods + */ +export namespace AndroidHelper { + export function setDrawableColor(color: number, drawable: any /* android.graphics.drawable.Drawable */, blendMode?: any /* androidx.core.graphics.BlendModeCompat */): void; + export function clearDrawableColor(drawable: any /* android.graphics.drawable.Drawable */): void; + export function getCopyOrDrawable(drawable: any /* android.graphics.drawable.Drawable */, resources?: any /* android.content.res.Resources */): any; /* android.graphics.drawable.Drawable */ +} + /** * Various iOS view helper methods */ diff --git a/packages/core/ui/list-picker/index.android.ts b/packages/core/ui/list-picker/index.android.ts index cd4332d42f..dce59d3b27 100644 --- a/packages/core/ui/list-picker/index.android.ts +++ b/packages/core/ui/list-picker/index.android.ts @@ -2,7 +2,6 @@ import { ListPickerBase, selectedIndexProperty, itemsProperty, ItemsSource } fro import { colorProperty } from '../styling/style-properties'; import { Color } from '../../color'; import { Device } from '../../platform'; -import lazy from '../../utils/lazy'; export * from './list-picker-common'; diff --git a/packages/core/ui/progress/index.android.ts b/packages/core/ui/progress/index.android.ts index 1e14535f50..eade177023 100644 --- a/packages/core/ui/progress/index.android.ts +++ b/packages/core/ui/progress/index.android.ts @@ -1,6 +1,7 @@ import { ProgressBase, valueProperty, maxValueProperty } from './progress-common'; import { Color } from '../../color'; import { colorProperty, backgroundColorProperty, backgroundInternalProperty } from '../styling/style-properties'; +import { AndroidHelper } from '../core/view'; export * from './progress-common'; @@ -37,9 +38,9 @@ export class Progress extends ProgressBase { } if (value instanceof Color) { - progressDrawable.setColorFilter(value.android, android.graphics.PorterDuff.Mode.SRC_IN); + AndroidHelper.setDrawableColor(value.android, progressDrawable); } else { - progressDrawable.clearColorFilter(); + AndroidHelper.clearDrawableColor(progressDrawable); } } @@ -56,9 +57,9 @@ export class Progress extends ProgressBase { const backgroundDrawable = progressDrawable.getDrawable(0); if (backgroundDrawable) { if (value instanceof Color) { - backgroundDrawable.setColorFilter(value.android, android.graphics.PorterDuff.Mode.SRC_IN); + AndroidHelper.setDrawableColor(value.android, backgroundDrawable); } else { - backgroundDrawable.clearColorFilter(); + AndroidHelper.clearDrawableColor(backgroundDrawable); } } } diff --git a/packages/core/ui/segmented-bar/index.android.ts b/packages/core/ui/segmented-bar/index.android.ts index 400334cb73..99448baf56 100644 --- a/packages/core/ui/segmented-bar/index.android.ts +++ b/packages/core/ui/segmented-bar/index.android.ts @@ -1,6 +1,6 @@ import { Font } from '../styling/font'; import { SegmentedBarItemBase, SegmentedBarBase, selectedIndexProperty, itemsProperty, selectedBackgroundColorProperty, selectedTextColorProperty } from './segmented-bar-common'; -import { isEnabledProperty } from '../core/view'; +import { AndroidHelper, isEnabledProperty } from '../core/view'; import { colorProperty, fontInternalProperty, fontSizeProperty } from '../styling/style-properties'; import { Color } from '../../color'; import { layout } from '../../utils'; @@ -170,8 +170,8 @@ export class SegmentedBarItem extends SegmentedBarItemBase { const color = value.android; const backgroundDrawable = viewGroup.getBackground(); if (SDK_VERSION > 21 && backgroundDrawable) { - const newDrawable = tryCloneDrawable(backgroundDrawable, nativeView.getResources()); - newDrawable.setColorFilter(new android.graphics.Paint(color).getColorFilter()); + const newDrawable = AndroidHelper.getCopyOrDrawable(backgroundDrawable, nativeView.getResources()); + AndroidHelper.setDrawableColor(color, newDrawable); viewGroup.setBackground(newDrawable); } else { const stateDrawable = new android.graphics.drawable.StateListDrawable(); @@ -183,23 +183,12 @@ export class SegmentedBarItem extends SegmentedBarItemBase { viewGroup.setBackground(stateDrawable); } } else { - const backgroundDrawable = tryCloneDrawable(value, nativeView.getResources()); + const backgroundDrawable = AndroidHelper.getCopyOrDrawable(value, nativeView.getResources()); viewGroup.setBackground(backgroundDrawable); } } } -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; -} - export class SegmentedBar extends SegmentedBarBase { nativeViewProtected: android.widget.TabHost; private _tabContentFactory: android.widget.TabHost.TabContentFactory; diff --git a/packages/core/ui/slider/index.android.ts b/packages/core/ui/slider/index.android.ts index b303f37e45..f76b283f78 100644 --- a/packages/core/ui/slider/index.android.ts +++ b/packages/core/ui/slider/index.android.ts @@ -2,6 +2,7 @@ import { Background } from '../styling/background'; import { SliderBase, valueProperty, minValueProperty, maxValueProperty } from './slider-common'; import { colorProperty, backgroundColorProperty, backgroundInternalProperty } from '../styling/style-properties'; import { Color } from '../../color'; +import { AndroidHelper } from '../core/view'; export * from './slider-common'; @@ -117,10 +118,11 @@ export class Slider extends SliderBase { return -1; } [colorProperty.setNative](value: number | Color) { + const drawable = this.nativeViewProtected.getThumb(); if (value instanceof Color) { - this.nativeViewProtected.getThumb().setColorFilter(value.android, android.graphics.PorterDuff.Mode.SRC_IN); + AndroidHelper.setDrawableColor(value.android, drawable); } else { - this.nativeViewProtected.getThumb().clearColorFilter(); + AndroidHelper.clearDrawableColor(drawable); } } @@ -128,10 +130,11 @@ export class Slider extends SliderBase { return -1; } [backgroundColorProperty.setNative](value: number | Color) { + const drawable = this.nativeViewProtected.getProgressDrawable(); if (value instanceof Color) { - this.nativeViewProtected.getProgressDrawable().setColorFilter(value.android, android.graphics.PorterDuff.Mode.SRC_IN); + AndroidHelper.setDrawableColor(value.android, drawable); } else { - this.nativeViewProtected.getProgressDrawable().clearColorFilter(); + AndroidHelper.clearDrawableColor(drawable); } } diff --git a/packages/core/ui/switch/index.android.ts b/packages/core/ui/switch/index.android.ts index 1f9670a66d..20017170d7 100644 --- a/packages/core/ui/switch/index.android.ts +++ b/packages/core/ui/switch/index.android.ts @@ -1,6 +1,7 @@ import { SwitchBase, checkedProperty, offBackgroundColorProperty } from './switch-common'; import { colorProperty, backgroundColorProperty, backgroundInternalProperty } from '../styling/style-properties'; import { Color } from '../../color'; +import { AndroidHelper } from '../core/view'; export * from './switch-common'; @@ -60,11 +61,11 @@ export class Switch extends SwitchBase { private setNativeBackgroundColor(value: string | number | Color) { if (this.nativeViewProtected) { + const drawable = this.nativeViewProtected.getTrackDrawable(); if (value instanceof Color) { - // todo: use https://developer.android.com/reference/androidx/core/graphics/BlendModeColorFilterCompat - this.nativeViewProtected.getTrackDrawable().setColorFilter(value.android, android.graphics.PorterDuff.Mode.SRC_OVER); + AndroidHelper.setDrawableColor(value.android, drawable, androidx.core.graphics.BlendModeCompat.SRC_OVER); } else { - this.nativeViewProtected.getTrackDrawable().clearColorFilter(); + AndroidHelper.clearDrawableColor(drawable); } } } @@ -92,11 +93,11 @@ export class Switch extends SwitchBase { return -1; } [colorProperty.setNative](value: number | Color) { + const drawable = this.nativeViewProtected.getThumbDrawable(); if (value instanceof Color) { - // todo: use https://developer.android.com/reference/androidx/core/graphics/BlendModeColorFilterCompat - this.nativeViewProtected.getThumbDrawable().setColorFilter(value.android, android.graphics.PorterDuff.Mode.SRC_ATOP); + AndroidHelper.setDrawableColor(value.android, drawable, androidx.core.graphics.BlendModeCompat.SRC_ATOP); } else { - this.nativeViewProtected.getThumbDrawable().clearColorFilter(); + AndroidHelper.clearDrawableColor(drawable); } } diff --git a/packages/core/ui/tab-view/index.android.ts b/packages/core/ui/tab-view/index.android.ts index 030bfd6f94..b15dd0ce8e 100644 --- a/packages/core/ui/tab-view/index.android.ts +++ b/packages/core/ui/tab-view/index.android.ts @@ -11,6 +11,7 @@ import { fontSizeProperty, fontInternalProperty } from '../styling/style-propert import { RESOURCE_PREFIX, ad, layout } from '../../utils'; import { Frame } from '../frame'; import { Application } from '../../application'; +import { AndroidHelper } from '../core/view'; export * from './tab-view-common'; @@ -757,7 +758,7 @@ export class TabView extends TabViewBase { if (value instanceof Color) { this._tabLayout.setBackgroundColor(value.android); } else { - this._tabLayout.setBackground(tryCloneDrawable(value, this.nativeViewProtected.getResources())); + this._tabLayout.setBackground(AndroidHelper.getCopyOrDrawable(value, this.nativeViewProtected.getResources())); } } @@ -797,14 +798,3 @@ export class TabView extends TabViewBase { tabLayout.setSelectedIndicatorColors([color]); } } - -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; -} diff --git a/packages/core/ui/utils.d.ts b/packages/core/ui/utils.d.ts index a48305c181..e8743f1108 100644 --- a/packages/core/ui/utils.d.ts +++ b/packages/core/ui/utils.d.ts @@ -1,4 +1,8 @@ -export interface NativeScriptUIView extends UIView { +export interface NativeScriptAndroidView extends android.view.View { + _cachedDrawable: android.graphics.drawable.Drawable; +} + +export interface NativeScriptUIView extends UIView { hasNonUniformBorder: boolean; hasNonUniformBorderColor: boolean; borderLayer: CAShapeLayer; diff --git a/packages/core/ui/utils.ios.ts b/packages/core/ui/utils.ios.ts index 2ee51bb540..8cc22a2358 100644 --- a/packages/core/ui/utils.ios.ts +++ b/packages/core/ui/utils.ios.ts @@ -1,18 +1,7 @@ import { Screen } from '../platform'; import * as utils from '../utils'; import { LinearGradient } from './styling/linear-gradient'; - -interface NativeScriptUIView extends UIView { - hasNonUniformBorder: boolean; - hasNonUniformBorderColor: boolean; - borderLayer: CAShapeLayer; - - maskType: ios.LayerMaskType; - originalMask: CALayer; - - gradientLayer: CAGradientLayer; - outerShadowContainerLayer: CALayer; -} +import { NativeScriptUIView } from './utils'; export namespace ios { export type LayerMaskType = 'BORDER' | 'CLIP_PATH'; From 488d8e573e185969a784232a5587e7d5b79d1027 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Wed, 29 Nov 2023 23:08:31 +0000 Subject: [PATCH 2/4] chore: Removed unused line --- packages/core/ui/core/view/index.android.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/core/ui/core/view/index.android.ts b/packages/core/ui/core/view/index.android.ts index f37eaedcb2..6caa723328 100644 --- a/packages/core/ui/core/view/index.android.ts +++ b/packages/core/ui/core/view/index.android.ts @@ -460,8 +460,6 @@ export class View extends ViewCommon { public initNativeView(): void { super.initNativeView(); - const nativeView = this.nativeViewProtected; - this._isClickable = this.nativeViewProtected.isClickable(); if (this.needsOnLayoutChangeListener()) { From a72ef8eba05926805d9b17bf5e47cdc9723ec1de Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Tue, 12 Mar 2024 10:55:23 +0000 Subject: [PATCH 3/4] ref: Added function for retrieving drawable background color --- .../ui/button/button-tests-native.android.ts | 11 ++----- .../ui/label/label-tests-native.android.ts | 15 +++------ .../text-field-tests-native.android.ts | 11 ++----- .../text-view-tests-native.android.ts | 10 ++---- .../ui/core/view/view-helper/index.android.ts | 31 +++++++++++++++++-- .../core/ui/core/view/view-helper/index.d.ts | 1 + .../ui/core/view/view-helper/index.ios.ts | 1 + packages/core/ui/index.ts | 2 +- 8 files changed, 42 insertions(+), 40 deletions(-) diff --git a/apps/automated/src/ui/button/button-tests-native.android.ts b/apps/automated/src/ui/button/button-tests-native.android.ts index c5c03d74c8..ac63dff76a 100644 --- a/apps/automated/src/ui/button/button-tests-native.android.ts +++ b/apps/automated/src/ui/button/button-tests-native.android.ts @@ -1,4 +1,5 @@ import { Color, Button, Utils, CoreTypes } from '@nativescript/core'; +import { AndroidHelper } from '@nativescript/core/ui/core/view'; export function getNativeText(button: Button): string { return button.android.getText(); @@ -19,15 +20,7 @@ export function getNativeColor(button: Button): Color { } export function getNativeBackgroundColor(button: Button): Color { - let bg = button.android.getBackground(); - if (bg instanceof org.nativescript.widgets.BorderDrawable) { - return new Color(bg.getBackgroundColor()); - } else if (bg instanceof android.graphics.drawable.ColorDrawable) { - console.log(bg); - return new Color(bg.getColor()); - } else { - return new Color(bg.backgroundColor); - } + return AndroidHelper.getDrawableColor(button.android.getBackground()); } export function getNativeTextAlignment(button: Button): string { diff --git a/apps/automated/src/ui/label/label-tests-native.android.ts b/apps/automated/src/ui/label/label-tests-native.android.ts index 426bddf5d3..c91ea1afd1 100644 --- a/apps/automated/src/ui/label/label-tests-native.android.ts +++ b/apps/automated/src/ui/label/label-tests-native.android.ts @@ -1,6 +1,6 @@ import * as labelModule from '@nativescript/core/ui/label'; -import { CoreTypes } from '@nativescript/core'; -import * as colorModule from '@nativescript/core/color'; +import { Color, CoreTypes } from '@nativescript/core'; +import { AndroidHelper } from '@nativescript/core/ui/core/view'; export function getNativeTextAlignment(label: labelModule.Label): string { let gravity = label.android.getGravity(); @@ -20,13 +20,6 @@ export function getNativeTextAlignment(label: labelModule.Label): string { return 'unexpected value'; } -export function getNativeBackgroundColor(label: labelModule.Label): colorModule.Color { - let bg = label.android.getBackground(); - if (bg instanceof org.nativescript.widgets.BorderDrawable) { - return new colorModule.Color(bg.getBackgroundColor()); - } else if (bg instanceof android.graphics.drawable.ColorDrawable) { - return new colorModule.Color(bg.getColor()); - } else { - return new colorModule.Color(bg.backgroundColor); - } +export function getNativeBackgroundColor(label: labelModule.Label): Color { + return AndroidHelper.getDrawableColor(label.android.getBackground()); } diff --git a/apps/automated/src/ui/text-field/text-field-tests-native.android.ts b/apps/automated/src/ui/text-field/text-field-tests-native.android.ts index bb8114ccf3..2f4b7742aa 100644 --- a/apps/automated/src/ui/text-field/text-field-tests-native.android.ts +++ b/apps/automated/src/ui/text-field/text-field-tests-native.android.ts @@ -1,4 +1,5 @@ import { TextField, Color, Utils, CoreTypes } from '@nativescript/core'; +import { AndroidHelper } from '@nativescript/core/ui/core/view'; export function getNativeText(textField: TextField): string { return textField.android.getText().toString(); @@ -29,15 +30,7 @@ export function getNativePlaceholderColor(textField: TextField): Color { } export function getNativeBackgroundColor(textField: TextField): Color { - let bg = textField.android.getBackground(); - if (bg instanceof org.nativescript.widgets.BorderDrawable) { - return new Color(bg.getBackgroundColor()); - } else if (bg instanceof android.graphics.drawable.ColorDrawable) { - console.log(bg); - return new Color(bg.getColor()); - } else { - return new Color(bg.backgroundColor); - } + return AndroidHelper.getDrawableColor(textField.android.getBackground()); } export function getNativeTextAlignment(textField: TextField): string { diff --git a/apps/automated/src/ui/text-view/text-view-tests-native.android.ts b/apps/automated/src/ui/text-view/text-view-tests-native.android.ts index 25b28656f3..2b4c9b7d74 100644 --- a/apps/automated/src/ui/text-view/text-view-tests-native.android.ts +++ b/apps/automated/src/ui/text-view/text-view-tests-native.android.ts @@ -1,4 +1,5 @@ import { TextView, Color, Utils, CoreTypes } from '@nativescript/core'; +import { AndroidHelper } from '@nativescript/core/ui/core/view'; export function getNativeText(textView: TextView): string { return textView.android.getText().toString(); @@ -27,14 +28,7 @@ export function getNativeColor(textView: TextView): Color { } export function getNativeBackgroundColor(textView: TextView): Color { - let bg = textView.android.getBackground(); - if (bg instanceof org.nativescript.widgets.BorderDrawable) { - return new Color(bg.getBackgroundColor()); - } else if (bg instanceof android.graphics.drawable.ColorDrawable) { - return new Color(bg.getColor()); - } else { - return new Color(bg.backgroundColor); - } + return AndroidHelper.getDrawableColor(textView.android.getBackground()); } export function getNativeTextAlignment(textView: TextView): string { diff --git a/packages/core/ui/core/view/view-helper/index.android.ts b/packages/core/ui/core/view/view-helper/index.android.ts index 7e3c582cf6..efe07ecc24 100644 --- a/packages/core/ui/core/view/view-helper/index.android.ts +++ b/packages/core/ui/core/view/view-helper/index.android.ts @@ -1,26 +1,53 @@ +import { Color } from '../../../../color'; import { Trace } from '../../../../trace'; import { SDK_VERSION } from '../../../../utils/constants'; export * from './view-helper-common'; +export const IOSHelper = 0; const androidxGraphics = androidx.core.graphics; export class AndroidHelper { + static getDrawableColor(drawable: android.graphics.drawable.Drawable): Color { + if (!drawable) { + return null; + } + + let color: number; + + if (drawable instanceof org.nativescript.widgets.BorderDrawable) { + color = drawable.getBackgroundColor(); + } else if (drawable instanceof android.graphics.drawable.ColorDrawable) { + color = drawable.getColor(); + } else { + // This is a way to retrieve drawable color when set using color filter + color = (drawable as any)._backgroundColor; + } + + return new Color(color); + } + static setDrawableColor(color: number, drawable: android.graphics.drawable.Drawable, blendMode?: androidx.core.graphics.BlendModeCompat): void { - // ColorDrawable is an old layer that had support for setColorFilter on API 21 + // ColorDrawable is an older class that had support for setColorFilter on API 21 if (SDK_VERSION < 21 && drawable instanceof android.graphics.drawable.ColorDrawable) { drawable.setColor(color); } else { drawable.setColorFilter(androidxGraphics.BlendModeColorFilterCompat.createBlendModeColorFilterCompat(color, blendMode ?? androidxGraphics.BlendModeCompat.SRC_IN)); + + // This is a way to retrieve drawable color when set using color filter + (drawable as any)._backgroundColor = color; } } static clearDrawableColor(drawable: android.graphics.drawable.Drawable): void { - // ColorDrawable is an old layer that had support for setColorFilter on API 21 + // ColorDrawable is an older class that had support for setColorFilter on API 21 if (SDK_VERSION < 21 && drawable instanceof android.graphics.drawable.ColorDrawable) { drawable.setColor(-1); } else { drawable.clearColorFilter(); + + // This is a way to retrieve drawable color when set using color filter + delete (drawable as any)._backgroundColor; } } diff --git a/packages/core/ui/core/view/view-helper/index.d.ts b/packages/core/ui/core/view/view-helper/index.d.ts index 303e451108..e0f246f49e 100644 --- a/packages/core/ui/core/view/view-helper/index.d.ts +++ b/packages/core/ui/core/view/view-helper/index.d.ts @@ -37,6 +37,7 @@ export class ViewHelper { * Various Android view helper methods */ export namespace AndroidHelper { + export function getDrawableColor(drawable: any /* android.graphics.drawable.Drawable */): Color; export function setDrawableColor(color: number, drawable: any /* android.graphics.drawable.Drawable */, blendMode?: any /* androidx.core.graphics.BlendModeCompat */): void; export function clearDrawableColor(drawable: any /* android.graphics.drawable.Drawable */): void; export function getCopyOrDrawable(drawable: any /* android.graphics.drawable.Drawable */, resources?: any /* android.content.res.Resources */): any; /* android.graphics.drawable.Drawable */ diff --git a/packages/core/ui/core/view/view-helper/index.ios.ts b/packages/core/ui/core/view/view-helper/index.ios.ts index 53d8f214bf..7b29b408cd 100644 --- a/packages/core/ui/core/view/view-helper/index.ios.ts +++ b/packages/core/ui/core/view/view-helper/index.ios.ts @@ -7,6 +7,7 @@ import { ios as iOSUtils, layout } from '../../../../utils'; import { Trace } from '../../../../trace'; export * from './view-helper-common'; +export const AndroidHelper = 0; @NativeClass class UILayoutViewController extends UIViewController { diff --git a/packages/core/ui/index.ts b/packages/core/ui/index.ts index 703df4784d..918f65bef2 100644 --- a/packages/core/ui/index.ts +++ b/packages/core/ui/index.ts @@ -13,7 +13,7 @@ export type { BindingOptions } from './core/bindable'; export { ControlStateChangeListener } from './core/control-state-change'; export { ViewBase, eachDescendant, getAncestor, getViewById, booleanConverter, querySelectorAll } from './core/view-base'; export type { ShowModalOptions } from './core/view-base'; -export { View, CSSType, ContainerView, ViewHelper, IOSHelper, isUserInteractionEnabledProperty, PseudoClassHandler, CustomLayoutView } from './core/view'; +export { View, CSSType, ContainerView, ViewHelper, AndroidHelper, IOSHelper, isUserInteractionEnabledProperty, PseudoClassHandler, CustomLayoutView } from './core/view'; export type { Template, KeyedTemplate, ShownModallyData, AddArrayFromBuilder, AddChildFromBuilder, Size } from './core/view'; export { Property, CoercibleProperty, InheritedProperty, CssProperty, InheritedCssProperty, ShorthandProperty, CssAnimationProperty, unsetValue, makeParser, makeValidator } from './core/properties'; export { addWeakEventListener, removeWeakEventListener } from './core/weak-event-listener'; From b2c2d035d61e06f81685cc067d4ef383b93ddffd Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Fri, 15 Mar 2024 20:11:50 +0000 Subject: [PATCH 4/4] chore: Making use of AndroidHelper functions in segmented bar module --- .../segmented-bar-tests-native.android.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/apps/automated/src/ui/segmented-bar/segmented-bar-tests-native.android.ts b/apps/automated/src/ui/segmented-bar/segmented-bar-tests-native.android.ts index b22fb4eb72..5b1ec9b90e 100644 --- a/apps/automated/src/ui/segmented-bar/segmented-bar-tests-native.android.ts +++ b/apps/automated/src/ui/segmented-bar/segmented-bar-tests-native.android.ts @@ -1,5 +1,6 @@ import * as segmentedBarModule from '@nativescript/core/ui/segmented-bar'; import { Color } from '@nativescript/core'; +import { AndroidHelper } from '@nativescript/core/ui/core/view'; export function getNativeTabWidget(bar: segmentedBarModule.SegmentedBar): android.widget.TabWidget { return (bar.android).getTabWidget(); @@ -44,7 +45,7 @@ export var checkBackgroundColorUpdatedAfterItemSelected = function (bar: segment const item = bar.items[i]; const textView = item?.nativeViewProtected; - const newDrawable = tryCloneDrawable(view.getBackground(), view.getResources()); + const newDrawable = AndroidHelper.getCopyOrDrawable(view.getBackground(), view.getResources()); newDrawable.setColorFilter(new android.graphics.Paint(bar.selectedBackgroundColor.android).getColorFilter()); if (bar.selectedIndex == i) { @@ -68,16 +69,5 @@ export var checkBackgroundColorUpdatedAfterItemSelected = function (bar: segment } } - 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; - } - return isValid === 0; };