본문 바로가기

모바일 개발/React Native-이론

TabBar

1. 코드 리뷰

(1) App.js

export default function App() {

  const [isOpened, setIsOpened]=useState(true);
  const [selectedTabIdx, setSelectedTabIdx] = useState(0);

  const onPressArrow = () => {
    console.log("clicked arrow")
    setIsOpened(!isOpened)
  }

  return (
    <View style={styles.container}>
      <View style={{
        flex: 1,
        paddingHorizontal: 15,
        }}>
      <Header />
      <Margin height = {10}/>
      <Profile
        uri={myProfile.uri}
        name={myProfile.name}
        introduction={myProfile.introduction}
      />
      <Margin height={15}/>
      <Division/>

      <Margin height={12}/>

      <FriendSection
        friendProfileLen = {friendProfiles.length}
        onPressArrow={onPressArrow}
        isOpened = {isOpened}
      />

      <FriendList
        data = {friendProfiles}
        isOpened = {isOpened}
      />     
      </View>
      
      <TabBar 
        selectedTabIdx = {selectedTabIdx}
        setSelectedTabIdx = {setSelectedTabIdx}

      />
    </View>

  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    paddingTop: statusBarHeight,
  },
});

a. tabBar 제외한 모든 컴포넌트들은 view로 한번 더 묶었다. 그리고 해당 View의 flex를 1로 지정해주었다. 

flex: 1은 flex-basis:0, flex-grow: 1, flex-shrink: 1 로써 고정너비 없이 화면 크기에 따라 늘어나고 줄어들겠다는 뜻이다. 

만약에 형제 요소도 flex가 지정되어 있다면 그들 사이의 flex 값의 비율에 따라 서로 차지할 너비가 정해진다. 

A 요소의 flex가 3이고, B 요소의 flex가 7이면 해당 형제 요소들은 3: 7의 비율로 너비를 차지 한다. 

 

여기서는 tabbar는 따로 flex를 정해주지 않았기 때문에 자신의 고정 너비 만큼만 화면을 차지 한다. 

따라서 다른 영역들의 view의 flex:1이므로 나머지 영역을 다 차지할 수 있게 되는 것이다.

 

b. FriendList의 ScrollView에 있던 paddingBottom을 지운다. 

why? 리스트에 패딩 바텀이 있었던 이유는 스크롤 되는 내용과 폰 내장 버튼들이 서로 겹치지 않게 하기 위해서 였다.

하지만 TabBar를 생성하면서 부터, List는 직접적으로 폰 내장 버튼들과 접촉이 없어졌다. 

이제 paddingBottom이 있으면 TabBar와 List 사이에 여백이 생겨버리기 때문에 지웠다.

 

c. selectedTabIdx UseState 

0,1,2,3의 상태를 둬서 각각 맨 왼쪽부터 버튼을 눌렀을 시 바뀌는 값 들이다. 

selectedTabIdx와 함수 setSelectedTabIdx 를 인자로 TabBar에 넣는다. 

 

(2) TabBar 컴포넌트 내부 

export default ({selectedTabIdx, setSelectedTabIdx}) => {
  return(
    <View style={{
      flexDirection: "row", 
      width: "100%", 
      padding: bottomSpace,
      borderTopWidth: 0.5,
      borderTopColor: "grey"
      }}>

      <TabButton
        isSelected={selectedTabIdx === 0}
        onPress = {() => setSelectedTabIdx(0)}
        activeIconName={"person"}
        inactiveIconName= {"persons"}
        isIconFontisto
      />

<TabButton
        isSelected={selectedTabIdx === 1}
        onPress = {() => setSelectedTabIdx(1)}
        activeIconName={"chatbubble"}
        inactiveIconName= {"chatbubble-outline"}
        isIconIonicons
      />

<TabButton
        isSelected={selectedTabIdx === 2}
        onPress = {() => setSelectedTabIdx(2)}
        activeIconName={"pricetag"}
        inactiveIconName= {"pricetag-outline"}
        isIconIonicons
      />

<TabButton
        isSelected={selectedTabIdx === 3}
        onPress = {() => setSelectedTabIdx(3)}
        activeIconName={"add-circle"}
        inactiveIconName= {"add-circle-outline"}
        isIconIonicons
      />
    </View>
  )
}

props로 안 받고, 속성 자체를 직접 받아도 된다. 

이때 인수를 여러 개 받을 경우 { }으로 한 번 감싸줘야 한다. 

const TabButton = (
{  isSelected,
  onPress,
  activeIcon,
  inActiveIcon,
  isFabioIcon,
  isIonIcon}

) => {
  return (

TabBar 내에 flexDirection: "row"로 해서 버튼을 4개 만든다. 

버튼은 중복되므로 따로 함수로 뺀다. 

const TabButton= ({
    isSelected, 
    onPress, 
    activeIconName, 
    inactiveIconName,
    isIconFontisto,
    isIconIonicons,
  }) => {
    return(
      <TouchableOpacity 
        activeOpacity={1}
        onPress={onPress} 
        style={{
          flex: 1,
          justifyContent: "center",
          alignItems: "center",
          paddingVertical: 10,
          

          }}>
      {isIconFontisto && <Fontisto name={isSelected? activeIconName : inactiveIconName} size={24} color="black" />}
      {isIconIonicons && <Ionicons name={isSelected? activeIconName : inactiveIconName}  size={24} color="black" />}
      </TouchableOpacity>
    )
  }

TabButton의 인수 설명 

isSelected는 해당 버튼이 선택되었는가? 이다. 

동등 연산자 (===)을 통해 해당 버튼이 대표하는 넘버를 useState 변수가 가지고 있는지 확인한다. 

isSelected={selectedTabIdx === 1}

상태 변수가 1이면 지금 맨 왼쪽 버튼이 눌러져있는 상태 라고 볼 수 있다.

onPress 함수 

해당 버튼을 눌렀을 때 동작하는 속성이다. 

함수가 눌러졌다면 selectedTabIdx가 해당 버튼의 대표 번호로 바뀌어야 한다. 

onPress = {() => setSelectedTabIdx(1)}

activeIcon, inActiveIcon 

선택 되었을 때랑 안 되었을 때랑 아이콘이 달라야 한다. 

이를 삼항 연산자로 구현했다.

name={isSelected? activeIconName : inactiveIconName}

isIconFontisto, isIconIonicons

아이콘을 수입해온 번지 수가 서로 틀리다. 따라서 

상위 컴포넌트에서 무슨 아이콘 사용하는지 받고 그 아이콘을 사용하면 하위 컴포넌트에서 해당 아이콘으로 작업하도록 해야한다. 

먼저 상위 컴포넌트에서 어떤 종류의 아이콘 사용하는지는 다음과 같이 나타낸다. 

<TabButton
        isSelected={selectedTabIdx === 1}
        onPress = {() => setSelectedTabIdx(1)}
        activeIconName={"chatbubble"}
        inactiveIconName= {"chatbubble-outline"}
        isIconIonicons
      />

해당 button은 Ionicons를 쓰는 것을 알 수 있다. 

위와 같이 그냥 속성 자체를 적어놓는 것만으로도 true 이다. 

속성이 안 적혀 있으면 false 이다.

{isIconFontisto && <Fontisto name={isSelected? activeIconName : inactiveIconName} size={24} color="black" />}
      {isIconIonicons && <Ionicons name={isSelected? activeIconName : inactiveIconName}  size={24} color="black" />}

위 내용은 TabButton 내부 로직이고, 만약 Fontisoicon 쓰면 위에꺼 발동, Ionicon 쓰면 밑에 거 발동이다.

 

** 참고 ** 

 activeOpacity={1}
이렇게 적으면, TouchOpacity 태그 눌렀을 때도 안 흐려진다. 불투명도를 100%로 한 것
0에 가까울 수록 눌렀을 때 많이 흐려지고, 1에 가까울 수록 안 흐려진다.

2. 직접 해보기 

1. main 매소드에서 tabBar와 다른 컴포넌트들 간의 크기 조정 (탭바- 고정불변, 다른 메소드들 화면 크기에 따라서 가변적)

2. List의 paddingBottom 지우기. (tabBar와의 공백 없애기)

3. 상태함수 만들어서 TabBar에 대입

4. TabBar에서 홈버튼 만들기. (눌려졌을 때 아닐 때 다른 아이콘 / 눌렀을 떄 흐려지는 것 없애기)

 

 

완성 제대로 돌아감! 

 

주의할 점 

컴포넌트의 인수로 값이 복수로 들어올 경우 

{}로 감쌀 것