ネイティブSoundCloudレプリカを反応させる

アニメーション+ナビゲーション

UnsplashのRachit Tankによる写真

毎日コードを書くと、いつも私に付随するアプリが1つあります。犯罪の私のパートナー。私の愛。最近、出会ったすべてのアプリの詳細を分析しています。何よりも、ナビゲーションとアニメーションが私の興味をそそりました。だから、私の視線が私の最も近い、そして最愛のtrenchであるSoundCloudに落ちたのは大きな驚きではないはずです。

SoundCloudを徹底的に楽しんでいます。私が気に入っている機能の1つは、モバイルでの画像スクロールです。これにより、アーティストは語られるストーリーの追加の小道具になることがわかります。これはそのような資産であり、うまくいけば、あなたは伝えることができます。

明るい目とふさふさした尾を私はタスクに設定しました。 SoundCloudレプリカを作成し、APIからデータを抽出することにしました。

「ああ、私の甘い夏の子。」

数か月前にSoundCloudはAPIキーの配布を停止したようです。最初は「借りる」だけだと確信していました。

イライラすることに、私はこれが認証上の理由からもあまり役に立たないことに気付きました。止められない、私は前進した。そもそもこの道を歩むきっかけを思い出しました。

アニメーション+ナビゲーション

仕事に行こう!

まず、環境を設定しましょう。 Macを使用している場合は、Homebrew、Yarn、およびXcodeまたはAndroid Studioがインストールされていることを確認してください。 Windowsを使用している場合は、Yarnがインストールされ、Android Studioがセットアップされていることを確認してください。これを行う方法については、前回の記事で説明しました。

反応ネイティブをインストールしましょう。

npm install -g react-native-cli

次にプロジェクトを作成します。 SoundCloudと呼びましょう。

反応ネイティブinit SoundCloud

次のいくつかのコマンドは、プロジェクトにcdし、いくつかの依存関係をインストールし、iOSシミュレーターまたはAndroidエミュレーターを起動します。

cd SoundCloud
糸は反応ナビゲーションを追加します
糸は、react-native-vector-iconsを追加します
反応ネイティブリンク

シミュレーターとしてiPhone Xを使用します。

反応ネイティブrun-ios --simulator = 'iPhone X'

アンドロイド用:

反応ネイティブrun-android

ナビゲーション

選択したエディターを開きます。 App.jsを調整する前に、「screens」という名前の新しいフォルダーを作成しましょう。画面内に2つのファイルを作成します。

(1)Login.js

(2)Home.js

Login.js内に次のコードを挿入します。

Home.jsの場合:

次のコードのようにApp.jsを調整します。

Login.jsとHome.jsの両方で、いくつかの反応ネイティブコンポーネントをインポートし、LoginScreenコンポーネント、HomeScreenコンポーネントを作成し、ボタンを挿入します。ボタンにタイトルを付け、別の画面に「切り替える」ための反応ナビゲーション構文を実装します。

App.jsに戻って、コンポーネントと反応ナビゲーションをインポートします。次に、Switchという名前のSwitchNavigatorを作成します。 Switchの内部で、Login.jsのLoginScreenコンポーネントに「Login」という名前を割り当て、Home.jsのHomeScreenコンポーネントにも同じことを行います。最初にLoginという名前を付けたため、これがアプリの「初期ルート」になります。これをより宣言的に行うより良い方法は、「initialRouteName」をSwitchNavigatorに追加することです。

プロジェクトを保存すると、LoginScreenとHomeScreenの間を移動できるようになります。クール!それでは、Login.jsを終了します。そうすれば、もう面倒なことはありません。

まず、react-native-vector-iconsをインポートします。 renderメソッドでは、ボタンの上にアイコンを挿入します。アイコンとボタンの両方のスタイルを設定し、応答性を高め、背景色を変更します。

甘い! SwitchNavigatorをマスターしました!次へ!

screensフォルダーに戻り、さらに3つのファイルを作成します。

(1)Stream.js

(2)Search.js

(3)Profile.js

最初にStream.jsを扱います。

今Search.js:

最後に、Profile.js:

また、Home.jsにアクセスし、Buttonタイトルを調整して「I'm the HomeScreen」、onPressメソッドを調整してStreamScreenに移動します。それらをApp.jsにインポートする必要があります。また、いくつかの依存関係をインポートします。 App.js内では、インポートを次のように調整します。

ここで、react-navigationからcreateBottomTabNavigatorをインポートします。また、react-native-vector-icons MaterialIconsおよびMaterialCommunityIconsディレクトリからアイコンをインポートします。最後に、残りの画面をインポートします。

tabNavigatorをインポートしたので、それを使用することもできます。私たちの目標は、LoginScreenからHomeScreenに移動することです。これは、tabNavigatorのinitialRouteです。これには、App.jsに対する次の調整が必要です。

まず、タブを作成します。内部で、コンポーネントにrouteNameを設定します。次に、react-navigationのnavigationOptionsを使用します。 navigationOptionsの内部で、構造化ステートメントと条件ステートメントを使用して、適切な画面にアイコンを割り当てます。各画面にアイコンを割り当てた後、tabBarOptionsを使用してスタイルを設定します。最後に、SwitchNavigatorの2番目の画面をタブに調整します。この方法では、ログインから直接tabNavigatorに移動します。

この厄介な警告を取り除くには、index.jsを次のように調整します。

見よ! TabNavigatorに同意しました!次の標本— StackNavigator。各タブ内の新しい画面に移動するには、StackNavigatorを使用します。しかし、最初に、ナビゲートする画面を少なくとももう1つ作成する必要があります。それに行きましょう!

App.jsでは、「react-navigation」からcreateStackNavigatorをインポートする必要があります。

import {createSwitchNavigator、createBottomTabNavigator、createStackNavigator} from 'react-navigation'

また、「ボタン」コンポーネントを「react-native」からインポートする必要があります。

必要な調整を行ったら、インポートのすぐ下とTabNavigatorの上に、次のコードを追加します。

最初にSongScreenコンポーネントを作成します。次に、通常のようにボタンを追加し、スタイルを設定します。最後に、4つのStackNavigatorを作成し、それぞれに個別の画面を割り当てます。これが機能するには、何らかの方法でTabNavigatorと通信する必要があります。これを実現するには、TabNavigatorのルートをStackNavigatorの名前に調整します。

Home.jsに移動し、ButtonのonPressナビゲーションルートを「Song」に、タイトルを「Play Song」に調整します。次に、HomeScreenコンポーネントの上部のrenderメソッドの上に、次のコードを追加します。

プロジェクトを保存すると、次のように表示されます。

ご覧のとおり、StackNavigatorは、ヘッダーのスタイルを設定するクールなメソッドと、画面間を前後に移動するための戻る矢印を提供します。 HomeStackナビゲーターが正常に動作するようになりました。 StreamStack、SearchStack、およびProfileStackのプロセスは同じです。

Home.jsで行ったのと同じ静的メソッドを作成するために、Stream.js、Search.js、およびProfile.jsに進みたいと思います。各画面のButton onPressメソッドを調整して、各スタックがSongScreenまたは任意の追加画面に進むようにすることができます。

アニメーション

App.jsでは、最後にもう一度インポートを調整する必要があります。 react-nativeのコンポーネントと、react-native-vector-iconsのFeather and Ioniconsディレクトリのアイコンがさらに必要です。以下に示す調整を行います。

「images」という名前の新しいフォルダを作成します。ここに配置する画像は、SoundCloud機能を模倣して、水平方向にスクロールするSongScreen画像になります。私と同じ画像を使用したい場合は、こちらで見つけることができます。この画像または任意の画像をダウンロードして、画像フォルダにドラッグします。

App.jsで、インポートのすぐ下とSongScreenコンポーネントの上に、次の2つのグローバル変数を追加します。

const SCREEN_WIDTH = Dimensions.get( "window")。width
const SCREEN_HEIGHT = Dimensions.get( 'window')。height

これら2つのまったく説明のつかない変数は、使用しているデバイスの高さと幅を取得します。

SoundCloudのモバイルアプリで曲を聴くとき、画像は画面の高さ全体を占めます。ヘッダーもタブバーもありません。他の画面で使用したような静的メソッドを実装するので、ヘッダーを取り除くのは簡単です。 SongScreenコンポーネントの最上部に次のコードを挿入します。

static navigationOptions = {
ヘッダー:null
}

react-navigation 2.0.1のごく最近のリリースでは、いくつかの重要な変更がありました。以前のバージョンでは、上記の静的メソッドで単にtabBarVisible:falseを使用してtabBarを削除できました。今はそれほど簡単ではありませんが、私たちは仕事に任せています! StackNavigatorsのすぐ下に次のコードを挿入します。

プロジェクトを保存すると、ヘッダーとtabBarが消えていることがわかります。このコードを調べて、ここで何が起こっているのかを理解してください。変数routeNameにnavigation.state.routes [navigation.state.index]を配置するために、構造化を使用します。これにより、現在のルート(または画面)がrouteName変数に配置されます。次に、「これがSongScreenの場合、その厄介なtabBarを取り除きます」という条件文を実装します。これはすべて、HomeStack StackNavigatorに接続されています。他の3つのスタックについても同様に行うことができますが、それはあなたにお任せします。

SongScreenレンダリング関数の内部では、コンポーネント全体を、react-nativeのおかげでインポートしたSafeAreaViewコンポーネント内にラップします。このコンポーネントは、すべてのデバイスでアプリが見やすく表示されることを保証するだけです(特に、iPhone Xの大画面に対応するために作られています)。私が読んだすべてのことから、これを行うためのベストプラクティスです。

このSafeAreaViewコンポーネントの内部には、Buttonを含む現在のViewコンポーネントを保持します。このビューの下で、Animated.Viewを実装します。これは、react-nativeを介してインポートしたAnimated APIのおかげです。通常のビューと同じですが、内部でアニメーションを使用できる点が異なります。このAnimated.View内に、画像をアニメーション化できるAnimated.Imageコンポーネントを実装します。

画像をスクロールするには、画面の幅より大きくする必要があります。これが、幅がSCREEN_WIDTH * 3に設定されている理由です。これにより、画像が3画面幅になります。そのため、スクロールをアニメートするときは、2つの画面の長さをスクロールする必要があります。コメントアウトされたmarginLeftについては心配しないでください。まだ戻ってきます。また、「imageContainer」というラベルの付いたAnimated.Viewのスタイル設定についても心配しないでください。この時点では空です。

goodbye.jpgという名前のインポートした画像が、SongScreenから移動する唯一の手段であるボタンを含むビューの上に置かれていることがわかります。これについては後で扱いますが、今のところは、シミュレーター(Macの場合はcommand + R)をリロードして、ログイン画面に戻ることができます。次に、x軸のスクロールアニメーションを処理します。

静的メソッドの後に、ヘッダーを取り除くために利用した以下のコードを挿入します。

コンストラクター関数内で、新しいAnimated.Valueを作成します。その後、componentDidMountを使用して、animateという名前の関数を初期化します。このアニメーション関数内で、animatedValueを0に設定します。これを初期化状態と考え、後でsetStateを使用して値を調整します。

Animated APIには複数の「タイプ」があり、Animated.timingもその1つです。ドキュメントを見てみましょう。

toValueを1に設定します。これにより、0〜1の初期値が使用されます。0は0%、1は100%と考えることができます。次に、イージング:Easing.linearを使用して、一定の速度でスクロールします。

スクロール期間を90000msまたは90秒に設定します。これは通常、再生中の曲の長さに設定されますが、SoundCloud APIの豪華さはないため、90秒で十分です。次に、.start()を使用してアニメーションを開始します。そのままにしておくと、1分半が経過するとアニメーションが開始および停止します。アニメーションループを作成するには、以下のコードを使用してプロセスを繰り返します。

.start(()=> this.animate())

スクロールが機能するには、最後にもう1つ必要なことがあります。レンダリング関数の下で、returnステートメントの上に、以下のコードを追加します。

ここで、returnステートメント内のAnimated.Imageコンポーネントからコメントアウトされた、奇妙なmarginLeft変数を作成します。補間は、入力範囲を出力範囲にマップします。したがって、0 = 0%と1 = 100%の場合、画像の幅はSCREEN_WIDTH * 3であるため、左マージンを先頭(または0%)から開始し、画像がさらに2画面幅(100%)スクロールしたら終了します。

Animated.ImageでmarginLeftのコメントを解除し、シミュレーターまたはエミュレーターを再ロードすると、次のアニメーションが表示されます。 gifに変換するために少し高速化する必要がありますが、アイデアは得られます。

GIFを作るのは本当にひどいです。私はこの写真ゼロ正義を行います! とにかく、前進!

これはSoundCloudレプリカですので、SongScreenイメージを一部に見せてみましょう。 Animated.Imageの下で、最も外側のAnimated.ViewおよびSafeAreaViewの内側に、次のコードを挿入します。

ここで、いくつかのアイコンとテキストを実装します。それらを3つのカテゴリに分けます:一番上のアイコン、UpperIcons、およびLowerIcons。これは、スタイルを設定してシミュレーターを見てみるとより明確になるので、それをやってみましょう。それに応じてStyleSheetを更新します。

App.jsを保存して、シミュレーターを見てください。

これで、最上部、上部、下部のアイコンがわかりました。しかし、なぜビューの一番上のアイコン(およびテキスト)を非常に高くしたのでしょうか?モバイルのSoundCloudに慣れている場合は、y軸のスクロール(またはパン)で、ビューの下部にSongScreenイメージをアニメーション化できることがわかります。これが行われたときに表示されるビューの唯一の部分は、最上部のアイコンです。これにより、Animated.Imageの背後にあるビューも公開され、ナビゲーションボタンに再びアクセスできるようになります。

そうは言っても、一番上のアイコンは常にそこにとどまるわけではありません。 Animated.Imageがビューの下部に到達するとフェードインします。同様に、Animated.Imageとそれに関連するアイコンは、Viewからフェードします。画像をパン上で下にスクロールさせ、不透明度アニメーションを作成する必要があります。

静的なnavigationOptionsメソッドの下とコンストラクター関数の上に、次のコードを追加します。

まず、react-nativeのcomponentWillMountライフサイクルメソッドを利用します。内部では、「アニメーション」を初期化し、xとyの値を0に設定します。次に、前にインポートした反応ネイティブのPanResponderを作成します。ドキュメントを見てみましょう。

どの機能を探しているのか自問してみましょう。このコードを順を追って説明すると、わかりやすくなります。

onMoveShouldSetPanResponder:()=> true、

ここでは、画面をジェスチャー(またはパン)するたびにPanResponderが応答するように、レスポンダーになる許可を求めます。

onPanResponderMove:(evt、GestureState)=> {
  this.animation.setValue({x:0、y:GestureState.dy})
}

onPanResponderMoveは、画面上のジェスチャの動きを追跡します。 x軸については心配しないので、0のままにします。gestureStateパラメーターを使用してy軸の位置を追跡し、その値をユーザーの指が画面上のどこにでも設定します。

onPanResponderReleaseは、ユーザーが画面から指を離したときに呼び出されます。このメソッドの内部には、3つの条件ステートメントがあります。 2番目から始めましょう。

else if(gestureState.dy <0){
  Animated.spring(this.animation.y、{
    toValue:0、
    テンション:1
  })。開始()
}

最初に理解することは、画面をパンするときです。これは負の数として解釈されます。逆に、画面を下にパンすると、これは正の数として解釈されます。したがって、gestureState.dy <0は、「画面を上に移動する場合、次のことを行います。」

そのため、上にスクロールすると、Animated.springが実装され、アニメーション状態が渡され、画像が画面の上部に戻るように指示されます。 Animated.springを使用して画像を所定の位置に「スプリング」するため、張力を利用します:1画像が所定の位置にバウンドするのを停止します。最後に、アニメーションを開始します。

else if(gestureState.dy> 0){
  Animated.spring(this.animation.y、{
    toValue:SCREEN_HEIGHT-60、
    テンション:1
  })。開始()
}

ここでは、画面をパンダウンする以外はまったく同じことを行います。正の数が減少し、負の数が増加することを忘れないでください。したがって、SCREEN_HEIGHTを数値として渡すと、画面の一番下から開始されます。次に、60を減算して、画面の下部に60単位上に画像を配置します。

必要な理由について理解が深まったら、後で最初の条件ステートメントに戻ります。

renderメソッドの上で、returnステートメントの上に次のコードを追加します。

animatedHeightの中には、画面上のジェスチャーを正の数と負の数に補間するメソッドを格納します。そのメソッドはgetTranslateTransform()です。

animatedIconOpacityは、スクロール時のupperMostIconsの不透明度を変更します。 animatedScreenOpacityは、Animated.Imageのテキストとアイコンに対して同じことを行います。 「クランプ」を使用して、不透明度の値が目的の範囲に収まるようにします。

animatedScreenOpacityとanimatedIconOpacityの入力範囲はほぼ同じで、逆になっています。ここでのアイデアは、animatedScreenOpacityがフェードアウトするのとほぼ同時にupperMostIconsをフェードインさせることです。 animatedIconOpacityは画面の一番上から始まり、bottom-200に進み、bottom-60で終わります。シミュレータでこれがどのように見えるかはすぐにわかります。

returnステートメントでは、PanResponderを渡して、animatedHeightとanimatedOpacityの値を追加する必要があります。最初に最も外側のAnimated.Viewを扱います。次の調整を行います。

  {... this.PanResponder.panHandlers}
  style = {[animatedHeight、styles.imageContainer]}>

次に、upperMostIcons Animated.Viewを次のように調整します。

upperIconsの場合:

最後に、lowerIcons:

App.jsを保存すると、次のアニメーションが表示されます。

画像が下と上に「湧き」ますが、跳ね返りません。また、画像を下に移動するために画面を下にパンすることにも気付くでしょう。ただし、onPress画像は画面の上部に表示され、SoundCloudの機能を模倣します。

ただし、この動作を不正に変更したい場合(これは変更しません)、PanResponder内に次のコードを追加します。

上記のgifの最後に、エッジケースを示しました。ユーザーが画面から画像をパンできるようにするのは少しばかげているでしょう。これを防ぐために何をしましたか?ここでどんな魔術が働いていますか? onPanResponderRelease内の最初の条件ステートメントを覚えていますか?もう一度見てみましょう:

if(gestureState.moveY 
  Animated.spring(this.animation.y、{
    toValue:0、
    テンション:1
  })。開始()
}

if(y軸ジェスチャは画面上の任意の場所にあり、上にスクロールします){画像を初期値に戻し、バウンスさせない}これは、gifのデモを見た後に理解しやすいと思います(少なくともそれです)。

最後にやること。私たちを家に誘導した最初のビューのボタンはどうなりましたか? flexs:styles.containerの1を取り除き、marginTopに55を指定すると、それが表示されます。そうは言っても、私はこのボタンに飽きてきました。それは歓迎されず、私はむしろそれを置き換えてもらいたいと思っています。 SafeAreaViewのすぐ内側で、それに応じて最初のAnimated.Viewの上のビューを調整します。

古いビューのすぐ下にビューが追加されていることがわかります。このビューを作成してflex:1を渡すと、関心のある部分(その上のビュー)を除いて、このビューで画面を埋めるように反応するよう指示されます。次に、スタイルシートのコンテナを調整し、次の2つのアイテムをスタイルシートに追加します。

App.jsを保存すると、プロジェクトが完了します。以下のビデオで少し調整しました。 Login.jsのSoundCloudアイコンのサイズを、とんでもない50から、より合理的な100単位に変更しました。私はまた、SoundCloudの好きなものを複製するために全力を尽くしました。お気に入りのコーディングソングの1つをたまたま追加しました。

それがどのように見えるか見てみましょう。

ハザ!それは戦いでしたが、SoundCloudの機能のいくつかを征服する間、誰も取り残さなかったことを願っています!

次回まで!

このプロジェクトは私のGithubで見つけることができます。手を差し伸べる:Twitter | DEV