前篇提及我昨天学到了构建屏幕,用tabs底层导航栏来切换页面以及给每个页面添加文本以及链接跳转,今天我速通了剩下的部分,现在分享一下学到了什么思路以及一些理解。
import domtoimage from 'dom-to-image';
import * as ImagePicker from 'expo-image-picker';
import * as MediaLibrary from 'expo-media-library';
import { useRef, useState } from 'react';
import { ImageSourcePropType, Platform, StyleSheet, View } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { captureRef } from 'react-native-view-shot';
import Button from '@/components/Button';
import CircleButton from '@/components/CircleButton';
import EmojiList from '@/components/EmojiList';
import EmojiPicker from '@/components/EmojiPicker';
import EmojiSticker from '@/components/EmojiSticker';
import IconButton from '@/components/IconButton';
import ImageViewer from '@/components/ImageViewer';
const PlaceholderImage = require('@/assets/images/background-image.png');
export default function Index() {
const [selectedImage, setSelectedImage] = useState<string | undefined>(undefined);
const [showAppOptions, setShowAppOptions] = useState<boolean>(false);
const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
const [pickedEmoji, setPickedEmoji] = useState<ImageSourcePropType | undefined>(undefined);
const [status, requestPermission] = MediaLibrary.usePermissions();
const imageRef = useRef<View>(null);
if (status === null) {
requestPermission();
}
const pickImageAsync = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ['images'],
allowsEditing: true,
quality: 1,
});
if (!result.canceled) {
setSelectedImage(result.assets[0].uri);
setShowAppOptions(true);
} else {
alert('You did not select any image.');
}
};
const onReset = () => {
setShowAppOptions(false);
};
const onAddSticker = () => {
setIsModalVisible(true);
};
const onModalClose = () => {
setIsModalVisible(false);
};
const onSaveImageAsync = async () => {
if (Platform.OS !== 'web') {
//Platform.OS 获取当前平台
//Platform.OS === 'web' 判断当前平台是否为web
try {
const localUri = await captureRef(imageRef, {
height: 440,
quality: 1,
});
await MediaLibrary.saveToLibraryAsync(localUri);
if (localUri) {
alert('Saved!');
}
} catch (e) {
console.log(e);
}
} else {
try {
const dataUrl = await domtoimage.toJpeg(imageRef.current, {
quality: 0.95,
width: 320,
height: 440,
});
let link = document.createElement('a');
link.download = 'sticker-smash.jpeg';
link.href = dataUrl;
link.click();
} catch (e) {
console.log(e);
}
}
};
return (
<GestureHandlerRootView style={styles.container}>
<View style={styles.imageContainer}>
<View ref={imageRef} collapsable={false}>
<ImageViewer imgSource={PlaceholderImage} selectedImage={selectedImage} />
{pickedEmoji && <EmojiSticker imageSize={40} stickerSource={pickedEmoji} />}
</View>
</View>
{showAppOptions ? (
<View style={styles.optionsContainer}>
<View style={styles.optionsRow}>
<IconButton icon="refresh" label="Reset" onPress={onReset} />
<CircleButton onPress={onAddSticker} />
<IconButton icon="save-alt" label="Save" onPress={onSaveImageAsync} />
</View>
</View>
) : (
<View style={styles.footerContainer}>
<Button theme="primary" label="Choose a photo" onPress={pickImageAsync} />
<Button label="Use this photo" onPress={() => setShowAppOptions(true)} />
</View>
)}
<EmojiPicker isVisible={isModalVisible} onClose={onModalClose}>
<EmojiList onSelect={setPickedEmoji} onCloseModal={onModalClose} />
</EmojiPicker>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#25292e',
alignItems: 'center',
},
imageContainer: {
flex: 1,
},
footerContainer: {
flex: 1 / 3,
alignItems: 'center',
},
optionsContainer: {
position: 'absolute',
bottom: 80,
},
optionsRow: {
alignItems: 'center',
flexDirection: 'row',
},
});
这是tabs/index.tsx,这是我的app的首页,里面的内容就是我今天所学。
首先第一个 知识点Async异步,我在launchImageLibraryAsync()发现了Async好像经常出现,那么肯定会意识到这是一个重要的关键字,代表的是异步,简单理解异步,就是和大家不一样,在页面中,我们可以理解不随大流,也就是我们让它执行它才执行,而不是直接渲染,那么在这函数中的写法是const result = await launchImageLibraryAsync(); 我们发现多了一个await,wait?代表需要等待函数完成?必须在异步函数前使用以及只能在async函数使用,缺失就会报错或者undefined
现在我们进入文档,第四个模块图像选择器,因为我写博客只是为了记录学习路程,所以我只会写我自己的思路以及理解,在首页index中,我们去代码区域找到selectedImage setSelecterImage,数组解构的用法,前者默认值未定义,后者用来更新前者的方法,然后我们定义异步函数pickImageAsync,在我们点击了本地图片库之后result获取我们选择的图片的对象以及包含我们自定义的样式,如果我们点击了就用setSelecterImage更新selectedImage的值为选择图片的url然后对应组件里面设置三元表达式设置 了默认URL以及selectedImage作为参数的URL,如果参数为空 则默认,如果有URL传参,则显示我们选择图片的URL
第五个模块创建模态框,我们去代码区域找到showAppOption setShowAppOption,依旧是数组解构只不过值是false类型,且初始值是false,然后我们发现return里面渲染区域出现了showAppOption?然后明显是三元表达式,我们可以理解核心思路是通过判断布尔值真假来选择渲染前者或者后者,模态框内容是前者,然后我们通过点击按钮来更新布尔值来切换前后的标签内容。然后剩下的无非就是设置对应的按钮图标组件然后设置样式后导入然后直接使用。
然后为模态框添加动态效果即要实现我们可以从添加的模态框按钮通过点击案件实现不同的功能,中间的按钮是打开一个模态框然后里面可以选择表情,然后表情出现在页面上面。
我们先定义模态框组件,设置模态框的基本样式。
上下两个部分,自然要分两个组件来写,第一个组件实现choose以及右边的关闭,
右边的叉号是关闭所有就是模态框设置的启动布尔值为false,index中设置函数实现更新isVisible的值为false就好,然后下面的列表我们通过另一个组件来实现。当然我们要记住模态框导入要通过组件名标签导入不要忘记。另一个组件我们就叫做列表吧,首先列表我们要使用官方的列表组件,这也是框架的核心以及我们为什么要使用框架,FlatList 是 React Native 官方提供的列表组件,用来展示一组一维数据(一排排的,比如图片、文字、卡片等),而且它会智能优化性能。就这样我们要用FlatList。
<FlatList
horizontal
showsHorizontalScrollIndicator={Platform.OS === 'web'}
data={emoji}
contentContainerStyle={styles.listContainer}
renderItem={({ item, index }) => (
<Pressable
onPress={() => {
onSelect(item);
onCloseModal();
}}>
<Image source={item} key={index} style={styles.image} />
</Pressable>
)}
/>
这是组件中使用FlatList中的部分,我们要先定义emoji数组然后依旧是初始化值为需要的表情路径,然后我们用data={emoji}定义列表的数据源后,FlatList会自动的遍历就再renderitem函数中渲染为可点击的表情,然后我们再index中导入组件,然后数组结构拿到更新发放以及初始化,然后再模态框中添加组件标签,然后就出现了列表。然后我们希望表情可以出现在图片上面,我们创建一个新的组件来实现,收取的参数为表情大小以及表情的路径,毕竟我们要考虑的就这些,然后拿到对应的路径后渲染页面就出现了。当然要记得在index导入并且使用标签渲染组件到页面。我们来回顾这条线。
.pickedEmoji 初始是 undefined,页面啥也不显示。用户打开模态框 EmojiPicker,
用户点击贴纸 => EmojiList 触发 onSelect(item),onSelect(item) 就是 setPickedEmoji(item)。
在你的主页面(Index 页面)中,你写了这一句:<EmojiList onSelect={setPickedEmoji} onCloseModal={onModalClose} />这句的意思就是:把 setPickedEmoji 这个函数传给 EmojiList,
EmojiList 收到之后,就可以通过 props.onSelect 这个名字来使用它然后pickedEmoji 被赋值了然后&&前面为真后面自动渲染我们就实现了整个过程。
这是我今天所学,当然这只是一部分,希望大家在我理解不对的点可以通过评论指点一下。