package com.marshalchen.common.uimodule.jazzyviewpager; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Camera; import android.graphics.Color; import android.graphics.Matrix; import android.os.Build; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import com.marshalchen.common.uimodule.R; import com.marshalchen.common.uimodule.nineoldandroids.view.ViewHelper; import java.util.HashMap; import java.util.LinkedHashMap; public class JazzyViewPager extends ViewPager { public static final String TAG = "JazzyViewPager"; private boolean mEnabled = true; private boolean mFadeEnabled = false; private boolean mOutlineEnabled = false; public static int sOutlineColor = Color.WHITE; private TransitionEffect mEffect = TransitionEffect.Standard; private HashMap<Integer, Object> mObjs = new LinkedHashMap<Integer, Object>(); private static final float SCALE_MAX = 0.5f; private static final float ZOOM_MAX = 0.5f; private static final float ROT_MAX = 15.0f; public enum TransitionEffect { Standard, Tablet, CubeIn, CubeOut, FlipVertical, FlipHorizontal, Stack, ZoomIn, ZoomOut, RotateUp, RotateDown, Accordion } private static final boolean API_11; static { API_11 = Build.VERSION.SDK_INT >= 11; } public JazzyViewPager(Context context) { this(context, null); } @SuppressWarnings("incomplete-switch") public JazzyViewPager(Context context, AttributeSet attrs) { super(context, attrs); setClipChildren(false); // now style everything! TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.JazzyViewPager); int effect = ta.getInt(R.styleable.JazzyViewPager_style, 0); String[] transitions = getResources().getStringArray(R.array.jazzy_effects); setTransitionEffect(TransitionEffect.valueOf(transitions[effect])); setFadeEnabled(ta.getBoolean(R.styleable.JazzyViewPager_fadeEnabled, false)); setOutlineEnabled(ta.getBoolean(R.styleable.JazzyViewPager_outlineEnabled, false)); setOutlineColor(ta.getColor(R.styleable.JazzyViewPager_outlineColor, Color.WHITE)); switch (mEffect) { case Stack: case ZoomOut: setFadeEnabled(true); } ta.recycle(); } public void setTransitionEffect(TransitionEffect effect) { mEffect = effect; // reset(); } public void setPagingEnabled(boolean enabled) { mEnabled = enabled; } public void setFadeEnabled(boolean enabled) { mFadeEnabled = enabled; } public boolean getFadeEnabled() { return mFadeEnabled; } public void setOutlineEnabled(boolean enabled) { mOutlineEnabled = enabled; wrapWithOutlines(); } public void setOutlineColor(int color) { sOutlineColor = color; } private void wrapWithOutlines() { for (int i = 0; i < getChildCount(); i++) { View v = getChildAt(i); if (!(v instanceof OutlineContainer)) { removeView(v); super.addView(wrapChild(v), i); } } } private View wrapChild(View child) { if (!mOutlineEnabled || child instanceof OutlineContainer) return child; OutlineContainer out = new OutlineContainer(getContext()); out.setLayoutParams(generateDefaultLayoutParams()); child.setLayoutParams(new OutlineContainer.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); out.addView(child); return out; } public void addView(View child) { super.addView(wrapChild(child)); } public void addView(View child, int index) { super.addView(wrapChild(child), index); } public void addView(View child, LayoutParams params) { super.addView(wrapChild(child), params); } public void addView(View child, int width, int height) { super.addView(wrapChild(child), width, height); } public void addView(View child, int index, LayoutParams params) { super.addView(wrapChild(child), index, params); } @Override public boolean onInterceptTouchEvent(MotionEvent arg0) { return mEnabled ? super.onInterceptTouchEvent(arg0) : false; } private State mState; private int oldPage; private View mLeft; private View mRight; private float mRot; private float mTrans; private float mScale; private enum State { IDLE, GOING_LEFT, GOING_RIGHT } // public void reset() { // resetPrivate(); // int curr = getCurrentItem(); // onPageScrolled(curr, 0.0f, 0); //} // //private void resetPrivate() { // for (int i = 0; i < getChildCount(); i++) { // View v = getChildAt(i); // // ViewHelper.setRotation(v, -ViewHelper.getRotation(v)); // // ViewHelper.setRotationX(v, -ViewHelper.getRotationX(v)); // // ViewHelper.setRotationY(v, -ViewHelper.getRotationY(v)); // // // // ViewHelper.setTranslationX(v, -ViewHelper.getTranslationX(v)); // // ViewHelper.setTranslationY(v, -ViewHelper.getTranslationY(v)); // // ViewHelper.setRotation(v, 0); // ViewHelper.setRotationX(v, 0); // ViewHelper.setRotationY(v, 0); // // ViewHelper.setTranslationX(v, 0); // ViewHelper.setTranslationY(v, 0); // // ViewHelper.setAlpha(v, 1.0f); // // ViewHelper.setScaleX(v, 1.0f); // ViewHelper.setScaleY(v, 1.0f); // // ViewHelper.setPivotX(v, 0); // ViewHelper.setPivotY(v, 0); // // logState(v, "Child " + i); // } //} private void logState(View v, String title) { Log.v(TAG, title + ": ROT (" + ViewHelper.getRotation(v) + ", " + ViewHelper.getRotationX(v) + ", " + ViewHelper.getRotationY(v) + "), TRANS (" + ViewHelper.getTranslationX(v) + ", " + ViewHelper.getTranslationY(v) + "), SCALE (" + ViewHelper.getScaleX(v) + ", " + ViewHelper.getScaleY(v) + "), ALPHA " + ViewHelper.getAlpha(v)); } protected void animateScroll(int position, float positionOffset) { if (mState != State.IDLE) { mRot = (float) (1 - Math.cos(2 * Math.PI * positionOffset)) / 2 * 30.0f; ViewHelper.setRotationY(this, mState == State.GOING_RIGHT ? mRot : -mRot); ViewHelper.setPivotX(this, getMeasuredWidth() * 0.5f); ViewHelper.setPivotY(this, getMeasuredHeight() * 0.5f); } } protected void animateTablet(View left, View right, float positionOffset) { if (mState != State.IDLE) { if (left != null) { manageLayer(left, true); mRot = 30.0f * positionOffset; mTrans = getOffsetXForRotation(mRot, left.getMeasuredWidth(), left.getMeasuredHeight()); ViewHelper.setPivotX(left, left.getMeasuredWidth() / 2); ViewHelper.setPivotY(left, left.getMeasuredHeight() / 2); ViewHelper.setTranslationX(left, mTrans); ViewHelper.setRotationY(left, mRot); logState(left, "Left"); } if (right != null) { manageLayer(right, true); mRot = -30.0f * (1 - positionOffset); mTrans = getOffsetXForRotation(mRot, right.getMeasuredWidth(), right.getMeasuredHeight()); ViewHelper.setPivotX(right, right.getMeasuredWidth() * 0.5f); ViewHelper.setPivotY(right, right.getMeasuredHeight() * 0.5f); ViewHelper.setTranslationX(right, mTrans); ViewHelper.setRotationY(right, mRot); logState(right, "Right"); } } } private void animateCube(View left, View right, float positionOffset, boolean in) { if (mState != State.IDLE) { if (left != null) { manageLayer(left, true); mRot = (in ? 90.0f : -90.0f) * positionOffset; ViewHelper.setPivotX(left, left.getMeasuredWidth()); ViewHelper.setPivotY(left, left.getMeasuredHeight() * 0.5f); ViewHelper.setRotationY(left, mRot); } if (right != null) { manageLayer(right, true); mRot = -(in ? 90.0f : -90.0f) * (1 - positionOffset); ViewHelper.setPivotX(right, 0); ViewHelper.setPivotY(right, right.getMeasuredHeight() * 0.5f); ViewHelper.setRotationY(right, mRot); } } } private void animateAccordion(View left, View right, float positionOffset) { if (mState != State.IDLE) { if (left != null) { manageLayer(left, true); ViewHelper.setPivotX(left, left.getMeasuredWidth()); ViewHelper.setPivotY(left, 0); ViewHelper.setScaleX(left, 1 - positionOffset); } if (right != null) { manageLayer(right, true); ViewHelper.setPivotX(right, 0); ViewHelper.setPivotY(right, 0); ViewHelper.setScaleX(right, positionOffset); } } } private void animateZoom(View left, View right, float positionOffset, boolean in) { if (mState != State.IDLE) { if (left != null) { manageLayer(left, true); mScale = in ? ZOOM_MAX + (1 - ZOOM_MAX) * (1 - positionOffset) : 1 + ZOOM_MAX - ZOOM_MAX * (1 - positionOffset); ViewHelper.setPivotX(left, left.getMeasuredWidth() * 0.5f); ViewHelper.setPivotY(left, left.getMeasuredHeight() * 0.5f); ViewHelper.setScaleX(left, mScale); ViewHelper.setScaleY(left, mScale); } if (right != null) { manageLayer(right, true); mScale = in ? ZOOM_MAX + (1 - ZOOM_MAX) * positionOffset : 1 + ZOOM_MAX - ZOOM_MAX * positionOffset; ViewHelper.setPivotX(right, right.getMeasuredWidth() * 0.5f); ViewHelper.setPivotY(right, right.getMeasuredHeight() * 0.5f); ViewHelper.setScaleX(right, mScale); ViewHelper.setScaleY(right, mScale); } } } private void animateRotate(View left, View right, float positionOffset, boolean up) { if (mState != State.IDLE) { if (left != null) { manageLayer(left, true); mRot = (up ? 1 : -1) * (ROT_MAX * positionOffset); mTrans = (up ? -1 : 1) * (float) (getMeasuredHeight() - getMeasuredHeight() * Math.cos(mRot * Math.PI / 180.0f)); ViewHelper.setPivotX(left, left.getMeasuredWidth() * 0.5f); ViewHelper.setPivotY(left, up ? 0 : left.getMeasuredHeight()); ViewHelper.setTranslationY(left, mTrans); ViewHelper.setRotation(left, mRot); } if (right != null) { manageLayer(right, true); mRot = (up ? 1 : -1) * (-ROT_MAX + ROT_MAX * positionOffset); mTrans = (up ? -1 : 1) * (float) (getMeasuredHeight() - getMeasuredHeight() * Math.cos(mRot * Math.PI / 180.0f)); ViewHelper.setPivotX(right, right.getMeasuredWidth() * 0.5f); ViewHelper.setPivotY(right, up ? 0 : right.getMeasuredHeight()); ViewHelper.setTranslationY(right, mTrans); ViewHelper.setRotation(right, mRot); } } } private void animateFlipHorizontal(View left, View right, float positionOffset, int positionOffsetPixels) { if (mState != State.IDLE) { if (left != null) { manageLayer(left, true); mRot = 180.0f * positionOffset; if (mRot > 90.0f) { left.setVisibility(View.INVISIBLE); } else { if (left.getVisibility() == View.INVISIBLE) left.setVisibility(View.VISIBLE); mTrans = positionOffsetPixels; ViewHelper.setPivotX(left, left.getMeasuredWidth() * 0.5f); ViewHelper.setPivotY(left, left.getMeasuredHeight() * 0.5f); ViewHelper.setTranslationX(left, mTrans); ViewHelper.setRotationY(left, mRot); } } if (right != null) { manageLayer(right, true); mRot = -180.0f * (1 - positionOffset); if (mRot < -90.0f) { right.setVisibility(View.INVISIBLE); } else { if (right.getVisibility() == View.INVISIBLE) right.setVisibility(View.VISIBLE); mTrans = -getWidth() - getPageMargin() + positionOffsetPixels; ViewHelper.setPivotX(right, right.getMeasuredWidth() * 0.5f); ViewHelper.setPivotY(right, right.getMeasuredHeight() * 0.5f); ViewHelper.setTranslationX(right, mTrans); ViewHelper.setRotationY(right, mRot); } } } } private void animateFlipVertical(View left, View right, float positionOffset, int positionOffsetPixels) { if (mState != State.IDLE) { if (left != null) { manageLayer(left, true); mRot = 180.0f * positionOffset; if (mRot > 90.0f) { left.setVisibility(View.INVISIBLE); } else { if (left.getVisibility() == View.INVISIBLE) left.setVisibility(View.VISIBLE); mTrans = positionOffsetPixels; ViewHelper.setPivotX(left, left.getMeasuredWidth() * 0.5f); ViewHelper.setPivotY(left, left.getMeasuredHeight() * 0.5f); ViewHelper.setTranslationX(left, mTrans); ViewHelper.setRotationX(left, mRot); } } if (right != null) { manageLayer(right, true); mRot = -180.0f * (1 - positionOffset); if (mRot < -90.0f) { right.setVisibility(View.INVISIBLE); } else { if (right.getVisibility() == View.INVISIBLE) right.setVisibility(View.VISIBLE); mTrans = -getWidth() - getPageMargin() + positionOffsetPixels; ViewHelper.setPivotX(right, right.getMeasuredWidth() * 0.5f); ViewHelper.setPivotY(right, right.getMeasuredHeight() * 0.5f); ViewHelper.setTranslationX(right, mTrans); ViewHelper.setRotationX(right, mRot); } } } } protected void animateStack(View left, View right, float positionOffset, int positionOffsetPixels) { if (mState != State.IDLE) { if (right != null) { manageLayer(right, true); mScale = (1 - SCALE_MAX) * positionOffset + SCALE_MAX; mTrans = -getWidth() - getPageMargin() + positionOffsetPixels; ViewHelper.setScaleX(right, mScale); ViewHelper.setScaleY(right, mScale); ViewHelper.setTranslationX(right, mTrans); } if (left != null) { left.bringToFront(); } } } @TargetApi(Build.VERSION_CODES.HONEYCOMB) private void manageLayer(View v, boolean enableHardware) { if (!API_11) return; int layerType = enableHardware ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE; if (layerType != v.getLayerType()) v.setLayerType(layerType, null); } @TargetApi(Build.VERSION_CODES.HONEYCOMB) private void disableHardwareLayer() { if (!API_11) return; View v; for (int i = 0; i < getChildCount(); i++) { v = getChildAt(i); if (v.getLayerType() != View.LAYER_TYPE_NONE) v.setLayerType(View.LAYER_TYPE_NONE, null); } } private Matrix mMatrix = new Matrix(); private Camera mCamera = new Camera(); private float[] mTempFloat2 = new float[2]; protected float getOffsetXForRotation(float degrees, int width, int height) { mMatrix.reset(); mCamera.save(); mCamera.rotateY(Math.abs(degrees)); mCamera.getMatrix(mMatrix); mCamera.restore(); mMatrix.preTranslate(-width * 0.5f, -height * 0.5f); mMatrix.postTranslate(width * 0.5f, height * 0.5f); mTempFloat2[0] = width; mTempFloat2[1] = height; mMatrix.mapPoints(mTempFloat2); return (width - mTempFloat2[0]) * (degrees > 0.0f ? 1.0f : -1.0f); } protected void animateFade(View left, View right, float positionOffset) { if (left != null) { ViewHelper.setAlpha(left, 1 - positionOffset); } if (right != null) { ViewHelper.setAlpha(right, positionOffset); } } protected void animateOutline(View left, View right) { if (!(left instanceof OutlineContainer)) return; if (mState != State.IDLE) { if (left != null) { manageLayer(left, true); ((OutlineContainer) left).setOutlineAlpha(1.0f); } if (right != null) { manageLayer(right, true); ((OutlineContainer) right).setOutlineAlpha(1.0f); } } else { if (left != null) ((OutlineContainer) left).start(); if (right != null) ((OutlineContainer) right).start(); } } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { if (mState == State.IDLE && positionOffset > 0) { oldPage = getCurrentItem(); mState = position == oldPage ? State.GOING_RIGHT : State.GOING_LEFT; } boolean goingRight = position == oldPage; if (mState == State.GOING_RIGHT && !goingRight) mState = State.GOING_LEFT; else if (mState == State.GOING_LEFT && goingRight) mState = State.GOING_RIGHT; float effectOffset = isSmall(positionOffset) ? 0 : positionOffset; // mLeft = getChildAt(position); // mRight = getChildAt(position+1); mLeft = findViewFromObject(position); mRight = findViewFromObject(position + 1); if (mFadeEnabled) animateFade(mLeft, mRight, effectOffset); if (mOutlineEnabled) animateOutline(mLeft, mRight); switch (mEffect) { case Standard: break; case Tablet: animateTablet(mLeft, mRight, effectOffset); break; case CubeIn: animateCube(mLeft, mRight, effectOffset, true); break; case CubeOut: animateCube(mLeft, mRight, effectOffset, false); break; case FlipVertical: animateFlipVertical(mLeft, mRight, positionOffset, positionOffsetPixels); break; case FlipHorizontal: animateFlipHorizontal(mLeft, mRight, effectOffset, positionOffsetPixels); case Stack: animateStack(mLeft, mRight, effectOffset, positionOffsetPixels); break; case ZoomIn: animateZoom(mLeft, mRight, effectOffset, true); break; case ZoomOut: animateZoom(mLeft, mRight, effectOffset, false); break; case RotateUp: animateRotate(mLeft, mRight, effectOffset, true); break; case RotateDown: animateRotate(mLeft, mRight, effectOffset, false); break; case Accordion: animateAccordion(mLeft, mRight, effectOffset); break; } super.onPageScrolled(position, positionOffset, positionOffsetPixels); if (effectOffset == 0) { disableHardwareLayer(); mState = State.IDLE; } } private boolean isSmall(float positionOffset) { return Math.abs(positionOffset) < 0.0001; } public void setObjectForPosition(Object obj, int position) { mObjs.put(Integer.valueOf(position), obj); } public View findViewFromObject(int position) { Object o = mObjs.get(Integer.valueOf(position)); if (o == null) { return null; } PagerAdapter a = getAdapter(); View v; for (int i = 0; i < getChildCount(); i++) { v = getChildAt(i); if (a.isViewFromObject(v, o)) return v; } return null; } }