diff --git a/android/res/values/attrs.xml b/android/res/values/attrs.xml index ffd4f5d6c5..c101e936aa 100644 --- a/android/res/values/attrs.xml +++ b/android/res/values/attrs.xml @@ -93,4 +93,9 @@ + + + + + diff --git a/android/src/com/mapswithme/maps/widget/ParallaxBackgroundPageListener.java b/android/src/com/mapswithme/maps/widget/ParallaxBackgroundPageListener.java new file mode 100755 index 0000000000..558477a7ec --- /dev/null +++ b/android/src/com/mapswithme/maps/widget/ParallaxBackgroundPageListener.java @@ -0,0 +1,174 @@ +package com.mapswithme.maps.widget; + +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.view.ViewPager; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.ImageView; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; + +public class ParallaxBackgroundPageListener implements ViewPager.OnPageChangeListener +{ + + private static final String ANDROID_SWITCHER_TAG_SEGMENT = "android:switcher:"; + private static final String SEPARATOR_TAG_SEGMENT = ":"; + private static final float POSITION_OFFSET_BASE = 0.5f; + private static final int POSITION_OFFSET_PIXELS_BASE = 1; + private static final float ALPHA_TRANSPARENT = 0.0F; + private static final float ALPHA_OPAQUE = 1.0f; + private static final int MINUS_INFINITY_EDGE = -1; + private static final int PLUS_INFINITY_EDGE = 1; + private static final float SETTLED_PAGE_POSITION = 0.0f; + + @NonNull + private final ViewPager mPager; + @NonNull + private final FragmentPagerAdapter mAdapter; + @NonNull + private final AppCompatActivity mActivity; + @NonNull + private final List mItems; + + private int mCurrentPagePosition; + private boolean mIsScrollToRight = true; + private boolean mIsScrollStarted; + private boolean mShouldCalculateScrollDirection; + + public ParallaxBackgroundPageListener(@NonNull AppCompatActivity activity, + @NonNull ViewPager pager, + @NonNull FragmentPagerAdapter adapter, + @NonNull List items) + { + mPager = pager; + mAdapter = adapter; + mActivity = activity; + mItems = items; + } + + @Override + public void onPageScrollStateChanged(int state) + { + if (state == ViewPager.SCROLL_STATE_IDLE) + { + mCurrentPagePosition = mPager.getCurrentItem(); + mIsScrollToRight = true; + } + + boolean isDragScroll = isDragScroll(state); + + mIsScrollStarted = isDragScroll; + if (isDragScroll) + mShouldCalculateScrollDirection = true; + } + + @Override + public void onPageSelected(int position) + { + if (position == 0) + onPageScrollStateChanged(ViewPager.SCROLL_STATE_IDLE); + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) + { + recalculateScrollDirection(positionOffset, positionOffsetPixels); + + int scrollX = mPager.getScrollX(); + if (canScrollToLeft(scrollX) || isRightEdge(scrollX)) + return; + + int animatedItemIndex = mIsScrollToRight ? mCurrentPagePosition : mCurrentPagePosition - 1; + setAlpha(animatedItemIndex, scrollX); + + if (isLeftEdge(scrollX)) + restoreInitialAlphaValues(); + } + + private boolean isDragScroll(int state) + { + return !mIsScrollStarted && state == ViewPager.SCROLL_STATE_DRAGGING; + } + + private boolean canScrollToLeft(int scrollX) + { + return isLeftEdge(scrollX) && !mIsScrollToRight; + } + + private void setAlpha(int animatedItemIndex, int scrollX) + { + View child = findFragmentViewByIndex(animatedItemIndex); + ViewPager.LayoutParams lp = (ViewPager.LayoutParams) child.getLayoutParams(); + if (lp.isDecor) + return; + + float transformPos = (float) (child.getLeft() - scrollX) / (float) getClientWidth(); + initCurrentAlpha(transformPos, animatedItemIndex); + } + + @NonNull + private View findFragmentViewByIndex(int index) + { + String tag = makePagerFragmentTag(index, mPager.getId()); + Fragment page = mActivity.getSupportFragmentManager().findFragmentByTag(tag); + if (page == null) + throw new NoSuchElementException("no such element for tag : " + tag); + + return Objects.requireNonNull(page.getView()); + } + + private void initCurrentAlpha(float transformPos, int itemIndex) + { + ImageView currentImage = mActivity.findViewById(mItems.get(itemIndex)); + if (transformPos <= MINUS_INFINITY_EDGE || transformPos >= PLUS_INFINITY_EDGE) + currentImage.setAlpha(ALPHA_TRANSPARENT); + else if (transformPos == SETTLED_PAGE_POSITION) + currentImage.setAlpha(ALPHA_OPAQUE); + else + currentImage.setAlpha(ALPHA_OPAQUE - Math.abs(transformPos)); + } + + private boolean isLeftEdge(int scrollX) + { + return scrollX == 0; + } + + private boolean isRightEdge(int scrollX) + { + return scrollX == mPager.getWidth() * mAdapter.getCount(); + } + + private void restoreInitialAlphaValues() + { + for (int j = mItems.size() - 1; j >= 0; j--) + { + View view = mActivity.findViewById(mItems.get(j)); + view.setAlpha(ALPHA_OPAQUE); + } + } + + private void recalculateScrollDirection(float positionOffset, int positionOffsetPixels) + { + if (mShouldCalculateScrollDirection) + { + mIsScrollToRight = + POSITION_OFFSET_BASE > positionOffset && positionOffsetPixels > POSITION_OFFSET_PIXELS_BASE; + mShouldCalculateScrollDirection = false; + } + } + + private int getClientWidth() + { + return mPager.getMeasuredWidth() - mPager.getPaddingLeft() - mPager.getPaddingRight(); + } + + @NonNull + private static String makePagerFragmentTag(int index, int pagerId) + { + return ANDROID_SWITCHER_TAG_SEGMENT + pagerId + SEPARATOR_TAG_SEGMENT + index; + } +} diff --git a/android/src/com/mapswithme/maps/widget/ParallaxBackgroundViewPager.java b/android/src/com/mapswithme/maps/widget/ParallaxBackgroundViewPager.java new file mode 100755 index 0000000000..c66485b2d1 --- /dev/null +++ b/android/src/com/mapswithme/maps/widget/ParallaxBackgroundViewPager.java @@ -0,0 +1,132 @@ +package com.mapswithme.maps.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Handler; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.view.MotionEventCompat; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import com.mapswithme.maps.R; + +public class ParallaxBackgroundViewPager extends ViewPager +{ + + private static final int DEFAULT_AUTO_SCROLL_PERIOD = 3000; + private static final int CAROUSEL_ITEMS_MIN_COUNT = 2; + private final int mAutoScrollPeriod; + private final boolean mHasAutoScroll; + @NonNull + private final Handler mAutoScrollHandler; + @NonNull + private final Runnable mAutoScrollMessage; + private int mCurrentPagePosition; + + public ParallaxBackgroundViewPager(@NonNull Context context) + { + this(context, null); + } + + public ParallaxBackgroundViewPager(@NonNull Context context, @Nullable AttributeSet attrs) + { + super(context, attrs); + TypedArray a = context.getTheme() + .obtainStyledAttributes(attrs, R.styleable.ParallaxBgViewPager, 0, 0); + try + { + mHasAutoScroll = a.getBoolean(R.styleable.ParallaxBgViewPager_autoScroll, false); + mAutoScrollPeriod = a.getInt(R.styleable.ParallaxBgViewPager_scrollPeriod, + DEFAULT_AUTO_SCROLL_PERIOD); + } + finally + { + a.recycle(); + } + mAutoScrollHandler = new Handler(); + mAutoScrollMessage = new AutoScrollMessage(); + OnPageChangeListener autoScrollPageListener = new AutoScrollPageListener(); + addOnPageChangeListener(autoScrollPageListener); + } + + @Override + protected void onDetachedFromWindow() + { + super.onDetachedFromWindow(); + stopAutoScroll(); + clearOnPageChangeListeners(); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) + { + int action = MotionEventCompat.getActionMasked(ev); + + if (action == MotionEvent.ACTION_DOWN && mHasAutoScroll) + stopAutoScroll(); + else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_OUTSIDE) + startAutoScroll(); + + return super.dispatchTouchEvent(ev); + } + + public void startAutoScroll() + { + mAutoScrollHandler.postDelayed(mAutoScrollMessage, mAutoScrollPeriod); + } + + public void stopAutoScroll() + { + mAutoScrollHandler.removeCallbacks(mAutoScrollMessage); + } + + private boolean isLastAutoScrollPosition() + { + PagerAdapter adapter = getAdapter(); + return adapter != null && adapter.getCount() - 1 == mCurrentPagePosition; + } + + private class AutoScrollPageListener implements OnPageChangeListener + { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) + { + } + + @Override + public void onPageSelected(int position) + { + mCurrentPagePosition = position; + mAutoScrollHandler.removeCallbacks(mAutoScrollMessage); + mAutoScrollHandler.postDelayed(mAutoScrollMessage, mAutoScrollPeriod); + } + + @Override + public void onPageScrollStateChanged(int state) + { + } + } + + private class AutoScrollMessage implements Runnable + { + @Override + public void run() + { + PagerAdapter adapter = getAdapter(); + if (adapter == null + || adapter.getCount() < CAROUSEL_ITEMS_MIN_COUNT + || !mHasAutoScroll) + return; + + if (isLastAutoScrollPosition()) + mCurrentPagePosition = 0; + else + mCurrentPagePosition++; + + setCurrentItem(mCurrentPagePosition, true); + } + } +}