このインタラクションはあまりにも破裂してうつ伏せになった.
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を初期化します.以下に示します.
コンポーネントを再表示したくないのでsetState()メソッドは使用しません.ScrollViewのonScrollプロパティを使用して、スクロール位置のある状態をマップします.
Animated.event()メソッドは、ScrollViewのYオフセットマッピング状態scrollYを使用して垂直オフセットを表します.
scrollEventThrottleは、スクロール時にスクロールイベントがトリガーされる頻度(ミリ秒単位の時間間隔)を制御します.数値を小さくすると、スクロール位置を追跡するコードの精度が向上しますが、ブリッジを介して送信される情報量が大きいため、スクロール性能に問題が発生する可能性があります.JSが画面更新率に同期してループを実行している場合、1~16の間で設定した値の間に差があることに気づかないでください.
輪郭画像をアニメーション処理して、画面の真ん中から左側のタイトルまでInterpolationでアニメーション処理する時です.
各アトリビュートは、まず補間によって実行できます.補間は通常、線形補間を使用して入力範囲を出力範囲にマッピングしますが、スローダウン機能もサポートされています.既定では、外挿カーブは所定の範囲を超えていますが、出力値をクランプすることもできます.
ここで、入力範囲0~80からデバイス幅に対する画像の位置を30から38に、80~140から38~10に変更します.ステータス値が以前に完了したScrollViewのonScrollマッピングから変更されたときに呼び出される補間方法.
左の位置と同様に、位置をtopに設定し、ScrollViewのスクロール位置に基づいて画像の高さと幅を設定します.今、Animated.イメージのスタイルは、次のように設定されます.
これで、ユーザーがScrollViewをスクロールすると、onScrollメソッドが呼び出され、ステータスscrollYが変更され、stateYが変更されると、補間メソッドが呼び出され、美しいアニメーションが完了します.現在の例では、画像の枠線の幅、画像の枠線の色、タイトルの不透明度など、多くの補間値を使用しています.すべての方法は、スクロール位置マッピングと同じ原理で動作します.
最終コード:
もちろん一部の相性の問題もあるかもしれませんが、これは時間をかけて調整する必要があります.この文章が好きになってほしいです.
この文書では、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コードとネイティブコードとの橋渡しをスキップすることで、アニメーションをスムーズにすることができます.
コンストラクション関数からのアニメーション値を使用して状態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),
},
});
もちろん一部の相性の問題もあるかもしれませんが、これは時間をかけて調整する必要があります.この文章が好きになってほしいです.