├── .expo └── packager-info.json ├── LICENSE ├── img └── img.gif ├── package.json ├── readme.md └── src ├── components ├── Header.android │ └── index.js └── Header.ios │ └── index.js └── index.js /.expo/packager-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "expoServerPort": null 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 wf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /img/img.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weifxn/react-native-scrollview-header/73a2f56e524d663b0aa0a4094647448d51756881/img/img.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-scrollview-header", 3 | "version": "1.0.6", 4 | "author": "weifxn", 5 | "dependencies": { 6 | "react": "16.5.0", 7 | "react-native": "https://github.com/expo/react-native/archive/sdk-32.0.0.tar.gz" 8 | }, 9 | "moduleDirectories": [ 10 | "/node_modules", 11 | "/src" 12 | ], 13 | "description": "An Animated ScrollView Header for React Native", 14 | "main": "src/index.js", 15 | "repository": "https://github.com/weifxn/react-native-scrollview-header", 16 | "license": "MIT", 17 | "private": false 18 | } 19 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # React Native ScrollView Header 2 | 3 | [![npm (scoped)](https://img.shields.io/badge/react--native--scrollview--header-v1.0.6-green.svg)](https://github.com/weifxn/react-native-scrollview-header) 4 | 5 | _Animated ScrollView Header for React Native_ 6 | 7 | 8 | 9 | ## Installation 10 | 11 | ```bash 12 | $ npm install react-native-scrollview-header 13 | ``` 14 | _or_ 15 | ```bash 16 | $ yarn add react-native-scrollview-header 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```js 22 | import React from 'react' 23 | import { View } from 'react-native' 24 | import Header from 'react-native-scrollview-header'; 25 | 26 | class Component extends React.Component { 27 | render() { 28 | const data = [{ name: 'Pink Floyd', age: '30' }]; 29 | 30 | return ( 31 |
32 | {data.map(item => ( 33 | {item.name} 34 | ))} 35 |
36 | ); 37 | } 38 | } 39 | ``` 40 | 41 | ## Props 42 | 43 | | Prop | Default | Type | Description | 44 | | :------------ |:---------------:| :---------------:| :-----| 45 | | title | undefined | `string` | Title for header | 46 | | titleStyle | undefined | `object` | Styles for header title | 47 | | headerStyle | undefined | `object` | Styles for title container | 48 | | barStyle | undefined | `object` | Styles for header bar | 49 | | maxHeight | 130 | `number` | Maximum height of header (animated) | 50 | | minHeight | 95 | `number` | Minimum height of header (animated) | 51 | | extras | undefined | `object` | Extra items above title (icon) | -------------------------------------------------------------------------------- /src/components/Header.android/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | View, 4 | Text, 5 | StyleSheet, 6 | Platform, 7 | FlatList, 8 | TouchableOpacity, 9 | Animated, 10 | RefreshControl, 11 | Alert, 12 | } from 'react-native'; 13 | import PropTypes from 'prop-types'; 14 | 15 | const HEADER_MAX_HEIGHT = 130; 16 | const HEADER_MIN_HEIGHT = 10; 17 | const HEADER_SCROLL_DISTANCE = HEADER_MAX_HEIGHT - HEADER_MIN_HEIGHT; 18 | 19 | export default class ScrollViewHeader extends Component { 20 | static propTypes = { 21 | title: PropTypes.string, 22 | maxHeight: PropTypes.number, 23 | minHeight: PropTypes.number, 24 | headerStyle: PropTypes.object, 25 | barStyle: PropTypes.object, 26 | titleStyle: PropTypes.object, 27 | children: PropTypes.node, 28 | extras: PropTypes.object, 29 | }; 30 | 31 | static defaultProps = { 32 | maxHeight: 130, 33 | minHeight: 95, 34 | }; 35 | constructor(props) { 36 | super(props); 37 | 38 | this.state = { 39 | scrollY: new Animated.Value(0), 40 | HEADER_MAX_HEIGHT: this.props.maxHeight, 41 | HEADER_MIN_HEIGHT: this.props.minHeight, 42 | HEADER_SCROLL_DISTANCE: this.props.maxHeight - this.props.minHeight, 43 | }; 44 | } 45 | 46 | render() { 47 | const { 48 | title, 49 | maxHeight, 50 | minHeight, 51 | headerStyle, 52 | barStyle, 53 | titleStyle, 54 | children, 55 | extras, 56 | ...rest 57 | } = this.props; 58 | 59 | const { 60 | HEADER_MAX_HEIGHT, 61 | HEADER_MIN_HEIGHT, 62 | HEADER_SCROLL_DISTANCE, 63 | } = this.state; 64 | 65 | const scrollY = Animated.add(this.state.scrollY, 0); 66 | 67 | const barOpacity = this.state.scrollY.interpolate({ 68 | inputRange: [0, 20], 69 | outputRange: [0, 1], 70 | extrapolate: 'clamp', 71 | }); 72 | const titleScale = scrollY.interpolate({ 73 | inputRange: [0, HEADER_SCROLL_DISTANCE / 2, HEADER_SCROLL_DISTANCE], 74 | outputRange: [1, 1, 0.6], 75 | extrapolate: 'clamp', 76 | }); 77 | const titleTranslate = scrollY.interpolate({ 78 | inputRange: [0, HEADER_SCROLL_DISTANCE / 2, HEADER_SCROLL_DISTANCE], 79 | outputRange: [0, 0, -45], 80 | extrapolate: 'clamp', 81 | }); 82 | return ( 83 | 84 | 99 | {children} 100 | 101 | 102 | 114 | {extras} 115 | {title} 116 | 117 | 118 | ); 119 | } 120 | } 121 | 122 | const styles = StyleSheet.create({ 123 | bar: { 124 | zIndex: 0, 125 | position: 'absolute', 126 | top: 0, 127 | left: 0, 128 | right: 0, 129 | backgroundColor: 'black', 130 | height: 95, 131 | elevation: 5, 132 | 133 | }, 134 | header: { 135 | zIndex: 1, 136 | backgroundColor: 'transparent', 137 | marginTop: 50, 138 | alignItems: 'center', 139 | justifyContent: 'center', 140 | position: 'absolute', 141 | top: 0, 142 | left: 0, 143 | right: 0, 144 | }, 145 | title: { 146 | zIndex: 1, 147 | color: 'white', 148 | padding: 8, 149 | fontSize: 28, 150 | elevation: 6, 151 | }, 152 | }); 153 | -------------------------------------------------------------------------------- /src/components/Header.ios/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | View, 4 | Text, 5 | StyleSheet, 6 | Platform, 7 | FlatList, 8 | TouchableOpacity, 9 | Animated, 10 | RefreshControl, 11 | Alert, 12 | } from 'react-native'; 13 | import PropTypes from 'prop-types'; 14 | 15 | const HEADER_MAX_HEIGHT = 130; 16 | const HEADER_MIN_HEIGHT = 95; 17 | const HEADER_SCROLL_DISTANCE = HEADER_MAX_HEIGHT - HEADER_MIN_HEIGHT; 18 | 19 | export default class ScrollViewHeader extends Component { 20 | static propTypes = { 21 | title: PropTypes.string, 22 | maxHeight: PropTypes.number, 23 | minHeight: PropTypes.number, 24 | headerStyle: PropTypes.object, 25 | barStyle: PropTypes.object, 26 | titleStyle: PropTypes.object, 27 | children: PropTypes.node, 28 | extras: PropTypes.object, 29 | }; 30 | 31 | static defaultProps = { 32 | maxHeight: 130, 33 | minHeight: 95, 34 | }; 35 | constructor(props) { 36 | super(props); 37 | 38 | this.state = { 39 | scrollY: new Animated.Value(-HEADER_MAX_HEIGHT), 40 | HEADER_MAX_HEIGHT: this.props.maxHeight, 41 | HEADER_MIN_HEIGHT: this.props.minHeight, 42 | HEADER_SCROLL_DISTANCE: this.props.maxHeight - this.props.minHeight, 43 | }; 44 | } 45 | 46 | render() { 47 | const { 48 | title, 49 | maxHeight, 50 | minHeight, 51 | headerStyle, 52 | barStyle, 53 | titleStyle, 54 | children, 55 | extras, 56 | ...rest 57 | } = this.props; 58 | 59 | const { 60 | HEADER_MAX_HEIGHT, 61 | HEADER_MIN_HEIGHT, 62 | HEADER_SCROLL_DISTANCE, 63 | } = this.state; 64 | 65 | const scrollY = Animated.add(this.state.scrollY, HEADER_MAX_HEIGHT); 66 | 67 | const barOpacity = this.state.scrollY.interpolate({ 68 | inputRange: [-100, -80], 69 | outputRange: [0, 1], 70 | extrapolate: 'clamp', 71 | }); 72 | const titleScale = scrollY.interpolate({ 73 | inputRange: [0, HEADER_SCROLL_DISTANCE / 2, HEADER_SCROLL_DISTANCE], 74 | outputRange: [1, 1, 0.6], 75 | extrapolate: 'clamp', 76 | }); 77 | const titleTranslate = scrollY.interpolate({ 78 | inputRange: [0, HEADER_SCROLL_DISTANCE / 2, HEADER_SCROLL_DISTANCE], 79 | outputRange: [0, 0, -45], 80 | extrapolate: 'clamp', 81 | }); 82 | return ( 83 | 84 | 99 | {children} 100 | 101 | 102 | 112 | 124 | {extras} 125 | {title} 126 | 127 | 128 | ); 129 | } 130 | } 131 | 132 | const styles = StyleSheet.create({ 133 | bar: { 134 | position: 'absolute', 135 | top: 0, 136 | left: 0, 137 | right: 0, 138 | backgroundColor: 'black', 139 | height: 95, 140 | ...Platform.select({ 141 | ios: { 142 | shadowColor: '#000', 143 | shadowOffset: { width: 0, height: 10 }, 144 | shadowOpacity: 0.5, 145 | shadowRadius: 15, 146 | }, 147 | android: { 148 | elevation: 5, 149 | }, 150 | }), 151 | }, 152 | header: { 153 | backgroundColor: 'transparent', 154 | marginTop: 50, 155 | alignItems: 'center', 156 | justifyContent: 'center', 157 | position: 'absolute', 158 | top: 0, 159 | left: 0, 160 | right: 0, 161 | }, 162 | title: { 163 | color: 'white', 164 | padding: 8, 165 | fontSize: 28, 166 | }, 167 | }); 168 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { Platform } from 'react-native' 2 | 3 | const Header = Platform.select({ 4 | ios: () => require('./components/Header.ios'), 5 | android: () => require('./components/Header.android'), 6 | })(); 7 | 8 | export default Header.default --------------------------------------------------------------------------------