React Native sticky row and header scroll performa

2019-05-01 02:47发布

问题:

I have cobbled together a working version of a Microsoft Excel like "freeze pains" view. The column header scrolls with the content horizontally and the row headers scroll with the content vertically but each is "stuck" in position when the other is scrolled.

You can try the working version here.
It's not optimal as it stutters if you stop a flicked scroll or just swipe around a lot.

The approach uses a couple techniques but the one causing the issue is the synced scroll view.

As outlined here, I've tried setting useNativeDriver: true, which necessitates changing
ScrollView to Animated.ScrollView and
ref={ref => (this.instance = ref)} to ref={ref => (this.instance = ref._component)} but then the synced goes completely haywire.

I'd love ideas on a more optimal approach. How can this be improved?

import React from 'react';
import { ScrollView, Animated, Text, View } from 'react-native';

export default class SyncScrollTest extends React.Component {
  constructor() {
    super();
    this.scrollPosition = new Animated.Value(0);
    this.scrollEvent = Animated.event(
      [{ nativeEvent: { contentOffset: { y: this.scrollPosition } } }],
      { useNativeDriver: false },
    );
  }

  render() {
    return (
      <View style={{ flex: 1 }}>
        <View style={{ flexDirection: 'row' }}>
          <ScrollViewVerticallySynced
            style={{ width: 50, marginTop: 60 }}
            name="C1"
            color="#F2AFAD"
            onScroll={this.scrollEvent}
            scrollPosition={this.scrollPosition}
          />
          <ScrollView horizontal bounces={false}>
            <View style={{ width: 600 }}>
              <View style={{ height: 60, justifyContent: 'center', backgroundColor: '#B8D2EC' }}>
                <Text>
                  I am Column Header!! I am Column Header!! I am Column Header!! I am Column
                  Header!! I am Column Header!! I am Column Header!! I am Column Header!!
                </Text>
              </View>
              <ScrollViewVerticallySynced
                style={{ width: 600 }}
                name="C2"
                color="#D9E4AA"
                onScroll={this.scrollEvent}
                scrollPosition={this.scrollPosition}
              />
            </View>
          </ScrollView>
        </View>
      </View>
    );
  }
}

class ScrollViewVerticallySynced extends React.Component {
  componentDidMount() {
    this.listener = this.props.scrollPosition.addListener((position) => {
      this.instance.scrollTo({
        y: position.value,
        animated: false,
      });
    });
  }

  render() {
    const { name, color, style, onScroll } = this.props;
    return (
      <ScrollView
        key={name}
        ref={ref => (this.instance = ref)}
        style={style}
        scrollEventThrottle={1}
        onScroll={onScroll}
        bounces={false}
        showsVerticalScrollIndicator={false}
      >
        {someRows(name, 25, color)}
      </ScrollView>
    );
  }
}

const someRows = (name, rowCount, color) =>
  Array.from(Array(rowCount).keys()).map(index =>
    (<View
      key={`${name}-${index}`}
      style={{
        height: 50,
        backgroundColor: index % 2 === 0 ? color : 'white',
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
      }}
    >
      <Text>
        {name} R{index + 1}
      </Text>
    </View>),
  );

```

回答1:

I've changed your example, instead of using listeners and Animated Event I use the scrollTo method from ScrollView to synchronize the scrolling. I think that listeners are the cause of lag between the rows when you are scrolling.

You can test the changes here.

import React from 'react';
import { ScrollView, Text, View } from 'react-native';
import { Constants } from 'expo'

export default class SyncScrollTest extends React.Component {
  constructor() {
    super();
    this.c1IsScrolling = false;
    this.c2IsScrolling = false;

  }

  render() {
    return (
      <View style={{ flex: 1, marginTop: Constants.statusBarHeight }}>
        <View style={{ flexDirection: 'row' }}>
          <ScrollViewVerticallySynced
            style={{ width: 50, marginTop: 60 }}
            refe= {ref => (this.c2View = ref)}
            name="C1"
            color="#F2AFAD"
            onScroll={e => {
                if (!this.c1IsScrolling) {
                  this.c2IsScrolling = true;
                  var scrollY = e.nativeEvent.contentOffset.y;
                  this.c1View.scrollTo({ y: scrollY });
                }
                this.c1IsScrolling = false;
              }}

          />
          <ScrollView horizontal bounces={false}>
            <View style={{ width: 400 }}>
              <View style={{ height: 60, justifyContent: 'center', backgroundColor: '#B8D2EC' }}>
                <Text>
                  I am Column Header!! I am Column Header!! I am Column Header!! I am Column
                  Header!! I am Column Header!! I am Column Header!! I am Column Header!!
                </Text>
              </View>
              <ScrollViewVerticallySynced
                style={{ width: 400 }}
                refe= {ref => (this.c1View = ref)}
                name="C2"
                color="#D9E4AA"
                onScroll= {e => {
                  if (!this.c2IsScrolling) {
                    this.c1IsScrolling = true;
                    var scrollY = e.nativeEvent.contentOffset.y;
                    this.c2View.scrollTo({ y: scrollY });
                  }
                  this.c2IsScrolling = false;
                }}

              />
            </View>
          </ScrollView>
        </View>
      </View>
    );
  }
}

class ScrollViewVerticallySynced extends React.Component {
  render() {
    const { name, color, style, onScroll, refe } = this.props;
    return (
      <ScrollView
        key={name}
        ref={refe}
        style={style}
        scrollEventThrottle={1}
        onScroll={onScroll}
        bounces={false}
        showsVerticalScrollIndicator={false}
      >
        {someRows(name, 25, color)}
      </ScrollView>
    );
  }
}


const someRows = (name, rowCount, color) =>
  Array.from(Array(rowCount).keys()).map(index =>
    (<View
      key={`${name}-${index}`}
      style={{
        height: 50,
        backgroundColor: index % 2 === 0 ? color : 'white',
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
      }}
    >
      <Text>
        {name} R{index + 1}
      </Text>
    </View>),
  );

You can find another example here