Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Animated] Question: Triggering a separate animation after a threshold #2072

Closed
felixakiragreen opened this issue Jul 21, 2015 · 23 comments
Closed
Labels
Resolution: Locked This issue was locked by the bot.

Comments

@felixakiragreen
Copy link
Contributor

I posted a few times in the IRC, and I can't seem to find an answer. I've watched @sahrens Fluid UX video a bunch of times, poured over the new Animation API, looked through @brentvatne 's tinder demo and I can't figure out how to do this particular animation.

I want to create something very similar to this: http://cl.ly/c0H3

Basically, as you slide the row past a certain threshold, it triggers a new timed animation that isn't based off of x in ValueXY.

At first I tried I tried interpolating the colors, but that's based off the X value, so the transition can be too fast or slow based on how quickly the user slides the row. I also tried adding a listener to the panning gesture, and then trying to trigger an animation after it passes the threshold, but I can't animate colors, I can only interpolate colors.

Any help or ideas?

@sahrens
Copy link
Contributor

sahrens commented Jul 21, 2015

Should be doable I think, and it sounds like you're going in the right direction.

Adding a listener to the pan x is the right starting point. Then in that listener, if the value crosses your threshold, start the color animation.

The trick is actually animating the colors. You'll want a dedicated Animated.Value just for color, and it will be kind of like an enum. Then interpolate it like so:

var BLACK = 0;
var RED = 1;
var BLUE = 2;

backgroundColor: this.state.color.interpolate({
  inputRange: [BLACK, RED, BLUE],
  outputRange: ['rgb(0, 0, 0)', 'rgb(255, 0, 0)', 'rgb(0, 0, 255)']
})

Then you can do

Animated.timing(this.state.color, {toValue: RED}).start();

This has its limitations of course - if you want to be able to go straight from RED to BLUE and BLACK to BLUE without going through RED, it's going to get more complicated, but I think you can get it to work by changing the outputRange of the interpolation function via setState in your listener. If you need that and can't figure it out, let me know.

@sahrens
Copy link
Contributor

sahrens commented Jul 21, 2015

Apologies for formatting and any bugs - I'm on my phone.

@brentvatne
Copy link
Collaborator

Very cool example! I think @sahrens' suggestion will work well! I cleaned up the formatting for him on the code because I'm on my computer :)

@sahrens
Copy link
Contributor

sahrens commented Jul 21, 2015

Wow, didn't realize you could edit other people comments. Thanks, Brent!

@sahrens
Copy link
Contributor

sahrens commented Jul 21, 2015

Oh, and please post what you come up with - it is a cool interaction indeed.

@brentvatne
Copy link
Collaborator

@DUBERT - any luck with this? 😄

@browniefed
Copy link
Contributor

Hmm, I figured I'd attempt it (I don't quite fully grasp Animated and PanRespnder so take it easy on me), except PanResponderMove wasn't firing and causing the color interpolation to happen consistently.
Code: https://rnplay.org/apps/32dWag/
http://i.imgur.com/cqxo4qq.gif

I dunno

@felixakiragreen
Copy link
Contributor Author

Woohoo! I've got it working using @sahrens suggestion! Thank you very much.

done

At the top:

var GREY = 0
var GREEN = 1
var RED = 2

var Triggered = {
  GREY: false,
  GREEN: false,
  RED: false,
}

The listener function:

_animateRow(value) {

  var threshold = deviceScreen.width / 2

  if (value.x < 0) {

    if (value.x > -50 && !Triggered.GREY) {
      Triggered.GREY = true
      Triggered.GREEN = false
      Triggered.RED = false

      Animated.timing(this.state.color, {
        toValue: GREY,
        duration: 180,
      }).start()
    }
    if (value.x < -50 && value.x > -threshold && !Triggered.GREEN) {
      Triggered.GREY = false
      Triggered.GREEN = true
      Triggered.RED = false

      Animated.timing(this.state.color, {
        toValue: GREEN,
        duration: 180,
      }).start()
    }
    if (value.x < -threshold && !Triggered.RED) {
      Triggered.GREY = false
      Triggered.GREEN = false
      Triggered.RED = true

      Animated.timing(this.state.color, {
        toValue: RED,
        duration: 180,
      }).start()
    }
  }
}

In render:

var bgColor = this.state.color.interpolate({
  inputRange: [
    GREY,
    BLUE,
    PURPLE,
  ],
  outputRange: [
    'rgb(180, 180, 180)',
    'rgb(63, 236, 35)',
    'rgb(233, 19, 19)',
  ],
})

I'm using Triggered to limit the animation to only executing once. (Because it was on the listener, it was being repeatedly called.) Please let me know if there is a better way to do this.

@browniefed
Copy link
Contributor

@DUBERT that's hot! Nice work, I am definitely stealing this concept.

@sahrens
Copy link
Contributor

sahrens commented Jul 22, 2015

That's great! It's mostly taste, but the only change I would make personally would be to have a single target value and set that to GREY, GREEN, or RED, rather than the Triggered Object, and just have a single chunk of code to run the animation, something like this:

var target = null;
if (value.x > -50 && this._target != GREY) {
  target = GREY;
} else if (value.x < -50 && value.x > -threshold && this._target != GREEN) {
  target = GREEN;
} else if (value.x < -threshold && this._target != RED) {
  target = RED;
}
if (target !== null) {
  this._target = target:
  Animated.timing(this.state.color, {
    toValue: target,
    duration: 180,
  }).start();
}

@sahrens
Copy link
Contributor

sahrens commented Jul 22, 2015

I'd also pick prettier colors ;)

Closing since you got it working :)

@sahrens sahrens closed this as completed Jul 22, 2015
@sahrens
Copy link
Contributor

sahrens commented Jul 22, 2015

One other thought - you might want to do this with a horizontal scroll view rather than a pan responder on a translated view of you're not already. That way I think the nested scroll views will negotiate their responder locks correctly. Let me know if you have issues with that.

@felixakiragreen
Copy link
Contributor Author

That's really smart @sahrens , thanks. I will definitely use the object to reduce the code.

And you anticipated my next issue! I was already thinking about a directional measurement on the Pan Responder to disabled vertical scrolling but that's much much better.

And the colors were placeholders :) I'll pick good ones

@browniefed
Copy link
Contributor

@DUBERT when you get that figured out can you post the code snippet? I'd be interested in seeing how that all works together.

@felixakiragreen
Copy link
Contributor Author

Will do

@browniefed
Copy link
Contributor

For anyone that stumbles upon this I finally got this all figured out.
Demo/Code here : https://rnplay.org/apps/LDI42g

My only issue, is that the

  1. With the horizontal ScrollView you have to define the height (at least from what I could figure out).
  2. The View in the horizontal ScrollView does not stretch completely regardless of what flex properties you add on it.
  3. The PanResponder vs ContentOffset X values are opposites, so you have to multiply by -1.
  4. The content offset is not as large as the PanResponder so I had to lower the RED threshold.

http://i.imgur.com/q9ZbOEh.png

I know this is more suited for StackOverflow however do you happen to have any insight on the matter of the size, I know I have the dimensions of the device, just want to confirm that I'm not running into a layout bug that needs to be fixed

@nicholasstephan
Copy link

@sahrens You say: if you want to be able to go straight from RED to BLUE and BLACK to BLUE without going through RED, it's going to get more complicated. That's exactly what I need, complicated or not. Any chance you can point me in the right direction? Thanks.

@felixakiragreen
Copy link
Contributor Author

@nicholasstephan do you need to do that on the row swipe? What Animated value are you tracking? You might need to set up separate animations.

@jorelllinsangan
Copy link

@DUBERT I know this is a an old thread but any chance you can share how you made it look like you're pulling in the colors from the side as opposed to @browniefed's way that changes the actual row background color?

@browniefed
Copy link
Contributor

@jorelllinsangan You would need to have the View that has your content with a solid background color and translateX the content view, then the animation of the color is on a separate wrapping View. That's likely your best option.

@jorelllinsangan
Copy link

I ended up doing it where I have 3 views wrapped in another view with {flexDirection: "row"}. I did a translateX on the parent view to render the center first. Then made it spring back onPanResponderRelease.

My next issue now is what @DUBERT was saying above. I need to figure out how to lock the ScrollView when dragging my view.

@browniefed
Copy link
Contributor

browniefed commented Nov 23, 2016

@jorelllinsangan this is why using a Horizontal scrollview per row will help w/ auto locking.
If you don't feel like doing that you can store something like scrollable on state, and when you have a PanResponder trigger in for a row swipe you can change the ScrollView scrollable prop to false and it should work.

I wrote up a very old blog post about this, wouldn't use any of that code but you can see it in action http://browniefed.com/blog/react-native-animated-listview-row-swipe/

Also check out https://github.com/jemise111/react-native-swipe-list-view

@jorelllinsangan
Copy link

jorelllinsangan commented Nov 25, 2016

@browniefed: I actually originally followed your old blogpost to get a feel for Animations. That's why I ended up asking above how to do it like how dubert did his.

I've tried doing the setting it to false but I'm not sure how to distinguish between a horizontal row swipe and a vertical swipe to scroll. I'm not sure how onPanResponderMove works. Currently, I just have Animated.event([null, {dx: this.pan.x}]) assigned to it. I was thinking of trying to detect using gesture properties in onPanResponderMove by assigning it a function, doing swipe categorizing, then doing the Animated.event(...) but that makes it so that I can't drag my view anymore.

I've used jemise111's library but it just doesn't handle row swipes the way I need it to work. I need it to work similarly to what dubert originally posted when he opened this thread.

I also noticed that when Im dragging horizontally then I accidentally scroll vertically, my animation stops calling panResponderHandlers and it just freezes. I saw your blog post about setting scrollable to false in panResponderGrant. The problem with that is that as soon as I touch the row, it will prevent me from scrolling vertically. Apparently its a known issue: #1046

@facebook facebook locked as resolved and limited conversation to collaborators Jul 22, 2018
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Jul 22, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests

7 participants