forked from facebook/react-native
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Migrate com.facebook.react.views.text.ReactSwipeRefreshLayout to Kotl…
…in (facebook#47610) Summary: Pull Request resolved: facebook#47610 As per title. Changelog: [Internal] Reviewed By: tdn120 Differential Revision: D65661766 fbshipit-source-id: 3d56f94f90e7b70160e4ca7898aa0a49997ddb87
- Loading branch information
1 parent
184eb17
commit db3c1a4
Showing
3 changed files
with
133 additions
and
153 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
151 changes: 0 additions & 151 deletions
151
...tAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.java
This file was deleted.
Oops, something went wrong.
131 changes: 131 additions & 0 deletions
131
...actAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} |