iT邦幫忙

2023 iThome 鐵人賽

DAY 17
0

這篇文章將會著重於建立 App 的 UI 架構,會使用一開始建立專案設定的 nativewind 和 tailwindcss 來快速地設計和實現 UI。

設計 UI

在開始寫 code 之前,先來簡單地設計一下 App 的 UI,預想中會有以下幾個部分:

  • 地點:定位顯示目前地點。
  • 日期:顯示月、日,還有星期幾
  • 現在天氣:顯示溫度、天氣狀況
  • 接下來幾個小時的天氣預報
  • 本週天氣預報

最後的畫面預計會長這樣:

這個畫面又可以分成 4 個部分:

  • 地點、日期
  • 現在天氣
  • 小時天氣預報
  • 本週天氣預報

接下來就可以按照這個規劃去分出幾個 component 來實作了。

建立 component

首先建立一個 src/components 的資料夾,裡面會放我們的 component。接著新增 4 個檔案:

$ touch src/components/HeaderInfo.tsx src/components/CurrentWeather.tsx src/components/HourlyForecast.tsx src/components/DailyForecast.tsx

HeaderInfo

HeaderInfo 會顯示地點和日期,這裡使用 ViewText,並且使用 tailwindcss 的 class 來設定樣式,就可以不用另外寫 css 了。

import { View, Text } from 'react-native'

const HeaderInfo = () => (
  <View className='items-center p-6 space-y-4 w-500'>
    <Text className='text-2xl font-semibold text-white'>Taipei</Text>
    <Text className='text-sm text-gray-200'>Monday, 12 September</Text>
  </View>
)

export default HeaderInfo

CurrentWeather

CurrentWeather 主要顯示現在的溫度、天氣狀況,以及天氣圖示。這裡使用了前面幾天介紹的 expo/vector-icons

import { View, Text } from 'react-native'
import { AntDesign } from '@expo/vector-icons'

const CurrentWeather = () => (
  <View className='items-center space-y-2'>
    <AntDesign name='cloudo' size={60} color='#ded8d8' />
    <Text className='text-6xl font-bold text-white'>28°</Text>
    <Text className='text-xl text-gray-300'>Partly Cloudy</Text>
  </View>
)

export default CurrentWeather

HourlyForecast

HourlyForecast 會顯示接下來幾個小時的天氣預報,這裡我們先放上 4 個小時的預報,每個預報會顯示時間、天氣圖示、溫度。

import React, { FC } from 'react'
import { View, Text } from 'react-native'
import { AntDesign, Feather } from '@expo/vector-icons'

type AntDesignNames = keyof typeof AntDesign.glyphMap
type FeatherNames = keyof typeof Feather.glyphMap

interface WeatherIconConfig {
  component: ComponentType<any>
  name: AntDesignNames | FeatherNames
}

interface HourlyForecastProps {
  time: string
  icon: keyof typeof weatherIcons
  temp: string
}

const weatherIcons: Record<string, WeatherIconConfig> = {
  sunny: { component: Feather, name: 'sun' },
  cloudy: { component: AntDesign, name: 'cloudo' },
  rainy: { component: Feather, name: 'cloud-rain' }
}

const HourlyForecast: FC<HourlyForecastProps> = ({ time, icon, temp }) => {
  const IconComponent = weatherIcons[icon]?.component || AntDesign || Feather
  const iconName = weatherIcons[icon]?.name || 'question'
  const iconColor = weatherIcons[icon]?.color || '#ffffff'

  return (
    <View className='items-center space-y-2'>
      <Text className='text-sm font-medium text-white'>{time}</Text>
      <IconComponent name={iconName} size={30} color={iconColor} />
      <Text className='text-lg font-semibold text-white'>{temp}°</Text>
    </View>
  )
}

export default HourlyForecast

這裡需要說明一下,我們使用了 Record<string, WeatherIconConfig> 來定義 weatherIcons 的型別,這樣就可以在 weatherIcons 裡面放入不同的 icon 設定,並且在 HourlyForecast 裡面使用 weatherIcons[icon]?.component 來取得 icon 的 component,這樣就可以不用寫很多的 if 判斷式。

DailyForecast

DailyForecast 會顯示本週的天氣預報,這裡我們先放上 7 天的預報,每個預報會顯示星期幾、天氣圖示、溫度。

import React, { FC, ComponentType } from 'react'
import { View, Text } from 'react-native'
import { AntDesign, Feather } from '@expo/vector-icons'

type AntDesignNames = keyof typeof AntDesign.glyphMap
type FeatherNames = keyof typeof Feather.glyphMap

interface WeatherIconConfig {
  component: ComponentType<any>
  name: AntDesignNames | FeatherNames
}

interface DailyForecastProps {
  day: string
  icon: keyof typeof weatherIcons
  tempHigh: string
  tempLow: string
}

const weatherIcons: Record<string, WeatherIconConfig> = {
  sunny: { component: Feather, name: 'sun' },
  cloudy: { component: AntDesign, name: 'cloudo' },
  rainy: { component: Feather, name: 'cloud-rain' }
}

const DailyForecast: FC<DailyForecastProps> = ({
  day,
  icon,
  tempHigh,
  tempLow
}) => {
  const IconComponent = weatherIcons[icon]?.component || AntDesign
  const iconName = weatherIcons[icon]?.name || 'question'

  return (
    <View className='flex-row justify-around items-center mb-8 mr-4'>
      <Text className='text-lg font-medium text-white w-28'>{day}</Text>
      <IconComponent name={iconName} size={30} color='#ded8d8' />
      <Text className='text-lg font-semibold text-white w-24'>
        {tempHigh}° / {tempLow}°
      </Text>
    </View>
  )
}

export default DailyForecast

DailyForecast 和 HourlyForecast 有點類似,也是使用 Record<string, WeatherIconConfig> 來定義 weatherIcons 的型別,並且在 DailyForecast 裡面使用 weatherIcons[icon]?.component 來取得 icon 的 component。

這個重複的東西我們之後再來處理。

組裝 App

接下來就可以把剛剛寫好的 component 組裝起來了,這裡我們使用 ScrollView 來讓 DailyForecast 的區塊可以滑動,並且使用 SafeAreaView 來讓畫面在 iPhone X 以上的機型上可以顯示正常。

import { View, SafeAreaView, ScrollView } from 'react-native'
import { StatusBar } from 'expo-status-bar'
import {
  CurrentWeather,
  DailyForecast,
  HourlyForecast,
  HeaderInfo
} from './src/components'

export default function App() {
  return (
    <SafeAreaView className='flex-1 bg-blue-500'>
      <StatusBar style='auto' />
      <HeaderInfo />
      <CurrentWeather />
      <View className='flex-row justify-around p-6'>
        <HourlyForecast time='4 PM' icon='sunny' temp='28' />
        <HourlyForecast time='5 PM' icon='cloudy' temp='26' />
        <HourlyForecast time='6 PM' icon='rainy' temp='26' />
        <HourlyForecast time='7 PM' icon='rainy' temp='25' />
      </View>
      <ScrollView>
        <View className='p-6'>
          <DailyForecast
            day='Tuesday'
            icon='rainy'
            tempHigh='73'
            tempLow='60'
          />
          <DailyForecast
            day='Wednesday'
            icon='cloudy'
            tempHigh='75'
            tempLow='62'
          />
          <DailyForecast
            day='Thursday'
            icon='sunny'
            tempHigh='77'
            tempLow='64'
          />
          <DailyForecast day='Friday' icon='sunny' tempHigh='79' tempLow='65' />
          <DailyForecast
            day='Saturday'
            icon='sunny'
            tempHigh='81'
            tempLow='66'
          />
          <DailyForecast day='Sunday' icon='sunny' tempHigh='82' tempLow='67' />
        </View>
      </ScrollView>
    </SafeAreaView>
  )
}

這樣我們的主要 UI 就完成了,不過剛剛有提到,HourlyForecastDailyForecast 有點類似,我們可以把它們抽出來,變成一個共用的 config,這樣就不用重複寫一樣的東西了。但這篇文章先到這裡,明天再來介紹如何抽出共用的 config。

明天見👋!


上一篇
Day 16 - 申請使用 OpenWeather
下一篇
Day 18 - 重構 TypeScript icon 設定
系列文
掌握 React Native 與 Expo:30天雙打,新手也能精通跨平台開發!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言