React Native реализует настраиваемый раскрывающийся компонент обновления

React Native
React Native реализует настраиваемый раскрывающийся компонент обновления

Автор: Ли Лэй

задний план

Если вы хотите обновить данные списка в веб-приложении, вы обычно выбираете кнопку обновления в верхнем левом углу или используете сочетание клавиш Ctrl+F5 для полного обновления ресурсов и данных страницы. Если на странице есть кнопка обновления или кнопка перелистывания страниц, вы также можете щелкнуть, чтобы обновить только данные.

Тем не менее, экран мобильного клиента очень дорог, будь то добавление кнопки обновления или взаимодействие с меньшим количеством кнопок мобильного телефона для выполнения операции обновления, это не очень удобное решение.

Поэтому в этой области различные схемы скольжения и схемы жестов для запуска событий стали общей тенденцией мобильных клиентов. Что касается обновления данных, наиболее распространенным решением для мобильного терминала является механизм обновления по запросу.

Что такое потянуть вниз, чтобы обновить?

Механизм обновления по запросу был впервые реализован в Tweetie 2 Лорен Брихтер. Tweetie, сторонний клиент для Twitter, позже был приобретен Twitter, и Лорен Брихтер также стала сотрудником Twitter (сейчас ушла).

Лорен Брихтер запатентовала обновление с раскрывающимся меню 8 апреля 2010 г. и получила лицензию.United States Patent: 8448084. Но он хотел бы, чтобы этот механизм был принят другими приложениями, и сказал, что приложения являются защитными.

Давайте взглянем на суверенные предметы с наибольшим объемом патентной защиты:

  • в одном устройстве отображение прокручиваемого списка, содержащего элементы контента;
  • Может принимать ввод, связанный с командами прокрутки;
  • В соответствии с командой прокрутки отобразите триггер обновления прокрутки;
  • На основе команды прокрутки после того, как определено, что триггер для обновления прокрутки активирован, содержимое в списке прокрутки обновляется.

Проще говоря, механизм вытягивания вниз состоит из трех состояний:

  • «Обновление с раскрывающимся списком»: отображает расширяемую пользователем раскрывающуюся операцию.
  • «Обновление выпуска»: подскажите пользователю для критической точки раскрывающейся операции.
  • «Анимация обновления данных»: жест отпускается, чтобы напомнить пользователю, что данные обновляются.

После этого многие мобильные клиенты на основе новостных лент переняли этот дизайн один за другим.

Поддерживает ли React Native обновление по запросу?

React Native предоставляетRefreshControlКомпонент, который можно использовать внутри ScrollView или FlatList, чтобы добавить к нему функцию обновления по запросу.

Внутренняя реализация RefreshControl инкапсулирована отдельно в среде iOS.UIRefreshControlИ в эндрюсской средеAndroidSwipeRefreshLayout, оба из которых являются нативными компонентами для мобильных устройств.

Из-за различных собственных решений адаптации RefreshControl не поддерживает настройку, а поддерживает только некоторые простые модификации параметров, такие как: обновить цвет индикатора, обновить шрифт под индикатором. И существующие параметры тоже ограничены разными платформами.

Наиболее распространенное требование потребует, чтобы выпадающий индикатор загрузки имел собственную анимацию загрузки, а индивидуальные требования также добавят текстовое описание операции и время последней загрузки. RefreshControl, который поддерживает только изменение цвета, определенно недостаточно.

Итак, как вы хотите настроить выпадающее обновление?

Решение 1

ScrollViewЭто официально предоставленный компонент, который инкапсулирует платформу ScrollView (представление прокрутки) и часто используется для отображения областей прокрутки. Также интегрирована сенсорная система «реагирующего на жесты».

Система отклика на жестыОн используется для определения реального намерения сенсорной операции пользователя. Обычно прикосновение пользователя должно пройти несколько этапов, чтобы его можно было оценить. Например, это началось с клика, а затем превратилось в свайп. Эти действия трансформируются со временем.

Кроме того, система отклика на жесты может также предоставляться другим компонентам, что позволяет компонентам самостоятельно обрабатывать сенсорные взаимодействия, не заботясь о родительских или дочерних компонентах.PanResponderКласс предоставляет предсказуемую оболочку для сенсорной системы. Он может координировать мультитач-операции в один жест. Он позволяет одним касанием принимать больше сенсорных операций, а также может использоваться для распознавания простых мультитач-жестов.

Он обеспечивает новыйgestureStateОбъект:

onPanResponderMove: (nativeEvent, gestureState) => {}

nativeEvent Объект собственного события содержит следующие поля:

  • changedTouches — коллекция массивов всех событий касания, которые изменились с момента последнего события (т. е. все точки касания, которые переместились с момента последнего события).
  • идентификатор - идентификатор точки касания
  • locationX - абсцисса точки касания относительно родительского элемента
  • locationY - ордината точки касания относительно родительского элемента
  • pageX — абсцисса точки касания относительно корневого элемента
  • pageY — вертикальная координата точки касания относительно корневого элемента
  • target - ID элемента, на котором расположена точка касания
  • timestamp - временная метка события касания, которую можно использовать для расчета скорости движения
  • touches — коллекция всех точек касания, находящихся в данный момент на экране

ОбъектgestState имеет следующие поля для отображения операций жестов:

  • stateID — идентификатор состояния касания. Этот идентификатор будет оставаться активным до тех пор, пока на экране есть хотя бы одна точка касания.
  • moveX - абсцисса экрана, когда был сделан последний ход
  • moveY - ордината экрана на последнем ходу
  • x0 - координаты экрана, когда был создан ответчик
  • y0 - координаты экрана, когда был создан ответчик
  • dx - совокупное поперечное расстояние с момента касания
  • dy - совокупное расстояние по вертикали от начала операции касания
  • vx - текущая скорость бокового перемещения
  • vy - текущая скорость вертикального перемещения
  • numberActiveTouches — количество активных точек касания на экране в данный момент.

можно посмотретьPanResponderБазовое использование:

componentWillMount: function() {
  this._panResponder = PanResponder.create({
    // 要求成为响应者:
    onStartShouldSetPanResponder: (evt, gestureState) => true,
    onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
    onMoveShouldSetPanResponder: (evt, gestureState) => true,
    onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
    onPanResponderGrant: (evt, gestureState) => {
      // 开始手势操作。给用户一些视觉反馈,让他们知道发生了什么事情!
      // gestureState.{x,y} 现在会被设置为0
    },
    onPanResponderMove: (evt, gestureState) => {
      // 最近一次的移动距离为gestureState.move{X,Y}
      // 从成为响应者开始时的累计手势移动距离为gestureState.d{x,y}
    },
    onPanResponderTerminationRequest: (evt, gestureState) => true,
    onPanResponderRelease: (evt, gestureState) => {
      // 用户放开了所有的触摸点,且此时视图已经成为了响应者。
      // 一般来说这意味着一个手势操作已经成功完成。
    },
    onPanResponderTerminate: (evt, gestureState) => {
      // 另一个组件已经成为了新的响应者,所以当前手势将被取消。
    },
    onShouldBlockNativeResponder: (evt, gestureState) => {
      // 返回一个布尔值,决定当前组件是否应该阻止原生组件成为JS响应者
      // 默认返回true。目前暂时只支持android。
      return true;
    },
  });
},

render: function() {
  return (
    <View {...this._panResponder.panHandlers} />
  );
},

В сочетании с приведенным выше анализом состояния мы видимonPanResponderMoveа такжеonPanResponderReleaseЭти два параметра могут в основном удовлетворить рабочий процесс механизма обновления с раскрывающимся списком.

onPanResponderMoveУправляйте процессом скольжения.

onPanResponderMove(event, gestureState) {
  // 最近一次的移动距离为 gestureState.move{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,
        });
      }
    }
  }
}

onPanResponderReleaseОбрабатывает действие при выпуске.

onPanResponderRelease(event, gestureState) {
  // 用户放开了所有的触摸点,且此时视图已经成为了响应者。
  // 一般来说这意味着一个手势操作已经成功完成。
  // 判断是否达到了触发刷新的条件
  const threshold = this.props.refreshTriggerHeight || this.props.headerHeight;
  if (this.containerTranslateY >= threshold) {
    // 触发刷新
    this.props.onRefresh();
  } else {
    // 没到刷新的位置,回退到顶部
    this._resetContainerPosition();
  }
  // 检查 scrollEnabled 开关
  this._checkScroll();
}

Остальное — как отличить скольжение контейнера от срабатывания выпадающего обновления.

Когда ScrollViewscrollEnabledКогда для свойства установлено значение false, пользователь может запретить прокрутку. Следовательно, ScrollView можно использовать в качестве контейнера содержимого. При прокрутке к верхней части контейнера закройте окно ScrollView.scrollEnabledсвойство, установив свойство Animated.ViewtranslateY, показывающий пользовательский загрузчик.

<Animated.View style={[{ flex: 1, transform: [{ translateY: this.state.containerTop }] }]}>
  {child}
</Animated.View>

expo pulltorefresh1

После проб выяснилось, что у этой программы есть следующие фатальные проблемы:

  1. Поскольку процесс выпадающего списка возвращается в исходное представление через внешний интерфейс через систему отклика на касание, большой объем данных и перерисовка страницы вызовут зависание страницы, что более очевидно, когда объем данных страницы большой;
  2. Переключение между скольжением вверх и вниз управляется свойством Enable ScrollView, что приведет к прерыванию работы жестов;
  3. Процесс раздвижного жеста не хватает функции демпфирования, а производительность не так естественна, как нативное потянутое обновление; Кроме того, есть проблема, что скольжение ScrollView и смоделированного вытягивающего процесса недостаточно совместимы.

Решение 2

ScrollView имеет функцию для устройств iOS: если диапазон содержимого больше, чем само представление прокрутки, он может эластично вытягивать раздел, когда он достигает конца содержимого. Индикатор загрузки можно разместить на верхнем краю страницы, чтобы он показывался при эластичной прокрутке. Таким образом, нет необходимости использовать жесты для воздействия на скорость рендеринга, а процессы прокрутки и раскрывающегося списка могут быть хорошо интегрированы.

Следовательно, пока события каждого этапа операции прокрутки обрабатываются хорошо.

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)
  }
}

Единственная ложка дегтя заключается в том, что iOS поддерживает скольжение за пределы контента, а Android — нет, и Android нужно адаптировать отдельно.

Поместите индикатор загрузки внутри страницы, черезscrollToМетод управляет расстоянием от верхней части страницы, чтобы имитировать раскрывающееся пространство. (решения для iOS и Android были представлены на выставке pulltorefresh2)

expo pulltorefresh2

(Демо рекомендуется для просмотра на мобильных устройствах, а для веб-адаптации можно попробоватьonScrollBeginDrag onScrollEndDragзаменитьonTouchStart onTouchEnd)

Суммировать

В этой статье в основном представлены технические исследования и процесс внедрения раскрывающегося компонента обновления в процессе разработки React Native. Демо-версия Expo содержит основную логику реализации двух решений. Читатели могут настроить ее в соответствии с потребностями своего бизнеса. Если у вас есть какие-либо вопросы, свяжитесь с нами.

Ссылка на ссылку

Эта статья была опубликована сКоманда внешнего интерфейса NetEase Cloud Music, может быть свободно воспроизведено, пожалуйста, указывайте перепечатку в названии и сохраняйте источник на видном месте. Мы всегда нанимаем, если вы готовы сменить работу и вам нравится облачная музыка, тоПрисоединяйтесь к нам!