React Native를 사용하여 사용자 정의 애니메이션 탭 표시줄을 만듭니다.

React Navigation의 기본 탭 구성 요소가 너무 평범해 보이거나, 단지 좀 더 현대적인 것을 만들고 싶을 뿐이라면, 당신은 나와 같다.이 안내서에서, 나는 당신에게 React 내비게이션에 사용할 사용자 정의 옵션 표시줄을 만드는 방법을 보여 줄 것입니다.
편집: 이 예시를 확장하고github에 코드를 발표했습니다.Link to repo
다음은 최종 제품의 외관입니다

거기 가는 방법이야.우선, 새로운 프로젝트를 초기화하고 의존 항목을 몇 개 설치합시다.우리는 터미널에서 명령을 실행할 것이다.
$ react-native init CustomTabBar
$ cd CustomTabBar
$ npm install react-navigation react-native-gesture-handler react-native-pose
React-Navigation은 v3부터 React-native 제스처 프로세서가 필요하기 때문에 설치해야 합니다. React-native pose는 훌륭한 라이브러리입니다. 애니메이션을 간소화하기 위해 사용할 것입니다.
현재 리액션 원본 제스처 프로세서가 안드로이드에서 작동하도록 하는 링크 절차가 있습니다.위에서 이미 설명했기 때문에 나는 설정 부분을 뛰어넘을 것이다.
이제 프로그램을 시작하고 옵션 표시줄을 인코딩할 수 있습니다.
우선, 우리는 사물의 질서성을 유지하는 데 도움이 될 디렉터리 구조를 만들 것이다.
/android
/ios
...
/src
  /AppEntry.js
  /router
    /router.js
    /index.js
  /components
  /screens
/index.js
우선, 코드와 프로젝트 루트 디렉터리의 다른 파일 (package.json, app.json,.gitignore 등) 을 분리하는 src 디렉터리를 만들 것입니다.screens, componentsrouter의 목록은 자명하다.
프로젝트의 루트 디렉토리에서 기본 App.js 파일을 삭제하고 index.js/src/AppEntry.js 가져오기로 변경했습니다.
/* /index.js */


/** @format */

import { AppRegistry } from "react-native";
import App from "./src/AppEntry";
import { name as appName } from "./app.json";

AppRegistry.registerComponent(appName, () => App);
현재 우리는react 내비게이션을 사용하여 공유기를 만들고 싶지만, 우선 가상 화면을 만들어야 한다.이름을 사용하여 여러 스크린을 시뮬레이션할 수 있는 일반 스크린 구성 요소를 만듭니다.
우리는 /src/screens/index.js 파일에 다음과 같은 내보내기를 추가했다
/* /src/screens/index.js */

import React from "react";

import Screen from "./Screen";

export const HomeScreen = () => <Screen name="Home" />;
export const SearchScreen = () => <Screen name="Search" />;
export const FavoritesScreen = () => <Screen name="Favorites" />;
export const ProfileScreen = () => <Screen name="Profile" />;
현재 우리는 화면 구성 요소를 만듭니다.
/* /src/screens/Screen.js */

import React from "react";
import { Text, View, StyleSheet } from "react-native";

const S = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#bbbbbb",
    justifyContent: "center",
    alignItems: "center"
  },
  text: { fontSize: 28, color: "#222222", textAlign: "center" }
});

const Screen = ({ name }) => (
  <View style={S.container}>
    <Text style={S.text}>This is the "{name}" screen</Text>
  </View>
);

export default Screen;
공유기를 만들 때가 됐습니다.
먼저 /src/router/index.js에 추가
/* /src/router/index.js */

export { default as Router } from "./router";
이제 router.js에서 기본 BottomTabNavigator를 만듭니다.화면을 가져오고 createBottomTabNavigator을 사용하여 기본 탭 탐색기를 만듭니다.
/* /src/router/index.js */

import { createAppContainer, createBottomTabNavigator } from "react-navigation";

import {
  HomeScreen,
  SearchScreen,
  FavoritesScreen,
  ProfileScreen
} from "../screens";

const TabNavigator = createBottomTabNavigator({
  HomeScreen,
  SearchScreen,
  FavoritesScreen,
  ProfileScreen
});

export default createAppContainer(TabNavigator);
현재 AppEntry.js에 라우터 제공
/* /src/AppEntry.js */

import React from "react";

import { Router } from "./router";

export default () => <Router />;
애플리케이션을 다시 로드하면 다음 화면이 표시됩니다.

기본 옵션 표시줄은 아이콘을 지원하기 때문에 아이콘을 추가합니다.이 강좌에서는 ascii 문자를 사용하지만, 실제 프로그램에서react 원본 벡터 아이콘이나 사용자 정의 아이콘 글꼴을 사용할 수 있습니다.
도구 namecolor을 받아들여 아이콘으로 돌아가는 아이콘 구성 요소를 만듭니다.
/* /src/components/index.js */

export { default as Icon } from "./Icon";
/* /src/components/Icon.js */

import React from "react";
import { Text } from "react-native";

const iconMap = {
  home: "",
  search: "",
  favorites: "",
  profile: ""
};

const Icon = ({ name, color, style, ...props }) => {
  const icon = iconMap[name];

  return <Text style={[{ fontSize: 26, color }, style]}>{icon}</Text>;
};

export default Icon;
현재 우리는 공유기에서 이 구성 요소를 사용할 수 있다.우리는 router.js에서 화면을 변경하여 navigationOptions 도구가 있는 대상을 받아들인다.기본 옵션 표시줄은 아이콘 색을 설정할 수 있도록 tintColor를 아이콘 구성 요소에 전달합니다.
/* /src/router/router.js */

const TabNavigator = createBottomTabNavigator({
  HomeScreen: {
    screen: HomeScreen,
    navigationOptions: {
      tabBarIcon: ({ tintColor }) => <Icon name="home" color={tintColor} />
    }
  },
  SearchScreen: {
    screen: SearchScreen,
    navigationOptions: {
      tabBarIcon: ({ tintColor }) => <Icon name="search" color={tintColor} />
    }
  },
  FavoritesScreen: {
    screen: FavoritesScreen,
    navigationOptions: {
      tabBarIcon: ({ tintColor }) => <Icon name="favorites" color={tintColor} />
    }
  },
  ProfileScreen: {
    screen: ProfileScreen,
    navigationOptions: {
      tabBarIcon: ({ tintColor }) => <Icon name="profile" color={tintColor} />
    }
  }
});
아래는 얘 모양이에요.

현재 우리의 탭 표시줄은 더 좋아 보이지만, 이것은 여전히react navigation의 기본 탭 표시줄입니다.다음은 실제 사용자 정의 옵션 모음 구성 요소를 추가합니다.
먼저 사용자 정의 TabBar 구성 요소를 만듭니다. 이 구성 요소는 텍스트만 보여주고 도구를 기록합니다. 그러면 내비게이션에서 얻은 도구를 볼 수 있습니다.
/* /src/components/index.js */

export { default as Icon } from "./Icon";
export { default as TabBar } from "./TabBar";
/* /src/components/TabBar.js */

import React from "react";
import { Text } from "react-native";

const TabBar = props => {
  console.log("Props", props);

  return <Text>Custom Tab Bar</Text>;
};

export default TabBar;
사용자 지정 탭 표시줄을 사용하도록 라우터를 설정해야 합니다.다음 구성을CreateBoottomTabNavigator의 두 번째 매개변수로 추가할 수 있습니다.
/* /src/router/router.js */

...
import { Icon, TabBar } from "../components";

const TabNavigator = createBottomTabNavigator(
  {
    HomeScreen: { /* ... */ },
    SearchScreen: { /* ... */ }
  },

  {
    tabBarComponent: TabBar,
    tabBarOptions: {
      activeTintColor: "#4F4F4F",
      inactiveTintColor: "#ddd"
    }
  }
);
...
만약 우리가 옵션 표시줄에 기록된 내용을 보면, 우리는 navigation.state의 내비게이션 상태에도 노선이 포함된 것을 볼 수 있다.그리고 renderIcon 함수, onTabPress 및 우리가 필요로 할 수 있는 많은 다른 것들.우리는 또한 우리가 공유기 설정에 설정한 tabBarOptions이 어떻게 도구로 우리의 구성 요소에 주입되었는지 알아차렸다.
이제 우리는 탭 표시줄을 쓰기 시작할 수 있다.우선 기본 옵션 표시줄을 다시 만듭니다.컨테이너에 탭 버튼을 한 줄로 배열하고 각 파이프라인에 탭 버튼을 표시하는 스타일을 설정합니다.우리는 renderIcon 함수를 사용하여 정확한 아이콘을 나타낼 수 있다. 원본 코드에서 사방을 검색하면 { route, focused, tintColor } 모양의 대상이 필요하다는 것을 알 수 있다.온프레스 프로세서와 접근 가능한 탭을 추가했습니다. 기본 옵션 표시줄이 있습니다.
/* /src/components/TabBar.js */

import React from "react";
import { View, Text, StyleSheet, TouchableOpacity } from "react-native";

const S = StyleSheet.create({
  container: { flexDirection: "row", height: 52, elevation: 2 },
  tabButton: { flex: 1, justifyContent: "center", alignItems: "center" }
});

const TabBar = props => {
  const {
    renderIcon,
    getLabelText,
    activeTintColor,
    inactiveTintColor,
    onTabPress,
    onTabLongPress,
    getAccessibilityLabel,
    navigation
  } = props;

  const { routes, index: activeRouteIndex } = navigation.state;

  return (
    <View style={S.container}>
      {routes.map((route, routeIndex) => {
        const isRouteActive = routeIndex === activeRouteIndex;
        const tintColor = isRouteActive ? activeTintColor : inactiveTintColor;

        return (
          <TouchableOpacity
            key={routeIndex}
            style={S.tabButton}
            onPress={() => {
              onTabPress({ route });
            }}
            onLongPress={() => {
              onTabLongPress({ route });
            }}
            accessibilityLabel={getAccessibilityLabel({ route })}
          >
            {renderIcon({ route, focused: isRouteActive, tintColor })}

            <Text>{getLabelText({ route })}</Text>
          </TouchableOpacity>
        );
      })}
    </View>
  );
};

export default TabBar;
다음은 모양새입니다.

이제 우리는 우리가 자신의 옵션 표시줄을 유연하게 만들 수 있다는 것을 알고 있기 때문에 실제적으로 그것을 확장하기 시작할 수 있다.우리는 react native pose를 사용하여 애니메이션 보기를 만들 것입니다. 이 보기는 활동 노선을 강조합니다. 이 보기를 스포트라이트라고 부릅니다.
우선 우리는 라벨을 제거할 수 있다.그런 다음 탭 표시줄 뒤에 절대 뷰를 추가하여 스포트라이트를 유지합니다.Dimensions API를 사용하여 스폿라이트의 오프셋을 계산합니다.
/* /src/components/TabBar.js */

import posed from "react-native-pose";

const windowWidth = Dimensions.get("window").width;
const tabWidth = windowWidth / 4;
const SpotLight = posed.View({
  route0: { x: 0 },
  route1: { x: tabWidth },
  route2: { x: tabWidth * 2 },
  route3: { x: tabWidth * 3 }
});

...
const S = StyleSheet.create({
  /* ... */
  spotLight: {
    width: tabWidth,
    height: "100%",
    backgroundColor: "rgba(128,128,255,0.2)",
    borderRadius: 8
  }
});

  /* ... */


    <View style={S.container}>
      <View style={StyleSheet.absoluteFillObject}>
        <SpotLight style={S.spotLight} pose={`route${activeRouteIndex}`} />
      </View>

      {routes.map((route, routeIndex) => {
        /* ... */
      }}
    </View>
다음은 모양새입니다.

애니메이션의 지속 시간과 동작을 지정하지 않았습니다.Pose는 이 점을 잘 처리하여 합리적인 기본값을 사용할 수 있도록 합니다.
이제 활성 아이콘에 배율을 추가합니다.다른 자세 보기를 만듭니다.
/* /src/components/TabBar.js */

...

const Scaler = posed.View({
  active: { scale: 1.25 },
  inactive: { scale: 1 }
});

...
이제 아이콘을 Scaler 구성 요소로 이렇게 포장할 수 있습니다.
/* /src/components/TabBar.js */

<Scaler style={S.scaler} pose={isRouteActive ? "active" : "inactive"}>
  {renderIcon({ route, focused: isRouteActive, tintColor })}
</Scaler>
우리는 이런 효과를 얻었다.

우리의 라벨란이 보기에 괜찮아지기 시작했다.남은 일은 조금만 다듬고 배색 방안을 바꾸고 스포트라이트를 조정하면 우리의 부품이 완성된다.

지금 우리는 개선할 수 있는 부분이 좀 있다.예를 들어 현재의 실현은 tab navigator에 항상 4개의 화면이 있고 스포트라이트의 색깔은 tab bar 구성 요소에서 하드코딩되고 스타일은 공유기의 tab Bar Options 설정을 통해 확장되어야 한다고 가정하지만, 나는 당분간 이 점을 언급하지 않겠다.
TabBar 구성 요소의 전체 소스 코드
/* /src/components/TabBar.js */

import React from "react";
import {
  View,
  Text,
  StyleSheet,
  TouchableOpacity,
  Dimensions
} from "react-native";
import posed from "react-native-pose";

const windowWidth = Dimensions.get("window").width;
const tabWidth = windowWidth / 4;
const SpotLight = posed.View({
  route0: { x: 0 },
  route1: { x: tabWidth },
  route2: { x: tabWidth * 2 },
  route3: { x: tabWidth * 3 }
});

const Scaler = posed.View({
  active: { scale: 1.25 },
  inactive: { scale: 1 }
});

const S = StyleSheet.create({
  container: {
    flexDirection: "row",
    height: 52,
    elevation: 2,
    alignItems: "center"
  },
  tabButton: { flex: 1 },
  spotLight: {
    width: tabWidth,
    height: "100%",
    justifyContent: "center",
    alignItems: "center"
  },
  spotLightInner: {
    width: 48,
    height: 48,
    backgroundColor: "#ee0000",
    borderRadius: 24
  },
  scaler: { flex: 1, alignItems: "center", justifyContent: "center" }
});

const TabBar = props => {
  const {
    renderIcon,
    activeTintColor,
    inactiveTintColor,
    onTabPress,
    onTabLongPress,
    getAccessibilityLabel,
    navigation
  } = props;

  const { routes, index: activeRouteIndex } = navigation.state;

  return (
    <View style={S.container}>
      <View style={StyleSheet.absoluteFillObject}>
        <SpotLight style={S.spotLight} pose={`route${activeRouteIndex}`}>
          <View style={S.spotLightInner} />
        </SpotLight>
      </View>

      {routes.map((route, routeIndex) => {
        const isRouteActive = routeIndex === activeRouteIndex;
        const tintColor = isRouteActive ? activeTintColor : inactiveTintColor;

        return (
          <TouchableOpacity
            key={routeIndex}
            style={S.tabButton}
            onPress={() => {
              onTabPress({ route });
            }}
            onLongPress={() => {
              onTabLongPress({ route });
            }}
            accessibilityLabel={getAccessibilityLabel({ route })}
          >
            <Scaler
              pose={isRouteActive ? "active" : "inactive"}
              style={S.scaler}
            >
              {renderIcon({ route, focused: isRouteActive, tintColor })}
            </Scaler>
          </TouchableOpacity>
        );
      })}
    </View>
  );
};

export default TabBar;
라우터 구성
/* /src/router/router.js */

...

const TabNavigator = createBottomTabNavigator(
  /* screen config ommited */,
  {
    tabBarComponent: TabBar,
    tabBarOptions: {
      activeTintColor: "#eeeeee",
      inactiveTintColor: "#222222"
    }
  }
);

...

좋은 웹페이지 즐겨찾기