最近工作较忙,勿发私信,最近看了一篇不错的博客(http://blog.csdn.net/t12x3456/article/details/13290349),使用第三方插件实现京东广告Banner,3D旋转的ViewPager,虽然博主只是讲解了一些简单的nineoldandroids,但是对nineoldandroids这个控件产生了浓厚的兴趣,于是又看了大神任玉刚的详解nineoldandroids(http://blog.csdn.net/singwhatiwanna/article/details/17639987),发现这个第三方控件实在牛逼,于是打算尝试下。还是先上图:
通过反编译京东的apk,看了一遍,京东是如何实现的这个3D旋转动画ViewPager。首先定义布局文件如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentRight="true" android:orientation="vertical" tools:context=".MainActivity" > <com.aimn.dviewpager.JazzyViewPager android:id="@+id/index_product_images_container" android:layout_width="match_parent" android:layout_height="125dp" android:background="@color/white" > </com.aimn.dviewpager.JazzyViewPager> <LinearLayout android:id="@+id/index_product_images_indicator" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/white" android:orientation="horizontal" /> </LinearLayout>
布局很简单,自定义了一个名为JazzyViewPager的控件,还有一个LinearLayout。
红色部分就是JazzyViewPager,而下面那个类似于RadioButton的就是LinearLayout。
然后就是去自定义JazzyViewPager,这个JazzyViewPager肯定继承了ViewPager,我把介绍写在注释里面了,大家可以看代码:
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 } public JazzyViewPager(Context context) { this(context, null); } // 构造器,构造器就是干了几个事情,一、定义了一个TypedArray,这是什么大家可以百度自定属性,绑定自定义的 // 属性和特效 @SuppressWarnings("incomplete-switch") public JazzyViewPager(Context context, AttributeSet attrs) { super(context, attrs); setClipChildren(false); 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; } 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 } 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) { 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) { 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) { 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) { 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) { ViewHelper.setPivotX(left, left.getMeasuredWidth()); ViewHelper.setPivotY(left, 0); ViewHelper.setScaleX(left, 1 - positionOffset); } if (right != null) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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(); } } } 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) { ((OutlineContainer) left).setOutlineAlpha(1.0f); } if (right != null) { ((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) { 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; } }代码中通过ViewHelper去画出了许多动画,而这个ViewHelper就是nineoldandroids第三方提供的,然后再复写viewpager的onPagerSocller方法去实现ViewPager在滑动或者替换的时候的动画效果。其中还有个OutlineContainer,其实我也没搞懂,为啥要通过这个外线容器去画,望大神指导下OutlineContainer的作用。
public class OutlineContainer extends FrameLayout implements Animatable { private Paint mOutlinePaint; private boolean mIsRunning = false; private long mStartTime; private float mAlpha = 1.0f; private static final long ANIMATION_DURATION = 500; private static final long FRAME_DURATION = 1000 / 60; private final Interpolator mInterpolator = new Interpolator() { public float getInterpolation(float t) { t -= 1.0f; return t * t * t + 1.0f; } }; public OutlineContainer(Context context) { super(context); init(); } public OutlineContainer(Context context, AttributeSet attrs) { super(context, attrs); init(); } public OutlineContainer(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { mOutlinePaint = new Paint(); mOutlinePaint.setAntiAlias(true); mOutlinePaint.setStrokeWidth(dpToPx(getResources(), 2)); mOutlinePaint.setColor(getResources().getColor(R.color.blue)); mOutlinePaint.setStyle(Style.STROKE); int padding = dpToPx(getResources(), 10); setPadding(padding, padding, padding, padding); } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); int offset = dpToPx(getResources(), 5); if (mOutlinePaint.getColor() != JazzyViewPager.sOutlineColor) { mOutlinePaint.setColor(JazzyViewPager.sOutlineColor); } mOutlinePaint.setAlpha((int) (mAlpha * 255)); Rect rect = new Rect(offset, offset, getMeasuredWidth() - offset, getMeasuredHeight() - offset); canvas.drawRect(rect, mOutlinePaint); } public void setOutlineAlpha(float alpha) { mAlpha = alpha; } @Override public boolean isRunning() { return mIsRunning; } @Override public void start() { if (mIsRunning) return; mIsRunning = true; mStartTime = AnimationUtils.currentAnimationTimeMillis(); post(mUpdater); } @Override public void stop() { if (!mIsRunning) return; mIsRunning = false; } private final Runnable mUpdater = new Runnable() { @Override public void run() { long now = AnimationUtils.currentAnimationTimeMillis(); long duration = now - mStartTime; if (duration >= ANIMATION_DURATION) { mAlpha = 0.0f; invalidate(); stop(); return; } else { mAlpha = mInterpolator.getInterpolation(1 - duration / (float) ANIMATION_DURATION); invalidate(); } postDelayed(mUpdater, FRAME_DURATION); } }; private int dpToPx(Resources res, int dp) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, res.getDisplayMetrics()); } }定义完以后使用就很简单了,在MainActivtiy里面绑定JazzyViewPager的ID,然后初始化控件,在定义一个handler去定时旋转。
mViewPager=(JazzyViewPager) findViewById(R.id.index_product_images_container);
mViewPager.setTransitionEffect(TransitionEffect.CubeOut);
mViewPager.setCurrentItem(0);
mHandler.sendEmptyMessageDelayed(MSG_CHANGE_PHOTO, PHOTO_CHANGE_TIME);
mViewPager.setAdapter(new MyAdapter());
mViewPager.setOnPageChangeListener(new MyPageChangeListener());
mViewPager.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
if (mImageUrls.size() == 0 || mImageUrls.size() == 1)
return true;
else
return false;
}
});
在MyAdapter里面去加载图片,京东的Banner就轻松实现了。