Author: Li Lei
background
To update list data in a Web application, click the refresh button in the upper left corner or press Ctrl+F5 to fully update page resources and data. If the page provides a refresh button or a page turn button, you can also click to update data only.
However, the mobile client screen cost a lot of money, whether adding a refresh button, or with fewer and fewer phone buttons to do the refresh operation, is not very convenient solution.
Therefore, between this inch, a variety of sliding scheme and gesture scheme to trigger events, has become a general trend of mobile clients. In the aspect of refreshing data, the most commonly used scheme on mobile terminal is the pull-down refresh mechanism.
What is a drop-down refresh?
The pull-down refresh mechanism was first implemented by Loren Brichter in Tweetie 2. Tweetie was a third-party client for Twitter that was later acquired by Twitter, and Loren Brichter became an employee (since departed).
Loren Brichter filed a Patent for the pull-down refresh on April 8, 2010, and was granted United States Patent: 8448084. But he would love to see it adopted by other apps and has said in the past that applications are defensive.
Let’s look at the sovereigns that have the most patent protection:
- In one arrangement, a scrolling list of content items is displayed;
- Can accept input associated with a scroll command;
- According to the scroll command, display a scroll refresh trigger;
- Refresh the contents of the scroll list after the trigger for scrolling refresh is activated based on the scroll command.
Simply put, the pull-down loading mechanism consists of three states:
- Drop-down Update: displays the expansion of the drop-down list.
- “Release update” : prompts the user to pull down the critical point.
- “Data Update animation” : Gestures are released to alert the user that data is being updated.
Since then, this design has been adopted by many mobile clients based on news Feed.
Does React Native support pull-down refresh?
React Native provides a RefreshControl component that can be used inside a ScrollView or FlatList to add drop-down refresh functionality.
RefreshControl encapsulates the internal implementation is of iOS environment UIRefreshControl AndroidSwipeRefreshLayout and android environment, both are the primary components on mobile.
The RefreshControl does not support customization due to the different native solutions, and only supports simple parameter changes, such as the color of the refresh indicator and the font under the refresh indicator. Existing parameters are also limited by platform.
The most common requirements require the pull-down loading indicator to have its own loading animation, and individual requirements will add a text description of the operation and the last loading time. RefreshControl that only supports changing colors is definitely not satisfying.
So what do you do if you want to customize a drop-down refresh?
Solution 1
ScrollView is an officially provided component that encapsulates the platform ScrollView and is often used to display the scroll area. It also integrates the touch “gesture responder” system.
Gesture response system is used to determine the user’s real intention of a touch. Typically, a user’s touch takes several stages to determine. For example, it starts with a click, and then it turns into a swipe. Depending on the duration, these operations translate.
In addition, a gesture-responsive system can be provided to other components, allowing them to handle touch interactions without caring about their parents or children. The PanResponder class provides a predictable wrapper around a touch-responsive system. It can coordinate multi-touch operations into one gesture. It allows a single touch to accept more touches, and can also be used to recognize simple multi-touch gestures.
It provides a new gestureState object in addition to the native event:
onPanResponderMove: (nativeEvent, gestureState) = > {}
Copy the code
The nativeEvent nativeEvent object contains the following fields:
- ChangedTouches – Array of all touch touches that have changed since the last event
- Identifier – The ID of the touch point
- LocationX – The abscissa of the touch point relative to the parent element
- LocationY – The ordinate of the touch point relative to the parent element
- PageX – The horizontal coordinate of the touch point relative to the root element
- PageY – The ordinate of the touch point relative to the root element
- Target – THE element ID of the touch point
- Timestamp – the timestamp of the touch event, which can be used to calculate movement speed
- Touches – A collection of all touches on the current screen
To represent gesture operations, the gestureState object has the following fields:
- StateID – ID of the touch state. This ID is valid for as long as there is at least one touch point on the screen.
- MoveX – The abscissa of the screen when the last movement was made
- MoveY – The screen ordinate of the last movement
- X0 – Screen coordinates when the responder is generated
- Y0 – Screen coordinates when the responder is generated
- Dx – The cumulative lateral distance from the start of the touch operation
- Dy – The cumulative longitudinal distance from the start of the touch operation
- Vx – Current lateral movement speed
- Vy – Current longitudinal movement speed
- NumberActiveTouches – The number of active touch points currently on the screen
Take a look at the basic usage of PanResponder:
componentWillMount: function() {
this._panResponder = PanResponder.create({
// Request to be a responder:
onStartShouldSetPanResponder: (evt, gestureState) = > true.onStartShouldSetPanResponderCapture: (evt, gestureState) = > true.onMoveShouldSetPanResponder: (evt, gestureState) = > true.onMoveShouldSetPanResponderCapture: (evt, gestureState) = > true.onPanResponderGrant: (evt, gestureState) = > {
// Start gesture operation. Give the user some visual feedback so they know what's going on!
// gestureState.{x,y} is now set to 0
},
onPanResponderMove: (evt, gestureState) = > {
Gesturestate. move{X,Y}
Gesturestate. d{x,y} gesturestate. d{x,y}
},
onPanResponderTerminationRequest: (evt, gestureState) = > true.onPanResponderRelease: (evt, gestureState) = > {
// The user releases all touch points, and the view becomes the responder.
// Generally this means that a gesture operation has completed successfully.
},
onPanResponderTerminate: (evt, gestureState) = > {
// The other component has become a new responder, so the current gesture will be cancelled.
},
onShouldBlockNativeResponder: (evt, gestureState) = > {
// Returns a Boolean value that determines whether the current component should prevent native components from becoming JS responders
// Returns true by default. For now, only Android is supported.
return true; }}); },render: function() {
return (
<View {. this._panResponder.panHandlers} / >
);
},
Copy the code
Combined with the above state analysis, it can be seen that the two parameters onPanResponderMove and onPanResponderRelease can basically meet the operation process of the drop-down refresh mechanism.
OnPanResponderMove handles the sliding process.
onPanResponderMove(event, gestureState) {
Gesturestate. move{X,Y}
Gesturestate. d{x,y} gesturestate. d{x,y}
if (gestureState.dy >= 0) {
if (gestureState.dy < 120) {
this.state.containerTop.setValue(gestureState.dy); }}else {
this.state.containerTop.setValue(0);
if (this.scrollRef) {
if (typeof this.scrollRef.scrollToOffset === 'function') {
// inner is FlatList
this.scrollRef.scrollToOffset({
offset: -gestureState.dy,
animated: true}); }else if(typeof this.scrollRef.scrollTo === 'function') {
// inner is ScrollView
this.scrollRef.scrollTo({
y: -gestureState.dy,
animated: true}); }}}}Copy the code
OnPanResponderRelease Handles the action at release time.
onPanResponderRelease(event, gestureState) {
// The user releases all touch points, and the view becomes the responder.
// Generally this means that a gesture operation has completed successfully.
// Determine whether the condition for triggering a refresh is met
const threshold = this.props.refreshTriggerHeight || this.props.headerHeight;
if (this.containerTranslateY >= threshold) {
// Trigger the refresh
this.props.onRefresh();
} else {
// Back to the top without reaching the refresh position
this._resetContainerPosition();
}
// Check the scrollEnabled switch
this._checkScroll();
}
Copy the code
All that remains is how to distinguish between a container swipe and a drop-down refresh trigger.
When the scrollEnabled property of the ScrollView is set to false, the user can be disabled from scrolling. Therefore, you can use a ScrollView as a content container. When scrolling to the top of the container, turn off the scrollEnabled property of the ScrollView and display the custom loader by setting the translateY of Animated.View.
<Animated.View style={[{ flex: 1, transform: [{ translateY: this.state.containerTop }] }]}>
{child}
</Animated.View>
Copy the code
expo pulltorefresh1
After trial use, the scheme was found to have the following fatal problems:
- Because the pull-down process is fed back to the native view through touch response system, a large amount of data communication and page redrawing will lead to page lag, which will be more obvious when the page data volume is large.
- The switch between slide up and drop down is controlled by the Enable property of ScrollView, which will cause the interruption of gesture operation.
- The process of gesture sliding lacks damping function, and it is not as natural as the native pull refresh. In addition, there is a problem that the sliding of ScrollView and the sliding process of simulation are not tacit enough.
Solution 2
ScrollView has a feature on iOS devices that allows you to flexibly pull a piece of content when it reaches the end of the content if it is larger than the ScrollView itself. You can place the load indicator on the top edge of the page, which is exposed when you scroll flexibly. This eliminates the need for gestures to affect rendering speed and allows for a seamless integration of scrolling and pull-down.
Therefore, just handle the phases of the scrolling operation.
onScroll = (event) = > {
// console.log('onScroll()');
const { y } = event.nativeEvent.contentOffset
this._offsetY = y
if (this._dragFlag) {
if (!this._isRefreshing) {
const height = this.props.refreshViewHeight
if (y <= -height) {
this.setState({
refreshStatus: RefreshStatus.releaseToRefresh,
refreshTitle: this.props.refreshableTitleRelease
})
} else {
this.setState({
refreshStatus: RefreshStatus.pullToRefresh,
refreshTitle: this.props.refreshableTitlePull
})
}
}
}
if (this.props.onScroll) {
this.props.onScroll(event)
}
}
onScrollBeginDrag = (event) = > {
// console.log('onScrollBeginDrag()');
this._dragFlag = true
this._offsetY = event.nativeEvent.contentOffset.y
if (this.props.onScrollBeginDrag) {
this.props.onScrollBeginDrag(event)
}
}
onScrollEndDrag = (event) = > {
// console.log('onScrollEndDrag()', y);
this._dragFlag = false
const { y } = event.nativeEvent.contentOffset
this._offsetY = y
const height = this.props.refreshViewHeight
if (!this._isRefreshing) {
if (this.state.refreshStatus === RefreshStatus.releaseToRefresh) {
this._isRefreshing = true
this.setState({
refreshStatus: RefreshStatus.refreshing,
refreshTitle: this.props.refreshableTitleRefreshing
})
this._scrollview.scrollTo({ x: 0.y: -height, animated: true });
this.props.onRefresh()
}
} else if (y <= 0) {
this._scrollview.scrollTo({ x: 0.y: -height, animated: true})}if (this.props.onScrollEndDrag) {
this.props.onScrollEndDrag(event)
}
}
Copy the code
The only catch is that iOS supports swiping over content, but Android doesn’t.
The load indicator is placed inside the page and the distance from the top of the page is controlled by scrollTo method to simulate the drop down space. (iOS and android solutions given in expo pulltorefresh2)
expo pulltorefresh2
(Demo suggests viewing on mobile devices, Web adaptation can try onScrollBeginDrag onScrollEndDrag onTouchStart onTouchEnd)
conclusion
This paper mainly introduces the technical investigation and implementation process of the pull-down refresh component during React Native development. The Expo demo contains the main implementation logic of the two solutions, which readers can customize according to their business needs. Please contact us if you have any questions.
Refer to the link
- Which designer came up with the idea of pull-refresh?
- United States Patent: 8448084
- Why do so many applications still use pull-refresh after it has been patented?
- React Native Chinese /RefreshControl
- GitHub facebook/React Native/RefreshControl
- React Native Custom pull-down refresh component
- React Native Chinese/Panresponder
- react-native-ultimate-listview
This article is published from netease Cloud Music big front end team, can be reproduced freely, reproduced in the title and reserve the source in a prominent place. We’re always looking for people, so if you’re ready to change jobs and you like cloud music, join us!