Release React Native for Android

This is an early release and there are several things that are known
not to work if you're porting your iOS app to Android.

See the Known Issues guide on the website.

We will work with the community to reach platform parity with iOS.
This commit is contained in:
Martin Konicek
2015-09-14 15:35:58 +01:00
parent c372dab213
commit 42eb5464fd
571 changed files with 44550 additions and 116 deletions

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.views.scroll;
import android.os.SystemClock;
/**
* Android has a bug where onScrollChanged is called twice per frame with the same params during
* flings. We hack around that here by trying to detect that duplicate call and not dispatch it. See
* https://code.google.com/p/android/issues/detail?id=39473
*/
public class OnScrollDispatchHelper {
private static final int MIN_EVENT_SEPARATION_MS = 10;
private int mPrevX = Integer.MIN_VALUE;
private int mPrevY = Integer.MIN_VALUE;
private long mLastScrollEventTimeMs = -(MIN_EVENT_SEPARATION_MS + 1);
/**
* Call from a ScrollView in onScrollChanged, returns true if this onScrollChanged is legit (not a
* duplicate) and should be dispatched.
*/
public boolean onScrollChanged(int x, int y) {
long eventTime = SystemClock.uptimeMillis();
boolean shouldDispatch =
eventTime - mLastScrollEventTimeMs > MIN_EVENT_SEPARATION_MS ||
mPrevX != x ||
mPrevY != y;
mLastScrollEventTimeMs = eventTime;
mPrevX = x;
mPrevY = y;
return shouldDispatch;
}
}

View File

@@ -0,0 +1,63 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.views.scroll;
import android.content.Context;
import android.view.MotionEvent;
import android.widget.HorizontalScrollView;
import com.facebook.react.uimanager.MeasureSpecAssertions;
import com.facebook.react.uimanager.events.NativeGestureUtil;
/**
* Similar to {@link ReactScrollView} but only supports horizontal scrolling.
*/
public class ReactHorizontalScrollView extends HorizontalScrollView {
private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper();
public ReactHorizontalScrollView(Context context) {
super(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
MeasureSpecAssertions.assertExplicitMeasureSpec(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(
MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// Call with the present values in order to re-layout if necessary
scrollTo(getScrollX(), getScrollY());
}
@Override
protected void onScrollChanged(int x, int y, int oldX, int oldY) {
super.onScrollChanged(x, y, oldX, oldY);
if (mOnScrollDispatchHelper.onScrollChanged(x, y)) {
ReactScrollViewHelper.emitScrollEvent(this, x, y);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (super.onInterceptTouchEvent(ev)) {
NativeGestureUtil.notifyNativeGestureStarted(this, ev);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.views.scroll;
import javax.annotation.Nullable;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
/**
* View manager for {@link ReactHorizontalScrollView} components.
*
* <p>Note that {@link ReactScrollView} and {@link ReactHorizontalScrollView} are exposed to JS
* as a single ScrollView component, configured via the {@code horizontal} boolean property.
*/
public class ReactHorizontalScrollViewManager
extends ViewGroupManager<ReactHorizontalScrollView>
implements ReactScrollViewCommandHelper.ScrollCommandHandler<ReactHorizontalScrollView> {
private static final String REACT_CLASS = "AndroidHorizontalScrollView";
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public ReactHorizontalScrollView createViewInstance(ThemedReactContext context) {
return new ReactHorizontalScrollView(context);
}
@Override
public void receiveCommand(
ReactHorizontalScrollView scrollView,
int commandId,
@Nullable ReadableArray args) {
ReactScrollViewCommandHelper.receiveCommand(this, scrollView, commandId, args);
}
@Override
public void scrollTo(
ReactHorizontalScrollView scrollView,
ReactScrollViewCommandHelper.ScrollToCommandData data) {
scrollView.smoothScrollTo(data.mDestX, data.mDestY);
}
}

View File

@@ -0,0 +1,123 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.views.scroll;
import javax.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ScrollView;
import com.facebook.react.uimanager.MeasureSpecAssertions;
import com.facebook.react.uimanager.events.NativeGestureUtil;
import com.facebook.react.views.view.ReactClippingViewGroup;
import com.facebook.react.views.view.ReactClippingViewGroupHelper;
import com.facebook.infer.annotation.Assertions;
/**
* A simple subclass of ScrollView that doesn't dispatch measure and layout to its children and has
* a scroll listener to send scroll events to JS.
*
* <p>ReactScrollView only supports vertical scrolling. For horizontal scrolling,
* use {@link ReactHorizontalScrollView}.
*/
public class ReactScrollView extends ScrollView implements ReactClippingViewGroup {
private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper();
private boolean mRemoveClippedSubviews;
private @Nullable Rect mClippingRect;
public ReactScrollView(Context context) {
super(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
MeasureSpecAssertions.assertExplicitMeasureSpec(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(
MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// Call with the present values in order to re-layout if necessary
scrollTo(getScrollX(), getScrollY());
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mRemoveClippedSubviews) {
updateClippingRect();
}
}
@Override
protected void onScrollChanged(int x, int y, int oldX, int oldY) {
super.onScrollChanged(x, y, oldX, oldY);
if (mOnScrollDispatchHelper.onScrollChanged(x, y)) {
if (mRemoveClippedSubviews) {
updateClippingRect();
}
ReactScrollViewHelper.emitScrollEvent(this, x, y);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (super.onInterceptTouchEvent(ev)) {
NativeGestureUtil.notifyNativeGestureStarted(this, ev);
return true;
}
return false;
}
@Override
public void setRemoveClippedSubviews(boolean removeClippedSubviews) {
if (removeClippedSubviews && mClippingRect == null) {
mClippingRect = new Rect();
}
mRemoveClippedSubviews = removeClippedSubviews;
updateClippingRect();
}
@Override
public boolean getRemoveClippedSubviews() {
return mRemoveClippedSubviews;
}
@Override
public void updateClippingRect() {
if (!mRemoveClippedSubviews) {
return;
}
Assertions.assertNotNull(mClippingRect);
ReactClippingViewGroupHelper.calculateClippingRect(this, mClippingRect);
View contentView = getChildAt(0);
if (contentView instanceof ReactClippingViewGroup) {
((ReactClippingViewGroup) contentView).updateClippingRect();
}
}
@Override
public void getClippingRect(Rect outClippingRect) {
outClippingRect.set(Assertions.assertNotNull(mClippingRect));
}
}

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.views.scroll;
import javax.annotation.Nullable;
import java.util.Map;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.common.MapBuilder;
/**
* Helper for view managers to handle commands like 'scrollTo'.
* Shared by {@link ReactScrollViewManager} and {@link ReactHorizontalScrollViewManager}.
*/
public class ReactScrollViewCommandHelper {
public static final int COMMAND_SCROLL_TO = 1;
public interface ScrollCommandHandler<T> {
void scrollTo(T scrollView, ScrollToCommandData data);
}
public static class ScrollToCommandData {
public final int mDestX, mDestY;
ScrollToCommandData(int destX, int destY) {
mDestX = destX;
mDestY = destY;
}
}
public static Map<String,Integer> getCommandsMap() {
return MapBuilder.of("scrollTo", COMMAND_SCROLL_TO);
}
public static <T> void receiveCommand(
ScrollCommandHandler<T> viewManager,
T scrollView,
int commandType,
@Nullable ReadableArray args) {
Assertions.assertNotNull(viewManager);
Assertions.assertNotNull(scrollView);
Assertions.assertNotNull(args);
switch (commandType) {
case COMMAND_SCROLL_TO:
int destX = Math.round(PixelUtil.toPixelFromDIP(args.getInt(0)));
int destY = Math.round(PixelUtil.toPixelFromDIP(args.getInt(1)));
viewManager.scrollTo(scrollView, new ScrollToCommandData(destX, destY));
return;
default:
throw new IllegalArgumentException(String.format(
"Unsupported command %d received by %s.",
commandType,
viewManager.getClass().getSimpleName()));
}
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.views.scroll;
import android.os.SystemClock;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.uimanager.UIManagerModule;
/**
* Helper class that deals with emitting Scroll Events.
*/
public class ReactScrollViewHelper {
/**
* Shared by {@link ReactScrollView} and {@link ReactHorizontalScrollView}.
*/
/* package */ static void emitScrollEvent(ViewGroup scrollView, int scrollX, int scrollY) {
View contentView = scrollView.getChildAt(0);
ReactContext reactContext = (ReactContext) scrollView.getContext();
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(
new ScrollEvent(
scrollView.getId(),
SystemClock.uptimeMillis(),
scrollX,
scrollY,
contentView.getWidth(),
contentView.getHeight(),
scrollView.getWidth(),
scrollView.getHeight()));
}
}

View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.views.scroll;
import javax.annotation.Nullable;
import java.util.Map;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.CatalystStylesDiffMap;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.UIProp;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.views.view.ReactClippingViewGroupHelper;
/**
* View manager for {@link ReactScrollView} components.
*
* <p>Note that {@link ReactScrollView} and {@link ReactHorizontalScrollView} are exposed to JS
* as a single ScrollView component, configured via the {@code horizontal} boolean property.
*/
public class ReactScrollViewManager
extends ViewGroupManager<ReactScrollView>
implements ReactScrollViewCommandHelper.ScrollCommandHandler<ReactScrollView> {
private static final String REACT_CLASS = "RCTScrollView";
@UIProp(UIProp.Type.BOOLEAN) public static final String PROP_SHOWS_VERTICAL_SCROLL_INDICATOR =
"showsVerticalScrollIndicator";
@UIProp(UIProp.Type.BOOLEAN) public static final String PROP_SHOWS_HORIZONTAL_SCROLL_INDICATOR =
"showsHorizontalScrollIndicator";
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public ReactScrollView createViewInstance(ThemedReactContext context) {
return new ReactScrollView(context);
}
@Override
public void updateView(ReactScrollView scrollView, CatalystStylesDiffMap props) {
super.updateView(scrollView, props);
if (props.hasKey(PROP_SHOWS_VERTICAL_SCROLL_INDICATOR)) {
scrollView.setVerticalScrollBarEnabled(
props.getBoolean(PROP_SHOWS_VERTICAL_SCROLL_INDICATOR, true));
}
if (props.hasKey(PROP_SHOWS_HORIZONTAL_SCROLL_INDICATOR)) {
scrollView.setHorizontalScrollBarEnabled(
props.getBoolean(PROP_SHOWS_HORIZONTAL_SCROLL_INDICATOR, true));
}
ReactClippingViewGroupHelper.applyRemoveClippedSubviewsProperty(scrollView, props);
}
@Override
public @Nullable Map<String, Integer> getCommandsMap() {
return ReactScrollViewCommandHelper.getCommandsMap();
}
@Override
public void receiveCommand(
ReactScrollView scrollView,
int commandId,
@Nullable ReadableArray args) {
ReactScrollViewCommandHelper.receiveCommand(this, scrollView, commandId, args);
}
@Override
public void scrollTo(
ReactScrollView scrollView,
ReactScrollViewCommandHelper.ScrollToCommandData data) {
scrollView.smoothScrollTo(data.mDestX, data.mDestY);
}
@Override
public @Nullable Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.builder()
.put(ScrollEvent.EVENT_NAME, MapBuilder.of("registrationName", "onScroll"))
.put("topScrollBeginDrag", MapBuilder.of("registrationName", "onScrollBeginDrag"))
.put("topScrollEndDrag", MapBuilder.of("registrationName", "onScrollEndDrag"))
.put("topScrollAnimationEnd", MapBuilder.of("registrationName", "onScrollAnimationEnd"))
.put("topMomentumScrollBegin", MapBuilder.of("registrationName", "onMomentumScrollBegin"))
.put("topMomentumScrollEnd", MapBuilder.of("registrationName", "onMomentumScrollEnd"))
.build();
}
}

View File

@@ -0,0 +1,88 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.views.scroll;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;
/**
* A event dispatched from a ScrollView scrolling.
*/
public class ScrollEvent extends Event<ScrollEvent> {
public static final String EVENT_NAME = "topScroll";
private final int mScrollX;
private final int mScrollY;
private final int mContentWidth;
private final int mContentHeight;
private final int mScrollViewWidth;
private final int mScrollViewHeight;
public ScrollEvent(
int viewTag,
long timestampMs,
int scrollX,
int scrollY,
int contentWidth,
int contentHeight,
int scrollViewWidth,
int scrollViewHeight) {
super(viewTag, timestampMs);
mScrollX = scrollX;
mScrollY = scrollY;
mContentWidth = contentWidth;
mContentHeight = contentHeight;
mScrollViewWidth = scrollViewWidth;
mScrollViewHeight = scrollViewHeight;
}
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public short getCoalescingKey() {
// All scroll events for a given view can be coalesced
return 0;
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
}
private WritableMap serializeEventData() {
WritableMap contentOffset = Arguments.createMap();
contentOffset.putDouble("x", PixelUtil.toDIPFromPixel(mScrollX));
contentOffset.putDouble("y", PixelUtil.toDIPFromPixel(mScrollY));
WritableMap contentSize = Arguments.createMap();
contentSize.putDouble("width", PixelUtil.toDIPFromPixel(mContentWidth));
contentSize.putDouble("height", PixelUtil.toDIPFromPixel(mContentHeight));
WritableMap layoutMeasurement = Arguments.createMap();
layoutMeasurement.putDouble("width", PixelUtil.toDIPFromPixel(mScrollViewWidth));
layoutMeasurement.putDouble("height", PixelUtil.toDIPFromPixel(mScrollViewHeight));
WritableMap event = Arguments.createMap();
event.putMap("contentOffset", contentOffset);
event.putMap("contentSize", contentSize);
event.putMap("layoutMeasurement", layoutMeasurement);
event.putInt("target", getViewTag());
event.putBoolean("responderIgnoreScroll", true);
return event;
}
}