From d699bf0033d557d67e8f95d5111f651b00b6a58f Mon Sep 17 00:00:00 2001 From: Martin Guillon Date: Wed, 17 Feb 2021 16:05:56 +0100 Subject: [PATCH] fix: refactor background handling. The idea is for views to handle sepecial case themselves. I realised that android.widget.Button was materialized even if you were not using it because there was a test for instanceof in the background handling. Now the special background handling is done in Button and ActionBar --- packages/core/ui/action-bar/index.android.ts | 26 +++++- packages/core/ui/button/index.android.ts | 21 +++++ packages/core/ui/core/view/index.android.ts | 66 +++++++++++--- packages/core/ui/core/view/index.d.ts | 6 ++ packages/core/ui/core/view/view-common.ts | 3 + .../core/ui/styling/background.android.ts | 87 +------------------ 6 files changed, 112 insertions(+), 97 deletions(-) diff --git a/packages/core/ui/action-bar/index.android.ts b/packages/core/ui/action-bar/index.android.ts index 3fe45f003..e8faf9c0c 100644 --- a/packages/core/ui/action-bar/index.android.ts +++ b/packages/core/ui/action-bar/index.android.ts @@ -6,6 +6,9 @@ import { layout, RESOURCE_PREFIX, isFontIconURI } from '../../utils'; import { colorProperty } from '../styling/style-properties'; import { ImageSource } from '../../image-source'; import * as application from '../../application'; +import type { Background } from 'ui/styling/background'; +import { Device } from '../../platform'; +import lazy from '../../utils/lazy'; export * from './action-bar-common'; @@ -13,6 +16,9 @@ const R_ID_HOME = 0x0102002c; const ACTION_ITEM_ID_OFFSET = 10000; const DEFAULT_ELEVATION = 4; +const sdkVersion = lazy(() => parseInt(Device.sdkVersion)); + + let AppCompatTextView; let actionItemIdGenerator = ACTION_ITEM_ID_OFFSET; function generateItemId(): number { @@ -58,7 +64,7 @@ function initializeMenuItemClickListener(): void { return; } - apiLevel = android.os.Build.VERSION.SDK_INT; + apiLevel = sdkVersion(); AppCompatTextView = androidx.appcompat.widget.AppCompatTextView; @@ -215,6 +221,24 @@ export class ActionBar extends ActionBarBase { this._updateNavigationButton(); } + public _applyBackground(background: Background, isBorderDrawable, onlyColor: boolean, backgroundDrawable: any) { + const nativeView = this.nativeViewProtected; + if (backgroundDrawable && onlyColor && sdkVersion() >= 21) { + if (isBorderDrawable && (nativeView)._cachedDrawable) { + backgroundDrawable = (nativeView)._cachedDrawable.newDrawable(nativeView.getResources()); + 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/button/index.android.ts b/packages/core/ui/button/index.android.ts index 29cd44bba..036737b3d 100644 --- a/packages/core/ui/button/index.android.ts +++ b/packages/core/ui/button/index.android.ts @@ -7,6 +7,7 @@ import { profile } from '../../profiling'; import { TouchGestureEventData, GestureTypes, TouchAction } from '../gestures'; import { Device } from '../../platform'; import lazy from '../../utils/lazy'; +import type { Background } from 'ui/styling/background'; export * from './button-common'; @@ -58,6 +59,26 @@ 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; + console.log('_applyBackground', nativeView, backgroundDrawable, onlyColor, isBorderDrawable); + if (backgroundDrawable && onlyColor) { + if (isBorderDrawable && (nativeView)._cachedDrawable) { + backgroundDrawable = (nativeView)._cachedDrawable.newDrawable(nativeView.getResources()); + 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 8cf7f0932..cd1ee6d74 100644 --- a/packages/core/ui/core/view/index.android.ts +++ b/packages/core/ui/core/view/index.android.ts @@ -42,6 +42,7 @@ import { } from '../../styling/style-properties'; import { Background, ad as androidBackground } from '../../styling/background'; +import { refreshBorderDrawable } from '../../styling/background.android'; import { profile } from '../../../profiling'; import { topmost } from '../../frame/frame-stack'; import { Screen } from '../../../platform'; @@ -1002,21 +1003,19 @@ export class View extends ViewCommon { [backgroundInternalProperty.getDefault](): android.graphics.drawable.Drawable { const nativeView = this.nativeViewProtected; - const drawable = nativeView.getBackground(); + let drawable = nativeView.getBackground(); if (drawable) { const constantState = drawable.getConstantState(); if (constantState) { try { - return constantState.newDrawable(nativeView.getResources()); - } catch (e) { - return drawable; - } - } else { - return drawable; + drawable = constantState.newDrawable(nativeView.getResources()); + // eslint-disable-next-line no-empty + } catch {} } } + (nativeView)._cachedDrawable = drawable; - return null; + return drawable; } [backgroundInternalProperty.setNative](value: android.graphics.drawable.Drawable | Background) { this._redrawNativeBackground(value); @@ -1038,9 +1037,56 @@ 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) { + backgroundDrawable.setColor(background.color.android); + backgroundDrawable.invalidateSelf(); + } else { + nativeView.setBackgroundColor(background.color.android); + } + } else if (!background.isEmpty()) { + if (!isBorderDrawable) { + backgroundDrawable = new org.nativescript.widgets.BorderDrawable(layout.getDisplayDensity(), this.toString()); + refreshBorderDrawable(this, backgroundDrawable); + nativeView.setBackground(backgroundDrawable); + } else { + refreshBorderDrawable(this, backgroundDrawable); + } + } else { + //empty background let s reset + const cachedDrawable = (nativeView)._cachedDrawable; + nativeView.setBackground(cachedDrawable); + } + } + protected onBackgroundOrBorderPropertyChanged() { + const nativeView = this.nativeViewProtected; + if (!nativeView) { + return; + } + + const background = this.style.backgroundInternal; + const drawable = nativeView.getBackground(); + const isBorderDrawable = drawable instanceof org.nativescript.widgets.BorderDrawable; + const onlyColor = !background.hasBorderWidth() && !background.hasBorderRadius() && !background.clipPath && !background.image && !!background.color; + this._applyBackground(background, isBorderDrawable, onlyColor, drawable); + + // TODO: Can we move BorderWidths as separate native setter? + // This way we could skip setPadding if borderWidth is not changed. + const leftPadding = Math.ceil(this.effectiveBorderLeftWidth + this.effectivePaddingLeft); + const topPadding = Math.ceil(this.effectiveBorderTopWidth + this.effectivePaddingTop); + const rightPadding = Math.ceil(this.effectiveBorderRightWidth + this.effectivePaddingRight); + const bottomPadding = Math.ceil(this.effectiveBorderBottomWidth + this.effectivePaddingBottom); + if (this._isPaddingRelative) { + nativeView.setPaddingRelative(leftPadding, topPadding, rightPadding, bottomPadding); + } else { + nativeView.setPadding(leftPadding, topPadding, rightPadding, bottomPadding); + } + } _redrawNativeBackground(value: android.graphics.drawable.Drawable | Background): void { if (value instanceof Background) { - androidBackground.onBackgroundOrBorderPropertyChanged(this); + this.onBackgroundOrBorderPropertyChanged(); } else { const nativeView = this.nativeViewProtected; nativeView.setBackground(value); @@ -1056,8 +1102,6 @@ export class View extends ViewCommon { } else { nativeView.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom); } - - (nativeView).background = undefined; } } } diff --git a/packages/core/ui/core/view/index.d.ts b/packages/core/ui/core/view/index.d.ts index 4ddd4c1ca..bd2341347 100644 --- a/packages/core/ui/core/view/index.d.ts +++ b/packages/core/ui/core/view/index.d.ts @@ -742,6 +742,12 @@ export abstract class View extends ViewBase { * @private */ _redrawNativeBackground(value: any): void; + /** + * @private + * method called on Android to apply the background. This allows custom handling + */ + _applyBackground(background: Background, isBorderDrawable: boolean, onlyColor: boolean, backgroundDrawable: any); + /** * @private */ diff --git a/packages/core/ui/core/view/view-common.ts b/packages/core/ui/core/view/view-common.ts index 552b92c0b..466b20520 100644 --- a/packages/core/ui/core/view/view-common.ts +++ b/packages/core/ui/core/view/view-common.ts @@ -991,6 +991,9 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { public _redrawNativeBackground(value: any): void { // } + public _applyBackground(background, isBorderDrawable: boolean, onlyColor: boolean, backgroundDrawable: any) { + // + } _onAttachedToWindow(): void { // diff --git a/packages/core/ui/styling/background.android.ts b/packages/core/ui/styling/background.android.ts index 65459fdb9..647ac9671 100644 --- a/packages/core/ui/styling/background.android.ts +++ b/packages/core/ui/styling/background.android.ts @@ -15,90 +15,7 @@ interface AndroidView { // TODO: Change this implementation to use // We are using "ad" here to avoid namespace collision with the global android object export namespace ad { - let SDK: number; - function getSDK() { - if (!SDK) { - SDK = android.os.Build.VERSION.SDK_INT; - } - - return SDK; - } - - function isSetColorFilterOnlyWidget(nativeView: android.view.View): boolean { - return ( - nativeView instanceof android.widget.Button || (nativeView instanceof androidx.appcompat.widget.Toolbar && getSDK() >= 21) // There is an issue with the DrawableContainer which was fixed for API version 21 and above: https://code.google.com/p/android/issues/detail?id=60183 - ); - } - - export function onBackgroundOrBorderPropertyChanged(view: View) { - const nativeView = view.nativeViewProtected; - if (!nativeView) { - return; - } - - const background = view.style.backgroundInternal; - let drawable = nativeView.getBackground(); - const androidView = (view) 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; - } - const isBorderDrawable = drawable instanceof org.nativescript.widgets.BorderDrawable; - const onlyColor = !background.hasBorderWidth() && !background.hasBorderRadius() && !background.clipPath && !background.image && !!background.color; - if (!isBorderDrawable && drawable instanceof android.graphics.drawable.ColorDrawable && onlyColor) { - drawable.setColor(background.color.android); - drawable.invalidateSelf(); - } else if (isSetColorFilterOnlyWidget(nativeView) && drawable && onlyColor) { - if (isBorderDrawable && androidView._cachedDrawable) { - if (!(androidView._cachedDrawable instanceof android.graphics.drawable.Drawable.ConstantState)) { - return; - } - - drawable = androidView._cachedDrawable.newDrawable(nativeView.getResources()); - nativeView.setBackground(drawable); - } - - const backgroundColor = ((drawable).backgroundColor = background.color.android); - drawable.mutate(); - drawable.setColorFilter(backgroundColor, android.graphics.PorterDuff.Mode.SRC_IN); - drawable.invalidateSelf(); // Make sure the drawable is invalidated. Android forgets to invalidate it in some cases: toolbar - (drawable).backgroundColor = backgroundColor; - } else if (!isBorderDrawable && onlyColor) { - // this is the fastest way to change only background color - nativeView.setBackgroundColor(background.color.android); - } else if (!background.isEmpty()) { - let backgroundDrawable = drawable as org.nativescript.widgets.BorderDrawable; - if (!isBorderDrawable) { - backgroundDrawable = new org.nativescript.widgets.BorderDrawable(layout.getDisplayDensity(), view.toString()); - refreshBorderDrawable(view, backgroundDrawable); - nativeView.setBackground(backgroundDrawable); - } else { - refreshBorderDrawable(view, backgroundDrawable); - } - } else { - const cachedDrawable = androidView._cachedDrawable; - let defaultDrawable: android.graphics.drawable.Drawable = null; - if (cachedDrawable) { - if (cachedDrawable instanceof android.graphics.drawable.Drawable.ConstantState) { - defaultDrawable = cachedDrawable.newDrawable(nativeView.getResources()); - } else if (cachedDrawable instanceof android.graphics.drawable.Drawable) { - defaultDrawable = cachedDrawable; - } - } - - nativeView.setBackground(defaultDrawable); - } - - // TODO: Can we move BorderWidths as separate native setter? - // This way we could skip setPadding if borderWidth is not changed. - const leftPadding = Math.ceil(view.effectiveBorderLeftWidth + view.effectivePaddingLeft); - const topPadding = Math.ceil(view.effectiveBorderTopWidth + view.effectivePaddingTop); - const rightPadding = Math.ceil(view.effectiveBorderRightWidth + view.effectivePaddingRight); - const bottomPadding = Math.ceil(view.effectiveBorderBottomWidth + view.effectivePaddingBottom); - - nativeView.setPadding(leftPadding, topPadding, rightPadding, bottomPadding); - } + } function fromBase64(source: string): android.graphics.Bitmap { @@ -129,7 +46,7 @@ function fromGradient(gradient: LinearGradient): org.nativescript.widgets.Linear } const pattern = /url\(('|")(.*?)\1\)/; -function refreshBorderDrawable(this: void, view: View, borderDrawable: org.nativescript.widgets.BorderDrawable) { +export function refreshBorderDrawable(this: void, view: View, borderDrawable: org.nativescript.widgets.BorderDrawable) { const nativeView = view.nativeViewProtected; const context = nativeView.getContext();