feat(android): elevation shadow support (#7136)

This commit is contained in:
Eduardo Speroni
2019-05-10 05:05:28 -03:00
committed by Manol Donev
parent f8754913c3
commit cf533a7b6d
12 changed files with 236 additions and 11 deletions

View File

@@ -0,0 +1,43 @@
Label, Button {
text-align: center;
padding: 10;
margin: 16;
background-color: #bbb;
}
.elevation-0 {
android-elevation: 0;
}
.elevation-2 {
android-elevation: 2;
}
.elevation-4 {
android-elevation: 4;
}
.elevation-4:highlighted {
android-elevation: 2;
}
.elevation-8 {
android-elevation: 8;
}
.elevation-10 {
android-elevation: 10;
}
.pressed-z-10 {
android-dynamic-elevation-offset: 10;
}
.round {
color: #fff;
background-color: #ff1744;
border-radius: 50%; /* TODO kills elevation */
width: 80;
height: 80;
android-elevation: 8;
}

View File

@@ -0,0 +1,20 @@
import { Button } from "tns-core-modules/ui/button/button";
import { EventData } from "tns-core-modules/ui/page/page";
const states = [
{ class: "", text: "default elevation" },
{ class: "elevation-10", text: "elevetion 10" },
{ class: "elevation-10 pressed-z-10", text: "elevetion 10 pressed-z 10" },
{ class: "elevation-0", text: "elevetion 0" },
]
let currentState = 0;
export function buttonTap(args: EventData) {
let btn: Button = args.object as Button;
currentState++;
if (currentState >= states.length) {
currentState = 0;
}
btn.className = states[currentState].class;
btn.text = states[currentState].text;
}

View File

@@ -0,0 +1,25 @@
<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="onLoaded">
<ScrollView>
<StackLayout backgroundColor="aliceblue" androidElevation="2" padding="10" margin="20">
<Button text="default elevation" tap="buttonTap"/>
<Button class="elevation-0" text="elevation 0"/>
<Label class="elevation-2" text="Label with elevation 2"/>
<StackLayout androidElevation="4" backgroundColor="#ff1744" horizontalAlignment="center" margin="20">
<Button class="elevation-2" text="elevation 2"/>
<Button class="elevation-4" text="elevation 4"/>
</StackLayout>
<StackLayout orientation="horizontal" horizontalAlignment="center">
<Button class="round" androidElevation="4" text="el. 4"/>
<Button class="elevation-8 round" text="el. 8"/>
</StackLayout>
<Label class="elevation-8" text="elevation 8"/>
<Button class="elevation-8" text="elevation 8, with radius" borderRadius="4"/>
<Button class="elevation-10" text="elevation 10"/>
</StackLayout>
</ScrollView>
</Page>

View File

@@ -38,6 +38,7 @@ export function loadExamples() {
examples.set("margins-paddings-with-percentage", "css/margins-paddings-with-percentage");
examples.set("padding-and-border", "css/padding-and-border");
examples.set("combinators", "css/combinators");
examples.set("elevation", "css/elevation");
examples.set("styled-formatted-text", "css/styled-formatted-text");
examples.set("non-uniform-radius", "css/non-uniform-radius");
examples.set("missing-background-image", "css/missing-background-image");

View File

@@ -3,11 +3,16 @@
paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty,
Length, zIndexProperty, textAlignmentProperty, TextAlignment
} from "./button-common";
import { androidElevationProperty, androidDynamicElevationOffsetProperty } from "../styling/style-properties";
import { profile } from "../../profiling";
import { TouchGestureEventData, GestureTypes, TouchAction } from "../gestures";
import { device } from "../../platform";
import lazy from "../../utils/lazy";
export * from "./button-common";
const sdkVersion = lazy(() => parseInt(device.sdkVersion));
interface ClickListener {
new(owner: Button): android.view.View.OnClickListener;
}
@@ -146,9 +151,28 @@ export class Button extends ButtonBase {
org.nativescript.widgets.ViewHelper.setZIndex(this.nativeViewProtected, value);
}
[androidElevationProperty.getDefault](): number {
if (sdkVersion() < 21) {
return 0;
}
// NOTE: Button widget has StateListAnimator that defines the elevation value and
// at the time of the getDefault() query the animator is not applied yet so we
// return the hardcoded @dimen/button_elevation_material value 2dp here instead
return 2;
}
[androidDynamicElevationOffsetProperty.getDefault](): number {
if (sdkVersion() < 21) {
return 0;
}
return 4; // 4dp @dimen/button_pressed_z_material
}
[textAlignmentProperty.setNative](value: TextAlignment) {
// Button initial value is center.
const newValue = value === "initial" ? "center" : value;
super[textAlignmentProperty.setNative](newValue);
}
}
}

View File

@@ -1018,7 +1018,7 @@ export const classNameProperty = new Property<ViewBase, string>({
valueChanged(view: ViewBase, oldValue: string, newValue: string) {
let classes = view.cssClasses;
classes.clear();
if (typeof newValue === "string") {
if (typeof newValue === "string" && newValue !== "") {
newValue.split(" ").forEach(c => classes.add(c));
}
view._onCssStateChange();

View File

@@ -683,6 +683,20 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
this.style.scaleY = value;
}
get androidElevation(): number {
return this.style.androidElevation;
}
set androidElevation(value: number) {
this.style.androidElevation = value;
}
get androidDynamicElevationOffset(): number {
return this.style.androidDynamicElevationOffset;
}
set androidDynamicElevationOffset(value: number) {
this.style.androidDynamicElevationOffset = value;
}
//END Style property shortcuts
public automationText: string;

View File

@@ -15,19 +15,27 @@ import {
minWidthProperty, minHeightProperty, widthProperty, heightProperty,
marginLeftProperty, marginTopProperty, marginRightProperty, marginBottomProperty,
rotateProperty, scaleXProperty, scaleYProperty, translateXProperty, translateYProperty,
zIndexProperty, backgroundInternalProperty
zIndexProperty, backgroundInternalProperty, androidElevationProperty, androidDynamicElevationOffsetProperty
} from "../../styling/style-properties";
import { Background, ad as androidBackground } from "../../styling/background";
import { profile } from "../../../profiling";
import { topmost } from "../../frame/frame-stack";
import { AndroidActivityBackPressedEventData, android as androidApp } from "../../../application";
import { device } from "../../../platform";
import lazy from "../../../utils/lazy";
export * from "./view-common";
const DOMID = "_domId";
const androidBackPressedEvent = "androidBackPressed";
const shortAnimTime = 17694720; // android.R.integer.config_shortAnimTime
const statePressed = 16842919; // android.R.attr.state_pressed
const stateEnabled = 16842910; // android.R.attr.state_enabled
const sdkVersion = lazy(() => parseInt(device.sdkVersion));
const modalMap = new Map<number, DialogOptions>();
let TouchListener: TouchListener;
@@ -255,6 +263,8 @@ export class View extends ViewCommon {
private layoutChangeListener: android.view.View.OnLayoutChangeListener;
private _manager: android.support.v4.app.FragmentManager;
private _rootManager: android.support.v4.app.FragmentManager;
private _originalElevation: number;
private _originalStateListAnimator: any; /* android.animation.StateListAnimator; */
nativeViewProtected: android.view.View;
@@ -709,6 +719,74 @@ export class View extends ViewCommon {
this.nativeViewProtected.setAlpha(float(value));
}
[androidElevationProperty.getDefault](): number {
if (sdkVersion() < 21) {
return 0;
}
// NOTE: overriden in Button implementation as for widgets with StateListAnimator (Button)
// nativeView.getElevation() returns 0 at the time of the getDefault() query
return layout.toDeviceIndependentPixels((<any>this.nativeViewProtected).getElevation());
}
[androidElevationProperty.setNative](value: number) {
if (sdkVersion() < 21) {
return;
}
this.refreshStateListAnimator();
}
[androidDynamicElevationOffsetProperty.getDefault](): number {
return 0;
}
[androidDynamicElevationOffsetProperty.setNative](value: number) {
if (sdkVersion() < 21) {
return;
}
this.refreshStateListAnimator();
}
private refreshStateListAnimator() {
const nativeView: any = this.nativeViewProtected;
const ObjectAnimator = android.animation.ObjectAnimator;
const AnimatorSet = android.animation.AnimatorSet;
const duration = nativeView.getContext().getResources().getInteger(shortAnimTime) / 2;
const elevation = layout.toDevicePixels(this.androidElevation || 0);
const z = layout.toDevicePixels(0);
const pressedZ = layout.toDevicePixels(this.androidDynamicElevationOffset || 0);
const pressedSet = new AnimatorSet();
pressedSet.playTogether(java.util.Arrays.asList([
ObjectAnimator.ofFloat(nativeView, "translationZ", [pressedZ])
.setDuration(duration),
ObjectAnimator.ofFloat(nativeView, "elevation", [elevation])
.setDuration(0),
]));
const notPressedSet = new AnimatorSet();
notPressedSet.playTogether(java.util.Arrays.asList([
ObjectAnimator.ofFloat(nativeView, "translationZ", [z])
.setDuration(duration),
ObjectAnimator.ofFloat(nativeView, "elevation", [elevation])
.setDuration(0),
]));
const defaultSet = new AnimatorSet();
defaultSet.playTogether(java.util.Arrays.asList([
ObjectAnimator.ofFloat(nativeView, "translationZ", [0]).setDuration(0),
ObjectAnimator.ofFloat(nativeView, "elevation", [0]).setDuration(0),
]));
const stateListAnimator = new (<any>android.animation).StateListAnimator();
stateListAnimator.addState([statePressed, stateEnabled], pressedSet);
stateListAnimator.addState([stateEnabled], notPressedSet);
stateListAnimator.addState([], defaultSet);
nativeView.setStateListAnimator(stateListAnimator);
}
[horizontalAlignmentProperty.getDefault](): HorizontalAlignment {
return <HorizontalAlignment>org.nativescript.widgets.ViewHelper.getHorizontalAlignment(this.nativeViewProtected);
}
@@ -946,7 +1024,7 @@ function createNativePercentLengthProperty(options: NativePercentLengthPropertyO
const { getter, setter, auto = 0 } = options;
let setPixels, getPixels, setPercent;
if (getter) {
View.prototype[getter] = function (this: View): PercentLength {
View.prototype[getter] = function(this: View): PercentLength {
if (options) {
setPixels = options.setPixels;
getPixels = options.getPixels;
@@ -962,7 +1040,7 @@ function createNativePercentLengthProperty(options: NativePercentLengthPropertyO
}
}
if (setter) {
View.prototype[setter] = function (this: View, length: PercentLength) {
View.prototype[setter] = function(this: View, length: PercentLength) {
if (options) {
setPixels = options.setPixels;
getPixels = options.getPixels;

View File

@@ -224,6 +224,16 @@ export abstract class View extends ViewBase {
*/
color: Color;
/**
* Gets or sets the elevation of the android view.
*/
androidElevation: number;
/**
* Gets or sets the dynamic elevation offset of the android view.
*/
androidDynamicElevationOffset: number;
/**
* Gets or sets the background style property.
*/

View File

@@ -1099,3 +1099,9 @@ export const visibilityProperty = new CssProperty<Style, Visibility>({
}
});
visibilityProperty.register(Style);
export const androidElevationProperty = new CssProperty<Style, number>({ name: "androidElevation", cssName: "android-elevation", valueConverter: parseFloat });
androidElevationProperty.register(Style);
export const androidDynamicElevationOffsetProperty = new CssProperty<Style, number>({ name: "androidDynamicElevationOffset", cssName: "android-dynamic-elevation-offset", valueConverter: parseFloat });
androidDynamicElevationOffsetProperty.register(Style);

View File

@@ -92,6 +92,8 @@ export class Style extends Observable {
public fontWeight: FontWeight;
public font: string;
public androidElevation: number;
public androidDynamicElevationOffset: number;
public zIndex: number;
public opacity: number;
public visibility: Visibility;
@@ -127,13 +129,13 @@ export class Style extends Observable {
public selectedTabTextColor: Color;
public androidSelectedTabHighlightColor: Color;
// ListView-specific props
// ListView-specific props
public separatorColor: Color;
//SegmentedBar-specific props
public selectedBackgroundColor: Color;
// Page-specific props
// Page-specific props
public statusBarStyle: "light" | "dark";
public androidStatusBarBackground: Color;
@@ -167,4 +169,4 @@ interface PropertyBagClass {
}
interface PropertyBag {
[property: string]: string;
}
}

View File

@@ -65,6 +65,8 @@ export class Style extends Observable implements StyleDefinition {
public fontWeight: FontWeight;
public font: string;
public androidElevation: number;
public androidDynamicElevationOffset: number;
public zIndex: number;
public opacity: number;
public visibility: Visibility;
@@ -100,13 +102,13 @@ export class Style extends Observable implements StyleDefinition {
public selectedTabTextColor: Color;
public androidSelectedTabHighlightColor: Color;
// ListView-specific props
// ListView-specific props
public separatorColor: Color;
//SegmentedBar-specific props
public selectedBackgroundColor: Color;
// Page-specific props
// Page-specific props
public statusBarStyle: "light" | "dark";
public androidStatusBarBackground: Color;
@@ -124,4 +126,4 @@ export class Style extends Observable implements StyleDefinition {
public PropertyBag: { new(): { [property: string]: string }, prototype: { [property: string]: string } };
}
Style.prototype.PropertyBag = class { [property: string]: string; }
Style.prototype.PropertyBag = class { [property: string]: string; }