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