feat: improved background handling (#9615)

This commit is contained in:
Igor Randjelovic
2022-01-14 21:13:16 +01:00
committed by Nathan Walker
parent 6c06c77618
commit dde9e02cac
6 changed files with 71 additions and 144 deletions

View File

@ -7,6 +7,9 @@ import { colorProperty } from '../styling/style-properties';
import { ImageSource } from '../../image-source'; import { ImageSource } from '../../image-source';
import * as application from '../../application'; import * as application from '../../application';
import { isAccessibilityServiceEnabled, updateContentDescription } from '../../accessibility'; import { isAccessibilityServiceEnabled, updateContentDescription } from '../../accessibility';
import type { Background } from '../styling/background';
import { Device } from '../../platform';
import lazy from '../../utils/lazy';
export * from './action-bar-common'; export * from './action-bar-common';
@ -14,6 +17,8 @@ const R_ID_HOME = 0x0102002c;
const ACTION_ITEM_ID_OFFSET = 10000; const ACTION_ITEM_ID_OFFSET = 10000;
const DEFAULT_ELEVATION = 4; const DEFAULT_ELEVATION = 4;
const sdkVersion = lazy(() => parseInt(Device.sdkVersion));
let AppCompatTextView; let AppCompatTextView;
let actionItemIdGenerator = ACTION_ITEM_ID_OFFSET; let actionItemIdGenerator = ACTION_ITEM_ID_OFFSET;
function generateItemId(): number { function generateItemId(): number {
@ -59,7 +64,7 @@ function initializeMenuItemClickListener(): void {
return; return;
} }
apiLevel = android.os.Build.VERSION.SDK_INT; apiLevel = sdkVersion();
AppCompatTextView = androidx.appcompat.widget.AppCompatTextView; AppCompatTextView = androidx.appcompat.widget.AppCompatTextView;
@ -216,6 +221,32 @@ export class ActionBar extends ActionBarBase {
this._updateNavigationButton(); this._updateNavigationButton();
} }
public _applyBackground(background: Background, isBorderDrawable, onlyColor: boolean, backgroundDrawable: any) {
const nativeView = this.nativeViewProtected;
if (backgroundDrawable && onlyColor && sdkVersion() >= 21) {
if (isBorderDrawable && (<any>nativeView)._cachedDrawable) {
backgroundDrawable = (<any>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 = ((<any>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
(<any>backgroundDrawable).backgroundColor = backgroundColor;
} else {
super._applyBackground(background, isBorderDrawable, onlyColor, backgroundDrawable);
}
}
public _onAndroidItemSelected(itemId: number): boolean { public _onAndroidItemSelected(itemId: number): boolean {
// Handle home button // Handle home button
if (this.navigationButton && itemId === R_ID_HOME) { if (this.navigationButton && itemId === R_ID_HOME) {

View File

@ -7,6 +7,7 @@ import { profile } from '../../profiling';
import { TouchGestureEventData, GestureTypes, TouchAction } from '../gestures'; import { TouchGestureEventData, GestureTypes, TouchAction } from '../gestures';
import { Device } from '../../platform'; import { Device } from '../../platform';
import lazy from '../../utils/lazy'; import lazy from '../../utils/lazy';
import type { Background } from 'ui/styling/background';
export * from './button-common'; export * from './button-common';
@ -58,6 +59,32 @@ export class Button extends ButtonBase {
private _stateListAnimator: any; private _stateListAnimator: any;
private _highlightedHandler: (args: TouchGestureEventData) => void; private _highlightedHandler: (args: TouchGestureEventData) => void;
public _applyBackground(background: Background, isBorderDrawable, onlyColor: boolean, backgroundDrawable: any) {
const nativeView = this.nativeViewProtected;
if (backgroundDrawable && onlyColor) {
if (isBorderDrawable && (<any>nativeView)._cachedDrawable) {
backgroundDrawable = (<any>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 = ((<any>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
(<any>backgroundDrawable).backgroundColor = backgroundColor;
} else {
super._applyBackground(background, isBorderDrawable, onlyColor, backgroundDrawable);
}
}
@profile @profile
public createNativeView() { public createNativeView() {
if (!AndroidButton) { if (!AndroidButton) {

View File

@ -1210,11 +1210,13 @@ export class View extends ViewCommon {
const topPadding = Math.ceil(this.effectiveBorderTopWidth + this.effectivePaddingTop); const topPadding = Math.ceil(this.effectiveBorderTopWidth + this.effectivePaddingTop);
const rightPadding = Math.ceil(this.effectiveBorderRightWidth + this.effectivePaddingRight); const rightPadding = Math.ceil(this.effectiveBorderRightWidth + this.effectivePaddingRight);
const bottomPadding = Math.ceil(this.effectiveBorderBottomWidth + this.effectivePaddingBottom); const bottomPadding = Math.ceil(this.effectiveBorderBottomWidth + this.effectivePaddingBottom);
if (this._isPaddingRelative) { if (this._isPaddingRelative) {
nativeView.setPaddingRelative(leftPadding, topPadding, rightPadding, bottomPadding); nativeView.setPaddingRelative(leftPadding, topPadding, rightPadding, bottomPadding);
} else { } else {
nativeView.setPadding(leftPadding, topPadding, rightPadding, bottomPadding); nativeView.setPadding(leftPadding, topPadding, rightPadding, bottomPadding);
} }
// reset clear flags // reset clear flags
background.clearFlags = BackgroundClearFlags.NONE; background.clearFlags = BackgroundClearFlags.NONE;
} }

View File

@ -830,6 +830,12 @@ export abstract class View extends ViewCommon {
* @private * @private
*/ */
_redrawNativeBackground(value: any): void; _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 * @private
*/ */

View File

@ -1082,6 +1082,9 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
public _redrawNativeBackground(value: any): void { public _redrawNativeBackground(value: any): void {
// //
} }
public _applyBackground(background, isBorderDrawable: boolean, onlyColor: boolean, backgroundDrawable: any) {
//
}
_onAttachedToWindow(): void { _onAttachedToWindow(): void {
// //

View File

@ -1,141 +1,11 @@
import { View } from '../core/view'; import { View } from '../core/view';
import { LinearGradient } from './linear-gradient'; import { LinearGradient } from './linear-gradient';
import { CoreTypes } from '../../core-types'; import { isDataURI, isFileOrResourcePath, RESOURCE_PREFIX, FILE_PREFIX } from '../../utils';
import { isDataURI, isFileOrResourcePath, layout, RESOURCE_PREFIX, FILE_PREFIX } from '../../utils';
import { parse } from '../../css-value'; import { parse } from '../../css-value';
import { path, knownFolders } from '../../file-system'; import { path, knownFolders } from '../../file-system';
import * as application from '../../application'; import * as application from '../../application';
import { profile } from '../../profiling';
import { CSSShadow } from './css-shadow';
import { Length } from './style-properties';
import { BackgroundClearFlags } from './background-common';
export * from './background-common'; export * from './background-common';
interface AndroidView {
_cachedDrawable: android.graphics.drawable.Drawable.ConstantState | android.graphics.drawable.Drawable;
}
// 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 {
// prettier-ignore
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 = <android.view.View>view.nativeViewProtected;
if (!nativeView) {
return;
}
const background = view.style.backgroundInternal;
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);
}
let drawable = nativeView.getBackground();
const androidView = (<any>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;
// prettier-ignore
const onlyColor = !background.hasBorderWidth()
&& !background.hasBorderRadius()
&& !background.hasBoxShadow()
&& !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 = ((<any>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
(<any>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;
if (drawable instanceof org.nativescript.widgets.BoxShadowDrawable) {
// if we have BoxShadow's we have to get the underlying drawable
backgroundDrawable = drawable.getWrappedDrawable();
}
if (backgroundDrawable instanceof org.nativescript.widgets.BorderDrawable) {
refreshBorderDrawable(view, backgroundDrawable);
} else {
backgroundDrawable = new org.nativescript.widgets.BorderDrawable(layout.getDisplayDensity(), view.toString());
refreshBorderDrawable(view, <org.nativescript.widgets.BorderDrawable>backgroundDrawable);
nativeView.setBackground(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);
}
if (background.hasBoxShadow()) {
drawBoxShadow(nativeView, view, background.getBoxShadow());
}
// 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);
// reset clear flags
background.clearFlags = BackgroundClearFlags.NONE;
}
}
function fromBase64(source: string): android.graphics.Bitmap { function fromBase64(source: string): android.graphics.Bitmap {
const bytes = android.util.Base64.decode(source, android.util.Base64.DEFAULT); const bytes = android.util.Base64.decode(source, android.util.Base64.DEFAULT);
@ -253,18 +123,6 @@ function createNativeCSSValueArray(css: string): androidNative.Array<org.natives
return nativeArray; return nativeArray;
} }
function drawBoxShadow(nativeView: android.view.View, view: View, boxShadow: CSSShadow) {
const config = {
shadowColor: boxShadow.color.android,
cornerRadius: Length.toDevicePixels(view.borderRadius as CoreTypes.LengthType, 0.0),
spreadRadius: Length.toDevicePixels(boxShadow.spreadRadius, 0.0),
blurRadius: Length.toDevicePixels(boxShadow.blurRadius, 0.0),
offsetX: Length.toDevicePixels(boxShadow.offsetX, 0.0),
offsetY: Length.toDevicePixels(boxShadow.offsetY, 0.0),
};
org.nativescript.widgets.Utils.drawBoxShadow(nativeView, JSON.stringify(config));
}
export enum CacheMode { export enum CacheMode {
none, none,
memory, memory,