1--- 2title: Build a screen 3--- 4 5import ImageSpotlight from '~/components/plugins/ImageSpotlight'; 6import { SnackInline, Terminal } from '~/ui/components/Snippet'; 7import { A } from '~/ui/components/Text'; 8import Video from '~/components/plugins/Video'; 9import { Step } from '~/ui/components/Step'; 10import { BoxLink } from '~/ui/components/BoxLink'; 11import { BookOpen02Icon } from '@expo/styleguide-icons'; 12 13In this chapter, we will create the first screen of the StickerSmash app. 14 15<ImageSpotlight 16 alt="Initial layout." 17 src="/static/images/tutorial/initial-layout.jpg" 18 style={{ maxWidth: 300 }} 19 containerStyle={{ marginBottom: 10 }} 20/> 21 22The screen above displays an image and two buttons. The app user can select an image using one of the two buttons. The first button allows the user to select an image from their device. The second button allows the user to continue with a default image provided by the app. 23 24Once the user selects an image, they'll be able to select and add a sticker to the image. So, let's get started creating this screen. 25 26<Step label="1"> 27 28## Break down the screen 29 30Before we build this screen by writing code, let's break it down into some essential elements. Most of these elements directly correspond to the built-in [Core Components](https://reactnative.dev/docs/components-and-apis) from React Native. 31 32<ImageSpotlight 33 alt="Break down of initial layout." 34 src="/static/images/tutorial/breakdown-of-layout.jpg" 35 style={{ maxWidth: 300 }} 36 containerStyle={{ marginBottom: 10 }} 37/> 38 39There are three essential elements: 40 41- The screen has a background color 42- There is a large image displayed at the center of the screen 43- There are two buttons in the bottom half of the screen 44 45The first button is composed of multiple components. The parent element provides a yellow border and contains an icon and text components inside a row. 46 47<ImageSpotlight 48 alt="Break down of the button component with row." 49 src="/static/images/tutorial/breakdown-of-buttons.png" 50 style={{ maxWidth: 400 }} 51 containerStyle={{ marginBottom: 10 }} 52/> 53 54In React Native, styling (such as the yellow border) is done using JavaScript as compared to the web, where CSS is used. Most of the React Native core components accept a `style` prop that accepts a JavaScript object as its value. For detailed information on styling, see [Styling in React Native](https://reactnative.dev/docs/style). 55 56Now that we've broken down the UI into smaller chunks, we're ready to start coding. 57 58</Step> 59 60<Step label="2"> 61 62## Style the background 63 64Let's change the background color. This value is defined in the `styles` object in the **App.js** file. 65 66Replace the default value of `#fff` with `#25292e` for the `styles.container.backgroundColor` property. It will change the background color of the screen. 67 68{/* prettier-ignore */} 69```jsx App.js 70import { StatusBar } from 'expo-status-bar'; 71import { StyleSheet, Text, View } from 'react-native'; 72 73export default function App() { 74 return ( 75 <View style={styles.container}> 76 <Text>Open up App.js to start working on your app!</Text> 77 <StatusBar style="auto" /> 78 </View> 79 ); 80} 81 82const styles = StyleSheet.create({ 83 container: { 84 flex: 1, 85 /* @info Replace the default value of backgroundColor property with '#25292e'. */ 86 backgroundColor: '#25292e', 87 /* @end */ 88 alignItems: 'center', 89 justifyContent: 'center', 90 }, 91}); 92``` 93 94> React Native uses the same color format as the web. It supports hex triplets (this is what `#fff` is), `rgba`, `hsl`, and a set of named colors like `red`, `green`, `blue`, `peru`, and `papayawhip`. For more information, see [Colors in React Native](https://reactnative.dev/docs/colors). 95 96</Step> 97 98<Step label="3"> 99 100## Change the text color 101 102The background is dark, so the text is difficult to read. The `<Text>` component uses `#000` (black) as its default color. Let's add a style to the `<Text>` component to change the text color to `#fff` (white) in **App.js**. 103 104<SnackInline label="Change the text color" dependencies={['expo-status-bar']}> 105 106{/* prettier-ignore */} 107```jsx 108import { StatusBar } from 'expo-status-bar'; 109import { StyleSheet, Text, View } from 'react-native'; 110 111export default function App() { 112 return ( 113 <View style={styles.container}> 114 /* @info Replace the default value of color property to '#fff'. */ 115 <Text style={{ color: '#fff' }}>Open up App.js to start working on your app!</Text> 116 /* @end */ 117 <StatusBar style="auto" /> 118 </View> 119 ); 120} 121 122/* @hide const styles = StyleSheet.create({*/ 123const styles = StyleSheet.create({ 124 container: { 125 flex: 1, 126 backgroundColor: '#25292e', 127 alignItems: 'center', 128 justifyContent: 'center', 129 }, 130}); 131/* @end */ 132``` 133 134</SnackInline> 135 136</Step> 137 138<Step label="4"> 139 140## Display the image 141 142We can use React Native's `<Image>` component to display the image in the app. The `<Image>` component requires a source of an image. This source can be a [static asset](https://reactnative.dev/docs/images#static-image-resources) or a URL. For example, the source can be required from the app's **./assets/images** directory, or the source can come from the [Network](https://reactnative.dev/docs/images#network-images) in the form of a `uri` property. 143 144<ImageSpotlight 145 alt="Background image that we are going to use as a placeholder for the tutorial." 146 src="/static/images/tutorial/background-image.png" 147 style={{ maxWidth: 250 }} 148 containerStyle={{ marginBottom: 10 }} 149/> 150 151Next, import and use the `<Image>` component from React Native and `background-image.png` in the **App.js**. Let's also add styles to display the image. 152 153<SnackInline label="Display placeholder image" dependencies={['expo-status-bar']} files={{ 154 'assets/images/background-image.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/503001f14bb7b8fe48a4e318ad07e910' 155}}> 156 157{/* prettier-ignore */} 158```jsx 159import { StatusBar } from 'expo-status-bar'; 160import { StyleSheet, View, /* @info Import the image component. */Image/* @end */ } from 'react-native'; 161 162/* @info Import the image from the "/assets/images" directory. Since this picture is a static resource, you have to reference it using "require". */ 163const PlaceholderImage = require('./assets/images/background-image.png'); 164/* @end */ 165 166export default function App() { 167 return ( 168 <View style={styles.container}> 169 /* @info Wrap the Image component inside a container. Also, add the image component to display the placeholder image. */ 170 <View style={styles.imageContainer}> 171 <Image source={PlaceholderImage} style={styles.image} /> 172 </View> /* @end */ 173 <StatusBar style="auto" /> 174 </View> 175 ); 176} 177 178const styles = StyleSheet.create({ 179 container: { 180 /* @info Modify container styles to remove justifyContent property. */ 181 flex: 1, 182 backgroundColor: '#25292e', 183 alignItems: 'center', 184 /* @end */ 185 }, 186 /* @info Add styles for the image. */ 187 imageContainer: { 188 flex: 1, 189 paddingTop: 58, 190 }, 191 image: { 192 width: 320, 193 height: 440, 194 borderRadius: 18, 195 }, 196 /* @end */ 197}); 198``` 199 200</SnackInline> 201 202The `PlaceholderImage` variable references the **./assets/images/background-image.png** and is used as the `source` prop on the `<Image>` component. 203 204</Step> 205 206<Step label="5"> 207 208## Dividing components into files 209 210As we add more components to this screen, let's divide the code into multiple files: 211 212- Create a **components** directory at the root of the project. This will contain all the custom components created throughout this tutorial. 213- Then, create a new file called **ImageViewer.js**, inside the **components** folder. 214- Move the code to display the image in this file along with the `image` styles. 215 216{/* prettier-ignore */} 217```jsx ImageViewer.js 218import { StyleSheet, Image } from 'react-native'; 219 220export default function ImageViewer({ placeholderImageSource }) { 221 return ( 222 <Image source={placeholderImageSource} style={styles.image} /> 223 ); 224} 225 226const styles = StyleSheet.create({ 227 image: { 228 width: 320, 229 height: 440, 230 borderRadius: 18, 231 }, 232}); 233``` 234 235Next, let's import this component and use it in the **App.js**: 236 237{/* prettier-ignore */} 238```jsx App.js 239import { StatusBar } from 'expo-status-bar'; 240import { StyleSheet, View } from 'react-native'; 241 242/* @info */ import ImageViewer from './components/ImageViewer'; /* @end */ 243 244const PlaceholderImage = require('./assets/images/background-image.png'); 245 246export default function App() { 247 return ( 248 <View style={styles.container}> 249 <View style={styles.imageContainer}> 250 /* @info Replace Image component with ImageViewer */ 251 <ImageViewer placeholderImageSource={PlaceholderImage} /> 252 /* @end */ 253 </View> 254 <StatusBar style="auto" /> 255 </View> 256 ); 257} 258 259/* @hide const styles = StyleSheet.create({ */ 260const styles = StyleSheet.create({ 261 container: { 262 flex: 1, 263 backgroundColor: '#25292e', 264 alignItems: 'center', 265 }, 266 imageContainer: { 267 flex: 1, 268 paddingTop: 58, 269 }, 270}); 271/* @end */ 272``` 273 274</Step> 275 276<Step label="6"> 277 278## Create buttons using `Pressable` 279 280React Native provides various components to handle touch events on native platforms. For this tutorial, we'll use the [`<Pressable>`](https://reactnative.dev/docs/pressable) component. It is a core component wrapper that can detect various stages of interactions, from basic single-tap events to advanced events such as a long press. 281 282In the design, there are two buttons we need to implement. Each has different styles and labels. Let's start by creating a component that can be reused to create the two buttons. 283 284Create a new file called **Button.js** inside the **components** directory with the following code: 285 286{/* prettier-ignore */} 287```jsx Button.js 288import { StyleSheet, View, Pressable, Text } from 'react-native'; 289 290export default function Button({ label }) { 291 return ( 292 <View style={styles.buttonContainer}> 293 <Pressable style={styles.button} onPress={() => alert('You pressed a button.')}> 294 <Text style={styles.buttonLabel}>{label}</Text> 295 </Pressable> 296 </View> 297 ); 298} 299 300const styles = StyleSheet.create({ 301 buttonContainer: { 302 width: 320, 303 height: 68, 304 marginHorizontal: 20, 305 alignItems: 'center', 306 justifyContent: 'center', 307 padding: 3, 308 }, 309 button: { 310 borderRadius: 10, 311 width: '100%', 312 height: '100%', 313 alignItems: 'center', 314 justifyContent: 'center', 315 flexDirection: 'row', 316 }, 317 buttonIcon: { 318 paddingRight: 8, 319 }, 320 buttonLabel: { 321 color: '#fff', 322 fontSize: 16, 323 }, 324}); 325``` 326 327Now, in the app, an alert will be displayed when the user taps any of the buttons on the screen. It happens because the `<Pressable>` calls an `alert()` in its `onPress` prop. 328 329Let's import this component into **App.js** file and add styles for `<View>` component that encapsulates these buttons: 330 331{/* prettier-ignore */} 332```jsx App.js 333import { StatusBar } from "expo-status-bar"; 334import { StyleSheet, View} from "react-native"; 335 336/* @info */import Button from './components/Button'; /* @end */ 337import ImageViewer from './components/ImageViewer'; 338 339const PlaceholderImage = require("./assets/images/background-image.png"); 340 341export default function App() { 342 return ( 343 <View style={styles.container}> 344 <View style={styles.imageContainer}> 345 <ImageViewer placeholderImageSource={PlaceholderImage} /> 346 </View> 347 /* @info Use the reusable Button component to create two buttons and encapsulate them inside a View component. */ 348 <View style={styles.footerContainer}> 349 <Button label="Choose a photo" /> 350 <Button label="Use this photo" /> 351 </View> 352 /* @end */ 353 <StatusBar style="auto" /> 354 </View> 355 ); 356} 357 358const styles = StyleSheet.create({ 359 /* @hide // Styles that are unchanged from previous step are hidden for brevity. */ 360 container: { 361 flex: 1, 362 backgroundColor: '#25292e', 363 alignItems: 'center', 364 }, 365 imageContainer: { 366 flex: 1, 367 paddingTop: 58, 368 }, 369 /* @end */ 370 /* @info Add the styles the following styles. */ 371 footerContainer: { 372 flex: 1 / 3, 373 alignItems: 'center', 374 }, 375 /* @end */ 376}); 377``` 378 379Let's take a look at our app on iOS, Android and the web: 380 381<ImageSpotlight 382 alt="Initial layout." 383 src="/static/images/tutorial/buttons-created.jpg" 384 style={{ maxWidth: 720 }} 385 containerStyle={{ marginBottom: 10 }} 386/> 387 388The second button with the label "Use this photo" resembles the actual button from the design. However, the first button needs more styling to match the design. 389 390</Step> 391 392<Step label="7"> 393 394## Enhance the reusable button component 395 396The "Choose a photo" button requires different styling than the "Use this photo" button, so we will add a new button theme prop that will allow us to apply a `primary` theme. 397This button also has an icon before the label. We will use an icon from the <A href="/guides/icons/#expovector-icons">`@expo/vector-icons`</A> library that includes icons from popular icon sets. 398 399Stop the development server by pressing <kbd>Ctrl</kbd> + <kbd>c</kbd> in the terminal. Then, install the `@expo/vector-icons` library: 400 401<Terminal cmd={['$ npx expo install @expo/vector-icons']} /> 402 403The <A href="/more/expo-cli/#installation">`npx expo install`</A> command will install the library and add it to the project's dependencies in **package.json**. 404 405After installing the library, restart the development server by running the `npx expo start` command. 406 407To load and display the icon on the button, let's use `FontAwesome` from the library. Modify **Button.js** to add the following code snippet: 408 409{/* prettier-ignore */} 410```jsx Button.js 411import { StyleSheet, View, Pressable, Text } from 'react-native'; 412/* @info Import FontAwesome. */import FontAwesome from "@expo/vector-icons/FontAwesome";/* @end */ 413 414export default function Button({ label, /* @info The prop theme to detect the button variant. */ theme/* @end */ }) { 415 /* @info Conditionally render the primary themed button. */ 416 if (theme === "primary") { 417 return ( 418 <View 419 style={[styles.buttonContainer, { borderWidth: 4, borderColor: "#ffd33d", borderRadius: 18 }]} 420 > 421 <Pressable 422 style={[styles.button, { backgroundColor: "#fff" }]} 423 onPress={() => alert('You pressed a button.')} 424 > 425 <FontAwesome 426 name="picture-o" 427 size={18} 428 color="#25292e" 429 style={styles.buttonIcon} 430 /> 431 <Text style={[styles.buttonLabel, { color: "#25292e" }]}>{label}</Text> 432 </Pressable> 433 </View> 434 ); 435 } 436 /* @end */ 437 438 return ( 439 <View style={styles.buttonContainer}> 440 <Pressable style={styles.button} onPress={() => alert('You pressed a button.')}> 441 <Text style={styles.buttonLabel}>{label}</Text> 442 </Pressable> 443 </View> 444 ); 445} 446 447const styles = StyleSheet.create({ 448 // Styles from previous step remain unchanged. 449}); 450``` 451 452Let's learn what the above code does: 453 454- The primary theme button uses **inline styles** which overrides the styles defined in the `StyleSheet.create()` with an object directly passed in the `style` prop. Inline styles use JavaScript. 455- The `<Pressable>` component in the primary theme uses a `backgroundColor` property of `#fff` to set the button's background color. If we add this property to the `styles.button`, then the background color value will be set for both the primary theme and the unstyled one. 456- Using inline styles allows overriding the default styles for a specific value. 457 458Now, modify the **App.js** file to use the `theme="primary"` prop on the first button. 459 460<SnackInline label="Screen layout" templateId="tutorial/01-layout/App" dependencies={['expo-status-bar', '@expo/vector-icons', '@expo/vector-icons/FontAwesome']} files={{ 461 'assets/images/background-image.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/503001f14bb7b8fe48a4e318ad07e910', 462 'components/ImageViewer.js': 'tutorial/01-layout/ImageViewer.js', 463 'components/Button.js': 'tutorial/01-layout/Button.js' 464}}> 465 466{/* prettier-ignore */} 467```jsx 468export default function App() { 469 return ( 470 <View style={styles.container}> 471 <View style={styles.imageContainer}> 472 <ImageViewer placeholderImageSource={PlaceholderImage} /> 473 </View> 474 <View style={styles.footerContainer}> 475 /* @info Add primary theme on the first button */ 476 <Button theme="primary" label="Choose a photo" /> 477 /* @end */ 478 <Button label="Use this photo" /> 479 </View> 480 <StatusBar style="auto" /> 481 </View> 482 ); 483} 484``` 485 486</SnackInline> 487 488Let's take a look at our app on iOS, Android and the web: 489 490<Video file="tutorial/02-complete-layout.mp4" /> 491 492</Step> 493 494## Next step 495 496We implemented the initial design. 497 498<BoxLink 499 title="Use an image picker" 500 Icon={BookOpen02Icon} 501 description="In the next chapter, we'll add the functionality to pick an image from the device's media library." 502 href="/tutorial/image-picker" 503/> 504