package com.github.paolorotolo.appintro;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
import java.lang.reflect.Field;
public final class AppIntroViewPager extends ViewPager {
private static final int ON_ILLEGALLY_REQUESTED_NEXT_PAGE_MAX_INTERVAL = 1000;
private boolean pagingEnabled;
private boolean nextPagingEnabled;
private float currentTouchDownX;
private long illegallyRequestedNextPageLastCalled;
private int lockPage;
private ScrollerCustomDuration mScroller = null;
private OnNextPageRequestedListener nextPageRequestedListener;
private OnPageChangeListener pageChangeListener;
public AppIntroViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
pagingEnabled = true;
nextPagingEnabled = true;
lockPage = 0;
initViewPagerScroller();
}
public void addOnPageChangeListener(AppIntroBase.PagerOnPageChangeListener listener) {
super.addOnPageChangeListener(listener);
this.pageChangeListener = listener;
}
public void goToNextSlide() {
if (isRtl(getResources())) {
setCurrentItem(getCurrentItem() - 1);
} else {
setCurrentItem(getCurrentItem() + 1);
}
}
public void goToPreviousSlide() {
try {
if (isRtl(getResources())) {
setCurrentItem(getCurrentItem() + 1);
} else {
setCurrentItem(getCurrentItem() - 1);
}
} catch (Exception e){
Log.e("AppIntroViewPager", "goToPreviousSlide: An error occured while switching to the previous slide. Was isFirstSlide checked before the call?");
}
}
public boolean isFirstSlide(int size) {
if (isRtl(getResources())) {
return getCurrentItem() - size + 1 == 0;
} else {
return getCurrentItem() == 0;
}
}
/**
* Override is required to trigger {@link OnPageChangeListener#onPageSelected} for the first page.
* This is needed to correctly handle progress button display after rotation on a locked first page.
*/
@Override
public void setCurrentItem(int item) {
// when you pass set current item to 0,
// the pageChangeListener won't be called so we call it on our own
boolean invokeMeLater = false;
if (super.getCurrentItem() == 0 && item == 0)
invokeMeLater = true;
super.setCurrentItem(item);
if (invokeMeLater && pageChangeListener != null)
pageChangeListener.onPageSelected(0);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
currentTouchDownX = event.getX();
return super.onInterceptTouchEvent(event);
} else if (checkPagingState(event) || checkCanRequestNextPage(event)) {
// Call callback method if threshold has been reached
checkIllegallyRequestedNextPage(event);
return false;
}
return super.onInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
currentTouchDownX = event.getX();
return super.onTouchEvent(event);
}
// Check if we should handle the touch event
else if (checkPagingState(event) || checkCanRequestNextPage(event)) {
// Call callback method if threshold has been reached
checkIllegallyRequestedNextPage(event);
return false;
}
return super.onTouchEvent(event);
}
private boolean checkPagingState(MotionEvent event) {
if (!pagingEnabled) {
return true;
}
if (!nextPagingEnabled) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
currentTouchDownX = event.getX();
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (detectSwipeToEnd(event)) {
return true;
}
}
}
return false;
}
private boolean checkCanRequestNextPage(MotionEvent event) {
return nextPageRequestedListener != null && !nextPageRequestedListener.onCanRequestNextPage();
}
private void checkIllegallyRequestedNextPage(MotionEvent event) {
int swipeThreshold = 25;
if (event.getAction() == MotionEvent.ACTION_MOVE &&
Math.abs(event.getX() - currentTouchDownX) >= swipeThreshold) {
if (System.currentTimeMillis() - illegallyRequestedNextPageLastCalled >=
ON_ILLEGALLY_REQUESTED_NEXT_PAGE_MAX_INTERVAL) {
illegallyRequestedNextPageLastCalled = System.currentTimeMillis();
if (nextPageRequestedListener != null) {
nextPageRequestedListener.onIllegallyRequestedNextPage();
}
}
}
}
/**
* Override the Scroller instance with our own class so we can change the
* duration
*/
private void initViewPagerScroller() {
try {
Field scroller = ViewPager.class.getDeclaredField("mScroller");
scroller.setAccessible(true);
Field interpolator = ViewPager.class.getDeclaredField("sInterpolator");
interpolator.setAccessible(true);
mScroller = new ScrollerCustomDuration(getContext(),
(Interpolator) interpolator.get(null));
scroller.set(this, mScroller);
} catch (Exception e) {
e.printStackTrace();
}
}
// Detects the direction of swipe. Right or left.
// Returns true if swipe is in right direction
private boolean detectSwipeToEnd(MotionEvent event) {
final int SWIPE_THRESHOLD = 0; // detect swipe
boolean result = false;
try {
float diffX = event.getX() - currentTouchDownX;
if (Math.abs(diffX) > SWIPE_THRESHOLD) {
if (diffX < 0) {
// swipe from right to left detected ie.SwipeLeft
result = true;
}
}
} catch (Exception exception) {
exception.printStackTrace();
}
if (isRtl(getResources())) {
return !result;
} else {
return result;
}
}
static boolean isRtl(Resources resources) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
return resources.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
}
return false;
}
/**
* Register an instance of OnNextPageRequestedListener.
* Before the user swipes to the next page, this listener will be called and can interrupt swiping by returning false.
*
* @param nextPageRequestedListener Instance of OnNextPageRequestedListener
*/
public void setOnNextPageRequestedListener(OnNextPageRequestedListener nextPageRequestedListener) {
this.nextPageRequestedListener = nextPageRequestedListener;
}
/**
* Set the factor by which the duration will change
*/
public void setScrollDurationFactor(double scrollFactor) {
mScroller.setScrollDurationFactor(scrollFactor);
}
public boolean isNextPagingEnabled() {
return nextPagingEnabled;
}
/**
* Enable or disable swiping to the next page
*
* @param nextPagingEnabled Whether swiping to the next page should be enabled or not
*/
public void setNextPagingEnabled(boolean nextPagingEnabled) {
this.nextPagingEnabled = nextPagingEnabled;
if (!nextPagingEnabled) {
lockPage = getCurrentItem();
}
}
public boolean isPagingEnabled() {
return pagingEnabled;
}
public void setPagingEnabled(boolean pagingEnabled) {
this.pagingEnabled = pagingEnabled;
}
public int getLockPage() {
return lockPage;
}
public void setLockPage(int lockPage) {
this.lockPage = lockPage;
}
public interface OnNextPageRequestedListener {
boolean onCanRequestNextPage();
void onIllegallyRequestedNextPage();
}
}