Skip to content

Commit

Permalink
feat(ImageHeader):add ImageHeader
Browse files Browse the repository at this point in the history
  • Loading branch information
hy916 committed Apr 26, 2023
1 parent 618ab6c commit b212886
Show file tree
Hide file tree
Showing 11 changed files with 530 additions and 2 deletions.
3 changes: 2 additions & 1 deletion example/examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"react-native-image-viewing": "~0.2.2",
"react-native-safe-area-context": "~4.3.1",
"react-native-screens": "~3.15.0",
"react-native-svg": "13.0.0"
"react-native-svg": "13.0.0",
"react-native-reanimated":"3.1.0"
},
"devDependencies": {
"@babel/core": "~7.20.7",
Expand Down
8 changes: 8 additions & 0 deletions example/examples/src/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -522,4 +522,12 @@ export const stackPageData: Routes[] = [
description: '登录页组件',
},
},
{
name: 'ImageHeader',
component: require('./routes/ImageHeader').default,
params: {
title: 'ImageHeader 图片头部组件',
description: '图片头部组件',
},
},
];
25 changes: 25 additions & 0 deletions example/examples/src/routes/ImageHeader/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import {ImageHeader, Icon, Flex, Text} from '@uiw/react-native';
import {ComProps} from '../../routes';
import Layout, {Container} from '../../Layout';

export interface ImageHeaderProps extends ComProps {}

export default class ImageHeaderView extends React.Component<ImageHeaderProps> {
render() {
return (
<ImageHeader
headerBackgroundImg={{uri: 'https://c-ssl.duitang.com/uploads/blog/201411/18/20141118232436_zkQVV.jpeg'}}
headerHeight={161}
headerLeftColor="#FFF"
headerLeft="返回"
headerRight={<Icon name="delete" size={20} color={'##FFF'} />}
statusBarColor="blue"
statusBarStyle="dark-content">
<Flex justify="center" style={{backgroundColor: 'white', height: 100, marginHorizontal: 20}}>
<Text>111</Text>
</Flex>
</ImageHeader>
);
}
}
4 changes: 3 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@
"prop-types": "15.7.2",
"react-native-gesture-handler": "2.8.0",
"react-native-root-siblings": "4.1.1",
"react-native-svg": "13.0.0"
"react-native-svg": "13.0.0",
"react-native-safe-area-context":"4.5.1",
"react-native-reanimated":"3.1.0"
},
"peerDependencies": {
"react": ">=16.9.0",
Expand Down
114 changes: 114 additions & 0 deletions packages/core/src/ImageHeader/AnimateHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React, { ReactNode } from 'react';
import { TextStyle, TouchableOpacity, Text } from 'react-native';
import Animated, { Extrapolate, interpolate, interpolateColor, useAnimatedStyle } from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Theme } from '../theme';
import { useTheme } from '@shopify/restyle';

import Box from '../Typography/Box';
import Flex from '../Flex';
import helpers from './helpers';
import Icon from '../Icon';

const { px, ONE_PIXEL, deviceWidth } = helpers;
const HEADER_HEIGHT = px(44);
export interface AnimateHeaderProps {
/** 头部文字 */
headerTitle: string;
/** 头部文字样式 */
headerTitleStyle?: TextStyle;
/** 滚动距离 */
scrollY: Animated.SharedValue<number>;
/** 纵向滚动到哪个值时显示ImageHeader */
scrollHeight?: number;
/** 头部右侧内容 */
headerRight?: ReactNode;
/** 左侧返回键和字体颜色 */
headerLeftColor?: string;
/** 头部左侧内容 */
headerLeft?: ReactNode;
/** 头部底色,默认为透明 */
headerBackgroundColor?: string;
/** 左侧点击事件 */
onPress?: () => void;
/** 是否显示左侧图标 */
showLeft?: boolean;
}

const AnimateHeader: React.FC<AnimateHeaderProps> = (props) => {
const theme = useTheme<Theme>();
const insets = useSafeAreaInsets();

const {
scrollY,
headerTitle,
headerTitleStyle,
scrollHeight = 300,
onPress,
showLeft = true,
headerRight,
headerLeftColor = theme.colors.gray500,
headerLeft,
headerBackgroundColor = theme.colors.background,
} = props;

const inputRange = [0, scrollHeight];
const style = useAnimatedStyle(() => {
const opacity = interpolate(scrollY.value, inputRange, [0, 1], Extrapolate.CLAMP);
const borderBottomWidth = interpolate(scrollY.value, inputRange, [0, ONE_PIXEL], Extrapolate.CLAMP);
const backgroundColor = interpolateColor(scrollY.value, inputRange, ['transparent', headerBackgroundColor]);

return {
borderBottomWidth,
backgroundColor,
opacity,
};
});

return (
<Animated.View
style={[
{
width: deviceWidth,
position: 'absolute',
top: 0,
zIndex: 99,
justifyContent: 'center',
alignItems: 'center',
borderBottomColor: theme.colors.border,
paddingTop: insets.top,
height: HEADER_HEIGHT + insets.top,
},
style,
]}
>
<Flex style={{ flex: 1 }}>
{showLeft ? (
<TouchableOpacity activeOpacity={0.5} onPress={onPress} style={{ flex: 1 }}>
<Flex>
<Icon name="left" size={px(24)} color={headerLeftColor} />
{typeof headerLeft === 'string' ? (
<Text style={{ color: headerLeftColor }}>{headerLeft}</Text>
) : (
headerLeft
)}
</Flex>
</TouchableOpacity>
) : (
<Box flex={1} />
)}
<Animated.View style={{ flex: 5, alignItems: 'center' }}>
<Text numberOfLines={1} style={[{ color: '#333333' }, headerTitleStyle]}>
{headerTitle}
</Text>
</Animated.View>
<Box flex={1} alignItems="flex-end">
{headerRight}
</Box>
</Flex>
</Animated.View>
);
};
AnimateHeader.displayName = 'AnimateHeader';

export default AnimateHeader;
172 changes: 172 additions & 0 deletions packages/core/src/ImageHeader/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
---
title: ImageHeader - 图片头部组件
nav:
title: RN组件
path: /react-native
group:
title: Display
path: /display
---

# ImageHeader 图片头部组件

## 效果演示

### 1. 普通 ImageHeader

```tsx | pure
<ImageHeader headerBackgroundImg={require('../../assets/images/bg_rank.png')} headerHeight={px(161)} {...props}>
<Flex justifyContent="center" backgroundColor="white" height={100}>
<Text>111</Text>
</Flex>
</ImageHeader>
```

<center>
<figure>
<img
alt="header-ios1.png"
src="https://td-dev-public.oss-cn-hangzhou.aliyuncs.com/maoyes-app/1609999430064140139.png"
style="width: 375px; margin-right: 10px; border: 1px solid #ddd;"
/>
</figure>
</center>

### 2. ImageHeader 配置 left、right 和 headerLeftColor

```tsx | pure
<ImageHeader
headerBackgroundImg={require('../../assets/images/bg_rank.png')}
headerHeight={px(161)}
headerLeftColor={theme.colors.white}
headerLeft="返回"
headerRight={<Icon name="delete" size={px(20)} color={theme.colors.white} />}
{...props}
>
<Flex justifyContent="center" backgroundColor="white" height={100}>
<Text>111</Text>
</Flex>
</ImageHeader>
```

<center>
<figure>
<img
alt="header-ios2.png"
src="https://td-dev-public.oss-cn-hangzhou.aliyuncs.com/maoyes-app/1609999550703021067.png"
style="width: 375px; margin-right: 10px; border: 1px solid #ddd;"
/>
</figure>
</center>

### 3. ImageHeader 配置 headerBackgroundColor

```tsx | pure
<ImageHeader
headerBackgroundImg={require('../../assets/images/bg_rank.png')}
headerHeight={px(161)}
headerBackgroundColor={theme.colors.white}
headerLeft="返回"
headerRight={<Icon name="delete" size={px(20)} color={theme.colors.white} />}
{...props}
>
<Flex justifyContent="center" height={100}>
<Text>111</Text>
</Flex>
</ImageHeader>
```

<center>
<figure>
<img
alt="header-ios3.png"
src="https://td-dev-public.oss-cn-hangzhou.aliyuncs.com/maoyes-app/1610000705310241428.png"
style="width: 375px; margin-right: 10px; border: 1px solid #ddd;"
/>
</figure>
</center>

### 4. AnimatedHeader

```tsx | pure
import { useScrollHandler } from 'react-native-redash';
import Animated from 'react-native-reanimated';

export default () => {
const { scrollHandler, y } = useScrollHandler();

return (
<AnimateHeader
scrollY={y}
scrollHeight={200}
headerTitle="测试啊啊啊啊啊"
headerLeft="返回"
headerBackgroundColor={theme.colors.white}
{...props}
headerRight={
<TouchableOpacity activeOpacity={0.5} onPress={() => props.navigation.goBack()}>
<Icon name="delete" size={px(20)} color={theme.colors.primaryColor} />
</TouchableOpacity>
}
/>
<Animated.ScrollView {...scrollHandler}>
<ImageHeader
headerBackgroundImg={require('../../assets/images/bg_rank.png')}
headerHeight={px(161)}
headerLeftColor={theme.colors.white}
headerRight={
<TouchableOpacity activeOpacity={0.5} onPress={() => props.navigation.goBack()}>
<Icon name="delete" size={px(20)} color={theme.colors.primaryColor} />
</TouchableOpacity>
}
{...props}
>
<Flex justifyContent="center" height={100}>
<Text>111</Text>
</Flex>
</ImageHeader>
<Box width={200} height={900} />
</Animated.ScrollView>
)
}
```

<center>
<figure>
<img
alt="header-ios4.gif"
src="https://td-dev-public.oss-cn-hangzhou.aliyuncs.com/maoyes-app/1608877076955547998.gif"
style="width: 375px; margin-right: 10px; border: 1px solid #ddd;"
/>
</figure>
</center>

## ImageHeader 组件 API

| 属性 | 必填 | 说明 | 类型 | 默认值 |
| --------------------- | ------- | -------------------- | --------------------- | --------------------------- |
| headerTitle | `false` | 头部文字 | `ReactNode` | |
| headerRight | `false` | 头部右侧内容 | `ReactNode` | |
| headerLeft | `false` | 头部左侧内容 | `ReactNode` | |
| headerLeftColor | `false` | 左侧返回键和字体颜色 | `string` | `theme.colors.primaryColor` |
| headerBackgroundColor | `false` | 头部背景颜色 | `string` | `transparent` |
| headerBackgroundImg | `true` | 头部背景图片 | `ImageSourcePropType` | |
| headerHeight | `false` | 头部高度 | `number` | |
| onPress | `false` | 左边图标点击事件 | `() => void` | |
| showLeft | `false` | 是否显示左边图标 | `boolean` | `true` |

## AnimateHeader 组件 API

| 属性 | 必填 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- | --- |
| headerTitle | `true` | 头部文字 | `string` | |
| headerTitleStyle | `false` | 头部文字样式 | `TextStyle` | |
| scrollY | `false` | 滚动距离 | `Animated.SharedValue<number>` | `0` |
| scrollHeight | `false` | 纵向滚动到哪个值时显示 `ImageHeader` | `number` | `300` |
| headerHeight | `true` | 头部高度 | `number` | |
| headerRight | `false` | 头部右侧内容 | `ReactNode` | |
| headerLeft | `false` | 头部左侧内容 | `ReactNode` | |
| headerLeftColor | `false` | 左侧返回键和字体颜色 | `string` | `theme.colors.primaryColor` |
| headerBackgroundColor | `false` | 头部背景颜色 | `string` | `transparent` |
| onPress | `false` | 左边按钮点击事件 | `() => void` | |
| showLeft | `false` | 是否显示左边图标 | `boolean` | `true` |
40 changes: 40 additions & 0 deletions packages/core/src/ImageHeader/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Platform, StyleProp } from 'react-native';

import { deviceHeight, deviceWidth, ONE_PIXEL, px } from './normalize';
// import rddNode from './rddNode';

/**
* 判断是否是IOS系统
*/
const isIOS = Platform.OS === 'ios';

/**
* 根据条件决定样式是否生效
* @param condition
* @param style
*/
const conditionalStyle = (condition: boolean, style: StyleProp<any>) => (condition ? style : {});

function hexToRgba(hex: string, alpha = 1) {
let c: any;
if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) {
let arr = hex.substring(1).split('');
if (arr.length == 3) {
arr = [arr[0], arr[0], arr[1], arr[1], arr[2], arr[2]];
}
c = '0x' + arr.join('');
return 'rgba(' + [(c >> 16) & 255, (c >> 8) & 255, c & 255].join(',') + `,${alpha})`;
}
throw new Error('Bad Hex');
}

export default {
// rddNode,
px,
deviceWidth,
deviceHeight,
ONE_PIXEL,
isIOS,
conditionalStyle,
hexToRgba,
};
Loading

0 comments on commit b212886

Please sign in to comment.