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