Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: improved background handling #9615

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion packages/core/ui/action-bar/index.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ 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';

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 {
Expand Down Expand Up @@ -59,7 +64,7 @@ function initializeMenuItemClickListener(): void {
return;
}

apiLevel = android.os.Build.VERSION.SDK_INT;
apiLevel = sdkVersion();

AppCompatTextView = androidx.appcompat.widget.AppCompatTextView;

Expand Down Expand Up @@ -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) {
Expand Down
27 changes: 27 additions & 0 deletions packages/core/ui/button/index.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 3 additions & 1 deletion packages/core/ui/core/view/index.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ 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 { CSSShadow } from 'ui/styling/css-shadow';
import { CSSShadow } from '../../styling/css-shadow';

export * from './view-common';
// helpers (these are okay re-exported here)
Expand Down Expand Up @@ -1211,11 +1211,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;
}
Expand Down
6 changes: 6 additions & 0 deletions packages/core/ui/core/view/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
3 changes: 3 additions & 0 deletions packages/core/ui/core/view/view-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,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 {
//
Expand Down
144 changes: 1 addition & 143 deletions packages/core/ui/styling/background.android.ts
Original file line number Diff line number Diff line change
@@ -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);

Expand Down Expand Up @@ -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,
Expand Down