├── package.json
├── README.md
├── ScreenUtil.js
└── index.js
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "_args": [
3 | [
4 | "git+https://github.com/thisSillyCow/react-native-parallax-header.git",
5 | "H:\\developmentProject\\reactNativeNovel"
6 | ]
7 | ],
8 | "_from": "git+https://github.com/thisSillyCow/react-native-parallax-header.git",
9 | "_id": "react-native-parallax-header@git+https://github.com/thisSillyCow/react-native-parallax-header.git#2a4f856337e755a131a54e349b134d65277cbc86",
10 | "_inBundle": false,
11 | "_integrity": "",
12 | "_location": "/react-native-parallax-header",
13 | "_phantomChildren": {},
14 | "_requested": {
15 | "type": "git",
16 | "raw": "git+https://github.com/thisSillyCow/react-native-parallax-header.git",
17 | "rawSpec": "git+https://github.com/thisSillyCow/react-native-parallax-header.git",
18 | "saveSpec": "git+https://github.com/thisSillyCow/react-native-parallax-header.git",
19 | "fetchSpec": "https://github.com/thisSillyCow/react-native-parallax-header.git",
20 | "gitCommittish": null
21 | },
22 | "_requiredBy": [
23 | "/"
24 | ],
25 | "_resolved": "git+https://github.com/thisSillyCow/react-native-parallax-header.git#2a4f856337e755a131a54e349b134d65277cbc86",
26 | "_shasum": "d02b6465ad02892832966f37af8e9148ed0f727d",
27 | "_spec": "git+https://github.com/thisSillyCow/react-native-parallax-header.git",
28 | "_where": "H:\\developmentProject\\reactNativeNovel",
29 | "author": {
30 | "name": "Chiew Carol"
31 | },
32 | "bugs": {
33 | "url": "https://github.com/kyaroru/RNParallax/issues"
34 | },
35 | "bundleDependencies": false,
36 | "deprecated": false,
37 | "description": "A react native scroll view component with Parallax header :p",
38 | "homepage": "https://github.com/kyaroru/RNParallax#readme",
39 | "keywords": [
40 | "parallax",
41 | "react-native-parallax",
42 | "header",
43 | "scrollview"
44 | ],
45 | "license": "ISC",
46 | "main": "index.js",
47 | "name": "react-native-parallax-header",
48 | "repository": {
49 | "type": "git",
50 | "url": "git+https://github.com/kyaroru/RNParallax.git"
51 | },
52 | "scripts": {
53 | "test": "echo \"Error: no test specified\" && exit 1"
54 | },
55 | "version": "1.1.3"
56 | }
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # RNParallax (react-native-parallax-header)
3 | [](https://github.com/kyaroru/RNParallax/stargazers)
4 | [](https://github.com/kyaroru/RNParallax/network)
5 | [](https://github.com/kyaroru/RNParallax/issues)
6 |
7 | [](https://nodei.co/npm/react-native-parallax-header/)
8 |
9 | - A react native scroll view component with Parallax header :p
10 | - Inspired by [GitHub - jaysoo/react-native-parallax-scroll-view](https://github.com/jaysoo/react-native-parallax-scroll-view)
11 | - Code is based on [React Native ScrollView animated header – App & Flow – Medium](https://medium.com/appandflow/react-native-scrollview-animated-header-10a18cb9469e) and added little customisation :p
12 |
13 | ## Installation
14 | ```bash
15 | $ npm i react-native-parallax-header --save
16 | ```
17 | ## Demo
18 | ### iPhone X or XS (Using `alwaysShowTitle={false}` & `alwaysShowNavBar={false}`)
19 | 
20 |
21 | ### iPhone X or XS
22 | 
23 |
24 | ### iPhone 8
25 | 
26 |
27 | ## Example
28 | ```jsx
29 | import Icon from 'react-native-vector-icons/MaterialIcons';
30 | import ReactNativeParallaxHeader from 'react-native-parallax-header';
31 |
32 | const IS_IPHONE_X = SCREEN_HEIGHT === 812 || SCREEN_HEIGHT === 896;
33 | const STATUS_BAR_HEIGHT = Platform.OS === 'ios' ? (IS_IPHONE_X ? 44 : 20) : 0;
34 | const HEADER_HEIGHT = Platform.OS === 'ios' ? (IS_IPHONE_X ? 88 : 64) : 64;
35 | const NAV_BAR_HEIGHT = HEADER_HEIGHT - STATUS_BAR_HEIGHT;
36 |
37 | const images = {
38 | background: require('../img/test.jpg'), // Put your own image here
39 | };
40 |
41 | const styles = StyleSheet.create({
42 | container: {
43 | flex: 1,
44 | },
45 | contentContainer: {
46 | flexGrow: 1,
47 | },
48 | navContainer: {
49 | height: HEADER_HEIGHT,
50 | marginHorizontal: 10,
51 | },
52 | statusBar: {
53 | height: STATUS_BAR_HEIGHT,
54 | backgroundColor: 'transparent',
55 | },
56 | navBar: {
57 | height: NAV_BAR_HEIGHT,
58 | justifyContent: 'space-between',
59 | alignItems: 'center',
60 | flexDirection: 'row',
61 | backgroundColor: 'transparent',
62 | },
63 | titleStyle: {
64 | color: 'white',
65 | fontWeight: 'bold',
66 | fontSize: 18,
67 | },
68 | });
69 |
70 | renderNavBar = () => (
71 |
72 |
73 |
74 | {}}>
75 |
76 |
77 | {}}>
78 |
79 |
80 |
81 |
82 | )
83 |
84 | render() {
85 | return (
86 |
87 | console.log('onScrollBeginDrag'),
103 | onScrollEndDrag: () => console.log('onScrollEndDrag'),
104 | }}
105 | />
106 |
107 | );
108 | }
109 | ```
110 |
111 | ## API Usage
112 | | Property | Type | Required | Description | Default |
113 | | -------- | ---- | -------- | ----------- | ------- |
114 | | `renderNavBar` | `func` | No | This renders the nav bar component | Empty `` |
115 | | `renderContent` | `func` | **YES** | This renders the scroll view content | - |
116 | | `headerMaxHeight` | `number` | No | This is the header maximum height | Default to `170` |
117 | | `headerMinHeight` | `number` | No | This is the header minimum height | Default to common ios & android navbar height (have support for iPhone X too :p) |
118 | | `backgroundImage` | `image source` | No | This renders the background image of the header (**if specified, background color will not take effect**) | Default to `null` |
119 | | `backgroundImageScale` | `number` | No | This is the image scale - either enlarge or shrink (after scrolling to bottom & exceed the headerMaxHeight) | Default is `1.5` |
120 | | `backgroundColor` | `string` | No | This is the color of the parallax background (before scrolling up), **will not be used if `backgroundImage` is specified** | Default color is `#303F9F` |
121 | | `extraScrollHeight` | `number` | No | This is the extra scroll height (after scrolling to bottom & exceed the headerMaxHeight) | Default is `30` |
122 | | `navbarColor` | `string` | No | This is the background color of the navbar (after scroll up) | Default color is `#3498db` |
123 | | `statusBarColor` | `string` | No | This is the status bar color (for android) navBarColor will be used if no statusBarColor is passed in | Default to `null` |
124 | | `title` | `any` | No | This is the title to be display in the header, can be string or component | Default to `null` |
125 | | `titleStyle` | `style` | No | This is the title style to override default font size/color | Default to `color: ‘white’ `text and `fontSize: 16` |
126 | | `headerTitleStyle` | `style` | No | This is the header title animated view style to override default `` style | Default to `null` |
127 | | `scrollEventThrottle` | `number` | No | This is the scroll event throttle | Default is `16` |
128 | | `contentContainerStyle` | `style` | No | This is the contentContainerStyle style to override default `` contentContainerStyle style | Default to null |
129 | | `containerStyle` | `style` | No | This is the style to override default outermost `` style | Default to null |
130 | | `scrollViewStyle` | `style` | No | This is the scrollview style to override default `` style | Default to null |
131 | | `innerContainerStyle` | `style` | No | This is the inner content style to override default `` style inside `` component | Default to null |
132 | | `alwaysShowTitle` | `bool` | No | This is to determine whether show or hide the title after scroll | Default to `true` |
133 | | `alwaysShowNavBar` | `bool` | No | This is to determine whether show or hide the navBar before scroll | Default to `true` |
134 | | `scrollViewProps` | `object` | No | This is to override default scroll view properties | Default to `{}` |
135 |
--------------------------------------------------------------------------------
/ScreenUtil.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 屏幕工具类
3 | * ui设计基准,iphone 6
4 | * width:750px
5 | * height:1334px
6 | */
7 | import { PixelRatio, Dimensions, Platform, StatusBar } from 'react-native'
8 | export let screenW = Dimensions.get('window').width
9 | export let screenH = Dimensions.get('window').height
10 | export let pixelRatio = PixelRatio.get()
11 | //像素密度
12 | export const DEFAULT_DENSITY = 1
13 | //px转换成dp
14 | //以iphone6为基准,如果以其他尺寸为基准的话,请修改下面的750和1334为对应尺寸即可.
15 | const w2 = 750 / DEFAULT_DENSITY
16 | //px转换成dp
17 | const h2 = 1334 / DEFAULT_DENSITY
18 | // iPhoneX
19 | export const X_WIDTH = 375
20 | export const X_HEIGHT = 812
21 | //iPhoneX底部高度
22 | export const IPHONEX_BOTTOM_HEIGHT = 54;
23 |
24 | export const STATUS_HEIGHT = Platform.OS === 'ios' ? 20 : (Platform.Version > 19 ? StatusBar.currentHeight : 0);
25 |
26 | // 边缘圆角
27 | export const radiusNum = scaleSize(5);
28 | /**
29 | * 设置字体的size(单位px)
30 | * @param size 传入设计稿上的px
31 | * @returns {Number} 返回实际sp
32 | */
33 | export function setSpText(size) {
34 | isIphoneX()
35 | let scaleWidth = screenW / w2
36 | let scaleHeight = screenH / h2
37 | let scale = Math.min(scaleWidth, scaleHeight)
38 | size = Math.round(size * 2 * scale + 0.5)
39 | return size / DEFAULT_DENSITY
40 | }
41 | export const getUrlParam = (name) => {
42 | const that = this
43 | var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
44 | var r = window.location.search.substr(1).match(reg)
45 | if (r != null) {
46 | return r[2]
47 | }
48 | }
49 |
50 | export const getParams = (params, name) => {
51 | const that = this
52 | var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
53 | var r = params.substr(1).match(reg)
54 | if (r != null) {
55 | return r[2]
56 | }
57 | }
58 |
59 | /**
60 | * 屏幕适配,缩放size
61 | * @param size
62 | * @returns {Number}
63 | */
64 | export function scaleSize(size) {
65 | isIphoneX()
66 | let scaleWidth = screenW / w2
67 | let scaleHeight = screenH / h2
68 | let scale = Math.min(scaleWidth, scaleHeight)
69 | size = Math.round(size * 2 * scale + 0.5)
70 | return size / DEFAULT_DENSITY
71 | }
72 |
73 | /**
74 | * 判断android API是否小于19(4.4以下),如果是则不能使用沉浸状态栏
75 | *
76 | */
77 | export function isLT19() {
78 | return Platform.OS === 'android' && Platform.Version < 19
79 | }
80 |
81 | //时间处理
82 | Date.prototype.format = function (format) {
83 | let date = {
84 | 'M+': this.getMonth() + 1,
85 | 'd+': this.getDate(),
86 | 'h+': this.getHours(),
87 | 'm+': this.getMinutes(),
88 | 's+': this.getSeconds(),
89 | 'q+': Math.floor((this.getMonth() + 3) / 3),
90 | 'S+': this.getMilliseconds()
91 | }
92 | if (/(y+)/i.test(format)) {
93 | format = format.replace(
94 | RegExp.$1,
95 | (this.getFullYear() + '').substr(4 - RegExp.$1.length)
96 | )
97 | }
98 | for (let k in date) {
99 | if (new RegExp('(' + k + ')').test(format)) {
100 | format = format.replace(
101 | RegExp.$1,
102 | RegExp.$1.length === 1
103 | ? date[k]
104 | : ('00' + date[k]).substr(('' + date[k]).length)
105 | )
106 | }
107 | }
108 | return format
109 | }
110 |
111 | export function TimesAgo(timestamp) {
112 | var mistiming = Math.round(new Date() / 1000) - timestamp;
113 | var arrr = ['年', '个月', '星期', '天', '小时', '分钟', '秒'];
114 | var arrn = [31536000, 2592000, 604800, 86400, 3600, 60, 1];
115 | for (var i = 6; i >= 0; i--) {
116 | var inm = Math.floor(mistiming / arrn[i]);
117 | if (inm != 0) {
118 | return inm + arrr[i] + '前';
119 | }
120 | }
121 | }
122 |
123 | //获取时间差 current:1497235409744 当前时间 start:1497235419744 开始时间
124 | export function getRemainingime(current, start) {
125 | let time = start - current
126 | return time / 1000 //["0", "0", "2", "7", "33", "30"]0年0月2日 7时33分30秒
127 | }
128 |
129 | //1497235419
130 | export function getRemainingimeDistance(distance) {
131 | let time = distance * 1000
132 | if (time < 0) {
133 | return ['0', '0', '0', '0', '0', '0']
134 | }
135 | let year = Math.floor(time / (365 * 30 * 24 * 3600 * 1000)) //年
136 | let month = Math.floor(time / (30 * 24 * 3600 * 1000)) //月
137 | let days = Math.floor(time / (24 * 3600 * 1000)) //日
138 | let temp1 = time % (24 * 3600 * 1000)
139 | let hours = Math.floor(temp1 / (3600 * 1000)) //时
140 | let temp2 = temp1 % (3600 * 1000)
141 | let minutes = Math.floor(temp2 / (60 * 1000)) //分
142 | let temp3 = temp2 % (60 * 1000)
143 | let seconds = Math.round(temp3 / 1000) //秒
144 |
145 | let strs = [
146 | year,
147 | toNormal(month),
148 | toNormal(days),
149 | toNormal(hours),
150 | toNormal(minutes),
151 | toNormal(seconds)
152 | ]
153 | return strs //["0", "0", "2", "7", "33", "30"]0年0月2日 7时33分30秒
154 | }
155 |
156 | export function toNormal(time) {
157 | return time >= 10 ? time : '0' + time
158 | }
159 |
160 | //转换成日期
161 | export function toDate(timestamp, format1 = 'yyyy-MM-dd hh:mm:ss') {
162 | try {
163 | let date = new Date()
164 | date.setTime(timestamp)
165 | return date.format(format1) //2014-07-10 10:21:12
166 | } catch (erro) {
167 | return ''
168 | }
169 | }
170 |
171 | //1970/1/1至今的秒数
172 | export function toTimestamp(date) {
173 | let timestamp = Date.parse(date)
174 | return timestamp / 1000 // 1497233827569/1000
175 | }
176 |
177 | //CST时间=>转换成日期yyyy-MM-dd hh:mm:ss
178 | export function getTaskTime(strDate) {
179 | if (null == strDate || '' == strDate) {
180 | return ''
181 | }
182 | let dateStr = strDate.trim().split(' ')
183 | let strGMT =
184 | dateStr[0] +
185 | ' ' +
186 | dateStr[1] +
187 | ' ' +
188 | dateStr[2] +
189 | ' ' +
190 | dateStr[5] +
191 | ' ' +
192 | dateStr[3] +
193 | ' GMT+0800'
194 | let date = new Date(Date.parse(strGMT))
195 | let y = date.getFullYear()
196 | let m = date.getMonth() + 1
197 | m = m < 10 ? '0' + m : m
198 | let d = date.getDate()
199 | d = d < 10 ? '0' + d : d
200 | let h = date.getHours()
201 | let minute = date.getMinutes()
202 | minute = minute < 10 ? '0' + minute : minute
203 | let second = date.getSeconds()
204 | second = second < 10 ? '0' + second : second
205 |
206 | return y + '-' + m + '-' + d + ' ' + h + ':' + minute + ':' + second
207 | }
208 |
209 | //1497235419
210 | export function getRemainingimeDistance2(distance) {
211 | let time = distance
212 | let days = Math.floor(time / (24 * 3600 * 1000))
213 | let temp1 = time % (24 * 3600 * 1000)
214 | let hours = Math.floor(temp1 / (3600 * 1000))
215 | let temp2 = temp1 % (3600 * 1000)
216 | let minutes = Math.floor(temp2 / (60 * 1000))
217 | if (time <= 60 * 1000) {
218 | minutes = 1
219 | }
220 | let temp3 = temp2 % (60 * 1000)
221 | let seconds = Math.round(temp3 / 1000)
222 | return [hours, minutes] //["0", "0", "2", "7", "33", "30"]0年0月2日 7时33分30秒
223 | }
224 |
225 | /**
226 | * 判断是否为iphoneX
227 | * @returns {boolean}
228 | */
229 | export function isIphoneX() {
230 | const dimen = Dimensions.get('window');
231 | return (
232 | Platform.OS === 'ios' &&
233 | ((dimen.height === 812 || dimen.width === 812) || (dimen.height === 896 || dimen.width === 896))
234 | )
235 | }
236 |
237 |
238 | export function isIphoneNum(mun) {
239 | const dimen = Dimensions.get('window');
240 | var topMum = mun;
241 | if (Platform.OS === 'ios' && !Platform.isPad && !Platform.isTVOS
242 | && ((dimen.height === 812 || dimen.width === 812) || (dimen.height === 896 || dimen.width === 896))) {
243 | topMum = mun + 20;
244 | }
245 | return scaleSize(topMum)
246 | }
247 |
248 | /**
249 | * 根据是否是iPhoneX返回不同的样式
250 | * @param iphoneXStyle
251 | * @param iosStyle
252 | * @param androidStyle
253 | * @returns {*}
254 | */
255 | export function ifIphoneX(iphoneXStyle, iosStyle = {}, androidStyle) {
256 | if (isIphoneX()) {
257 | return iphoneXStyle
258 | } else if (Platform.OS === 'ios') {
259 | return iosStyle
260 | } else {
261 | if (androidStyle) return androidStyle
262 | return iosStyle
263 | }
264 | }
265 |
266 |
267 | export function guid() {
268 | function S4() {
269 | return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
270 | }
271 | return (S4() + S4() + S4() + S4() + S4() + S4() + S4() + S4());
272 | }
273 | export function Accuracy(num, len) {
274 | const wordCount = num + ''
275 | var countNum = num;
276 | if (wordCount.length > 4 && wordCount.length < 9) {
277 | countNum = parseInt(wordCount.substring(0, wordCount.length - 4) + '.' + wordCount.substring(wordCount.length - 4, wordCount.length)).toFixed(len || 0)
278 | } else if (wordCount.length > 8) {
279 | countNum = parseInt(wordCount.substring(0, wordCount.length - 8) + '.' + wordCount.substring(wordCount.length - 8, wordCount.length - 7)).toFixed(len || 0)
280 | }
281 | return countNum;
282 | }
283 | export function AccuracyNUm(num) {
284 | const wordCount = num + ''
285 | var countNum = '';
286 | if (wordCount.length > 4 && wordCount.length < 9) {
287 | countNum = '万'
288 | } else if (wordCount.length > 8) {
289 | countNum = '亿'
290 | }
291 | return countNum;
292 |
293 | }
294 |
295 | export function insertionSort(array) {
296 | for (var i = 1; i < array.length; i++) {
297 | var key = array[i];
298 | var j = i - 1;
299 | while (j >= 0 && array[j] > key) {
300 | array[j + 1] = array[j];
301 | j--;
302 | }
303 | array[j + 1] = key;
304 | }
305 | return array;
306 | }
307 | export function insertionSortJSON(array) {
308 | for (var i = 1; i < array.length; i++) {
309 | var key = array[i];
310 | var j = i - 1;
311 | while (j >= 0 && array[j].seqNum > key.seqNum) {
312 | array[j + 1] = array[j];
313 | j--;
314 | }
315 | array[j + 1] = key;
316 | }
317 | return array;
318 | }
319 |
320 | export default class ScreenUtil {
321 | static screenW = screenW
322 | static screenH = screenH
323 | static pixelRatio = pixelRatio
324 | static DEFAULT_DENSITY = DEFAULT_DENSITY
325 | static setSpText(size) {
326 | return setSpText(size)
327 | }
328 |
329 | static scaleSize(size) {
330 | return scaleSize(size)
331 | }
332 |
333 | static getRemainingimeDistance(distance) {
334 | return getRemainingimeDistance(distance)
335 | }
336 |
337 | static toDate(timestamp, format1 = 'yyyy-MM-dd hh:mm:ss') {
338 | return toDate(timestamp, format1)
339 | }
340 |
341 | static toTimestamp(date) {
342 | return toTimestamp(date)
343 | }
344 |
345 | static getTaskTime(strDate) {
346 | return getTaskTime(strDate)
347 | }
348 | static guid() {
349 | return guid()
350 | }
351 |
352 | static Accuracy(num) {
353 | return Accuracy(num)
354 | }
355 | static AccuracyNUm(num) {
356 | return AccuracyNUm(num)
357 | }
358 | static getRemainingimeDistance2(distance) {
359 | return getRemainingimeDistance2(distance)
360 | }
361 | static insertionSort(array) {
362 | return insertionSort(array)
363 | }
364 | static insertionSortJSON(array) {
365 | return insertionSortJSON(array)
366 | }
367 | }
368 |
369 |
370 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { StyleSheet, Platform, Animated, Text, View, Dimensions, StatusBar, } from 'react-native';
4 | import { screenW, screenH, setSpText, scaleSize, isIphoneNum } from "./ScreenUtil";
5 | const { height: SCREEN_HEIGHT, } = Dimensions.get('window');
6 | const IS_IPHONE_X = SCREEN_HEIGHT === scaleSize(812) || SCREEN_HEIGHT === scaleSize(896);
7 | const STATUS_BAR_HEIGHT = Platform.OS === 'ios' ? (IS_IPHONE_X ? scaleSize(44) : scaleSize(20)) : scaleSize(0);
8 | const NAV_BAR_HEIGHT = Platform.OS === 'ios' ? (IS_IPHONE_X ? scaleSize(88) : scaleSize(64)) : scaleSize(64);
9 |
10 | const SCROLL_EVENT_THROTTLE = scaleSize(16);
11 | const DEFAULT_HEADER_MAX_HEIGHT = scaleSize(170);
12 | const DEFAULT_HEADER_MIN_HEIGHT = NAV_BAR_HEIGHT;
13 | const DEFAULT_EXTRA_SCROLL_HEIGHT = scaleSize(30);
14 | const DEFAULT_BACKGROUND_IMAGE_SCALE = 1.5;
15 |
16 | const DEFAULT_NAVBAR_COLOR = 'rgba(255,255,255,.5)';
17 | const DEFAULT_BACKGROUND_COLOR = 'rgba(255,255,255,.9)';
18 | const DEFAULT_TITLE_COLOR = 'white';
19 |
20 | const styles = StyleSheet.create({
21 | containerTitle: {
22 | position: 'absolute',
23 | top: 0,
24 | left: 0,
25 | right: 0,
26 | height: scaleSize(70),
27 | },
28 | container: {
29 | backgroundColor: 'white',
30 | flex: 1,
31 | },
32 | scrollView: {
33 | flex: 1,
34 | },
35 | header: {
36 | position: 'absolute',
37 | top: 0,
38 | left: 0,
39 | right: 0,
40 | backgroundColor: DEFAULT_NAVBAR_COLOR,
41 | overflow: 'hidden',
42 | },
43 | backgroundImage: {
44 | position: 'absolute',
45 | top: 0,
46 | left: 0,
47 | right: 0,
48 | width: null,
49 | height: DEFAULT_HEADER_MAX_HEIGHT,
50 | resizeMode: 'cover',
51 | },
52 | bar: {
53 | backgroundColor: 'transparent',
54 | height: DEFAULT_HEADER_MIN_HEIGHT,
55 | position: 'absolute',
56 | top: 0,
57 | left: 0,
58 | right: 0,
59 | },
60 | headerTitle: {
61 | backgroundColor: 'transparent',
62 | position: 'absolute',
63 | top: 0,
64 | left: 0,
65 | right: 0,
66 | paddingTop: STATUS_BAR_HEIGHT,
67 | alignItems: 'center',
68 | justifyContent: 'center',
69 | },
70 | headerText: {
71 | color: DEFAULT_TITLE_COLOR,
72 | textAlign: 'center',
73 | fontSize: setSpText(16),
74 | },
75 | absoluteAll: {
76 | width: screenW,
77 | height: scaleSize(280),
78 | position: 'absolute',
79 | top: 0,
80 | left: 0,
81 | },
82 | });
83 |
84 | class RNParallax extends Component {
85 | constructor() {
86 | super();
87 | this.state = {
88 | scrollY: new Animated.Value(0),
89 | };
90 | }
91 |
92 | getHeaderMaxHeight() {
93 | const { headerMaxHeight } = this.props;
94 | return headerMaxHeight;
95 | }
96 |
97 | getHeaderMinHeight() {
98 | const { headerMinHeight } = this.props;
99 | return headerMinHeight;
100 | }
101 |
102 | getHeaderScrollDistance() {
103 | return this.getHeaderMaxHeight() - this.getHeaderMinHeight();
104 | }
105 |
106 | getExtraScrollHeight() {
107 | const { extraScrollHeight } = this.props;
108 | return extraScrollHeight;
109 | }
110 |
111 | getBackgroundImageScale() {
112 | const { backgroundImageScale } = this.props;
113 | return backgroundImageScale;
114 | }
115 |
116 | getInputRange() {
117 | return [-this.getExtraScrollHeight(), 0, this.getHeaderScrollDistance()];
118 | }
119 |
120 | getHeaderHeight() {
121 | const { scrollY } = this.state;
122 | return scrollY.interpolate({
123 | inputRange: this.getInputRange(),
124 | outputRange: [this.getHeaderMaxHeight() + this.getExtraScrollHeight(), this.getHeaderMaxHeight(), this.getHeaderMinHeight()],
125 | extrapolate: 'clamp',
126 | });
127 | }
128 |
129 | getNavBarOpacity() {
130 | const { scrollY } = this.state;
131 | return scrollY.interpolate({
132 | inputRange: this.getInputRange(),
133 | outputRange: [0, 1, 1],
134 | extrapolate: 'clamp',
135 | });
136 | }
137 |
138 | getNavBarForegroundOpacity() {
139 | const { scrollY } = this.state;
140 | const { alwaysShowNavBar } = this.props;
141 | return scrollY.interpolate({
142 | inputRange: this.getInputRange(),
143 | outputRange: [alwaysShowNavBar ? 1 : 0, alwaysShowNavBar ? 1 : 0, 1],
144 | extrapolate: 'clamp',
145 | });
146 | }
147 |
148 | getImageOpacity() {
149 | const { scrollY } = this.state;
150 | return scrollY.interpolate({
151 | inputRange: this.getInputRange(),
152 | outputRange: [1, 1, 0],
153 | extrapolate: 'clamp',
154 | });
155 | }
156 |
157 | getImageTranslate() {
158 | const { scrollY } = this.state;
159 | return scrollY.interpolate({
160 | inputRange: this.getInputRange(),
161 | outputRange: [0, 0, -50],
162 | extrapolate: 'clamp',
163 | });
164 | }
165 |
166 | getImageScale() {
167 | const { scrollY } = this.state;
168 | return scrollY.interpolate({
169 | inputRange: this.getInputRange(),
170 | outputRange: [this.getBackgroundImageScale(), 1, 1],
171 | extrapolate: 'clamp',
172 | });
173 | }
174 |
175 | getTitleTranslateY() {
176 | const { scrollY } = this.state;
177 | return scrollY.interpolate({
178 | inputRange: this.getInputRange(),
179 | outputRange: [5, 0, 0],
180 | extrapolate: 'clamp',
181 | });
182 | }
183 |
184 | getTitleOpacity() {
185 | const { scrollY } = this.state;
186 | const { alwaysShowTitle } = this.props;
187 | return scrollY.interpolate({
188 | inputRange: this.getInputRange(),
189 | outputRange: [1, 1, alwaysShowTitle ? 1 : 0],
190 | extrapolate: 'clamp',
191 | });
192 | }
193 | getTitleOpacityHide() {
194 | const { scrollY } = this.state;
195 | const { alwaysShowTitle } = this.props;
196 | return scrollY.interpolate({
197 | inputRange: this.getInputRange(),
198 | outputRange: [1, 1, alwaysShowTitle ? 0 : 1],
199 | extrapolate: 'clamp',
200 | });
201 | }
202 |
203 | renderBackgroundImage() {
204 | const { backgroundImage } = this.props;
205 | const imageOpacity = this.getImageOpacity();
206 | const imageTranslate = this.getImageTranslate();
207 | const imageScale = this.getImageScale();
208 |
209 | return (
210 |
221 | );
222 | }
223 |
224 | renderPlainBackground() {
225 | const { backgroundColor } = this.props;
226 | const imageOpacity = this.getImageOpacity();
227 | const imageTranslate = this.getImageTranslate();
228 | const imageScale = this.getImageScale();
229 | return (
230 |
238 | );
239 | }
240 |
241 | renderNavbarBackground() {
242 | const { renderContainer, renderContainerStyle } = this.props;
243 | const titleTranslateY = this.getTitleTranslateY();
244 | const titleOpacity = this.getTitleOpacityHide();
245 | return (
246 |
256 | {renderContainer()}
257 |
258 | );
259 | }
260 |
261 | renderHeaderBackground() {
262 | const { backgroundImage, backgroundColor } = this.props;
263 | const imageOpacity = this.getImageOpacity();
264 |
265 | return (
266 |
276 | {backgroundImage && this.renderBackgroundImage()}
277 | {!backgroundImage && this.renderPlainBackground()}
278 |
279 | );
280 | }
281 |
282 | renderHeaderTitle() {
283 | const { title, } = this.props;
284 | const titleTranslateY = this.getTitleTranslateY();
285 | const titleOpacity = this.getTitleOpacity();
286 | return (
287 |
298 | {title()}
299 |
300 | );
301 | }
302 |
303 | renderHeaderForeground() {
304 | const { renderNavBar } = this.props;
305 | const navBarOpacity = this.getNavBarForegroundOpacity();
306 | return (
307 |
316 | {renderNavBar()}
317 |
318 | );
319 | }
320 | renderScrollView() {
321 | const {
322 | renderContent, scrollEventThrottle, scrollViewStyle, contentContainerStyle, innerContainerStyle, scrollViewProps,
323 | } = this.props;
324 | const { scrollY } = this.state;
325 | return (
326 |
336 |
337 | {renderContent()}
338 |
339 |
340 | );
341 | }
342 |
343 | render() {
344 | const { navbarColor, statusBarColor, containerStyle } = this.props;
345 | return (
346 |
347 |
348 | {this.renderHeaderTitle()}
349 | {this.renderScrollView()}
350 | {this.renderNavbarBackground()}
351 | {this.renderHeaderForeground()}
352 |
353 | );
354 | }
355 | }
356 |
357 | RNParallax.propTypes = {
358 | renderNavBar: PropTypes.func,
359 | renderContent: PropTypes.func.isRequired,
360 | backgroundColor: PropTypes.string,
361 | backgroundImage: PropTypes.any,
362 | navbarColor: PropTypes.string,
363 | title: PropTypes.any,
364 | titleStyle: PropTypes.any,
365 | headerTitleStyle: PropTypes.any,
366 | headerMaxHeight: PropTypes.number,
367 | headerMinHeight: PropTypes.number,
368 | scrollEventThrottle: PropTypes.number,
369 | extraScrollHeight: PropTypes.number,
370 | backgroundImageScale: PropTypes.number,
371 | contentContainerStyle: PropTypes.any,
372 | innerContainerStyle: PropTypes.any,
373 | scrollViewStyle: PropTypes.any,
374 | containerStyle: PropTypes.any,
375 | alwaysShowTitle: PropTypes.bool,
376 | alwaysShowNavBar: PropTypes.bool,
377 | statusBarColor: PropTypes.string,
378 | scrollViewProps: PropTypes.object,
379 | };
380 |
381 | RNParallax.defaultProps = {
382 | renderNavBar: () => ,
383 | navbarColor: DEFAULT_NAVBAR_COLOR,
384 | backgroundColor: DEFAULT_BACKGROUND_COLOR,
385 | backgroundImage: null,
386 | title: null,
387 | titleStyle: styles.headerText,
388 | headerTitleStyle: null,
389 | headerMaxHeight: DEFAULT_HEADER_MAX_HEIGHT,
390 | headerMinHeight: DEFAULT_HEADER_MIN_HEIGHT,
391 | scrollEventThrottle: SCROLL_EVENT_THROTTLE,
392 | extraScrollHeight: DEFAULT_EXTRA_SCROLL_HEIGHT,
393 | backgroundImageScale: DEFAULT_BACKGROUND_IMAGE_SCALE,
394 | contentContainerStyle: null,
395 | innerContainerStyle: null,
396 | scrollViewStyle: null,
397 | containerStyle: null,
398 | alwaysShowTitle: true,
399 | alwaysShowNavBar: true,
400 | statusBarColor: null,
401 | scrollViewProps: {},
402 | };
403 |
404 | export default RNParallax;
405 |
--------------------------------------------------------------------------------