Delete ReactViewPager

Summary:
All ReactViewPager functionality should now be possible by using a ScrollView.

Changelog: [Internal] ReactViewPager has been deprecated on Android and was removed from core as part of LeanCore

Reviewed By: mdvacca

Differential Revision: D21751774

fbshipit-source-id: 292475b9ffe7788b745329d13fd88dc3b613819e
This commit is contained in:
Joshua Gross
2020-05-28 19:04:55 -07:00
committed by Facebook GitHub Bot
parent 3148d1746e
commit f438a6e8f6
7 changed files with 0 additions and 629 deletions
@@ -64,7 +64,6 @@ rn_android_library(
react_native_target("java/com/facebook/react/views/text/frescosupport:frescosupport"),
react_native_target("java/com/facebook/react/views/textinput:textinput"),
react_native_target("java/com/facebook/react/views/view:view"),
react_native_target("java/com/facebook/react/views/viewpager:viewpager"),
react_native_target("java/com/facebook/react/module/annotations:annotations"),
react_native_target("res:shell"),
],
@@ -1,31 +0,0 @@
load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_target", "rn_android_library")
rn_android_library(
name = "viewpager",
srcs = glob(["**/*.java"]),
is_androidx = True,
labels = ["supermodule:xplat/default/public.react_native.infra"],
provided_deps = [
react_native_dep("third-party/android/androidx:annotation"),
react_native_dep("third-party/android/androidx:core"),
react_native_dep("third-party/android/androidx:fragment"),
react_native_dep("third-party/android/androidx:legacy-support-core-ui"),
react_native_dep("third-party/android/androidx:legacy-support-core-utils"),
],
required_for_source_only_abi = True,
visibility = [
"PUBLIC",
],
deps = [
react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"),
react_native_dep("third-party/java/infer-annotations:infer-annotations"),
react_native_dep("third-party/java/jsr-305:jsr-305"),
react_native_target("java/com/facebook/react/bridge:bridge"),
react_native_target("java/com/facebook/react/common:common"),
react_native_target("java/com/facebook/react/module/annotations:annotations"),
react_native_target("java/com/facebook/react/uimanager:uimanager"),
react_native_target("java/com/facebook/react/uimanager/annotations:annotations"),
react_native_target("java/com/facebook/react/views/scroll:scroll"),
react_native_target("java/com/facebook/react/viewmanagers:viewmanagers"),
],
)
@@ -1,59 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its 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.viewpager;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;
/**
* Event emitted by {@link ReactViewPager} when user scrolls between pages (or when animating
* between pages).
*
* <p>Additional data provided by this event:
*
* <ul>
* <li>position - index of first page from the left that is currently visible
* <li>offset - value from range [0,1) describing stage between page transitions. Value x means
* that (1 - x) fraction of the page at "position" index is visible, and x fraction of the
* next page is visible.
* </ul>
*/
/* package */ class PageScrollEvent extends Event<PageScrollEvent> {
public static final String EVENT_NAME = "topPageScroll";
private final int mPosition;
private final float mOffset;
protected PageScrollEvent(int viewTag, int position, float offset) {
super(viewTag);
mPosition = position;
// folly::toJson default options don't support serialize NaN or Infinite value
mOffset = (Float.isInfinite(offset) || Float.isNaN(offset)) ? 0.0f : offset;
}
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
}
private WritableMap serializeEventData() {
WritableMap eventData = Arguments.createMap();
eventData.putInt("position", mPosition);
eventData.putDouble("offset", mOffset);
return eventData;
}
}
@@ -1,50 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its 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.viewpager;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;
/**
* Event emitted by {@link ReactViewPager} when user scrolling state changed.
*
* <p>Additional data provided by this event:
*
* <ul>
* <li>pageScrollState - {Idle,Dragging,Settling}
* </ul>
*/
class PageScrollStateChangedEvent extends Event<PageScrollStateChangedEvent> {
public static final String EVENT_NAME = "topPageScrollStateChanged";
private final String mPageScrollState;
protected PageScrollStateChangedEvent(int viewTag, String pageScrollState) {
super(viewTag);
mPageScrollState = pageScrollState;
}
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
}
private WritableMap serializeEventData() {
WritableMap eventData = Arguments.createMap();
eventData.putString("pageScrollState", mPageScrollState);
return eventData;
}
}
@@ -1,50 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its 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.viewpager;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;
/**
* Event emitted by {@link ReactViewPager} when selected page changes.
*
* <p>Additional data provided by this event:
*
* <ul>
* <li>position - index of page that has been selected
* </ul>
*/
/* package */ class PageSelectedEvent extends Event<PageSelectedEvent> {
public static final String EVENT_NAME = "topPageSelected";
private final int mPosition;
protected PageSelectedEvent(int viewTag, int position) {
super(viewTag);
mPosition = position;
}
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
}
private WritableMap serializeEventData() {
WritableMap eventData = Arguments.createMap();
eventData.putInt("position", mPosition);
return eventData;
}
}
@@ -1,263 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its 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.viewpager;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.uimanager.events.NativeGestureUtil;
import java.util.ArrayList;
import java.util.List;
/**
* Wrapper view for {@link ViewPager}. It's forwarding calls to {@link ViewGroup#addView} to add
* views to custom {@link PagerAdapter} instance which is used by {@link NativeViewHierarchyManager}
* to add children nodes according to react views hierarchy.
*/
public class ReactViewPager extends ViewPager {
private class Adapter extends PagerAdapter {
private final List<View> mViews = new ArrayList<>();
private boolean mIsViewPagerInIntentionallyInconsistentState = false;
void addView(View child, int index) {
mViews.add(index, child);
notifyDataSetChanged();
// This will prevent view pager from detaching views for pages that are not currently selected
// We need to do that since {@link ViewPager} relies on layout passes to position those views
// in a right way (also thanks to {@link ReactViewPagerManager#needsCustomLayoutForChildren}
// returning {@code true}). Currently we only call {@link View#measure} and
// {@link View#layout} after yoga step.
// TODO(7323049): Remove this workaround once we figure out a way to re-layout some views on
// request
setOffscreenPageLimit(mViews.size());
}
void removeViewAt(int index) {
mViews.remove(index);
notifyDataSetChanged();
// TODO(7323049): Remove this workaround once we figure out a way to re-layout some views on
// request
setOffscreenPageLimit(mViews.size());
}
/** Replace a set of views to the ViewPager adapter and update the ViewPager */
void setViews(List<View> views) {
mViews.clear();
mViews.addAll(views);
notifyDataSetChanged();
// we want to make sure we return POSITION_NONE for every view here, since this is only
// called after a removeAllViewsFromAdapter
mIsViewPagerInIntentionallyInconsistentState = false;
}
/**
* Remove all the views from the adapter and de-parents them from the ViewPager After calling
* this, it is expected that notifyDataSetChanged should be called soon afterwards.
*/
void removeAllViewsFromAdapter(ViewPager pager) {
mViews.clear();
pager.removeAllViews();
// set this, so that when the next addViews is called, we return POSITION_NONE for every
// entry so we can remove whichever views we need to and add the ones that we need to.
mIsViewPagerInIntentionallyInconsistentState = true;
}
View getViewAt(int index) {
return mViews.get(index);
}
@Override
public int getCount() {
return mViews.size();
}
@Override
public int getItemPosition(Object object) {
// if we've removed all views, we want to return POSITION_NONE intentionally
return mIsViewPagerInIntentionallyInconsistentState || !mViews.contains(object)
? POSITION_NONE
: mViews.indexOf(object);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
View view = mViews.get(position);
container.addView(view, 0, generateDefaultLayoutParams());
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
private class PageChangeListener implements ViewPager.OnPageChangeListener {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
mEventDispatcher.dispatchEvent(new PageScrollEvent(getId(), position, positionOffset));
}
@Override
public void onPageSelected(int position) {
if (!mIsCurrentItemFromJs) {
mEventDispatcher.dispatchEvent(new PageSelectedEvent(getId(), position));
}
}
@Override
public void onPageScrollStateChanged(int state) {
String pageScrollState;
switch (state) {
case SCROLL_STATE_IDLE:
pageScrollState = "idle";
break;
case SCROLL_STATE_DRAGGING:
pageScrollState = "dragging";
break;
case SCROLL_STATE_SETTLING:
pageScrollState = "settling";
break;
default:
throw new IllegalStateException("Unsupported pageScrollState");
}
mEventDispatcher.dispatchEvent(new PageScrollStateChangedEvent(getId(), pageScrollState));
}
}
private final EventDispatcher mEventDispatcher;
private boolean mIsCurrentItemFromJs;
private boolean mScrollEnabled = true;
public ReactViewPager(ReactContext reactContext) {
super(reactContext);
mEventDispatcher =
Assertions.assertNotNull(reactContext.getNativeModule(UIManagerModule.class))
.getEventDispatcher();
mIsCurrentItemFromJs = false;
setOnPageChangeListener(new PageChangeListener());
setAdapter(new Adapter());
}
@Override
public Adapter getAdapter() {
return (Adapter) super.getAdapter();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (!mScrollEnabled) {
return false;
}
try {
if (super.onInterceptTouchEvent(ev)) {
NativeGestureUtil.notifyNativeGestureStarted(this, ev);
return true;
}
} catch (IllegalArgumentException e) {
// Log and ignore the error. This seems to be a bug in the android SDK and
// this is the commonly accepted workaround.
// https://tinyurl.com/mw6qkod (Stack Overflow)
FLog.w(ReactConstants.TAG, "Error intercepting touch event.", e);
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (!mScrollEnabled) {
return false;
}
try {
return super.onTouchEvent(ev);
} catch (IllegalArgumentException e) {
// Log and ignore the error. This seems to be a bug in the android SDK and
// this is the commonly accepted workaround.
// https://fburl.com/5d3iw7d9
FLog.w(ReactConstants.TAG, "Error handling touch event.", e);
}
return false;
}
public void setCurrentItemFromJs(int item, boolean animated) {
mIsCurrentItemFromJs = true;
setCurrentItem(item, animated);
mIsCurrentItemFromJs = false;
}
public void setScrollEnabled(boolean scrollEnabled) {
mScrollEnabled = scrollEnabled;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
// The viewpager reset an internal flag on this method so we need to run another layout pass
// after attaching to window.
this.requestLayout();
post(measureAndLayout);
}
private final Runnable measureAndLayout =
new Runnable() {
@Override
public void run() {
measure(
MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
layout(getLeft(), getTop(), getRight(), getBottom());
}
};
/*package*/ void addViewToAdapter(View child, int index) {
getAdapter().addView(child, index);
}
/*package*/ void removeViewFromAdapter(int index) {
getAdapter().removeViewAt(index);
}
/*package*/ int getViewCountInAdapter() {
return getAdapter().getCount();
}
/*package*/ View getViewFromAdapter(int index) {
return getAdapter().getViewAt(index);
}
public void setViews(List<View> views) {
getAdapter().setViews(views);
}
public void removeAllViewsFromAdapter() {
getAdapter().removeAllViewsFromAdapter(this);
}
}
@@ -1,175 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its 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.viewpager;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.viewmanagers.AndroidViewPagerManagerDelegate;
import com.facebook.react.viewmanagers.AndroidViewPagerManagerInterface;
import java.util.Map;
/** Instance of {@link ViewManager} that provides native {@link ViewPager} view. */
@ReactModule(name = ReactViewPagerManager.REACT_CLASS)
public class ReactViewPagerManager extends ViewGroupManager<ReactViewPager>
implements AndroidViewPagerManagerInterface<ReactViewPager> {
public static final String REACT_CLASS = "AndroidViewPager";
public static final int COMMAND_SET_PAGE = 1;
public static final int COMMAND_SET_PAGE_WITHOUT_ANIMATION = 2;
private final ViewManagerDelegate<ReactViewPager> mDelegate;
public ReactViewPagerManager() {
mDelegate = new AndroidViewPagerManagerDelegate<>(this);
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected ReactViewPager createViewInstance(ThemedReactContext reactContext) {
return new ReactViewPager(reactContext);
}
@Override
@ReactProp(name = "scrollEnabled", defaultBoolean = true)
public void setScrollEnabled(ReactViewPager viewPager, boolean value) {
viewPager.setScrollEnabled(value);
}
@Override
public boolean needsCustomLayoutForChildren() {
return true;
}
@Override
public Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of(
PageScrollEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageScroll"),
PageScrollStateChangedEvent.EVENT_NAME,
MapBuilder.of("registrationName", "onPageScrollStateChanged"),
PageSelectedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageSelected"));
}
@Override
public Map<String, Integer> getCommandsMap() {
return MapBuilder.of(
"setPage", COMMAND_SET_PAGE, "setPageWithoutAnimation", COMMAND_SET_PAGE_WITHOUT_ANIMATION);
}
@Override
public void receiveCommand(
ReactViewPager viewPager, int commandType, @Nullable ReadableArray args) {
Assertions.assertNotNull(viewPager);
Assertions.assertNotNull(args);
switch (commandType) {
case COMMAND_SET_PAGE:
{
viewPager.setCurrentItemFromJs(args.getInt(0), true);
return;
}
case COMMAND_SET_PAGE_WITHOUT_ANIMATION:
{
viewPager.setCurrentItemFromJs(args.getInt(0), false);
return;
}
default:
throw new IllegalArgumentException(
String.format(
"Unsupported command %d received by %s.", commandType, getClass().getSimpleName()));
}
}
@Override
public void receiveCommand(
ReactViewPager viewPager, String commandType, @Nullable ReadableArray args) {
Assertions.assertNotNull(viewPager);
Assertions.assertNotNull(args);
switch (commandType) {
case "setPage":
{
viewPager.setCurrentItemFromJs(args.getInt(0), true);
return;
}
case "setPageWithoutAnimation":
{
viewPager.setCurrentItemFromJs(args.getInt(0), false);
return;
}
default:
throw new IllegalArgumentException(
String.format(
"Unsupported command %d received by %s.", commandType, getClass().getSimpleName()));
}
}
@Override
public void addView(ReactViewPager parent, View child, int index) {
parent.addViewToAdapter(child, index);
}
@Override
public int getChildCount(ReactViewPager parent) {
return parent.getViewCountInAdapter();
}
@Override
public View getChildAt(ReactViewPager parent, int index) {
return parent.getViewFromAdapter(index);
}
@Override
public void removeViewAt(ReactViewPager parent, int index) {
parent.removeViewFromAdapter(index);
}
@Override
@ReactProp(name = "pageMargin", defaultInt = 0)
public void setPageMargin(ReactViewPager pager, int margin) {
pager.setPageMargin((int) PixelUtil.toPixelFromDIP(margin));
}
@Override
@ReactProp(name = "peekEnabled", defaultBoolean = false)
public void setPeekEnabled(ReactViewPager pager, boolean peekEnabled) {
pager.setClipToPadding(!peekEnabled);
}
@Override
public void setInitialPage(ReactViewPager view, int value) {}
@Override
public void setKeyboardDismissMode(ReactViewPager view, @Nullable String value) {}
@Override
public void setPage(ReactViewPager view, int page) {
view.setCurrentItemFromJs(page, true);
}
@Override
public void setPageWithoutAnimation(ReactViewPager view, int page) {
view.setCurrentItemFromJs(page, false);
}
@Override
public ViewManagerDelegate<ReactViewPager> getDelegate() {
return mDelegate;
}
}