/**
* 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;
}
}