18
18
#import " MaterialElevation.h"
19
19
#import " MaterialInk.h"
20
20
#import " MaterialRipple.h"
21
+ #import " MaterialShadow.h"
21
22
#import " MaterialShadowElevations.h"
22
23
#import " MaterialShapeLibrary.h"
23
24
#import " MaterialShapes.h"
@@ -112,6 +113,9 @@ @interface MDCButton () {
112
113
BOOL _mdc_adjustsFontForContentSizeCategory;
113
114
BOOL _cornerRadiusObserverAdded;
114
115
CGFloat _inkMaxRippleRadius;
116
+
117
+ MDCShapeMediator *_shapedLayer;
118
+ CGFloat _currentElevation;
115
119
}
116
120
@property (nonatomic , strong , readonly , nonnull ) MDCStatefulRippleView *rippleView;
117
121
#pragma clang diagnostic push
@@ -130,14 +134,21 @@ @interface MDCButton () {
130
134
131
135
@implementation MDCButton
132
136
137
+ static BOOL gEnablePerformantShadow = NO ;
138
+
133
139
@synthesize mdc_overrideBaseElevation = _mdc_overrideBaseElevation;
134
140
@synthesize mdc_elevationDidChangeBlock = _mdc_elevationDidChangeBlock;
135
141
@synthesize visibleAreaInsets = _visibleAreaInsets;
136
142
@synthesize visibleAreaLayoutGuide = _visibleAreaLayoutGuide;
143
+ @synthesize shadowsCollection = _shadowsCollection;
137
144
@dynamic layer;
138
145
139
146
+ (Class )layerClass {
140
- return [MDCShapedShadowLayer class ];
147
+ if (gEnablePerformantShadow ) {
148
+ return [super class ];
149
+ } else {
150
+ return [MDCShapedShadowLayer class ];
151
+ }
141
152
}
142
153
143
154
- (instancetype )init {
@@ -170,7 +181,9 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder {
170
181
171
182
// Storyboards will set the backgroundColor via the UIView backgroundColor setter, so we have
172
183
// to write that in to our _backgroundColors dictionary.
173
- _backgroundColors[@(UIControlStateNormal)] = self.layer .shapedBackgroundColor ;
184
+ _backgroundColors[@(UIControlStateNormal)] = gEnablePerformantShadow
185
+ ? _shapedLayer.shapedBackgroundColor
186
+ : self.layer .shapedBackgroundColor ;
174
187
[self updateBackgroundColor ];
175
188
}
176
189
return self;
@@ -179,6 +192,9 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder {
179
192
- (void )commonMDCButtonInit {
180
193
// TODO(b/142861610): Default to `NO`, then remove once all internal usage is migrated.
181
194
_enableTitleFontForState = YES ;
195
+ if (gEnablePerformantShadow ) {
196
+ _shapedLayer = [[MDCShapeMediator alloc ] initWithViewLayer: self .layer];
197
+ }
182
198
_disabledAlpha = MDCButtonDisabledAlpha;
183
199
_enabledAlpha = self.alpha ;
184
200
_uppercaseTitle = YES ;
@@ -191,6 +207,7 @@ - (void)commonMDCButtonInit {
191
207
_accessibilityTraitsIncludesButton = YES ;
192
208
_adjustsFontForContentSizeCategoryWhenScaledFontIsUnavailable = YES ;
193
209
_mdc_overrideBaseElevation = -1 ;
210
+ _currentElevation = 0 ;
194
211
195
212
if (!_backgroundColors) {
196
213
// _backgroundColors may have already been initialized by setting the backgroundColor setter.
@@ -206,11 +223,12 @@ - (void)commonMDCButtonInit {
206
223
#endif
207
224
208
225
self.layer .cornerRadius = MDCButtonDefaultCornerRadius;
209
- if (!self.layer .shapeGenerator ) {
210
- self.layer .shadowPath = [self boundingPath ].CGPath ;
226
+ if (gEnablePerformantShadow ) {
227
+ self.layer .shadowColor = MDCShadowColor ().CGColor ;
228
+ } else {
229
+ self.layer .shadowColor = [UIColor blackColor ].CGColor ;
230
+ self.layer .elevation = [self elevationForState: self .state];
211
231
}
212
- self.layer .shadowColor = [UIColor blackColor ].CGColor ;
213
- self.layer .elevation = [self elevationForState: self .state];
214
232
215
233
_shadowColors = [NSMutableDictionary dictionary ];
216
234
_shadowColors[@(UIControlStateNormal)] = [UIColor colorWithCGColor: self .layer.shadowColor];
@@ -336,8 +354,15 @@ - (void)layoutSubviews {
336
354
}
337
355
}
338
356
339
- if (!self.layer .shapeGenerator ) {
340
- self.layer .shadowPath = [self boundingPath ].CGPath ;
357
+ if (gEnablePerformantShadow ) {
358
+ if (_shapedLayer.shapeGenerator ) {
359
+ [_shapedLayer layoutShapedSublayers ];
360
+ }
361
+ [self updateShadow ];
362
+ } else {
363
+ if (!self.layer .shapeGenerator ) {
364
+ self.layer .shadowPath = [self boundingPath ].CGPath ;
365
+ }
341
366
}
342
367
343
368
// Center unbounded ink view frame taking into account possible insets using contentRectForBounds.
@@ -732,12 +757,17 @@ - (void)setEnableRippleBehavior:(BOOL)enableRippleBehavior {
732
757
733
758
- (void )animateButtonToHeightForState : (UIControlState)state {
734
759
CGFloat newElevation = [self elevationForState: state];
735
- if (MDCCGFloatEqual (self.layer . elevation , newElevation)) {
760
+ if (MDCCGFloatEqual (self.mdc_currentElevation , newElevation)) {
736
761
return ;
737
762
}
763
+ _currentElevation = newElevation;
738
764
[CATransaction begin ];
739
765
[CATransaction setAnimationDuration: MDCButtonAnimationDuration];
740
- self.layer .elevation = newElevation;
766
+ if (gEnablePerformantShadow ) {
767
+ [self updateShadow ];
768
+ } else {
769
+ self.layer .elevation = newElevation;
770
+ }
741
771
[CATransaction commit ];
742
772
[self mdc_elevationDidChange ];
743
773
}
@@ -754,7 +784,8 @@ - (void)setBackgroundColor:(nullable UIColor *)backgroundColor {
754
784
}
755
785
756
786
- (UIColor *)backgroundColor {
757
- return self.layer .shapedBackgroundColor ;
787
+ return gEnablePerformantShadow ? _shapedLayer.shapedBackgroundColor
788
+ : self.layer .shapedBackgroundColor ;
758
789
}
759
790
760
791
- (UIColor *)backgroundColorForState : (UIControlState)state {
@@ -818,10 +849,15 @@ - (void)setElevation:(CGFloat)elevation forState:(UIControlState)state {
818
849
_userElevations[@(state)] = @(elevation);
819
850
MDCShadowElevation newElevation = [self elevationForState: self .state];
820
851
// If no change to the current elevation, don't perform updates
821
- if (MDCCGFloatEqual (newElevation, self.layer . elevation )) {
852
+ if (MDCCGFloatEqual (newElevation, self.mdc_currentElevation )) {
822
853
return ;
823
854
}
824
- self.layer .elevation = newElevation;
855
+ _currentElevation = newElevation;
856
+ if (gEnablePerformantShadow ) {
857
+ [self updateShadow ];
858
+ } else {
859
+ self.layer .elevation = newElevation;
860
+ }
825
861
[self mdc_elevationDidChange ];
826
862
827
863
// The elevation of the normal state controls whether this button is flat or not, and flat buttons
@@ -832,6 +868,18 @@ - (void)setElevation:(CGFloat)elevation forState:(UIControlState)state {
832
868
}
833
869
}
834
870
871
+ - (void )updateShadow {
872
+ if (_shapedLayer.shapeGenerator == nil ) {
873
+ MDCConfigureShadowForView (self,
874
+ [self .shadowsCollection shadowForElevation: self .mdc_currentElevation],
875
+ [self shadowColorForState: self .state] ?: MDCShadowColor ());
876
+ } else {
877
+ MDCConfigureShadowForViewWithPath (
878
+ self, [self .shadowsCollection shadowForElevation: self .mdc_currentElevation],
879
+ [self shadowColorForState: self .state] ?: MDCShadowColor (), self.layer .shadowPath );
880
+ }
881
+ }
882
+
835
883
#pragma mark - Border Color
836
884
837
885
- (UIColor *)borderColorForState : (UIControlState)state {
@@ -891,7 +939,11 @@ - (void)updateBorderWidth {
891
939
// We fall back to UIControlStateNormal if there is no value for the current state.
892
940
width = _borderWidths[@(UIControlStateNormal)];
893
941
}
894
- self.layer .shapedBorderWidth = (width != nil ) ? (CGFloat)width.doubleValue : 0 ;
942
+ if (gEnablePerformantShadow ) {
943
+ _shapedLayer.shapedBorderWidth = (width != nil ) ? (CGFloat)width.doubleValue : 0 ;
944
+ } else {
945
+ self.layer .shapedBorderWidth = (width != nil ) ? (CGFloat)width.doubleValue : 0 ;
946
+ }
895
947
}
896
948
897
949
#pragma mark - Title Font
@@ -942,7 +994,14 @@ - (void)setTitleFont:(nullable UIFont *)font forState:(UIControlState)state {
942
994
#pragma mark - MaterialElevation
943
995
944
996
- (CGFloat)mdc_currentElevation {
945
- return [self elevationForState: self .state];
997
+ return _currentElevation;
998
+ }
999
+
1000
+ - (MDCShadowsCollection *)shadowsCollection {
1001
+ if (!_shadowsCollection) {
1002
+ _shadowsCollection = MDCShadowsCollectionDefault ();
1003
+ }
1004
+ return _shadowsCollection;
946
1005
}
947
1006
948
1007
#pragma mark - Private methods
@@ -1025,7 +1084,11 @@ - (void)updateAlphaAndBackgroundColorAnimated:(BOOL)animated {
1025
1084
- (void )updateBackgroundColor {
1026
1085
// When shapeGenerator is unset then self.layer.shapedBackgroundColor sets the layer's
1027
1086
// backgroundColor. Whereas when shapeGenerator is set the sublayer's fillColor is set.
1028
- self.layer .shapedBackgroundColor = [self backgroundColorForState: self .state];
1087
+ if (gEnablePerformantShadow ) {
1088
+ _shapedLayer.shapedBackgroundColor = [self backgroundColorForState: self .state];
1089
+ } else {
1090
+ self.layer .shapedBackgroundColor = [self backgroundColorForState: self .state];
1091
+ }
1029
1092
[self updateDisabledTitleColor ];
1030
1093
}
1031
1094
@@ -1060,7 +1123,11 @@ - (void)updateBorderColor {
1060
1123
// We fall back to UIControlStateNormal if there is no value for the current state.
1061
1124
color = _borderColors[@(UIControlStateNormal)];
1062
1125
}
1063
- self.layer .shapedBorderColor = color ?: NULL ;
1126
+ if (gEnablePerformantShadow ) {
1127
+ _shapedLayer.shapedBorderColor = color ?: NULL ;
1128
+ } else {
1129
+ self.layer .shapedBorderColor = color ?: NULL ;
1130
+ }
1064
1131
}
1065
1132
1066
1133
- (void )updateTitleFont {
@@ -1088,29 +1155,53 @@ - (void)configureLayerWithShapeGenerator:(id<MDCShapeGenerating>)shapeGenerator
1088
1155
if (shapeGenerator) {
1089
1156
self.layer .shadowPath = nil ;
1090
1157
} else {
1091
- self.layer .shadowPath = [self boundingPath ].CGPath ;
1158
+ if (gEnablePerformantShadow ) {
1159
+ MDCConfigureShadowForView (
1160
+ self, [self .shadowsCollection shadowForElevation: self .mdc_currentElevation],
1161
+ [self shadowColorForState: self .state] ?: MDCShadowColor ());
1162
+ } else {
1163
+ self.layer .shadowPath = [self boundingPath ].CGPath ;
1164
+ }
1165
+ }
1166
+
1167
+ if (gEnablePerformantShadow ) {
1168
+ _shapedLayer.shapeGenerator = shapeGenerator;
1169
+ } else {
1170
+ self.layer .shapeGenerator = shapeGenerator;
1092
1171
}
1093
- self.layer .shapeGenerator = shapeGenerator;
1094
1172
// The imageView is added very early in the lifecycle of a UIButton, therefore we need to move
1095
1173
// the colorLayer behind the imageView otherwise the image will not show.
1096
1174
// Because the inkView needs to go below the imageView, but above the colorLayer
1097
1175
// we need to have the colorLayer be at the back
1098
- [self .layer.colorLayer removeFromSuperlayer ];
1099
- if (self.enableRippleBehavior ) {
1100
- [self .layer insertSublayer: self .layer.colorLayer below: self .rippleView.layer];
1176
+ if (gEnablePerformantShadow ) {
1177
+ [_shapedLayer.colorLayer removeFromSuperlayer ];
1178
+ if (self.enableRippleBehavior ) {
1179
+ [self .layer insertSublayer: _shapedLayer.colorLayer below: self .rippleView.layer];
1180
+ } else {
1181
+ [self .layer insertSublayer: _shapedLayer.colorLayer below: self .inkView.layer];
1182
+ }
1101
1183
} else {
1102
- [self .layer insertSublayer: self .layer.colorLayer below: self .inkView.layer];
1184
+ [self .layer.colorLayer removeFromSuperlayer ];
1185
+ if (self.enableRippleBehavior ) {
1186
+ [self .layer insertSublayer: self .layer.colorLayer below: self .rippleView.layer];
1187
+ } else {
1188
+ [self .layer insertSublayer: self .layer.colorLayer below: self .inkView.layer];
1189
+ }
1103
1190
}
1104
1191
[self updateBackgroundColor ];
1105
1192
[self updateInkForShape ];
1106
1193
}
1107
1194
1108
1195
- (id <MDCShapeGenerating>)shapeGenerator {
1196
+ if (gEnablePerformantShadow ) {
1197
+ return _shapedLayer.shapeGenerator ;
1198
+ }
1109
1199
return self.layer .shapeGenerator ;
1110
1200
}
1111
1201
1112
1202
- (void )updateInkForShape {
1113
- CGRect boundingBox = CGPathGetBoundingBox (self.layer .shapeLayer .path );
1203
+ CGRect boundingBox = CGPathGetBoundingBox (gEnablePerformantShadow ? _shapedLayer.shapeLayer .path
1204
+ : self.layer .shapeLayer .path );
1114
1205
self.inkView .maxRippleRadius =
1115
1206
(CGFloat)(hypot (CGRectGetHeight (boundingBox), CGRectGetWidth (boundingBox)) / 2 + 10 );
1116
1207
self.inkView .layer .masksToBounds = NO ;
@@ -1348,4 +1439,14 @@ - (void)inferMinimumAndMaximumSize {
1348
1439
}
1349
1440
}
1350
1441
1442
+ #pragma mark - Performant Shadow Toggle
1443
+
1444
+ + (void )setEnablePerformantShadow : (BOOL )enable {
1445
+ gEnablePerformantShadow = enable;
1446
+ }
1447
+
1448
+ + (BOOL )enablePerformantShadow {
1449
+ return gEnablePerformantShadow ;
1450
+ }
1451
+
1351
1452
@end
0 commit comments