このインタラクションはあまりにも破裂してうつ伏せになった.

17515 ワード

アニメーションはappのために優れたユーザー体験を創造する重要な構成部分です.その重要な課題は、アプリケーションの論理をユーザーに説明することですが、一般的なエラーは、アニメーションを無謀に使用することであり、ユーザー体験を改善する全体的な観点を否定します.優れたアプリケーションや平凡なアプリケーションだけでなく、アプリケーションを優れたものにするには、アニメーションを正しく統合し、余計なことをしないようにする必要があります.
この文書では、ScrollViewとreact-nativeのAnimated APIを使用してタイトルアニメーションを作成する方法について説明します.文章の最後に、次の出力が得られます.
How it works?
ScrollViewでヘッダーをレンダリングし、ScrollViewの上部の位置をヘッダーのオフセット量に設定します.次に、ScrollViewスクロール位置を使用してヘッダーを簡単に挿入できます.そこで、理解してみましょう.
Let’s Code
Interpolateを使用して、位置、サイズ、色、Animatedを変更します.イベント()は、ScrollViewの位置とアニメーションの状態値をマッピングします.Interpolate(https://animationbook.codedaily.io/interpolation)とAnimated.event(https://animationbook.codedaily.io/animated-event/)も参照してください.
常にアニメーション設定でuseNativeDriver:trueを設定します.各レンダリングフレームのJSコードとネイティブコードとの橋渡しをスキップすることで、アニメーションをスムーズにすることができます.
  • React-Nativeの制限のため、seNativeDriverは現在絶対位置をサポートしていません.このためtransformプロパティを使用しました.上部ではtranslate Y、左側ではtranslate Xを使用します.

  • コンストラクション関数からのアニメーション値を使用して状態scrollYを初期化します.以下に示します.
    constructor(props) {        
       super(props);       
       this.state = {            
           scrollY: new Animated.Value(0)        
        };    
    }
    

    コンポーネントを再表示したくないのでsetState()メソッドは使用しません.ScrollViewのonScrollプロパティを使用して、スクロール位置のある状態をマップします.
    
          //child here
    
    

    Animated.event()メソッドは、ScrollViewのYオフセットマッピング状態scrollYを使用して垂直オフセットを表します.
    scrollEventThrottleは、スクロール時にスクロールイベントがトリガーされる頻度(ミリ秒単位の時間間隔)を制御します.数値を小さくすると、スクロール位置を追跡するコードの精度が向上しますが、ブリッジを介して送信される情報量が大きいため、スクロール性能に問題が発生する可能性があります.JSが画面更新率に同期してループを実行している場合、1~16の間で設定した値の間に差があることに気づかないでください.
    輪郭画像をアニメーション処理して、画面の真ん中から左側のタイトルまでInterpolationでアニメーション処理する時です.
    //artist profile image position from left
    _getImageLeftPosition = () => {
        const {scrollY} = this.state;
    
        return scrollY.interpolate({
            inputRange: [0, 80, 140],
            outputRange: [ThemeUtils.relativeWidth(30), ThemeUtils.relativeWidth(38), ThemeUtils.relativeWidth(10)],
            extrapolate: 'clamp',
            useNativeDriver: true
        });
    };
    

    各アトリビュートは、まず補間によって実行できます.補間は通常、線形補間を使用して入力範囲を出力範囲にマッピングしますが、スローダウン機能もサポートされています.既定では、外挿カーブは所定の範囲を超えていますが、出力値をクランプすることもできます.
    ここで、入力範囲0~80からデバイス幅に対する画像の位置を30から38に、80~140から38~10に変更します.ステータス値が以前に完了したScrollViewのonScrollマッピングから変更されたときに呼び出される補間方法.
    //artist profile image position from top
    _getImageTopPosition = () => {
        const {scrollY} = this.state;
    
        return scrollY.interpolate({
            inputRange: [0, 140],
            outputRange: [ThemeUtils.relativeHeight(20), Platform.OS === 'ios' ? 8 : 10],
            extrapolate: 'clamp',
            useNativeDriver: true
        });
    };
    
     //artist profile image width
      _getImageWidth = () => {
          const {scrollY} = this.state;
    
          return scrollY.interpolate({
              inputRange: [0, 140],
              outputRange: [ThemeUtils.relativeWidth(40), ThemeUtils.APPBAR_HEIGHT - 20],
              extrapolate: 'clamp',
              useNativeDriver: true
          });
      };
    
      //artist profile image height
      _getImageHeight = () => {
          const {scrollY} = this.state;
    
          return scrollY.interpolate({
              inputRange: [0, 140],
              outputRange: [ThemeUtils.relativeWidth(40), ThemeUtils.APPBAR_HEIGHT - 20],
              extrapolate: 'clamp',
              useNativeDriver: true
          });
      };
    

    左の位置と同様に、位置をtopに設定し、ScrollViewのスクロール位置に基づいて画像の高さと幅を設定します.今、Animated.イメージのスタイルは、次のように設定されます.
    render() {
        const profileImageLeft = this._getImageLeftPosition();
        const profileImageTop = this._getImageTopPosition();
        const profileImageWidth = this._getImageWidth();
        const profileImageHeight = this._getImageHeight();
    
    return(
          
        );
    }
    

    これで、ユーザーがScrollViewをスクロールすると、onScrollメソッドが呼び出され、ステータスscrollYが変更され、stateYが変更されると、補間メソッドが呼び出され、美しいアニメーションが完了します.現在の例では、画像の枠線の幅、画像の枠線の色、タイトルの不透明度など、多くの補間値を使用しています.すべての方法は、スクロール位置マッピングと同じ原理で動作します.
    最終コード:
    import React, {Component, PureComponent} from 'react';
    import {
        Animated,
        View,
        StatusBar,
        Text,
        Image,
        Platform,
        StyleSheet,
        Linking,
        TouchableOpacity,
    } from 'react-native';
    
    /*Data*/
    import artistData from './assets/data/SongData.json';
    import MaterialAnimatedView from './MaterialAnimatedView';
    import {HTouchable} from '../../components/HTouchable';
    import {Navigation, Options} from 'react-native-navigation';
    import {MyColors} from '../../config/Colors';
    import {ThemeUtils} from './utils/ThemeUtils';
    const ARTIST_NAME = 'Villa_Mou';
    const coverImage = require('./assets/images/bg.png');
    const profileImage = require('./assets/images/icon.png');
    const backImage = require('./assets/images/back.png');
    
    interface Props {
        componentId: string;
    }
    interface State {
        scrollY: any;
    }
    /**
     *        30%
     */
    const HEADER_IMAGE_HEIGHT = ThemeUtils.relativeHeight(30);
    export default class ArtistScreen extends PureComponent {
        static options(): Options {
            return {
                topBar: {
                    visible: false,
                },
                statusBar: {drawBehind: true, style: 'light', backgroundColor: 'rgba(0,0,0,0.4)'},
            };
        }
        constructor(props) {
            super(props);
            this.state = {
                scrollY: new Animated.Value(0),
            };
        }
    
        render() {
            return (
                
                    {this.renderHeaderImageBg()}
                    {this.renderHeader()}
                    {this.renderUserIcon()}
                    {this.renderVipCard()}
                    {this.renderList()}
                
            );
        }
    
        private renderVipCard = () => {
            const top = this._getVipCardTop();
            const opacity = this._getVipCardOpacity();
            return (
                
                      VIP 
                
            );
        };
    
        private renderList = () => {
            const listViewTop = this._getListViewTopPosition();
            const normalTitleOpacity = this._getNormalTitleOpacity();
            return (
                
                    
                        {ARTIST_NAME}
                    
    
                    
                        {artistData.map((item, index) => this.renderItem(index, item))}
                    
                
            );
        };
    
        private renderUserIcon = () => {
            const profileImageLeft = this._getImageLeftPosition();
    
            const profileImageTop = this._getImageTopPosition();
    
            const profileImageWidth = this._getImageWidth();
    
            const profileImageHeight = this._getImageHeight();
    
            const profileImageBorderWidth = this._getImageBorderWidth();
    
            const profileImageBorderColor = this._getImageBorderColor();
            return (
                
            );
        };
    
        private renderHeaderImageBg = () => {
            const headerImageOpacity = this._getHeaderImageOpacity();
            return (
                
            );
        };
    
        private renderHeader = () => {
            const headerBackgroundColor = this._getHeaderBackgroundColor();
            const headerTitleOpacity = this._getHeaderTitleOpacity();
            return (
                
                    {Platform.OS === 'android' && (
                        
                    )}
                    
                         {
                                Navigation.pop(this.props.componentId);
                            }}>
                            
                        
    
                        
                            {ARTIST_NAME}
                        
                    
                
            );
        };
    
        renderItem = (index, item) => {
            return (
                
                     {}}>
                        
                        
                            
                                {item.songName}
                            
                            {item.albumName}
                        
                    
                
            );
        };
    
        // scrollY  0  140 ,        
        _getHeaderBackgroundColor = () => {
            const {scrollY} = this.state;
    
            return scrollY.interpolate({
                inputRange: [0, 140],
                outputRange: ['rgba(0,0,0,0.0)', MyColors.BLACK],
                extrapolate: 'clamp',
                useNativeDriver: true,
            });
        };
    
        //scrollY  0  140 ,    1 0
        _getHeaderImageOpacity = () => {
            const {scrollY} = this.state;
    
            return scrollY.interpolate({
                inputRange: [0, 140],
                outputRange: [1, 0],
                extrapolate: 'clamp',
                useNativeDriver: true,
            });
        };
    
        //artist profile image position from left
        _getImageLeftPosition = () => {
            const {scrollY} = this.state;
    
            return scrollY.interpolate({
                inputRange: [0, 80, 140],
                outputRange: [
                    ThemeUtils.relativeWidth(35),
                    ThemeUtils.relativeWidth(38),
                    ThemeUtils.relativeWidth(12),
                ],
                extrapolate: 'clamp',
                useNativeDriver: true,
            });
        };
    
        //artist profile image position from top
        _getImageTopPosition = () => {
            const {scrollY} = this.state;
    
            return scrollY.interpolate({
                inputRange: [0, 140],
                outputRange: [
                    ThemeUtils.relativeHeight(20),
                    Platform.OS === 'ios' ? 8 : 10 + StatusBar.currentHeight,
                ],
                extrapolate: 'clamp',
                useNativeDriver: true,
            });
        };
    
        //artist profile image width
        _getImageWidth = () => {
            const {scrollY} = this.state;
    
            return scrollY.interpolate({
                inputRange: [0, 140],
                outputRange: [ThemeUtils.relativeWidth(30), ThemeUtils.APPBAR_HEIGHT - 20],
                extrapolate: 'clamp',
                useNativeDriver: true,
            });
        };
    
        //artist profile image height
        _getImageHeight = () => {
            const {scrollY} = this.state;
    
            return scrollY.interpolate({
                inputRange: [0, 140],
                outputRange: [ThemeUtils.relativeWidth(30), ThemeUtils.APPBAR_HEIGHT - 20],
                extrapolate: 'clamp',
                useNativeDriver: true,
            });
        };
    
        //artist profile image border width
        _getImageBorderWidth = () => {
            const {scrollY} = this.state;
    
            return scrollY.interpolate({
                inputRange: [0, 140],
                outputRange: [StyleSheet.hairlineWidth * 3, StyleSheet.hairlineWidth],
                extrapolate: 'clamp',
                useNativeDriver: true,
            });
        };
    
        //artist profile image border color
        _getImageBorderColor = () => {
            const {scrollY} = this.state;
    
            return scrollY.interpolate({
                inputRange: [0, 140],
                outputRange: [MyColors.CARD_BG_COLOR, 'rgba(255,255,255,1)'],
                extrapolate: 'clamp',
                useNativeDriver: true,
            });
        };
    
        _getVipCardTop = () => {
            const {scrollY} = this.state;
            return scrollY.interpolate({
                inputRange: [0, 50],
                outputRange: [ThemeUtils.relativeHeight(37), ThemeUtils.relativeHeight(30)],
                extrapolate: 'clamp',
                useNativeDriver: true,
            });
        };
    
        _getVipCardOpacity = () => {
            const {scrollY} = this.state;
            return scrollY.interpolate({
                inputRange: [0, 50, 60],
                outputRange: [1, 1, 0],
                extrapolate: 'clamp',
                useNativeDriver: true,
            });
        };
    
        //Song list container position from top
        _getListViewTopPosition = () => {
            const {scrollY} = this.state;
            //TODO:        
            return scrollY.interpolate({
                inputRange: [0, 250],
                outputRange: [ThemeUtils.relativeHeight(100) - ThemeUtils.relativeHeight(70), 0],
                extrapolate: 'clamp',
                useNativeDriver: true,
            });
        };
    
        //header title opacity
        _getHeaderTitleOpacity = () => {
            const {scrollY} = this.state;
    
            return scrollY.interpolate({
                inputRange: [0, 20, 50],
                outputRange: [0, 0.5, 1],
                extrapolate: 'clamp',
                useNativeDriver: true,
            });
        };
    
        //artist name opacity
        _getNormalTitleOpacity = () => {
            const {scrollY} = this.state;
    
            return scrollY.interpolate({
                inputRange: [0, 20, 30],
                outputRange: [1, 0.5, 0],
                extrapolate: 'clamp',
                useNativeDriver: true,
            });
        };
    }
    
    const styles = StyleSheet.create({
        container: {
            flex: 1,
            backgroundColor: 'white',
        },
        /*header style*/
        headerLeftIcon: {
            position: 'absolute',
            left: ThemeUtils.relativeWidth(2),
        },
        headerRightIcon: {
            position: 'absolute',
            right: ThemeUtils.relativeWidth(2),
        },
        headerStyle: {
            height: ThemeUtils.APPBAR_HEIGHT,
            width: '100%',
            alignItems: 'center',
            justifyContent: 'center',
        },
        headerTitle: {
            textAlign: 'center',
            justifyContent: 'center',
            color: MyColors.HEADER_TEXT_COLOR,
            fontSize: ThemeUtils.fontNormal,
            fontWeight: 'bold',
        },
        /*Top Image Style*/
        headerImageStyle: {
            height: HEADER_IMAGE_HEIGHT,
            width: '100%',
            top: 0,
            alignSelf: 'center',
            position: 'absolute',
        },
        /*profile image style*/
        profileImage: {
            position: 'absolute',
            zIndex: 100,
        },
        /*profile title style*/
        profileTitle: {
            textAlign: 'center',
            color: MyColors.white,
            top: ThemeUtils.relativeHeight(29),
            left: 0,
            fontWeight: 'bold',
            right: 0,
            fontSize: 20,
        },
        /*song count text style*/
        songCountStyle: {
            position: 'absolute',
            textAlign: 'center',
            fontWeight: '400',
            top: ThemeUtils.relativeHeight(40),
            left: 0,
            right: 0,
            fontSize: ThemeUtils.fontNormal,
        },
        artistCardContainerStyle: {
            backgroundColor: MyColors.CARD_BG_COLOR,
            elevation: 5,
            shadowRadius: 3,
            shadowOffset: {
                width: 3,
                height: 3,
            },
            paddingVertical: 5,
            paddingHorizontal: 15,
            borderRadius: 6,
            marginBottom: 5,
            marginHorizontal: 15,
            flexDirection: 'row',
            alignItems: 'center',
        },
        artistImage: {
            height: ThemeUtils.relativeWidth(15),
            width: ThemeUtils.relativeWidth(15),
            borderRadius: ThemeUtils.relativeWidth(7.5),
            borderWidth: 1,
        },
        songTitleStyle: {
            fontSize: ThemeUtils.fontNormal,
            color: MyColors.BLACK,
        },
        cardTextContainer: {
            flex: 1,
            margin: ThemeUtils.relativeWidth(3),
        },
    });
    
    

    もちろん一部の相性の問題もあるかもしれませんが、これは時間をかけて調整する必要があります.この文章が好きになってほしいです.