mirror of
https://github.com/facebook/react-native.git
synced 2024-11-21 22:10:14 +00:00
Migrate com.facebook.react.views.text.ReactSwipeRefreshLayout to Kotlin (#47610)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/47610 As per title. Changelog: [Internal] Reviewed By: tdn120 Differential Revision: D65661766 fbshipit-source-id: 3d56f94f90e7b70160e4ca7898aa0a49997ddb87
This commit is contained in:
parent
184eb17261
commit
db3c1a47d5
@ -7113,14 +7113,14 @@ public final class com/facebook/react/views/scroll/ScrollEventType$Companion {
|
||||
public final fun getJSEventName (Lcom/facebook/react/views/scroll/ScrollEventType;)Ljava/lang/String;
|
||||
}
|
||||
|
||||
public class com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout : androidx/swiperefreshlayout/widget/SwipeRefreshLayout {
|
||||
public final class com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout : androidx/swiperefreshlayout/widget/SwipeRefreshLayout {
|
||||
public fun <init> (Lcom/facebook/react/bridge/ReactContext;)V
|
||||
public fun canChildScrollUp ()Z
|
||||
public fun onInterceptTouchEvent (Landroid/view/MotionEvent;)Z
|
||||
public fun onLayout (ZIIII)V
|
||||
public fun onTouchEvent (Landroid/view/MotionEvent;)Z
|
||||
public fun requestDisallowInterceptTouchEvent (Z)V
|
||||
public fun setProgressViewOffset (F)V
|
||||
public final fun setProgressViewOffset (F)V
|
||||
public fun setRefreshing (Z)V
|
||||
}
|
||||
|
||||
|
@ -1,151 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.swiperefresh;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import com.facebook.infer.annotation.Nullsafe;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.events.NativeGestureUtil;
|
||||
|
||||
/** Basic extension of {@link SwipeRefreshLayout} with ReactNative-specific functionality. */
|
||||
@Nullsafe(Nullsafe.Mode.LOCAL)
|
||||
public class ReactSwipeRefreshLayout extends SwipeRefreshLayout {
|
||||
|
||||
private static final float DEFAULT_CIRCLE_TARGET = 64;
|
||||
|
||||
private boolean mDidLayout = false;
|
||||
private boolean mRefreshing = false;
|
||||
private float mProgressViewOffset = 0;
|
||||
private int mTouchSlop;
|
||||
private float mPrevTouchX;
|
||||
private boolean mIntercepted;
|
||||
private boolean mNativeGestureStarted = false;
|
||||
|
||||
public ReactSwipeRefreshLayout(ReactContext reactContext) {
|
||||
super(reactContext);
|
||||
mTouchSlop = ViewConfiguration.get(reactContext).getScaledTouchSlop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRefreshing(boolean refreshing) {
|
||||
mRefreshing = refreshing;
|
||||
|
||||
// `setRefreshing` must be called after the initial layout otherwise it
|
||||
// doesn't work when mounting the component with `refreshing = true`.
|
||||
// Known Android issue: https://code.google.com/p/android/issues/detail?id=77712
|
||||
if (mDidLayout) {
|
||||
super.setRefreshing(refreshing);
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgressViewOffset(float offset) {
|
||||
mProgressViewOffset = offset;
|
||||
|
||||
// The view must be measured before calling `getProgressCircleDiameter` so
|
||||
// don't do it before the initial layout.
|
||||
if (mDidLayout) {
|
||||
int diameter = getProgressCircleDiameter();
|
||||
int start = Math.round(PixelUtil.toPixelFromDIP(offset)) - diameter;
|
||||
int end = Math.round(PixelUtil.toPixelFromDIP(offset + DEFAULT_CIRCLE_TARGET) - diameter);
|
||||
setProgressViewOffset(false, start, end);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
|
||||
if (!mDidLayout) {
|
||||
mDidLayout = true;
|
||||
|
||||
// Update values that must be set after initial layout.
|
||||
setProgressViewOffset(mProgressViewOffset);
|
||||
setRefreshing(mRefreshing);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canChildScrollUp() {
|
||||
View firstChild = getChildAt(0);
|
||||
if (firstChild != null) {
|
||||
return firstChild.canScrollVertically(-1);
|
||||
} else {
|
||||
return super.canChildScrollUp();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link SwipeRefreshLayout} overrides {@link ViewGroup#requestDisallowInterceptTouchEvent} and
|
||||
* swallows it. This means that any component underneath SwipeRefreshLayout will now interact
|
||||
* incorrectly with Views that are above SwipeRefreshLayout. We fix that by transmitting the call
|
||||
* to this View's parents.
|
||||
*/
|
||||
@Override
|
||||
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
||||
if (getParent() != null) {
|
||||
getParent().requestDisallowInterceptTouchEvent(disallowIntercept);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
if (shouldInterceptTouchEvent(ev) && super.onInterceptTouchEvent(ev)) {
|
||||
NativeGestureUtil.notifyNativeGestureStarted(this, ev);
|
||||
mNativeGestureStarted = true;
|
||||
|
||||
// If the pull-to-refresh gesture is interrupted by a parent with its own
|
||||
// onInterceptTouchEvent then the refresh indicator gets stuck on-screen
|
||||
// so we ask the parent to not intercept this touch event after it started
|
||||
if (getParent() != null) {
|
||||
getParent().requestDisallowInterceptTouchEvent(true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
int action = ev.getActionMasked();
|
||||
if (action == MotionEvent.ACTION_UP && mNativeGestureStarted) {
|
||||
NativeGestureUtil.notifyNativeGestureEnded(this, ev);
|
||||
mNativeGestureStarted = false;
|
||||
}
|
||||
return super.onTouchEvent(ev);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link SwipeRefreshLayout} completely bypasses ViewGroup's "disallowIntercept" by overriding
|
||||
* {@link ViewGroup#onInterceptTouchEvent} and never calling super.onInterceptTouchEvent(). This
|
||||
* means that horizontal scrolls will always be intercepted, even though they shouldn't, so we
|
||||
* have to check for that manually here.
|
||||
*/
|
||||
private boolean shouldInterceptTouchEvent(MotionEvent ev) {
|
||||
switch (ev.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mPrevTouchX = ev.getX();
|
||||
mIntercepted = false;
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
final float eventX = ev.getX();
|
||||
final float xDiff = Math.abs(eventX - mPrevTouchX);
|
||||
|
||||
if (mIntercepted || xDiff > mTouchSlop) {
|
||||
mIntercepted = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.swiperefresh
|
||||
|
||||
import android.view.MotionEvent
|
||||
import android.view.ViewConfiguration
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.facebook.react.bridge.ReactContext
|
||||
import com.facebook.react.uimanager.PixelUtil
|
||||
import com.facebook.react.uimanager.events.NativeGestureUtil
|
||||
|
||||
/** Basic extension of [SwipeRefreshLayout] with ReactNative-specific functionality. */
|
||||
public class ReactSwipeRefreshLayout(reactContext: ReactContext) :
|
||||
SwipeRefreshLayout(reactContext) {
|
||||
|
||||
private var didLayout: Boolean = false
|
||||
private var refreshing: Boolean = false
|
||||
private var progressViewOffset: Float = 0f
|
||||
private val touchSlop: Int = ViewConfiguration.get(reactContext).scaledTouchSlop
|
||||
private var prevTouchX: Float = 0f
|
||||
private var intercepted: Boolean = false
|
||||
private var nativeGestureStarted: Boolean = false
|
||||
|
||||
public override fun setRefreshing(refreshing: Boolean) {
|
||||
this.refreshing = refreshing
|
||||
|
||||
// `setRefreshing` must be called after the initial layout otherwise it
|
||||
// doesn't work when mounting the component with `refreshing = true`.
|
||||
// Known Android issue: https://code.google.com/p/android/issues/detail?id=77712
|
||||
if (didLayout) {
|
||||
super.setRefreshing(refreshing)
|
||||
}
|
||||
}
|
||||
|
||||
public fun setProgressViewOffset(offset: Float) {
|
||||
progressViewOffset = offset
|
||||
|
||||
// The view must be measured before calling `getProgressCircleDiameter` so
|
||||
// don't do it before the initial layout.
|
||||
if (didLayout) {
|
||||
val diameter = progressCircleDiameter
|
||||
val start = Math.round(PixelUtil.toPixelFromDIP(offset)) - diameter
|
||||
val end = Math.round(PixelUtil.toPixelFromDIP(offset + DEFAULT_CIRCLE_TARGET)) - diameter
|
||||
setProgressViewOffset(false, start, end)
|
||||
}
|
||||
}
|
||||
|
||||
public override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||
super.onLayout(changed, left, top, right, bottom)
|
||||
|
||||
if (!didLayout) {
|
||||
didLayout = true
|
||||
|
||||
// Update values that must be set after initial layout.
|
||||
setProgressViewOffset(progressViewOffset)
|
||||
setRefreshing(refreshing)
|
||||
}
|
||||
}
|
||||
|
||||
public override fun canChildScrollUp(): Boolean {
|
||||
val firstChild = getChildAt(0)
|
||||
return firstChild?.canScrollVertically(-1) ?: super.canChildScrollUp()
|
||||
}
|
||||
|
||||
/**
|
||||
* [SwipeRefreshLayout] overrides [ViewGroup.requestDisallowInterceptTouchEvent] and swallows it.
|
||||
* This means that any component underneath SwipeRefreshLayout will now interact incorrectly with
|
||||
* Views that are above SwipeRefreshLayout. We fix that by transmitting the call to this View's
|
||||
* parents.
|
||||
*/
|
||||
public override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
|
||||
parent?.requestDisallowInterceptTouchEvent(disallowIntercept)
|
||||
}
|
||||
|
||||
public override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
|
||||
if (shouldInterceptTouchEvent(ev) && super.onInterceptTouchEvent(ev)) {
|
||||
NativeGestureUtil.notifyNativeGestureStarted(this, ev)
|
||||
nativeGestureStarted = true
|
||||
|
||||
// If the pull-to-refresh gesture is interrupted by a parent with its own
|
||||
// onInterceptTouchEvent then the refresh indicator gets stuck on-screen
|
||||
// so we ask the parent to not intercept this touch event after it started
|
||||
parent?.requestDisallowInterceptTouchEvent(true)
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public override fun onTouchEvent(ev: MotionEvent): Boolean {
|
||||
if (ev.actionMasked == MotionEvent.ACTION_UP && nativeGestureStarted) {
|
||||
NativeGestureUtil.notifyNativeGestureEnded(this, ev)
|
||||
nativeGestureStarted = false
|
||||
}
|
||||
return super.onTouchEvent(ev)
|
||||
}
|
||||
|
||||
/**
|
||||
* [SwipeRefreshLayout] completely bypasses ViewGroup's "disallowIntercept" by overriding
|
||||
* [ViewGroup.onInterceptTouchEvent] and never calling super.onInterceptTouchEvent(). This means
|
||||
* that horizontal scrolls will always be intercepted, even though they shouldn't, so we have to
|
||||
* check for that manually here.
|
||||
*/
|
||||
private fun shouldInterceptTouchEvent(ev: MotionEvent): Boolean {
|
||||
when (ev.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
prevTouchX = ev.x
|
||||
intercepted = false
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
val eventX = ev.x
|
||||
val xDiff = Math.abs(eventX - prevTouchX)
|
||||
|
||||
if (intercepted || xDiff > touchSlop) {
|
||||
intercepted = true
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val DEFAULT_CIRCLE_TARGET = 64f
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user