5

問題

各スワイプで常に 1 つの画面全体 (つまり、1 つの「ページ」) をスクロールする水平スクロール可能なリストを開発しています。それが最初のページにあり、ユーザーが再び右に引っ張ると、リスト全体を更新する必要があります。

垂直方向については、ビューが一番上の位置にあるときにユーザーがプルダウンするとScrollView、標準が表示されます。RefreshControlただし、水平バージョンの場合、標準的なソリューションはありません。

私の実験

アイデア

PanResponder感動的で感動的なイベントをキャプチャするために を追加しようとしました。VirtualizedListアプリケーションでa を使用していることに注意してください。基本的なロジックは次のとおりです。

  • を含む非表示View(デフォルトで幅が 0 に設定されている) を作成しActivityIndicator、このビューを として挿入しますListHeaderComponentVirtualizedList
  • VirtualizedListのイベントをリッスンしonScroll、最新のスクロール位置を保存し続けます
  • スクロール位置がほぼ 0 で、ユーザーが右に引っ張ると (イベントが を介してジェスチャをキャプチャPanResponder)、非表示の View の幅を ActivityIndi​​cator でアニメーション化して、RefreshControl の動作をシミュレートします。

結果

ただし、PanResponderは目的のイベントを適切にキャプチャしません。VirtualizedListほとんどの場合、 (Android デバイスで)開始時に右に引っ張ると、標準のヒット エッジ アニメーションのみが表示されます (グラデーション ブルー レイヤーが表示されます)。

onPanResponderGrantいくつかのログを追加することで、が時々トリガーされる可能性があることがわかりましたがonPanResponderMove、 、onPanResponderReleaseonPanResponderTerminateまたはのいずれも起動されませんonPanResponderTerminationRequest

onPanResponderGrantトリガーされると、非表示が部分的に表示ActivityIndicatorされることがあります。「右に引っ張る」効果は短時間しか現れず、隠れたビューの非常に狭い部分だけが表示され、常に即座に停止します。

基になっているものScrollViewがイベントを盗み、コードが応答しないようにしていると思います。しかしonPanResponderTerminationRequest、常に戻るように設定しても、falseまだ役に立ちません。を aに直接代入VirtualizedListしてScrollViewも、同じ結果が得られます。

ScrollView のスクロールが一番上にあるときに、タッチと移動のイベントを適切にキャプチャする方法を知っている人はいますか? または、の水平バージョンを実装する別の実行可能な方法はありRefreshControlますか?

実験コード

import React from 'react'
import {
  Text, View, ActivityIndicator, ScrollView, VirtualizedList,
  Dimensions, PanResponder, Animated, Easing
} from 'react-native'


let DATA = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];

class Test extends React.PureComponent {

  constructor(props) {
    super(props);

    this.state = {
      refreshIndicatorWidth: new Animated.Value(0),
    };

    this.onScroll = evt => this._scrollPos = evt.nativeEvent.contentOffset.x;
    this.onPullToRefresh = dist => Animated.timing(this.state.refreshIndicatorWidth, {
      toValue: dist,
      duration: 0,
      easing: Easing.linear,
    }).start();
    this.resetRefreshStatus = () => {
      Animated.timing(this.state.refreshIndicatorWidth, {
        toValue: 0,
      }).start();
    };

    const shouldRespondPan = ({dx, dy}) => (this._scrollPos||0) < 50 && dx > 10 && Math.abs(dx) > Math.abs(dy);
    this._panResponder = PanResponder.create({
      onMoveShouldSetPanResponder: (evt, gestureState) => {
        return shouldRespondPan(gestureState)
      },
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => {
        return shouldRespondPan(gestureState)
      },
      onPanResponderGrant: (evt, gestureState) => {
        console.warn('----pull-Grant: ' + JSON.stringify(gestureState));
      },
      onPanResponderMove: (evt, gestureState) => {
        console.warn('----pull-Move: ' + JSON.stringify(gestureState));
        this.onPullToRefresh(gestureState.dx)
      },
      onPanResponderTerminationRequest: () => {
        console.warn('----pull-TerminationRequest: ' + JSON.stringify(gestureState));
        return false
      },
      onPanResponderRelease: (evt, gestureState) => {
        console.warn('----pull-Release: ' + JSON.stringify(gestureState));
        this.props.refreshMoodList();
      },
      onPanResponderTerminate: () => {
        console.warn('----pull-Terminate: ' + JSON.stringify(gestureState));
        this.resetRefreshStatus();
      },
    });

    this.renderRow = this.renderRow.bind(this);
  }

  componentDidUpdate(prevProps, prevState, prevContext) {
    console.warn('----updated: ' + JSON.stringify(this.props));
    this.resetRefreshStatus();
  }

  renderRow({item, index}) {
    return (
      <View style={{flex: 1, alignItems: 'center', justifyContent: 'center', width: Dimensions.get('window').width, height: '100%'}}>
        <Text>{item}</Text>
      </View>
    );
  }

  render() {
    /******* Test with a VirtualizedList *******/
    return (
      <VirtualizedList
        {...this._panResponder.panHandlers}
        data={DATA}
        ListEmptyComponent={<View/>}
        getItem={getItem}
        getItemCount={getItemCount}
        keyExtractor={keyExtractor}
        renderItem={this.renderRow}
        horizontal={true}
        pagingEnabled={true}
        showsHorizontalScrollIndicator={false}
        ListHeaderComponent={(
          <Animated.View style={{flex: 1, height: '100%', width: this.state.refreshIndicatorWidth, justifyContent: 'center', alignItems: 'center'}}>
            <ActivityIndicator size="large"/>
          </Animated.View>
        )}
        onScroll={this.onScroll}
      />
    );
    /******* Test with a ScrollView *******/
    return (
      <View style={{height:Dimensions.get('window').height}}>
        <ScrollView
          {...this._panResponder.panHandlers}
          horizontal={true}
        >
          <View style={{width: Dimensions.get('window').width * 2, height: 100, backgroundColor: 'green'}}>
            <Text>123</Text>
          </View>
        </ScrollView>
      </View>
    );
  }
}

const getItem = (data, index) => data[index];
const getItemCount = data => data.length;
const keyExtractor = (item, index) => 'R' + index;

export default Test;

一時的な回避策 (完璧ではない)

  • 内部で直接更新イベント (およびアニメーション) を発生させますonPanResponderGrant。この方法では、まだタッチの動きを追跡できません。そのため、スクロール ペインはタッチ ポイントをたどることができません。

  • 追加の条件で操作を移動しonPanResponderMoveますonMoveShouldSetPanResponder(たとえば、独自の「許可」条件判定を追加する): この方法では、タッチの動きを追跡できますが、実際の「許可」を取得していないため、キャプチャできません。 //イベント (つまり、いつ touch イベントが終了する*Releaseかわかりません。したがって、実際にいつ更新イベントを開始するかはわかりません! *Terminate)*End

完璧なソリューション

あなたのアイデアを待っています...

4

1 に答える 1