Index: chrome/android/java/src/org/chromium/chrome/browser/firstrun/ImageCarousel.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/ImageCarousel.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/ImageCarousel.java |
deleted file mode 100644 |
index c4d17b2562ffcf2f45a9671383b60c7334c0ceba..0000000000000000000000000000000000000000 |
--- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/ImageCarousel.java |
+++ /dev/null |
@@ -1,437 +0,0 @@ |
-// Copyright 2015 The Chromium Authors. All rights reserved. |
-// Use of this source code is governed by a BSD-style license that can be |
-// found in the LICENSE file. |
- |
-package org.chromium.chrome.browser.firstrun; |
- |
-import android.animation.Animator; |
-import android.animation.AnimatorSet; |
-import android.animation.ObjectAnimator; |
-import android.content.Context; |
-import android.graphics.Bitmap; |
-import android.util.AttributeSet; |
-import android.util.Property; |
-import android.view.GestureDetector; |
-import android.view.Gravity; |
-import android.view.MotionEvent; |
-import android.view.View; |
-import android.view.animation.DecelerateInterpolator; |
-import android.widget.FrameLayout; |
-import android.widget.ImageView; |
- |
-import org.chromium.chrome.R; |
- |
-import java.util.Arrays; |
- |
-/** |
- * Account chooser that displays profile images in a carousel and allows users to rotate it to |
- * select an account. |
- * |
- * Internally it is implemented using four ImageViews that get translated along the X axis based |
- * on the current carousel position. |
- * |
- * |'''''| |'''''| |'''''| |'''''| |'''''| |
- * |'''| |'''| |'''| |'''| |'''| |'''| |'''| |'''| |'''| |'''| |
- * |IM3| IM0 |IM1| -> |IM0| IM1 |IM2| -> |IM1| IM2 |IM3| -> |IM2| IM3 |IM0| -> |IM3| IM0 |IM1| |
- * |,,,| |,,,| |,,,| |,,,| |,,,| |,,,| |,,,| |,,,| |,,,| |,,,| |
- * |,,,,,| |,,,,,| |,,,,,| |,,,,,| |,,,,,| |
- * |
- * mPosition=0 mPosition=1 mPosition=2 mPosition=3 mPosition=4 |
- * |
- * IM0 is mViews[0] |
- * IM1 is mViews[1] |
- * IM2 is mViews[2] |
- * IM3 is mViews[3] |
- * |
- * Each ImageView is displaying a profile image if there is one, however it is not necessarily true |
- * that IM0 is showing mImages[0] and IM1 is showing mImages[1], and so on. This changes when there |
- * are more than 4 accounts and ImageViews get reused for new accounts. |
- */ |
-public class ImageCarousel extends FrameLayout implements GestureDetector.OnGestureListener { |
- |
- /** |
- * Constant used together image width to calculate how far should should each image move in |
- * x axis. This value was tweaked until images did not overlap with each other when scrolling. |
- */ |
- private static final float TRANSLATION_FACTOR = 0.64f; |
- |
- /** |
- * Constant used together with carousel width to calculate how should fling velocity in x axis |
- * be scaled when changing ImageCarousel position. It was tweaked for flings to look natural. |
- */ |
- private static final float FLING_FACTOR = 20f * 0.92f / 2f; |
- |
- /** |
- * Constant used together with carousel width to calculate how should scroll distance in x axis |
- * be scaled when changing ImageCarousel position. It was tweaked for image to follow user's |
- * finger when scrolling. |
- */ |
- private static final float SCROLL_FACTOR = 0.92f / 2f; |
- |
- /** |
- * Listener to ImageCarousel center position changes. |
- */ |
- public interface ImageCarouselPositionChangeListener { |
- /** |
- * @param position The new center position of the ImageCarousel. It is a number in |
- * range [0, mImages.length). |
- */ |
- void onPositionChanged(int position); |
- } |
- |
- private static final int SCROLL_ANIMATION_DURATION_MS = 200; |
- private static final int ACCOUNT_SIGNED_IN_ANIMATION_DURATION_MS = 200; |
- |
- private static final float MINIMUM_POSITION_TWO_IMAGES = -0.1f; |
- private static final float MAXIMUM_POSITION_TWO_IMAGES = 1.1f; |
- |
- /** |
- * Number of ImageViews used in ImageCarousel. |
- */ |
- private static final int VIEW_COUNT = 4; |
- |
- private static final int[] ORDER_OFFSETS = {2, 1, 3, 0}; |
- |
- private static final int[] POSITION_OFFSETS = {0, -1, 2, 1}; |
- |
- private static final int[] BITMAP_OFFSETS = {2, 1, -1, 0}; |
- |
- /** |
- * Property used to animate scrolling of the ImageCarousel. |
- */ |
- private static final Property<ImageCarousel, Float> POSITION_PROPERTY = |
- new Property<ImageCarousel, Float>(Float.class, "") { |
- @Override |
- public Float get(ImageCarousel object) { |
- return object.mPosition; |
- } |
- |
- @Override |
- public void set(ImageCarousel object, Float value) { |
- object.setPosition(value); |
- } |
- }; |
- |
- /** |
- * Property used to animate the alpha value of the images that are currently on the left and |
- * the right of the center image. |
- */ |
- private static final Property<ImageCarousel, Float> BACKGROUND_IMAGE_ALPHA = |
- new Property<ImageCarousel, Float>(Float.class, "") { |
- @Override |
- public Float get(ImageCarousel object) { |
- return object.mViews[object.getChildDrawingOrder(VIEW_COUNT, 1)].getAlpha(); |
- } |
- |
- @Override |
- public void set(ImageCarousel object, Float value) { |
- object.mViews[object.getChildDrawingOrder(VIEW_COUNT, 1)].setAlpha(value); |
- object.mViews[object.getChildDrawingOrder(VIEW_COUNT, 2)].setAlpha(value); |
- } |
- }; |
- |
- /** |
- * Gesture detector used to capture scrolls, flings and taps on the image carousel. |
- */ |
- private GestureDetector mGestureDetector; |
- |
- /** |
- * Array that holds four ImageViews that are used to display images in the carousel. |
- */ |
- private ImageView[] mViews = new ImageView[VIEW_COUNT]; |
- |
- /** |
- * Images that shown in the image carousel. |
- */ |
- private Bitmap[] mImages; |
- |
- private Animator mScrollAnimator; |
- private Animator mFadeInOutAnimator; |
- |
- private float mPosition = 0f; |
- |
- private ImageCarouselPositionChangeListener mListener; |
- private ImageView mCheckmark; |
- private int mLastPosition = 0; |
- private boolean mNeedsPositionUpdates = true; |
- |
- private int mCarouselWidth; |
- private int mImageWidth; |
- private float mScrollScalingFactor; |
- private float mFlingScalingFactor; |
- private float mTranslationFactor; |
- |
- private boolean mScrollingDisabled; |
- private boolean mAccountSelected; |
- |
- public ImageCarousel(Context context, AttributeSet attrs) { |
- super(context, attrs); |
- mGestureDetector = new GestureDetector(getContext(), this); |
- } |
- |
- /** |
- * Scrolls ImageCarousel to the closest whole position for the desired position. |
- * @param position Desired ImageCarousel position. |
- * @param decelerate Whether animation should be decelerating. |
- * @param needsPositionUpdates Whether this scroll should trigger position update calls to |
- * mListener. |
- */ |
- public void scrollTo(float position, boolean decelerate, boolean needsPositionUpdates) { |
- mNeedsPositionUpdates = needsPositionUpdates; |
- if (mScrollAnimator != null) mScrollAnimator.cancel(); |
- |
- position = Math.round(position); |
- if (mImages != null && mImages.length == 2) { |
- if (position < 0) position = 0; |
- if (position > 1) position = 1; |
- } |
- mScrollAnimator = ObjectAnimator.ofFloat(this, POSITION_PROPERTY, mPosition, position); |
- mScrollAnimator.setDuration(SCROLL_ANIMATION_DURATION_MS); |
- if (decelerate) mScrollAnimator.setInterpolator(new DecelerateInterpolator()); |
- mScrollAnimator.start(); |
- } |
- |
- /** |
- * @param listener Listener that should be notified on ImageCarousel center position changes. |
- */ |
- public void setListener(ImageCarouselPositionChangeListener listener) { |
- mListener = listener; |
- } |
- |
- /** |
- * @param images Images that should be displayed in the ImageCarousel. |
- */ |
- public void setImages(Bitmap[] images) { |
- switch (images.length) { |
- case 0: |
- mImages = null; |
- mScrollingDisabled = true; |
- break; |
- case 1: |
- mScrollingDisabled = true; |
- mImages = Arrays.copyOf(images, images.length); |
- break; |
- default: |
- // Enable scrolling only if no account has already been selected. |
- mScrollingDisabled = mAccountSelected; |
- mImages = Arrays.copyOf(images, images.length); |
- break; |
- } |
- |
- updateImageViews(); |
- } |
- |
- /** |
- * Sets whether the ImageCarousel is in signed in mode. This mode disables scrolling, animates |
- * away the background images, and displays a checkmark next to the chosen account image. |
- * @param isSignedIn Whether the ImageCarousel should in signed in mode or not. |
- */ |
- public void setSignedInMode(boolean isSignedIn) { |
- if (isSignedIn == mAccountSelected) return; |
- |
- mScrollingDisabled = isSignedIn; |
- mAccountSelected = isSignedIn; |
- setPosition(getCenterPosition()); |
- |
- if (mCheckmark == null) { |
- mCheckmark = new ImageView(getContext()); |
- mCheckmark.setImageResource(R.drawable.verify_checkmark); |
- setLayoutParamsForCheckmark(mCheckmark); |
- addView(mCheckmark); |
- } |
- |
- if (mFadeInOutAnimator != null) mFadeInOutAnimator.cancel(); |
- AnimatorSet animatorSet = new AnimatorSet(); |
- if (isSignedIn) { |
- animatorSet.playTogether(ObjectAnimator.ofFloat(this, BACKGROUND_IMAGE_ALPHA, 0), |
- ObjectAnimator.ofFloat(mCheckmark, View.ALPHA, 0.0f, 1.0f)); |
- } else { |
- animatorSet.playTogether(ObjectAnimator.ofFloat(this, BACKGROUND_IMAGE_ALPHA, 1), |
- ObjectAnimator.ofFloat(mCheckmark, View.ALPHA, 1.0f, 0.0f)); |
- } |
- mFadeInOutAnimator = animatorSet; |
- mFadeInOutAnimator.setDuration(ACCOUNT_SIGNED_IN_ANIMATION_DURATION_MS); |
- mFadeInOutAnimator.start(); |
- } |
- |
- @Override |
- public void onFinishInflate() { |
- super.onFinishInflate(); |
- |
- mImageWidth = getResources().getDimensionPixelSize(R.dimen.fre_image_height); |
- for (int i = 0; i < VIEW_COUNT; ++i) { |
- ImageView view = new ImageView(getContext()); |
- FrameLayout.LayoutParams params = |
- new FrameLayout.LayoutParams(mImageWidth, mImageWidth); |
- params.gravity = Gravity.CENTER; |
- view.setLayoutParams(params); |
- mViews[i] = view; |
- addView(view); |
- } |
- |
- mCarouselWidth = getResources().getDimensionPixelSize(R.dimen.signin_image_carousel_width); |
- mScrollScalingFactor = SCROLL_FACTOR * mCarouselWidth; |
- mFlingScalingFactor = FLING_FACTOR * mCarouselWidth; |
- mTranslationFactor = TRANSLATION_FACTOR * mImageWidth; |
- |
- setChildrenDrawingOrderEnabled(true); |
- setPosition(0f); |
- } |
- |
- /** |
- * @return The index of the view that should be drawn on the given iteration. |
- */ |
- @Override |
- protected int getChildDrawingOrder(int childCount, int iteration) { |
- // Draw the views that are not our 4 ImagesViews in their normal order. |
- if (iteration >= VIEW_COUNT) return iteration; |
- |
- // Draw image views in the correct z order based on the current position. |
- return (Math.round(mPosition) + ORDER_OFFSETS[iteration]) % VIEW_COUNT; |
- } |
- |
- @Override |
- public boolean onTouchEvent(MotionEvent event) { |
- if (mScrollingDisabled) return false; |
- if (mGestureDetector.onTouchEvent(event)) return true; |
- |
- if (event.getAction() == MotionEvent.ACTION_UP |
- || event.getAction() == MotionEvent.ACTION_CANCEL) { |
- scrollTo(mPosition, false, true); |
- } |
- |
- return false; |
- } |
- |
- // Implementation of GestureDetector.OnGestureListener |
- |
- @Override |
- public boolean onDown(MotionEvent motionEvent) { |
- return true; |
- } |
- |
- @Override |
- public void onShowPress(MotionEvent motionEvent) {} |
- |
- @Override |
- public boolean onSingleTapUp(MotionEvent motionEvent) { |
- mNeedsPositionUpdates = true; |
- if (motionEvent.getX() < (mCarouselWidth - mImageWidth) / 2f) { |
- scrollTo(mPosition - 1, false, true); |
- return true; |
- } else if (motionEvent.getX() > (mCarouselWidth + mImageWidth) / 2f) { |
- scrollTo(mPosition + 1, false, true); |
- return true; |
- } |
- return false; |
- } |
- |
- @Override |
- public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { |
- // Once the user has started scrolling, prevent the parent view from handling touch events. |
- // This allows the ImageCarousel to be behave reasonably when nested inside a ScrollView. |
- getParent().requestDisallowInterceptTouchEvent(true); |
- |
- mNeedsPositionUpdates = true; |
- setPosition(mPosition + distanceX / mScrollScalingFactor); |
- return true; |
- } |
- |
- @Override |
- public void onLongPress(MotionEvent motionEvent) {} |
- |
- @Override |
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { |
- mNeedsPositionUpdates = true; |
- scrollTo(mPosition - velocityX / mFlingScalingFactor, true, true); |
- return true; |
- } |
- |
- // Internal methods |
- |
- /** |
- * Updates the position, scale, alpha and image shown for all four ImageViews used by |
- * the ImageCarousel. |
- */ |
- private void updateImageViews() { |
- if (mImages == null) return; |
- |
- for (int i = 0; i < VIEW_COUNT; i++) { |
- if (mAccountSelected && i != getCenterPosition()) continue; |
- |
- ImageView image = mViews[i]; |
- |
- updateBitmap(i); |
- |
- final float position = mPosition + POSITION_OFFSETS[i]; |
- |
- // X translation is a sin function with a period of 4 and with range |
- // [-mTranslationFactor, mTranslationFactor] |
- image.setTranslationX( |
- -mTranslationFactor * ((float) Math.sin(position * Math.PI / 2f))); |
- |
- // scale is a cos function with a period of 4 and range [1/3, 1] |
- // scale is 1 when the image is in the front and 1/3 when the image is behind other |
- // images. |
- final float scale = (float) Math.cos(position * Math.PI / 2f) / 3f + 2f / 3f; |
- image.setScaleY(scale); |
- image.setScaleX(scale); |
- |
- // alpha is a cos^2 function with a period of 2 and range [0, 1] |
- // alpha is 1 when the image is in the center in the front and 0 when it is in the back. |
- final float alpha = (float) Math.pow(Math.cos(position * Math.PI / 4f), 2); |
- image.setAlpha(alpha); |
- } |
- } |
- |
- private void updateBitmap(int i) { |
- int drawingOrder = getChildDrawingOrder(VIEW_COUNT, i); |
- // Only draw one top bitmap for one image case. |
- if (mImages.length == 1 && drawingOrder > 0) return; |
- // Only draw two top bitmaps for two images case. |
- if (mImages.length == 2 && drawingOrder > 1) return; |
- ImageView image = mViews[drawingOrder]; |
- image.setImageBitmap(mImages[ |
- (mImages.length + Math.round(mPosition) + BITMAP_OFFSETS[i]) % mImages.length]); |
- } |
- |
- private void setPosition(float position) { |
- if (mImages != null) { |
- if (mImages.length == 2) { |
- position = Math.max(MINIMUM_POSITION_TWO_IMAGES, position); |
- position = Math.min(MAXIMUM_POSITION_TWO_IMAGES, position); |
- mPosition = position; |
- } else { |
- mPosition = ((position % mImages.length) + mImages.length) % mImages.length; |
- } |
- } |
- |
- int adjustedPosition = getCenterPosition(); |
- if (adjustedPosition != mLastPosition) { |
- mLastPosition = adjustedPosition; |
- if (mListener != null && mNeedsPositionUpdates) { |
- mListener.onPositionChanged(adjustedPosition); |
- } |
- } |
- |
- // Need to call invalidate() for getChildDrawingOrder() to be called since the image |
- // order has changed. |
- updateImageViews(); |
- invalidate(); |
- } |
- |
- private int getCenterPosition() { |
- if (mImages == null) return 0; |
- return Math.round(mPosition) % mImages.length; |
- } |
- |
- private void setLayoutParamsForCheckmark(View view) { |
- int size = getResources().getDimensionPixelSize(R.dimen.signin_checkmark_size); |
- FrameLayout.LayoutParams params = |
- new FrameLayout.LayoutParams(size, size); |
- params.gravity = Gravity.CENTER; |
- view.setLayoutParams(params); |
- view.setTranslationX((mImageWidth - size) / 2f); |
- view.setTranslationY((mImageWidth - size) / 2f); |
- } |
-} |