mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 11:01:21 +08:00
feat: improved background handling (#9615)
This commit is contained in:

committed by
Nathan Walker

parent
6c06c77618
commit
dde9e02cac
@ -7,6 +7,9 @@ import { colorProperty } from '../styling/style-properties';
|
||||
import { ImageSource } from '../../image-source';
|
||||
import * as application from '../../application';
|
||||
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';
|
||||
|
||||
@ -14,6 +17,8 @@ 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 {
|
||||
@ -59,7 +64,7 @@ function initializeMenuItemClickListener(): void {
|
||||
return;
|
||||
}
|
||||
|
||||
apiLevel = android.os.Build.VERSION.SDK_INT;
|
||||
apiLevel = sdkVersion();
|
||||
|
||||
AppCompatTextView = androidx.appcompat.widget.AppCompatTextView;
|
||||
|
||||
@ -216,6 +221,32 @@ 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 && (<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 {
|
||||
// Handle home button
|
||||
if (this.navigationButton && itemId === R_ID_HOME) {
|
||||
|
@ -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,32 @@ 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 && (<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
|
||||
public createNativeView() {
|
||||
if (!AndroidButton) {
|
||||
|
@ -1210,11 +1210,13 @@ export class View extends ViewCommon {
|
||||
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);
|
||||
}
|
||||
|
||||
// reset clear flags
|
||||
background.clearFlags = BackgroundClearFlags.NONE;
|
||||
}
|
||||
|
6
packages/core/ui/core/view/index.d.ts
vendored
6
packages/core/ui/core/view/index.d.ts
vendored
@ -830,6 +830,12 @@ export abstract class View extends ViewCommon {
|
||||
* @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
|
||||
*/
|
||||
|
@ -1082,6 +1082,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 {
|
||||
//
|
||||
|
@ -1,141 +1,11 @@
|
||||
import { View } from '../core/view';
|
||||
import { LinearGradient } from './linear-gradient';
|
||||
import { CoreTypes } from '../../core-types';
|
||||
import { isDataURI, isFileOrResourcePath, layout, RESOURCE_PREFIX, FILE_PREFIX } from '../../utils';
|
||||
import { isDataURI, isFileOrResourcePath, RESOURCE_PREFIX, FILE_PREFIX } from '../../utils';
|
||||
import { parse } from '../../css-value';
|
||||
import { path, knownFolders } from '../../file-system';
|
||||
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';
|
||||
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
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 {
|
||||
none,
|
||||
memory,
|
||||
|
Reference in New Issue
Block a user