From 6c353fd7e9fd324717951ad62754d817537d7339 Mon Sep 17 00:00:00 2001 From: Julien K Date: Tue, 27 Feb 2018 12:52:21 -0800 Subject: [PATCH] onPress animation with magnification Summary: Related to: #15454 Motivation: Improve tvOS feeling for TouchableHighlight ![changewithaniamtion](https://user-images.githubusercontent.com/7658664/29193477-b99b4a10-7e25-11e7-8b31-e0e4ca9d7720.gif) - When you select the button he is focus and the underlay is show - When you press the button, there is an animation, but after the animation, the focus is on the button and the underlay is show Play with tvParallaxProperties on tvOS, test with and without patch just to see the actual behaviour ``` (console.log("onShowUnderlay")} onHideUnderlay={() => (console.log("onHideUnderlay")} onPress={() => (console.log("onPress")} > ``` Closes https://github.com/facebook/react-native/pull/15455 Differential Revision: D6887437 Pulled By: hramos fbshipit-source-id: e18b695068bc99643ba4006fb3f39215b38a74c1 --- .../Components/AppleTV/TVViewPropTypes.js | 3 + .../Touchable/TouchableHighlight.js | 16 +++- RNTester/js/ListExampleShared.js | 3 + RNTester/js/RNTesterList.ios.js | 2 +- RNTester/js/TouchableExample.js | 1 + React/Views/RCTTVView.m | 93 +++++++++++++++---- 6 files changed, 95 insertions(+), 23 deletions(-) diff --git a/Libraries/Components/AppleTV/TVViewPropTypes.js b/Libraries/Components/AppleTV/TVViewPropTypes.js index cf5f5b03b7679e..fab7a30b84d40f 100644 --- a/Libraries/Components/AppleTV/TVViewPropTypes.js +++ b/Libraries/Components/AppleTV/TVViewPropTypes.js @@ -37,6 +37,9 @@ var TVViewPropTypes = { * shiftDistanceY: Defaults to 2.0. * tiltAngle: Defaults to 0.05. * magnification: Defaults to 1.0. + * pressMagnification: Defaults to 1.0. + * pressDuration: Defaults to 0.3. + * pressDelay: Defaults to 0.0. * * @platform ios */ diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js index 6493a5147b67b4..19680b236528c0 100644 --- a/Libraries/Components/Touchable/TouchableHighlight.js +++ b/Libraries/Components/Touchable/TouchableHighlight.js @@ -13,6 +13,7 @@ const ColorPropType = require('ColorPropType'); const NativeMethodsMixin = require('NativeMethodsMixin'); const PropTypes = require('prop-types'); +const Platform = require('Platform'); const React = require('React'); const ReactNativeViewAttributes = require('ReactNativeViewAttributes'); const StyleSheet = require('StyleSheet'); @@ -172,6 +173,9 @@ const TouchableHighlight = createReactClass({ * shiftDistanceY: Defaults to 2.0. * tiltAngle: Defaults to 0.05. * magnification: Defaults to 1.0. + * pressMagnification: Defaults to 1.0. + * pressDuration: Defaults to 0.3. + * pressDelay: Defaults to 0.0. * * @platform ios */ @@ -246,11 +250,13 @@ const TouchableHighlight = createReactClass({ touchableHandlePress: function(e: PressEvent) { clearTimeout(this._hideTimeout); - this._showUnderlay(); - this._hideTimeout = setTimeout( - this._hideUnderlay, - this.props.delayPressOut, - ); + if (!Platform.isTVOS) { + this._showUnderlay(); + this._hideTimeout = setTimeout( + this._hideUnderlay, + this.props.delayPressOut, + ); + } this.props.onPress && this.props.onPress(e); }, diff --git a/RNTester/js/ListExampleShared.js b/RNTester/js/ListExampleShared.js index 96ace615655be1..c5eef14dbac902 100644 --- a/RNTester/js/ListExampleShared.js +++ b/RNTester/js/ListExampleShared.js @@ -62,6 +62,9 @@ class ItemComponent extends React.PureComponent<{ onPress={this._onPress} onShowUnderlay={this.props.onShowUnderlay} onHideUnderlay={this.props.onHideUnderlay} + tvParallaxProperties={{ + pressMagnification: 1.1, + }} style={horizontal ? styles.horizItem : styles.item}> diff --git a/RNTester/js/RNTesterList.ios.js b/RNTester/js/RNTesterList.ios.js index 1542a4e4ac80ad..d45e42fa596077 100644 --- a/RNTester/js/RNTesterList.ios.js +++ b/RNTester/js/RNTesterList.ios.js @@ -189,7 +189,7 @@ const ComponentExamples: Array = [ { key: 'TouchableExample', module: require('./TouchableExample'), - supportsTVOS: false, + supportsTVOS: true, }, { key: 'TransparentHitTestExample', diff --git a/RNTester/js/TouchableExample.js b/RNTester/js/TouchableExample.js index f505891177d571..e0c227d9ca3f7f 100644 --- a/RNTester/js/TouchableExample.js +++ b/RNTester/js/TouchableExample.js @@ -55,6 +55,7 @@ exports.examples = [ style={styles.wrapper} activeOpacity={1} animationVelocity={0} + tvParallaxProperties={{pressMagnification: 1.3, pressDuration: 0.6}} underlayColor="rgb(210, 230, 255)" onPress={() => console.log('custom THW text - highlight')}> diff --git a/React/Views/RCTTVView.m b/React/Views/RCTTVView.m index dcd086499c1f54..4351978629a807 100644 --- a/React/Views/RCTTVView.m +++ b/React/Views/RCTTVView.m @@ -27,24 +27,50 @@ @implementation RCTTVView - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { - self.tvParallaxProperties = @{ - @"enabled": @YES, - @"shiftDistanceX": @2.0f, - @"shiftDistanceY": @2.0f, - @"tiltAngle": @0.05f, - @"magnification": @1.0f - }; + dispatch_once(&onceToken, ^{ + defaultTVParallaxProperties = @{ + @"enabled": @YES, + @"shiftDistanceX": @2.0f, + @"shiftDistanceY": @2.0f, + @"tiltAngle": @0.05f, + @"magnification": @1.0f, + @"pressMagnification": @1.0f, + @"pressDuration": @0.3f, + @"pressDelay": @0.0f + }; + }); + self.tvParallaxProperties = defaultTVParallaxProperties; } return self; } +static NSDictionary* defaultTVParallaxProperties = nil; +static dispatch_once_t onceToken; + +- (void)setTvParallaxProperties:(NSDictionary *)tvParallaxProperties { + if (_tvParallaxProperties == nil) { + _tvParallaxProperties = [defaultTVParallaxProperties copy]; + return; + } + + NSMutableDictionary *newParallaxProperties = [NSMutableDictionary dictionaryWithDictionary:_tvParallaxProperties]; + for (NSString *k in [defaultTVParallaxProperties allKeys]) { + if (tvParallaxProperties[k]) { + newParallaxProperties[k] = tvParallaxProperties[k]; + } + } + _tvParallaxProperties = [newParallaxProperties copy]; +} + RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused) - (void)setIsTVSelectable:(BOOL)isTVSelectable { self->_isTVSelectable = isTVSelectable; - if(isTVSelectable) { - UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSelect:)]; + if (isTVSelectable) { + UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] + initWithTarget:self + action:@selector(handleSelect:)]; recognizer.allowedPressTypes = @[@(UIPressTypeSelect)]; _selectRecognizer = recognizer; [self addGestureRecognizer:_selectRecognizer]; @@ -57,8 +83,37 @@ - (void)setIsTVSelectable:(BOOL)isTVSelectable { - (void)handleSelect:(__unused UIGestureRecognizer *)r { - [[NSNotificationCenter defaultCenter] postNotificationName:RCTTVNavigationEventNotification - object:@{@"eventType":@"select",@"tag":self.reactTag}]; + if ([self.tvParallaxProperties[@"enabled"] boolValue] == YES) { + float magnification = [self.tvParallaxProperties[@"magnification"] floatValue]; + float pressMagnification = [self.tvParallaxProperties[@"pressMagnification"] floatValue]; + + // Duration of press animation + float pressDuration = [self.tvParallaxProperties[@"pressDuration"] floatValue]; + + // Delay of press animation + float pressDelay = [self.tvParallaxProperties[@"pressDelay"] floatValue]; + + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:pressDelay]]; + + [UIView animateWithDuration:(pressDuration/2) + animations:^{ + self.transform = CGAffineTransformMakeScale(pressMagnification, pressMagnification); + } + completion:^(__unused BOOL finished1){ + [UIView animateWithDuration:(pressDuration/2) + animations:^{ + self.transform = CGAffineTransformMakeScale(magnification, magnification); + } + completion:^(__unused BOOL finished2) { + [[NSNotificationCenter defaultCenter] postNotificationName:RCTTVNavigationEventNotification + object:@{@"eventType":@"select",@"tag":self.reactTag}]; + }]; + }]; + + } else { + [[NSNotificationCenter defaultCenter] postNotificationName:RCTTVNavigationEventNotification + object:@{@"eventType":@"select",@"tag":self.reactTag}]; + } } - (BOOL)isUserInteractionEnabled @@ -79,15 +134,15 @@ - (void)addParallaxMotionEffects // Make horizontal movements shift the centre left and right UIInterpolatingMotionEffect *xShift = [[UIInterpolatingMotionEffect alloc] - initWithKeyPath:@"center.x" - type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; + initWithKeyPath:@"center.x" + type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; xShift.minimumRelativeValue = @( shiftDistanceX * -1.0f); xShift.maximumRelativeValue = @( shiftDistanceX); // Make vertical movements shift the centre up and down UIInterpolatingMotionEffect *yShift = [[UIInterpolatingMotionEffect alloc] - initWithKeyPath:@"center.y" - type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis]; + initWithKeyPath:@"center.y" + type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis]; yShift.minimumRelativeValue = @( shiftDistanceY * -1.0f); yShift.maximumRelativeValue = @( shiftDistanceY); @@ -95,7 +150,9 @@ - (void)addParallaxMotionEffects CGFloat const tiltAngle = [self.tvParallaxProperties[@"tiltAngle"] floatValue]; // Now make horizontal movements effect a rotation about the Y axis for side-to-side rotation. - UIInterpolatingMotionEffect *xTilt = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"layer.transform" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; + UIInterpolatingMotionEffect *xTilt = [[UIInterpolatingMotionEffect alloc] + initWithKeyPath:@"layer.transform" + type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; // CATransform3D value for minimumRelativeValue CATransform3D transMinimumTiltAboutY = CATransform3DIdentity; @@ -112,7 +169,9 @@ - (void)addParallaxMotionEffects xTilt.maximumRelativeValue = [NSValue valueWithCATransform3D: transMaximumTiltAboutY]; // Now make vertical movements effect a rotation about the X axis for up and down rotation. - UIInterpolatingMotionEffect *yTilt = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"layer.transform" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis]; + UIInterpolatingMotionEffect *yTilt = [[UIInterpolatingMotionEffect alloc] + initWithKeyPath:@"layer.transform" + type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis]; // CATransform3D value for minimumRelativeValue CATransform3D transMinimumTiltAboutX = CATransform3DIdentity;