Skip to content

Commit

Permalink
Merge pull request #623 from hy916/dev
Browse files Browse the repository at this point in the history
feat(Skeleton):增加Skeleton骨架屏组件
  • Loading branch information
ChenlingasMx committed May 8, 2023
2 parents 5020231 + 9d311fb commit 47e7ee9
Show file tree
Hide file tree
Showing 6 changed files with 276 additions and 1 deletion.
8 changes: 8 additions & 0 deletions example/examples/src/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -530,4 +530,12 @@ export const stackPageData: Routes[] = [
description: '图片头部组件',
},
},
{
name: 'Skeleton',
component: require('./routes/Skeleton').default,
params: {
title: 'Skeleton 骨架屏组件',
description: '骨架屏组件',
},
},
];
86 changes: 86 additions & 0 deletions example/examples/src/routes/Skeleton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {Skeleton} from '@uiw/react-native';
import React, {useState, useEffect} from 'react';
import {StyleSheet, View, Text, TouchableOpacity} from 'react-native';
import Layout, {Container} from '../../Layout';

const {Header, Body, Card, Footer} = Layout;

const App = (props: any) => {
const [loading, setLoading] = useState(true);
const [data, setData] = useState<string[]>(['', '', '']);
useEffect(() => {
fetchData();
}, []);

const fetchData = () => {
setLoading(true);
setTimeout(() => {
setData(['项目1', '项目2', '项目3']);
setLoading(false);
}, 3000);
};
const {route} = props;
const description = route.params.description;
const title = route.params.title;

const skeletonStyles = [styles.item, styles.item, styles.item];

return (
<Container>
<Layout>
<Header title={title} description={description} />
<Body style={{paddingLeft: 16, paddingRight: 16}}>
<Card title="基础实例">
<View style={styles.container}>
{loading ? (
<Skeleton loading={loading} styles={skeletonStyles} duration={1200} containerStyle={styles.list} />
) : (
<View style={styles.list}>
{data.map((item, index) => (
<Text key={index} style={styles.item}>
{item}
</Text>
))}
</View>
)}
<TouchableOpacity style={styles.button} onPress={fetchData}>
<Text style={styles.buttonText}>重新加载</Text>
</TouchableOpacity>
</View>
</Card>
</Body>
<Footer />
</Layout>
</Container>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
list: {
width: '100%',
},
item: {
backgroundColor: '#E1E9EE',
height: 30,
marginBottom: 10,
justifyContent: 'center',
alignItems: 'center',
},
button: {
backgroundColor: '#007AFF',
paddingHorizontal: 20,
paddingVertical: 10,
borderRadius: 5,
},
buttonText: {
color: '#FFF',
fontSize: 16,
},
});

export default App;
2 changes: 1 addition & 1 deletion example/examples/src/routes/VerificationCode/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const VerificationCodeDemo: React.FC<VerificationCodeProps> = ({route}) => {
<Card title="发验证码之前的回调">
<VerificationCode value={value} count={3} onChange={(text: string) => onChange(text)} onBefore={onBefore} outerStyle={{borderColor: '#ccc'}} />
</Card>
<Card title="发送验证码">
<Card title="发验证码时的回调">
<VerificationCode value={value} count={3} onChange={(text: string) => onChange(text)} onSend={onSend} outerStyle={{borderColor: '#ccc'}} />
</Card>
<Card title="倒计时结束后的回调">
Expand Down
103 changes: 103 additions & 0 deletions packages/core/src/Skeleton/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
Skeleton 骨架屏
---

在需要等待加载内容的位置提供一个占位图形组合。

### 基础示例

```jsx mdx:preview&background=#bebebe29
import { Skeleton } from '@uiw/react-native';
import React, { useState, useEffect } from 'react';
import { StyleSheet, View, Text, TouchableOpacity } from 'react-native';

const Demo = () => {
const [loading, setLoading] = useState(true);
const [data, setData] = useState(['', '', '']);
useEffect(() => {
fetchData();
}, []);

const fetchData = () => {
setLoading(true);
setTimeout(() => {
setData(['项目1', '项目2', '项目3']);
setLoading(false);
}, 3000);
};

const skeletonStyles = [
styles.item,
styles.item,
styles.item,
];

return (
<View style={styles.container}>
{loading ? (
<Skeleton
loading={loading}
styles={skeletonStyles}
duration={1200}
containerStyle={styles.list}
/>
) : (
<View style={styles.list}>
{data.map((item, index) => (
<Text key={index} style={styles.item}>
{item}
</Text>
))}
</View>
)}
<TouchableOpacity style={styles.button} onPress={fetchData}>
<Text style={styles.buttonText}>重新加载</Text>
</TouchableOpacity>
</View>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
list: {
width: '100%',
paddingHorizontal: 20,
},
item: {
backgroundColor: '#E1E9EE',
height: 30,
marginBottom: 10,
justifyContent: 'center',
alignItems: 'center',
},
button: {
backgroundColor: '#007AFF',
paddingHorizontal: 20,
paddingVertical: 10,
borderRadius: 5,
},
buttonText: {
color: '#FFF',
fontSize: 16,
},
});

export default Demo;

```

### Props

| 参数 | 必填 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- | --- |
| loading | `false` | 是否正在加载 | `boolean` | `true` |
| styles | `true` | 骨架屏的样式 | `ViewStyle[]` | |
| duration | `false` | 动画的执行速度 | `number` | `1200` |
| easing | `false` | 动画的执行方式 | `Animated.EasingFunction` | `Easing.bezierFn(0.5, 0, 0.25, 1)` |
| containerStyle | `false` | 容器样式 | `StyleProp<ViewStyle>` | |
| animationType | `false` | 动画类型(条纹/脉搏/无) | `AnimationType` | `shiver` |
| boneColor | `false` | 基础颜色 | `string` | `#E1E9EE` |
| highlightColor | `false` | 高亮颜色 | `string` | `#F2F8FC` |
76 changes: 76 additions & 0 deletions packages/core/src/Skeleton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React from 'react';
import { View, Animated, EasingFunction, Easing, ViewStyle, StyleProp } from 'react-native';
type AnimationType = 'none' | 'stripe' | 'pulse';

interface SkeletonProps {
loading?: boolean;
styles: ViewStyle[];
duration?: number;
easing?: EasingFunction;
containerStyle?: StyleProp<ViewStyle>;
animationType?: AnimationType;
boneColor?: string;
highlightColor?: string;
}

const Skeleton: React.FC<SkeletonProps> = ({
loading = true,
styles,
duration = 1200,
easing = Easing.bezier(0.5, 0, 0.25, 1),
containerStyle,
animationType = 'stripe',
boneColor = '#E1E9EE',
highlightColor = '#F2F8FC',
}) => {
const animatedValue = React.useRef(new Animated.Value(0)).current;

const getInterpolatedColor = () => {
return animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [boneColor, highlightColor],
});
};
const getAnimationStyle = () => {
switch (animationType) {
case 'stripe':
return {
backgroundColor: getInterpolatedColor(),
};
case 'pulse':
return {
opacity: animatedValue.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [0.5, 1, 0.5],
}),
};
default:
return {};
}
};

React.useEffect(() => {
if (loading) {
Animated.loop(
Animated.timing(animatedValue, {
toValue: 1,
duration,
easing: easing,
useNativeDriver: false,
}),
).start();
} else {
animatedValue.setValue(0);
}
}, [loading]);

return (
<View style={containerStyle}>
{styles.map((style, index) => (
<Animated.View key={index} style={[style, getAnimationStyle()]} />
))}
</View>
);
};

export default Skeleton;
2 changes: 2 additions & 0 deletions packages/core/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ export { default as LoginPage } from './LoginPage';
export * from './LoginPage';
export { default as ImageHeader } from './ImageHeader';
export * from './ImageHeader';
export { default as Skeleton } from './Skeleton';
export * from './Skeleton';
/**
* Typography
*/
Expand Down

0 comments on commit 47e7ee9

Please sign in to comment.