preface

  • Since the business needs to have the effect of rolling the details of the Tencent class course, considering the possibility of new presentation in the future, the components provided by RN do not have such rolling control, so it is better to encapsulate them by ourselves. In fact, I wrote a paper last year, but it was rather messy, so I spent some time on rewriting and sorting out the work over the weekend.

  • The project address is here. If you have good comments, please feel free to issue or PR.

start

  • Let’s take a look first, Tencent classroom video playing details page is what?

  • At first glance, the interface feels a little complicated. In fact, to simplify, this interface can be regarded as TAB + Scroll component. Philosophically speaking, the main contradiction in this problem is the scroll component implementation, which is the outermost RNFixScrollView.

  • At this point, I tried to write a test JS example, the outer layer of a ReactNative ScrollView and set the video playback control to 200 and the Tab navigation control style={{height: At 120, the scroll bar is now at the bottom and the area of the broadcast control is 80 points away from the top of the screen.

  • A serious problem is that if the Tab navigation control has a ScrollView or ListView in its content area, it cannot scroll, and only the outermost layer can scroll.

  • There are two big ideas to start with: One is implemented entirely at the JS level, using apis exposed by ScrollView, and the other is native +JS, which involves a couple of key things, How to find the ScrollView or ListView in the Tab navigation control and control gesture to achieve the effect — the outer scroll container to the top + gesture up to inform the inner scroll container to start scrolling; Inner layer to top + gesture down to inform the outer layer to start rolling.

  • Finding that the first method doesn’t have a solution for finding child controls and determining scroll state (maybe I didn’t) and performance considerations, I adopted the second method.

Analysis of the

To solve the above problem, we need to understand a few key points.

  • One is how to determine gesture sliding and outer scrolling container bottom and inner scrolling to top?
  • The second is to look for the scroll component and tell the inner scroll component to start scrolling, okay?

Therefore, search the Internet for information about these two problems and solutions to determine whether the bottom is easy to find, of course, understand the principle. In addition, the question of determining whether the gesture is sliding up or down will be explained later.

The Hierarchy Viewer tool found that the three ScrollView instances are visible. We then compare the three ScrollView properties and find that their LocationOnScreenX coordinates on the screen are different, equal to 0 if the scroll container is currently displayed.

How do I tell the inner container to scroll? To keep things in suspense, let’s take a look at how the View event is passed in Android before we solve this problem.

What are the types of Touch events in Android? Let’s think about what happens to your fingers when you play with your phone: Dropping a finger, lifting a finger, and moving a finger are three basic operations, which are actually three touch events, representing MotionEvent.ACTION_DOWN, MotionEvent.ACTION_UP, and MotionEvent

In simple terms, as shown below: After a touch event occurs, if the coordinates of the event are under the jurisdiction of the ViewGroup, the dispatchTouchEvent method of the ViewGroup is called first, and the onInterceptTouchEvent() method is called internally to determine whether to intercept the touch event. The onTouchEvent() method of the ViewGroup is called if the event is intercepted, otherwise the dispatchTouchEvent of its child View is handled.

Specific can refer to what I wrote before
Learning the event distribution mechanism.

To return false, the onInterceptTouchEvent method should be overwritted by the onInterceptTouchEvent method. This method will be called over and over again with the gesture, so what do you think of? Determine whether the gesture goes up or down based on the difference in the y-coordinate of the hand touching the screen. When a finger slides, it produces a series of touch events. Here are two situations: the top left corner of the screen is the origin of the coordinates, along the right side is the x axis, and on the left side is the Y axis. ① Down -> Move… -> Move -> UP ② Down -> Move ->… -> Move

Record the Y coordinate value of Down touch event as the starting value, and the Y coordinate value of Move or UP as the end value. If the difference between the two is greater than the minimum sliding value, it means sliding UP, and if the difference is less than the minimum sliding value, it means sliding UP (the condition is simplified here, If OnGestureListener is implemented, there are X and Y values to determine the sliding speed. Now that we’ve solved both of those problems, we’re really starting to get into the game.

How do I encapsulate RN components

  • Referring to RN 0.51, we need to do these things:
Things to do primordial
  • 1. Create native fixed scroll controls
  • 2. Create a subclass that manages the scroll control ViewManager
  • 3. Create classes that implement the ReactPackage interface
Things to do in JavaScript
  • 4. Implement the corresponding JavaScript module

start

######1. Create a native fixed scroll control

According to the previous analysis, we know that writing the native scroll control is mainly to rewrite the control interception event method onInterceptTouchEvent, here we need to determine the current Tab navigation control ScrollView to enter our logic for interception control, otherwise according to the default logic.

  • Need to be inMotionEvent.ACTION_DOWNEvent, the first child is found by the condition analyzed previouslyScrollView, the code is as follows:
  private ScrollView findScrollView(ViewGroup group) {
        if(group ! = null) {for (int i = 0, j = group.getChildCount(); i < j; i++) {
                View child = group.getChildAt(i);
                ifInt [] location = new int[2]; (child instanceof ScrollView) {// Get the coordinates of the view in the whole screen. child.getLocationOnScreen(location); System.out.print("locationx:" + location[0] + ",locationy:" + location[1]);
                    if (location[0] == 0)
                        return (ScrollView) child;
                    else
                        continue;

                } else if (child instanceof ViewGroup) {
                    ScrollView result = findScrollView((ViewGroup) child);
                    if(result ! = null)returnresult; }}}return null;
    }
Copy the code
  • The declaration calculates the two points of the sliding gesture: Down point (x1, y1) Move point (x2, y2), so that there are two cases: slide up, slide Down

  • Check whether RNFixScrollView slides to the bottom using isAtBottom.

    public boolean isAtBottom() {
        return getScrollY() == getChildAt(getChildCount() - 1).getBottom() + getPaddingBottom() - getHeight();
    }
Copy the code
  • Based on the above known conditions, only a few critical cases need to be found:

1.RNFixScrolView to bottom && up: Do not intercept

2.RNFixScrolView not to bottom && up: intercept

3.RNFixScrolView not at bottom && down && child ScrollView at top: intercept

4.RNFixScrolView to bottom && down && child ScrollView not to top: no intercept,

  • The code is as follows:
   @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if(! mScrollEnabled) {return false;
        }

        int action = ev.getAction();
        if(action == motionEvent.action_down) {x1 = ev.getx (); y1 = ev.getY(); scrollView = findScrollView(this); isIntercept =false;
        }

        if((action = = MotionEvent. ACTION_MOVE) | | (action = = MotionEvent. ACTION_UP)) {/ / Tab navigation controls the existence of a ScrollViewif(scrollView ! = null) {x2 = ev.getx (); y2 = ev.getY(); RNFixScrollView isbottom = isAtBottom(); // Slide upif (y1 - y2 > FLING_MIN_DISTANCE ) {
                    if(! isbottom) { isIntercept =true;
                    } else {
                        isIntercept = false;
                    }
                    returnisIntercept; } // Slide downelse if (y2 - y1 > FLING_MIN_DISTANCE ) {
                    int st = scrollView.getScrollY();
                    if(! isbottom) { isIntercept =true;
                    } else {
                        if (st == 0) {
                            isIntercept = true;
                        } else {
                            isIntercept = false; }}returnisIntercept; }} // ReactScrollView will not slide if it is not addedif (super.onInterceptTouchEvent(ev)) {
            NativeGestureUtil.notifyNativeGestureStarted(this, ev);
            ReactScrollViewHelper.emitScrollBeginDragEvent(this);
            mDragging = true;
            enableFpsListener();
            return true;
        }
        return false;
    }

Copy the code

The above code completes the first step in creating the main logic of the native fixed scroll control.

2. Create a subclass that manages the scroll control ViewManager

Just to keep things simple, copy RN’s built-in ScrollViewManager class, changing the class name and other references to ScrollViewManager. Also note the modification field, REACT_CLASS = “RNFixedScrollView”, which maps to the JS module name.

3. Create and register a class that implements the ReactPackage interface

RNAppViewsPackage class

public class RNAppViewsPackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(
            ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        return modules;
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        returnArrays.<ViewManager>asList( new RNFixedScrollViewManager() ); }}Copy the code

The MainApplication class is registered

 @Override
    protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(
          new MainReactPackage(),
              new RNAppViewsPackage()
      );
    }
Copy the code
4. Implement the corresponding JavaScript module

To simplify, copy RN comes with the module of ScrollViewJS. Modify the providesModule value RNFixedScrollView on the comment and export the name of the native module, which is mapped to the value in step 2.

if (Platform.OS === 'android') {
  nativeOnlyProps = {
    nativeOnly: {
      sendMomentumEvents: true,}}; AndroidScrollView = requireNativeComponent('RNFixedScrollView',
    (ScrollView: React.ComponentType<any>),
    nativeOnlyProps
  );
}
Copy the code

After completing the above content, can by importing the import RNFixedScrollView from ‘. / modules/RNFixedScrollView, use RNFixedScrollView controls

test

To simulate this interface, the following code is built, where the ViewPagerPage component is a Tab navigation control. For details, go to Github.

  • The main page
 <View style={styles.container}>
                <RNFixedScrollView showsVerticalScrollIndicator={false}>
                    <View style={{
                        backgroundColor: '#87cefa',
                        height: 200,
                    }}>
                    </View>
                    <ViewPagerPage style={{height: windowHeight- 80}}/>
                </RNFixedScrollView>
            </View>
Copy the code
  • Tab navigation control, second Tab content area nestedFlatListThe other two display text.
import {StyleSheet, View, Text, Platform, Image, TouchableOpacity, Animated, Dimensions, FlatList} from 'react-native';
import React, {Component} from 'react';
import {PagerTabIndicator, IndicatorViewPager, PagerTitleIndicator, PagerDotIndicator} from 'rn-viewpager';

const windowWidth = Dimensions.get('window').width;
export default class ViewPagerPage extends Component {

    static title = '<FlatList>';
    static description = 'Performant, scrollable list of data.';

    state = {
        data: this.genItemData(20,0),
        debug: false,
        horizontal: false,
        filterText: ' ',
        fixedHeight: true.logViewable: false,
        virtualized: true}; genItemData(loadNum,counts){let items = [];
       for(leti=counts; i<counts+loadNum; i++){ items.push({key:i}); }return items;
    };

    _onEndReached(){
        this.setState((state) => ({
            data: state.data.concat(this.genItemData(10, state.data.length)),
        }));
    };

    render() {
        return (

                <IndicatorViewPager
                    style={[{backgroundColor: 'white', flexDirection: 'column-reverse'},this.props.style]}
                    indicator={this._renderTitleIndicator()}
                >
                    <View style={{backgroundColor: 'cornflowerblue'}}> < span style={{backgroundColor: RGB (51, 51, 51);'cadetblue'}}>
                        <FlatList
                            ItemSeparatorComponent={() => <View
                                style={{height: 1, backgroundColor: 'black', marginLeft: Data = 0}} / >} {this. State. Data} onEndReached = {this. _onEndReached. Bind (this)} onEndReachedThreshold = {} 0.2 renderItem={({item}) => <View style={{ justifyContent:'center',height:40,alignItems:'center'}}><Text
                                style={{fontSize: 16}}>{"Directory"+item.key}</Text></View>}
                        />
                    </View>
                    <View style={{backgroundColor: '#1AA094'}}> <Text> </Text> </View> </IndicatorViewPager>); }_renderTitleIndicator() {
        return <PagerTitleIndicator style={{
            backgroundColor: 0x00000020,
            height: 48
        }} trackScroll={true} itemStyle={{width: windowWidth / 3}}
                                    selectedItemStyle={{width: windowWidth / 3}} titles={['More Information'.'the directory'.'Related Courses']} / >; }}Copy the code

conclusion

  • I’ve learned a lot from writing and playing with this component in RN component packaging, and I’ve learned a lot about weighing different options.
  • Debugging code is tricky, and commenting on different code segments is a good way to render the interface without rendering it.
  • You’ll make a lot less mistakes when you figure out how it works.

Reference:

Talk about Android event blocking

Android screen gestures swipe