Open source ViewPagerAndroid

Reviewed By: @foghina

Differential Revision: D2513014

fb-gh-sync-id: d9bb668d76939ad85b657233c8b7beac9b244fab
This commit is contained in:
Martin Konicek
2015-10-06 19:43:31 -07:00
committed by facebook-github-bot-4
parent 6244fd003d
commit 0a419650ce
10 changed files with 716 additions and 0 deletions

View File

@@ -34,6 +34,7 @@ import com.facebook.react.views.text.ReactVirtualTextViewManager;
import com.facebook.react.views.textinput.ReactTextInputManager;
import com.facebook.react.views.toolbar.ReactToolbarManager;
import com.facebook.react.views.view.ReactViewManager;
import com.facebook.react.views.viewpager.ReactViewPagerManager;
/**
* Package defining basic modules and view managers.
@@ -68,6 +69,7 @@ public class MainReactPackage implements ReactPackage {
new ReactTextViewManager(),
new ReactToolbarManager(),
new ReactViewManager(),
new ReactViewPagerManager(),
new ReactVirtualTextViewManager());
}
}

View File

@@ -0,0 +1,56 @@
/**
* 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.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).
*
* Additional data provided by this event:
* - position - index of first page from the left that is currently visible
* - 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.
*/
/* 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, long timestampMs, int position, float offset) {
super(viewTag, timestampMs);
mPosition = position;
mOffset = 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;
}
}

View File

@@ -0,0 +1,49 @@
/**
* 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.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.
*
* Additional data provided by this event:
* - position - index of page that has been selected
*/
/* package */ class PageSelectedEvent extends Event<PageSelectedEvent> {
public static final String EVENT_NAME = "topPageSelected";
private final int mPosition;
protected PageSelectedEvent(int viewTag, long timestampMs, int position) {
super(viewTag, timestampMs);
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;
}
}

View File

@@ -0,0 +1,132 @@
/**
* 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.viewpager;
import java.util.ArrayList;
import java.util.List;
import android.os.SystemClock;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.uimanager.events.NativeGestureUtil;
/**
* 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.
*/
/* package */ class ReactViewPager extends ViewPager {
private class Adapter extends PagerAdapter {
private List<View> mViews = new ArrayList<>();
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 CSSLayout step.
// TODO(7323049): Remove this workaround once we figure out a way to re-layout some views on
// request
setOffscreenPageLimit(mViews.size());
}
@Override
public int getCount() {
return mViews.size();
}
@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) {
View view = mViews.get(position);
container.removeView(view);
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
private class PageChangeListener implements OnPageChangeListener {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
mEventDispatcher.dispatchEvent(
new PageScrollEvent(getId(), SystemClock.uptimeMillis(), position, positionOffset));
}
@Override
public void onPageSelected(int position) {
if (!mIsCurrentItemFromJs) {
mEventDispatcher.dispatchEvent(
new PageSelectedEvent(getId(), SystemClock.uptimeMillis(), position));
}
}
@Override
public void onPageScrollStateChanged(int state) {
// don't send events
}
}
private final EventDispatcher mEventDispatcher;
private boolean mIsCurrentItemFromJs;
public ReactViewPager(ReactContext reactContext) {
super(reactContext);
mEventDispatcher = 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 (super.onInterceptTouchEvent(ev)) {
NativeGestureUtil.notifyNativeGestureStarted(this, ev);
return true;
}
return false;
}
/* package */ void addViewToAdapter(View child, int index) {
getAdapter().addView(child, index);
}
/* package */ void setCurrentItemFromJs(int item) {
mIsCurrentItemFromJs = true;
setCurrentItem(item);
mIsCurrentItemFromJs = false;
}
}

View File

@@ -0,0 +1,61 @@
/**
* 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.viewpager;
import java.util.Map;
import android.view.View;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.ReactProp;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
/**
* Instance of {@link ViewManager} that provides native {@link ViewPager} view.
*/
public class ReactViewPagerManager extends ViewGroupManager<ReactViewPager> {
private static final String REACT_CLASS = "AndroidViewPager";
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected ReactViewPager createViewInstance(ThemedReactContext reactContext) {
return new ReactViewPager(reactContext);
}
@ReactProp(name = "selectedPage")
public void setSelectedPage(ReactViewPager view, int page) {
// TODO(8496821): Handle selectedPage property cleanup correctly, now defaults to 0
view.setCurrentItemFromJs(page);
}
@Override
public boolean needsCustomLayoutForChildren() {
return true;
}
@Override
public Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of(
PageScrollEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageScroll"),
PageSelectedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageSelected")
);
}
@Override
public void addView(ReactViewPager parent, View child, int index) {
parent.addViewToAdapter(child, index);
}
}