React Native: Creating an Animated View that slides directly with finger drag - animation

Desired Effect:
I want to create a basic animation of a smooth sliding View (left or right) in sync with the drag pace of my finger.(e.g. sliding an off- canvas menu into and out of view)
Currently I'm using Animated.spring in a parent component that handles all gestures. Then I'm using transform,translateX in a child component to slide a View left or right.
For Example:
Root.js(Parent Component that handles gestures)
_handlePanResponderMove(e: Object, gestureState: Object) {
let dx = gestureState.moveX - this.state.swipeStart;
Animated.spring(
this.state.slideValue,
{
toValue: newValue,
velocity: gestureState.vx,
tension: 2,
friction: 8,
}).start();
}
Navigation.js(Child Component that slides)
render(){
<Animated.View style={[styles.navigation,{transform: [{translateX: this.props.slideValue}]}]}>
<View >
</View>
</Animated.View>
}
The Problem:
There is sticky/lagging behavior with animation instead of a smooth movement that paces the finger sliding guesture.
My reasoning so far:
From my limited Animation experience - Animated.spring, Animated.ease and Animated.timing don't really describe well the equally paced sliding movement I'm after - but I suppose I need to be using one of them to get optimized native performance
(otherwise I'd just use .setState(slideValue) and do some math with the current location of my finger to figure the position of the View.)
Question:
What would be the preferred way to describe this type of smooth sliding animation using the optimized React-Native Animated library?
What I've tried out:
1) Using Animated.timing and setting duration low and easing to linear(my best guess at what I should do)
Animated.timing(
this.state.navSlideValue,
{
toValue: newValue,
duration: 10,
easing: Easing.linear
}).start();
2) Moving up the tension on Animated.spring
Animated.spring(
this.state.navSlideValue,
{
toValue: newValue,
velocity: (gestureState.vx),
tension: 300,
friction: 30,
}).start();

The preset functions timing, spring are useful for running animations to a specified value with a given easing. If you want to tie an Animated value to an event, you can use Animated.event instead:
PanResponder.create({
// on each move event, set slideValue to gestureState.dx -- the
// first value `null` ignores the first `event` argument
onPanResponderMove: Animated.event([
null, {dx: this.state.slideValue}
])
// ...rest of your panResponder handlers
});

Related

fluttercandies extended_image. How do I zoom in on an image with double tap of 1 finger instead of needing 2?

https://github.com/fluttercandies/extended_image
https://github.com/fluttercandies/extended_image/tree/master/example
https://github.com/fluttercandies/extended_image/blob/master/example/lib/pages/zoom_image_demo.dart
fluttercandies extended_image. How do I zoom in on an image with double tap of 1 finger instead of needing 2? How do I get a sliding image to navigator pop back when it slides off the page?
Trying to implement a zoom/pan image with double tap by 1 finger instead of the need for 2 to expand zoom an image. Have two issues with this code and was wondering if anyone has any ideas. The class is very simple with just 2 strings: image & title passed into it.
1, I need the image to expand on double tap. I would like the user to have to power to expand the image with one finger and not two. Think I need to put this code near the very end.
The good thing is that once it is expanded the double tap works to reduce the image size. How do I get it to do the opposite when it is at normal size?
2, the sliding of the image off the page results in a black screen. Thankfully, this doesn’t freeze or crash the app but it leaves the user with a blank screen and the need to press the system back button. I would like the slide to result in a navigator pop back to the original screen.
Firstly, here’s a sample code of how I’m passing an image and a title into expandimage.dart.
FlatButton(
            child: Image.asset(_kAsset5),
            onPressed: () async {
              Navigator.push(
                context,
                MaterialPageRoute(
                    builder: (context) => ExpandImage(
                          image: _kAsset5,
                          title: "\'go help\' 1",
                        )),
              );
            },
          ),
Here’s the code that I’m using for this ‘expandimage.dart’ and a lot of it is based on the pan/zoom example from flutter candies / extended image example.
import 'package:flutter/material.dart';
import 'package:extended_image/extended_image.dart';
class ExpandImage extends StatelessWidget {
  final String image, title;
  ExpandImage({this.image, this.title});
  #override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        backgroundColor: Colors.red[900],
        appBar: AppBar(
          backgroundColor: Colors.red[900],
          leading: IconButton(
            icon: Icon(Icons.close),
            onPressed: Navigator.of(context).pop,
          ),
          title: Text(
            title,
            style: TextStyle(
              color: Colors.yellow,
              inherit: true,
              fontWeight: FontWeight.w300,
              fontStyle: FontStyle.italic,
              letterSpacing: 2.0, //1.2
            ),
          ),
          centerTitle: true,
        ),
        body: SizedBox.expand(
          // child: Hero(
          // tag: heroTag,
          child: ExtendedImageSlidePage(
            slideAxis: SlideAxis.both,
            slideType: SlideType.onlyImage,
            child: ExtendedImage(
              //disable to stop image sliding off page && entering dead end without back button.
//setting to false means it won't slide at all.
              enableSlideOutPage: true,
              mode: ExtendedImageMode.gesture,
              initGestureConfigHandler: (state) => GestureConfig(
                minScale: 1.0,
                animationMinScale: 0.8,
                maxScale: 3.0,
                animationMaxScale: 3.5,
                speed: 1.0,
                inertialSpeed: 100.0,
                initialScale: 1.0,
                inPageView: false,
              ),
              // onDoubleTap: ? zoom in on image
              fit: BoxFit.scaleDown,
              image: AssetImage(
                image,
              ),
            ),
          ),
        ),
      ),
    );
  }
}
Here is a sample image passed in. the page turns red when sliding the image around and then it goes black as the image slides off the page.
Hope it will help, if have any doubt ask in the comments.
class _DetailState extends State<Detail> with TickerProviderStateMixin{
#override
Widget build(BuildContext context) {
AnimationController _animationController = AnimationController(duration: Duration(milliseconds: 200),vsync: this);
Function() animationListener = () {};
Animation? _animation;
return Scaffold(
body: Container(
height: MediaQuery.of(context).size.height,
child: ExtendedImage.network(
widget.wallpaper.path,
fit: BoxFit.contain,
mode: ExtendedImageMode.gesture,
initGestureConfigHandler: (state) {
return GestureConfig(
minScale: 0.9,
animationMinScale: 0.7,
maxScale: 3.0,
animationMaxScale: 3.5,
speed: 1.0,
inertialSpeed: 100.0,
initialScale: 1.0,
inPageView: false,
initialAlignment: InitialAlignment.center,
);
},
onDoubleTap: (ExtendedImageGestureState state) {
///you can use define pointerDownPosition as you can,
///default value is double tap pointer down postion.
var pointerDownPosition = state.pointerDownPosition;
double begin = state.gestureDetails!.totalScale!;
double end;
_animation?.removeListener(animationListener);
_animationController.stop();
_animationController.reset();
if (begin == 1) {
end = 1.5;
} else {
end = 1;
}
animationListener = () {
//print(_animation.value);
state.handleDoubleTap(
scale: _animation!.value,
doubleTapPosition: pointerDownPosition);
};
_animation = _animationController
.drive(Tween<double>(begin: begin, end: end));
_animation!.addListener(animationListener);
_animationController.forward();
},
),
),
}
}
If it helps, mark it as right.

Nativescript - how to get layout to open on a tap

I've got a StackLayout where one of the entries is a GridLayout of a fixed size. Normally this GridLayout is not visible.
On tapping a button, I'd like the GridLayout be made visible - but I'd like to animate it open - - like a menu open.
Any ideas?
Actually toggling visibility is not too bad - it seems to animate the open - any way to control the speed?
The close operation is maybe too fast for what I'm trying to achieve.
You could animate the opacity of your grid. so when you click on it you would
// View is your gridView, this would hide it completely
view.opacity = 0;
// when you want to show it.
// fade in view.
view.animate({
opacity: 1,
duration: 250
}).then(() => {
//Set the visibility to collapsed after the animation is complete
//I believe you will want to do this so that the surrounding views adjust accordingly.
view.visibility='collapse';
}, (err) => {});
// when you want to hide it.
// fade out.
view.animate({
opacity: 0,
duration: 250
}).then(() => {
view.visibility='visible';
}, (err) => {});
You also may want to look into translate for you animations so you can move view down, left, up, right any way you want.

Can I cause React Native's Switch component to animate when changing its state when not being directly pressed by the user?

In my React Native project I'm using the Switch component. It can be switched directly by pressing it, but I also want to let the user change it by pressing nearby related items.
It of course animates the switching when pressed, but when I changed its state using setState() it just jumps directly to the other position without animation.
Is there a way I can programmatically trigger the animation when changing its state from code?
(There is a question with a similar wording but it seems to be to an unrelated problem I can't quite work out.)
Depends a little on the approach you're taking, but I'd say your best bet is with Animated (which is part of React Native).
You can do something like this:
render(){
var animVal = new Animated.Value(0);
Animated.timing(animVal, {toValue: 1, duration: 300}).start();
var animatedViewStyles =
{
marginLeft: animVal.interpolate({
inputRange: [0, 1],
outputRange: this.state.switchOn ? [100, 0] : [0, 100],
}),
// add some other styles here
color: 'green'
};
}
return (
<Animated.View style={animatedViewStyles}>
<TouchableHighlight onPress(() => this.setState({switchOn: !this.state.switchOn}))><Text>This is my switch</Text></TouchableHighlight>
</Animated.View>
);
See the docs https://facebook.github.io/react-native/docs/animated.html for more info.

In React Native - How to move View element in parallely while ScrollView scrolls

I'm developing a TimeLine component. There are Views list inside horizontal ScrollView that represents half an hour blocks. And I have a Component called TimeRangeSelector, which I use to select a range of time in TimeLine ScrollView. So while I scroll the ScrollView I need to move the TimeRangeSelector in parallel without any lag. Below is the TimeLine component. You can see the 30mins blocks are filled inside ScrollView. The yellow color one is the TimeRangeSelector which is a Animated.View. And the left position of the TimeRangeSelector is set using the scroll position. Each time ScrollView moves im setting the state as below.
<ScrollView
horizontal={true}
onScroll={this.onScrollFunc}
>
{timelineElements}
</ScrollView>
onScrollFunc = (event, gesture) => {
this.setState({
contentOffsetX: event.nativeEvent.contentOffset.x,
scrollStopped: false
});
};
And Im passing the scrollBarPosition as props to the TimeRangeSelector and setting its left position style as shown in below code
<TimeRangeSelector
scrollBarPosition={this.state.contentOffsetX}
/>
let styles= [
{
position: "absolute",
left: this.props.scrollBarPosition,
backgroundColor: backgroundColor,
marginTop: 20,
marginLeft: 1,
width: this.state.selectionWidth,
height: 100
}
];
But the issue is when I scroll the ScrollView the TimeRangeSelector moves with it, but it has a delay.. when I scroll faster the distance between where it should be and where it is, is becoming high. Can anyone give me some ideas according to your knowledge.
My assumption: According to my understanding, I think the lag is because of it takes several instances to reach the setState in and set the ScrollBarPosition as below steps.
ScrollView moved 1 frame.
ScrollView fires the onScroll function and emmits the event with new x point.
Then in the onScroll function it sets the State.
As I understand it takes some time to happen all these steps from JavaScript thread all the way to Native Thread and come back to JS thread again.
You should use something like const onScroll = Animated.event([{ nativeEvent: { contentOffset: { x: animatedValue } } }], { useNativeDriver: true }); with const animatedValue = new Animated.Value(0). This way the animation is only done on the native level without going back and forth through the JS thread.
This animated value can only be used effectively with opacity and transform style properties, but it should help you with your problem.
You can read more about this, in this awesome article.

Replace one component with another using animation

I'm looking to animate a text field into view and a button out of view at the same time, so that it looks like the text field is replacing the button. (They are both equal size and take up the same area of the screen).
What's the best way to do this using React Native animation?
At this point, I am rendering the button if one of my state values is false, and the text field if it is true.
You can animate any style property in react-native using the Animated API.
If you are able to represent the changes in a sequence of style changes, the Animated API can do it. For instance animating the opacity from 1 to 0 and back to 1 will give a nice fade in fade out effect. The docs explain the Animations much more clearly
Also you can you selective rendering to mount or hide the component
<View style={{/*style props that need to be animated*/}}
{ boolShowText? <Text/> : <View/> }
</View>
The fading example as found in react-native docs
class FadeInView extends React.Component {
constructor(props) {
super(props);
this.state = {
fadeAnim: new Animated.Value(0), // init opacity 0
};
}
componentDidMount() {
Animated.timing( // Uses easing functions
this.state.fadeAnim, // The value to drive
{toValue: 1}, // Configuration
).start(); // Don't forget start!
}
render() {
return (
<Animated.View // Special animatable View
style={{opacity: this.state.fadeAnim}}> // Binds
{this.props.children}
</Animated.View>
);
}
}

Resources