React Native动态渐变色动画

8
我正在尝试在React Native中创建一个渐变,当应用程序打开时,它将以一种颜色开始,然后每30秒逐渐变成另一种颜色。常规的线性渐变可以正常工作,无需添加动画。我尝试使用插值和Animated timing,如React Native文档中所示,但似乎没有任何效果。
我的代码:
import React, {Component} from 'react';
import {processColor, AppRegistry, StyleSheet, Dimensions, Animated, Image, Easing, View} from 'react-native';

import TimerMixin from 'react-timer-mixin';
import LinearGradient from 'react-native-linear-gradient';

var screenWidth = Dimensions.get('window').width;
var screenHeight = Dimensions.get('window').height;

//HEX version of colors
var gradientColors = [['#EF2A2A', '#EF6A2A'], //Red
['#EF6A2A', '#EFD82A'], //Orange
['#1BD170', '#61E822'], //Green
['#22D2E6', '#26F084'], //Aqua
['#2A3BEF', '#2ADCEF'], //Blue
['#EF2AD2', '#2A3BEF'], //Purple
['#EF2AD2', '#EF2A2A'] //Pink
]
var gradientColorsNoHash = [['EF2A2A', 'EF6A2A'], //Red
['EF6A2A', 'EFD82A'], //Orange
['1BD170', '61E822'], //Green
['22D2E6', '26F084'], //Aqua
['2A3BEF', '2ADCEF'], //Blue
['EF2AD2', '2A3BEF'], //Purple
['EF2AD2', 'EF2A2A'] //Pink
]
/*var gradientColors = [['ef2a2a', 'ef6a2a'], //Red
['ef6a2a', 'efd82a'], //Orange
['1bd170', '61e822'], //Green
['22d2e6', '26f084'], //Aqua
['2a3bef', '2adcef'], //Blue
['ef2ad2', '2a3bef'], //Purple
['ef2ad2', 'ef2a2a'] //Pink
]*/
//RGBA Version of Colors
/*var gradientColors = [['rgba(239, 42, 42, 1)', 'rgba(239, 106, 42, 1)'], //Red
['rgba(239, 106, 42, 1)', 'rgba(239, 216, 42, 1)'], //Orange
['rgba(0, 221, 103, 1)', 'rgba(97, 232, 35, 1)'], //Green
['rgba(34, 210, 230, 1)', 'rgba(38, 240, 132, 1)'], //Aqua
['rgba(42, 59, 239, 1)', 'rgba(42, 220, 239, 1)'], //Blue
['rgba(239, 42, 210, 1)', 'rgba(42, 59, 239, 1)'], //Purple
['rgba(239, 42, 210, 1)', 'rgba(239, 42, 42, 1)'] //Pink
]*/

function hex(c) {
  var s = "0123456789abcdef";
  var i = parseInt(c);
  if (i == 0 || isNaN(c))
    return "00";
  i = Math.round(Math.min (Math.max (0, i), 255));
  //console.log('hex(c) complete!');
  return s.charAt((i - i % 16) / 16) + s.charAt(i % 16);
}

// Convert an RGB triplet to a hex string
function convertToHex (rgb) {
  return hex(rgb[0]) + hex(rgb[1]) + hex(rgb[2]);
}

// Convert a hex string to an RGB triplet
function convertToRGB(hex) {
  var color = [];
  color[0] = parseInt(hex.substring(0, 2), 16);
  color[1] = parseInt(hex.substring(2, 4), 16);
  color[2] = parseInt(hex.substring(4, 6), 16);
  return color;
}

function generateColor(colorStart,colorEnd,colorCount) {

    // The beginning of your gradient
    var start = convertToRGB(colorStart);

    // The end of your gradient
    var end   = convertToRGB(colorEnd);

    // The number of colors to compute
    var len = colorCount;

    //Alpha blending amount
    var alpha = 0.0;

    var saida = [];

    for (i = 0; i < len; i++) {
        var c = [];
        alpha += (1.0/len);

        c[0] = start[0] * alpha + (1 - alpha) * end[0];
        c[1] = start[1] * alpha + (1 - alpha) * end[1];
        c[2] = start[2] * alpha + (1 - alpha) * end[2];

        saida.push(convertToHex(c));

    }

    return saida;

}

var number = randomIntFromInterval(0,6)
function randomIntFromInterval(min,max) { return Math.floor(Math.random()*(max-min+1)+min); }

const GradientView = React.createClass({
  mixins: [TimerMixin],

  getInitialState() {
    return {
      gradIndex: number,
      colorTop: gradientColors[number][0],
      colorBottom: gradientColors[number][1],
    }
  },
  componentDidMount() {
    this.setInterval(() => {

      var count = 0
      var topGradArray = generateColor(gradientColorsNoHash[this.state.gradIndex][0],(this.state.gradIndex === 6 ? 0 : gradientColorsNoHash[this.state.gradIndex+1][0] ),770);
      var bottomGradArray = generateColor(gradientColorsNoHash[this.state.gradIndex][1],(this.state.gradIndex === 6 ? 0 : gradientColorsNoHash[this.state.gradIndex+1][1] ),770);
      console.log('Gradients Made');
      var clearId = this.setInterval(() => {
        if (count == 0) {
          this.setState({ clearId: clearId, gradIndex: ( this.state.gradIndex === 6 ? 0 : this.state.gradIndex+1 ) });
          console.log('clearId SET!');
            }

            this.setState({
              colorTop: processColor(topGradArray[count]),
              colorBottom: processColor(bottomGradArray[count]),
            });
            count = count+1

            if (count == 769) {
              console.log('colorTop and Bottom Saved');
              this.clearInterval(this.state.clearId)
            }
          }, 13);

    }, 30000);
  },

  render(){
    return(
      <LinearGradient colors={[this.state.colorTop, this.state.colorBottom]}>
        <View style={styles.translucentContainer}/>
      </LinearGradient>
    );
  }
});

const styles = StyleSheet.create({
  translucentContainer: {
    width: screenWidth,
    height: screenHeight,
    backgroundColor: 'white',
    opacity: 0.3,
  },
});

export default GradientView;
AppRegistry.registerComponent('GradientView', () => GradientView);

更新:经过查阅许多不同的资源,我得出结论,唯一实现 LinearGradient 类动画的方法是逐步快速地改变颜色,就像他们的文档中所示。然而,他们的示例是连续的,并且不允许您设置所需的最终颜色。对于我的应用程序,我希望渐变保持一个颜色30秒,然后经过10秒的过渡到下一个颜色渐变,然后重复。例如,它会看起来像这样:红色渐变(30秒),从红色到橙色的过渡(10秒),橙色渐变(30秒),从橙色到绿色的过渡(10秒),等等。
我使用此代码时遇到了两种类型的错误,似乎交替出现。通常,第一个错误是当第一个计时器(30秒)触发时出现的错误:

The first error

当我忽略了那个错误信息后,当同一个定时器再次触发时,出现了这个错误:

The second error

此时,我认为错误的源头在于在componentDidMount()函数中生成颜色时没有正确处理。

1
请查看以下链接以了解有关React Native动画的更多信息:
  • https://medium.com/react-native-training/react-native-animations-using-the-animated-api-ebe8e0669fae#.dwp3h9m0f
  • https://dev59.com/QFwY5IYBdhLWcg3wcHbZ 祝好:)
- Codesingh
@Codesingh 谢谢你提供的资源!阅读完之后,我尝试应用它们,虽然有进展,但是在应用程序启动时出现了一个错误。我更新了我的问题,附上了我添加的代码和错误信息。你有什么改动建议吗? - Armin
你有找到这个的任何东西吗? - Codesingh
@Codesingh 我在我的问题中更新了代码,但现在我正试图找出如何修复图片中的错误。 - Armin
你有很多语法错误和混淆的colorTop topColor。 - AlexB
@Armin 我已经完成了我的解决方案。现在动画流畅了 :-) - AlexB
2个回答

9

我找到了一个可行的解决方案!

使用线性插值来生成你的渐变色。这是我发现的控制渐变色的最简单方法。

chroma.js

我找到了一个名为chroma.js的库,可以很好地完成这项工作!他们有一个叫做scale.colors的方法,可以为您完成这项工作!

安装该软件包:

npm install chroma-js

你可以调整 INTERVALGRADIENT_COLOR_LENGTH 常量来改变效果。
然后在代码中使用生成的光谱变量:
import React from 'react'
import { AppRegistry, StyleSheet, Dimensions, View } from 'react-native'

import TimerMixin from 'react-timer-mixin'
import LinearGradient from 'react-native-linear-gradient'
import Chroma from 'chroma-js'

var screenWidth = Dimensions.get('window').width
var screenHeight = Dimensions.get('window').height

const TOP_COLORS = ['#EF2A2A', '#EF6A2A', '#1BD170', '#22D2E6', '#2A3BEF', '#EF2AD2', '#EF2AD2']
const BOTTOM_COLORS = ['#EF6A2A', '#EFD82A', '#61E822', '#26F084', '#2ADCEF', '#2A3BEF', '#EF2A2A']
const GRADIENT_COLOR_LENGTH = 700
const TOP_COLORS_SPECTRUM = Chroma.scale(TOP_COLORS).colors(GRADIENT_COLOR_LENGTH)
const BOTTOM_COLORS_SPECTRUM = Chroma.scale(BOTTOM_COLORS).colors(GRADIENT_COLOR_LENGTH)
const INTERVAL = 50

const GradientView = React.createClass({
  mixins: [TimerMixin],

  getInitialState () {
    return {
      topIndex: 0,
      bottomIndex: 0,
      colorTop: TOP_COLORS_SPECTRUM[0],
      colorBottom: BOTTOM_COLORS_SPECTRUM[0]
    }
  },

  componentDidMount () {
    this.setInterval(() => {
      let { topIndex, bottomIndex } = this.state

      topIndex++
      if (topIndex === TOP_COLORS_SPECTRUM.length) {
        topIndex = 0
      }

      bottomIndex++
      if (bottomIndex === BOTTOM_COLORS_SPECTRUM.length) {
        bottomIndex = 0
      }

      this.setState({
        topIndex: topIndex,
        bottomIndex: bottomIndex,
        colorTop: TOP_COLORS_SPECTRUM[topIndex],
        colorBottom: BOTTOM_COLORS_SPECTRUM[bottomIndex]
      })
    }, INTERVAL)
  },

  render () {
    return (
      <LinearGradient colors={[this.state.colorTop, this.state.colorBottom]}>
        <View style={styles.translucentContainer} />
      </LinearGradient>
    )
  }
})

const styles = StyleSheet.create({
  translucentContainer: {
    width: screenWidth,
    height: screenHeight,
    backgroundColor: 'white',
    opacity: 0.3
  }
})

export default GradientView
AppRegistry.registerComponent('GradientView', () => GradientView)

这太棒了,非常感谢!动画流畅而美丽。 - Armin
没问题。谢谢你提出这个有趣的问题。 - AlexB
这太棒了。我已经将这种方法实现到一个onScroll解决方案中,它非常有效。谢谢! - robabby
先生,您是英雄!谢谢您。 - Tareq El-Masri

1
首先,请确保您正确地按照以下步骤将LinearGradient添加到您的项目中:https://github.com/react-native-community/react-native-linear-gradient#add-it-to-your-project (尝试不使用动画渲染组件)
其次,修复您的代码。
每次都需要调用setState来触发重新渲染。
您编写的<Animated.LinearGradient ...>元素无效。它不存在。将其更改为LinearGradient元素。
在componentDidMount中,调用setInterval并在回调函数中更改动画的状态。
这是一个来自文档的工作示例:
import React from 'react';
import {
  StyleSheet,
  Text,
  View,
} from 'react-native';
import TimerMixin from 'react-timer-mixin';
import LinearGradient from 'react-native-linear-gradient';

function incrementColor(color, step) {
  const intColor = parseInt(color.substr(1), 16);
  const newIntColor = (intColor + step).toString(16);
  return `#${'0'.repeat(6 - newIntColor.length)}${newIntColor}`;
};

const AnimatedGradient = React.createClass({
  mixins: [TimerMixin],

  getInitialState() {
    return {
      count: 0,
      colorTop: '#000000',
      colorBottom: '#cccccc',
    }
  },

  componentDidMount() {
    this.setInterval(() => {
      this.setState({
        count: this.state.count + 1,
        colorTop: incrementColor(this.state.colorTop, 1),
        colorBottom: incrementColor(this.state.colorBottom, -1),
      });
    }, 20);
  },

  render() {
    return (
      <View style={styles.container}>
        <LinearGradient
          colors={[this.state.colorTop, this.state.colorBottom]}
          style={styles.gradient} />
        <Text style={{color: this.state.colorTop}}>{this.state.colorTop}</Text>
        <Text style={{color: this.state.colorBottom}}>{this.state.colorBottom}</Text>
      </View>
    );
  }
});

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  gradient: {
    width: 200,
    height: 200,
  },
});

export default AnimatedGradient;

谢谢回答,AlexB。我更新了我的问题,并提供了更多关于你的答案的信息,请阅读它。文档示例实际上是我在尝试实现这个功能时首先参考的东西。 - Armin
你尝试过使用上述示例而不进行修改吗?它是否有效? - AlexB
setInterval函数应该在componentDidMount中而不是render函数中,正如您在更新中所说的那样。 - AlexB
我对整个代码进行了彻底的改造,并更新了我的问题。首先,我按照文档完全实现了它,虽然可以工作,但并不符合我的确切需求。在进行修改后,我留下了你在我的问题中看到的代码。 - Armin
我的问题帮助了你。你考虑给我一个加1吗?另外,我正在努力解决问题,我快要完成了。 - AlexB
1
哦,没错,你有加一分。你绝对值得拥有它,感谢你迄今为止的帮助,我期待着看到你的解决方案。 - Armin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接