Skip to content

Commit 1d36644

Browse files
committed
fix: android plugins should all be much faster
Only the textfield view creations are still slow material-components/material-components-android#1745
1 parent 0110e5a commit 1d36644

File tree

11 files changed

+278
-256
lines changed

11 files changed

+278
-256
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package com.nativescript.material.core;
2+
3+
import android.animation.StateListAnimator;
4+
import android.animation.AnimatorSet;
5+
import android.animation.ObjectAnimator;
6+
import android.view.View;
7+
import android.view.ViewGroup;
8+
import android.content.Context;
9+
import android.graphics.drawable.ShapeDrawable;
10+
import android.graphics.drawable.shapes.RoundRectShape;
11+
import android.graphics.drawable.RippleDrawable;
12+
import android.graphics.drawable.Drawable;
13+
import android.content.res.ColorStateList;
14+
import android.graphics.drawable.StateListDrawable;
15+
import android.graphics.Color;
16+
import android.os.Build;
17+
18+
public class Utils {
19+
static final int shortAnimTime = android.R.integer.config_shortAnimTime;
20+
static final int statePressed = android.R.attr.state_pressed;
21+
static final int stateEnabled = android.R.attr.state_enabled;
22+
23+
public static void createStateListAnimator(Context context, View view, float elevation, float pressedZ) {
24+
int duration = context.getResources().getInteger(shortAnimTime);
25+
26+
AnimatorSet pressedSet = new AnimatorSet();
27+
pressedSet.playTogether(ObjectAnimator.ofFloat(view, "translationZ", pressedZ).setDuration(duration),
28+
ObjectAnimator.ofFloat(view, "elevation", elevation).setDuration(0));
29+
30+
AnimatorSet notPressedSet = new AnimatorSet();
31+
notPressedSet.playTogether(ObjectAnimator.ofFloat(view, "translationZ", 0).setDuration(duration),
32+
ObjectAnimator.ofFloat(view, "elevation", elevation).setDuration(0));
33+
34+
AnimatorSet defaultSet = new AnimatorSet();
35+
defaultSet.playTogether(ObjectAnimator.ofFloat(view, "translationZ", 0).setDuration(0),
36+
ObjectAnimator.ofFloat(view, "elevation", 0).setDuration(0));
37+
38+
StateListAnimator stateListAnimator = new StateListAnimator();
39+
stateListAnimator.addState(new int[] { statePressed, stateEnabled }, pressedSet);
40+
stateListAnimator.addState(new int[] { stateEnabled }, notPressedSet);
41+
stateListAnimator.addState(new int[] {}, defaultSet);
42+
43+
view.setStateListAnimator(stateListAnimator);
44+
}
45+
46+
public static ColorStateList getEnabledColorStateList(int color, String variant) {
47+
int[][] states = new int[][] { new int[] { -android.R.attr.state_enabled }, // enabled
48+
android.util.StateSet.NOTHING, // disabled
49+
};
50+
int disabledColor = (variant == "text" || variant == "outline") ? 0 : Color.argb(0.117f, 0f, 0f, 0f);
51+
int[] colors = new int[] { disabledColor, color };
52+
return new android.content.res.ColorStateList(states, colors);
53+
}
54+
55+
public static ColorStateList getFullColorStateList(int activeColor, int inactiveColor, int disabledColor) {
56+
int[][] states = new int[][] { new int[] { android.R.attr.state_focused }, // focused
57+
android.util.StateSet.NOTHING, // other
58+
new int[] { -android.R.attr.state_enabled } // disabled
59+
};
60+
int[] colors = new int[] { activeColor, inactiveColor, disabledColor };
61+
return new android.content.res.ColorStateList(states, colors);
62+
}
63+
64+
public static ShapeDrawable createForegroundShape(float radius) {
65+
RoundRectShape shape = new RoundRectShape(
66+
new float[] { radius, radius, radius, radius, radius, radius, radius, radius }, null, null);
67+
return new ShapeDrawable(shape);
68+
}
69+
70+
public static Drawable createRippleDrawable(int rippleColor, float radius) {
71+
ShapeDrawable rippleShape = radius != 0 ? createForegroundShape(radius) : null;
72+
if (Build.VERSION.SDK_INT >= 22) {
73+
return new RippleDrawable(ColorStateList.valueOf(rippleColor), null, rippleShape);
74+
} else {
75+
StateListDrawable rippleDrawable = new StateListDrawable();
76+
rippleShape.getPaint().setColor(rippleColor);
77+
rippleDrawable.addState(new int[] { statePressed }, rippleShape);
78+
return rippleShape;
79+
}
80+
}
81+
82+
static void handleClearFocus(View view) {
83+
final View root = view.getRootView();
84+
boolean oldValue = true;
85+
int oldDesc = ViewGroup.FOCUS_BEFORE_DESCENDANTS;
86+
87+
if (root != null) {
88+
if (root instanceof ViewGroup) {
89+
oldDesc = ((ViewGroup) root).getDescendantFocusability();
90+
((ViewGroup) root).setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
91+
}
92+
oldValue = root.isFocusable();
93+
root.setFocusable(false);
94+
}
95+
view.clearFocus();
96+
if (root != null) {
97+
root.setFocusable(oldValue);
98+
if (root instanceof ViewGroup) {
99+
((ViewGroup) root).setDescendantFocusability(oldDesc);
100+
}
101+
}
102+
}
103+
}

src/bottomnavigationbar/bottomnavigationbar.android.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -127,15 +127,15 @@ export class BottomNavigationBar extends BottomNavigationBarBase {
127127
this.nativeViewProtected.setOnNavigationItemSelectedListener(null);
128128
this.reselectListener = null;
129129
this.selectListener = null;
130-
this._items.forEach(item => this._removeView(item));
130+
this._items.forEach((item) => this._removeView(item));
131131
super.disposeNativeView();
132132
}
133133

134134
showBadge(index: number, value?: number): void {
135135
// showBadge method is available in v1.1.0-alpha07 of material components
136136
// but NS team has the .d.ts for version 1
137137
// that's why we need to cast the nativeView to any to avoid typing errors
138-
const badge = (this.nativeViewProtected).getOrCreateBadge(index);
138+
const badge = this.nativeViewProtected.getOrCreateBadge(index);
139139
if (this.badgeColor) {
140140
badge.setBackgroundColor(this.badgeColor.android);
141141
}
@@ -171,8 +171,8 @@ export class BottomNavigationBar extends BottomNavigationBarBase {
171171
}
172172

173173
[inactiveColorCssProperty.setNative](inactiveColor: Color) {
174-
const color2= inactiveColor instanceof Color ? inactiveColor.android : inactiveColor;
175-
const color1 = this.activeColor instanceof Color ? this.activeColor.android : (this.nativeViewProtected.getItemTextColor().getColorForState(stateSets.FOCUSED_STATE_SET, color2));
174+
const color2 = inactiveColor instanceof Color ? inactiveColor.android : inactiveColor;
175+
const color1 = this.activeColor instanceof Color ? this.activeColor.android : this.nativeViewProtected.getItemTextColor().getColorForState(stateSets.FOCUSED_STATE_SET, color2);
176176
const colorStateList = createColorStateList(color1, color2);
177177
this.nativeViewProtected.setItemTextColor(colorStateList);
178178
this.nativeViewProtected.setItemIconTintList(colorStateList);
@@ -238,16 +238,26 @@ export class BottomNavigationTab extends BottomNavigationTabBase {
238238
[activeColorCssProperty.setNative](activeColor: Color) {
239239
// not working for now
240240
const color1 = activeColor instanceof Color ? activeColor.android : activeColor;
241-
const color2 = this.inactiveColor instanceof Color ? this.inactiveColor.android : (this.nativeViewProtected.getIconTintList()?this.nativeViewProtected.getIconTintList().getColorForState(stateSets.BACKGROUND_DEFAULT_STATE_2, color1): 0);
241+
const color2 =
242+
this.inactiveColor instanceof Color
243+
? this.inactiveColor.android
244+
: this.nativeViewProtected.getIconTintList()
245+
? this.nativeViewProtected.getIconTintList().getColorForState(stateSets.BACKGROUND_DEFAULT_STATE_2, color1)
246+
: 0;
242247
const colorStateList = createColorStateList(color1, color2);
243248
// this.nativeViewProtected.color(colorStateList); // can we set the text color?
244249
this.nativeViewProtected.setIconTintList(colorStateList);
245250
}
246251

247252
[inactiveColorCssProperty.setNative](inactiveColor: Color) {
248253
// not working for now
249-
const color2= inactiveColor instanceof Color ? inactiveColor.android : inactiveColor;
250-
const color1 = this.activeColor instanceof Color ? this.activeColor.android : (this.nativeViewProtected.getIconTintList()?this.nativeViewProtected.getIconTintList().getColorForState(stateSets.FOCUSED_STATE_SET, color2):0);
254+
const color2 = inactiveColor instanceof Color ? inactiveColor.android : inactiveColor;
255+
const color1 =
256+
this.activeColor instanceof Color
257+
? this.activeColor.android
258+
: this.nativeViewProtected.getIconTintList()
259+
? this.nativeViewProtected.getIconTintList().getColorForState(stateSets.FOCUSED_STATE_SET, color2)
260+
: 0;
251261
const colorStateList = createColorStateList(color1, color2);
252262
// this.nativeViewProtected.setText(colorStateList); // can we set the text color?
253263
this.nativeViewProtected.setIconTintList(colorStateList);

src/button/button.android.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { VerticalTextAlignment, verticalTextAlignmentProperty } from '@nativescript-community/text';
22
import { dynamicElevationOffsetProperty, elevationProperty, rippleColorProperty } from '@nativescript-community/ui-material-core';
3-
import { createStateListAnimator, getLayout, isPostLollipop } from '@nativescript-community/ui-material-core/android/utils';
3+
import { createStateListAnimator, getColorStateList, getLayout, isPostLollipop } from '@nativescript-community/ui-material-core/android/utils';
44
import {
55
Background,
66
Color,
@@ -60,7 +60,7 @@ export class Button extends ButtonBase {
6060
if (!LayoutInflater) {
6161
LayoutInflater = android.view.LayoutInflater;
6262
}
63-
const view = android.view.LayoutInflater.from(this._context).inflate(layoutId, null, false) as com.google.android.material.button.MaterialButton;
63+
const view = LayoutInflater.from(this._context).inflate(layoutId, null, false) as com.google.android.material.button.MaterialButton;
6464
if (this.src) {
6565
layoutStringId += '_icon';
6666
view.setIconGravity(0x2); //com.google.android.material.button.MaterialButton.ICON_GRAVITY_TEXT_START
@@ -88,7 +88,7 @@ export class Button extends ButtonBase {
8888
// (<any>nativeView).clickListener = clickListener;
8989
// }
9090
[rippleColorProperty.setNative](color: Color) {
91-
this.nativeViewProtected.setRippleColor(android.content.res.ColorStateList.valueOf(color.android));
91+
this.nativeViewProtected.setRippleColor(getColorStateList(color.android));
9292
}
9393

9494
getDefaultElevation(): number {
@@ -99,17 +99,26 @@ export class Button extends ButtonBase {
9999
return 6; // 6dp @dimen/mtrl_btn_pressed_z
100100
}
101101

102+
createStateListAnimatorTimeout;
103+
createStateListAnimator() {
104+
if (!this.createStateListAnimatorTimeout) {
105+
this.createStateListAnimatorTimeout = setTimeout(() => {
106+
this.createStateListAnimatorTimeout = null;
107+
createStateListAnimator(this, this.nativeViewProtected);
108+
});
109+
}
110+
}
102111
[elevationProperty.setNative](value: number) {
103112
if (isPostLollipop()) {
104-
createStateListAnimator(this, this.nativeViewProtected);
113+
this.createStateListAnimator();
105114
} else {
106115
const newValue = Length.toDevicePixels(typeof value === 'string' ? Length.parse(value) : value, 0);
107116
androidx.core.view.ViewCompat.setElevation(this.nativeViewProtected, newValue);
108117
}
109118
}
110119
[dynamicElevationOffsetProperty.setNative](value: number) {
111120
if (isPostLollipop()) {
112-
createStateListAnimator(this, this.nativeViewProtected);
121+
this.createStateListAnimator();
113122
} else {
114123
const newValue = Length.toDevicePixels(typeof value === 'string' ? Length.parse(value) : value, 0);
115124
androidx.core.view.ViewCompat.setTranslationZ(this.nativeViewProtected, newValue);
@@ -138,16 +147,12 @@ export class Button extends ButtonBase {
138147
view.setBackgroundDrawable(value);
139148
} else {
140149
if (value.color) {
141-
// if (value.color.android === 0 && this.variant === 'flat') {
142150
view.setBackgroundColor(value.color.android);
143-
// } else {
144-
// view.setBackgroundTintList(getEnabledColorStateList(value.color.android, this.variant));
145-
// }
146151
}
147152
this.setCornerRadius(value.borderTopLeftRadius);
148153
view.setStrokeWidth(value.borderTopWidth);
149154
if (value.borderTopColor) {
150-
view.setStrokeColor(android.content.res.ColorStateList.valueOf(value.borderTopColor.android));
155+
view.setStrokeColor(getColorStateList(value.borderTopColor.android));
151156
}
152157
}
153158
}

src/cardview/cardview.android.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { dynamicElevationOffsetProperty, elevationProperty, rippleColorProperty } from '@nativescript-community/ui-material-core';
1+
import { dynamicElevationOffsetProperty, elevationProperty, getRippleColor, rippleColorProperty, themer } from '@nativescript-community/ui-material-core';
22
import { createStateListAnimator, getAttrColor, isPostLollipop } from '@nativescript-community/ui-material-core/android/utils';
33
import { Color, Length, backgroundInternalProperty } from '@nativescript/core';
44
import { CardViewBase } from './cardview-common';
@@ -265,8 +265,10 @@ export class CardView extends CardViewBase {
265265
}
266266

267267
getCardRippleColor() {
268-
const color = this.style['rippleColor'] ? this.style['rippleColor'] : new Color(getAttrColor(this._context, 'colorControlHighlight'));
269-
return color.android;
268+
if (this.rippleColor) {
269+
return getRippleColor(this.rippleColor);
270+
}
271+
return getRippleColor(themer.getAccentColor());
270272
}
271273
// createForegroundDrawable(view: com.google.android.material.card.MaterialCardView, strokeWidth, strokeColor: Color) {
272274
// this.fgDrawable = new android.graphics.drawable.GradientDrawable();

0 commit comments

Comments
 (0)