主流移动应用开发框架(3)——ViewPagerIndicator+3DViewpager

本文介绍了如何在Android应用中使用ViewPagerIndicator创建炫酷的页面指示器,并展示了如何结合3DViewPager实现更生动的滑动效果。通过提供项目链接和使用步骤,帮助开发者快速理解和集成这两个开源框架。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天向大家介绍下第三个开发框架——多样化的ViewPager指示器及其3DViewPager效果。


(一)ViewPager指示器——ViewPagerIndicator


网易新闻的app使用了炫酷的ViewPager指示器,通过滑动页面切换fragment并且有指示效果,参考一些网友的图片:

           

而今天为大家引入的是github的一个强大的开源框架,ViewPagerIndicator为我们提供了丰富的指示器,参考github图示

          

github项目链接地址:https://github.com/JakeWharton/Android-ViewPagerIndicator

github项目下载地址:https://codeload.github.com/JakeWharton/Android-ViewPagerIndicator/zip/master

首先给大家介绍下这个的使用,具体为三个步骤,使用起来非常简单且功能十分强大,这便是开源的极大好处!

(1)在ViewPager当前所在的xml布局中添加下述代码

<com.viewpagerindicator.TitlePageIndicator
    android:id="@+id/titles"
    android:layout_height="wrap_content"
    android:layout_width="fill_parent" />

(2)在当前初始化ViewPager的activity的onCreat方法或者fragment的onCreatView方法中,绑定ViewPager与Indicator

 //Set the pager with an adapter
 ViewPager pager = (ViewPager)findViewById(R.id.pager);
 pager.setAdapter(new TestAdapter(getSupportFragmentManager()));

 //Bind the title indicator to the adapter
 TitlePageIndicator titleIndicator = (TitlePageIndicator)findViewById(R.id.titles);
 titleIndicator.setViewPager(pager);

(3)如果你想监听指示器变化或者ViewPager滑动,则可以绑定监听器

 //continued from above
 titleIndicator.setOnPageChangeListener(mPageChangeListener);


经过上述3个步骤,就可以完美地使用 ViewPagerIndicator,读者可以根据自己的实际需要选择自己喜欢的指示器。


(二)3D ViewPapger

介绍到这里,炫酷的指示器我们有了,有些开发者希望能给予指示器,再开发出更漂亮的ViewPager滑动效果,3DViewPager会是你的不错选择。见效果图

                 

     如何使用3DViewPager,参考了github开源项目

     github 3DViewPager项目链接:https://github.com/inovex/ViewPager3D

     github    3DViewPager项目下载:https://codeload.github.com/inovex/ViewPager3D/zip/master

     具体实现也分成3个步骤,使用起来也很简单。

     (1)自定义3DViewPager,重写ViewPager类,添加动画效果。

  

package de.inovex.android.widgets.example;

import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Camera;
import android.graphics.Rect;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewConfigurationCompat;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.FloatMath;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;



public class ViewPager3D extends ViewPager {

	/**
	 * maximum overscroll rotation of the children is 90 divided by this value
	 */
	final static float DEFAULT_OVERSCROLL_ROTATION = 2f;

	/**
	 * maximum z distance to translate child view
	 */
	final static int DEFAULT_OVERSCROLL_TRANSLATION = 150;

	/**
	 * maximum z distanze during swipe
	 */
	final static int DEFAULT_SWIPE_TRANSLATION = 100;

	/**
	 * maximum rotation during swipe is 90 divided by this value
	 */
	final static float DEFAULT_SWIPE_ROTATION = 3;

	/**
	 * duration of overscroll animation in ms
	 */
	final private static int DEFAULT_OVERSCROLL_ANIMATION_DURATION = 400;

	/**
	 * if true alpha of children gets animated during swipe and overscroll
	 */
	final private static boolean DEFAULT_ANIMATE_ALPHA = true;

	@SuppressWarnings("unused")
	private final static String DEBUG_TAG = ViewPager.class.getSimpleName();
	private final static int INVALID_POINTER_ID = -1;
	private final static double RADIANS = 180f / Math.PI;

	/**
	 * @author renard
	 */
	private class OverscrollEffect {
		private float mOverscroll;
		private Animator mAnimator;

		/**
		 * @param deltaDistance [0..1] 0->no overscroll, 1>full overscroll
		 */
		public void setPull(final float deltaDistance) {
			mOverscroll = deltaDistance;
			invalidateVisibleChilds();
		}

		/**
		 * called when finger is released. starts to animate back to default
		 * position
		 */
		private void onRelease() {
			if (mAnimator != null && mAnimator.isRunning()) {
				mAnimator.addListener(new AnimatorListener() {

					@Override
					public void onAnimationStart(Animator animation) {
					}

					@Override
					public void onAnimationRepeat(Animator animation) {
					}

					@Override
					public void onAnimationEnd(Animator animation) {
						startAnimation(0);
					}

					@Override
					public void onAnimationCancel(Animator animation) {
					}
				});
				mAnimator.cancel();
			} else {
				startAnimation(0);
			}
		}

		private void startAnimation(final float target) {
			mAnimator = ObjectAnimator.ofFloat(this, "pull", mOverscroll, target);
			mAnimator.setInterpolator(new DecelerateInterpolator());
			final float scale = Math.abs(target - mOverscroll);
			mAnimator.setDuration((long) (mOverscrollAnimationDuration * scale));
			mAnimator.start();
		}

		private boolean isOverscrolling() {
			if (mScrollPosition == 0 && mOverscroll < 0) {
				return true;
			}
			if (getAdapter() != null) {
				final boolean isLast = (getAdapter().getCount() - 1) == mScrollPosition;
				if (isLast && mOverscroll > 0) {
					return true;
				}
			}
			return false;
		}

	}

	final private OverscrollEffect mOverscrollEffect = new OverscrollEffect();
	final private Camera mCamera = new Camera();

	private OnPageChangeListener mScrollListener;
	private float mLastMotionX;
	private int mActivePointerId;
	private int mScrollPosition;
	private float mScrollPositionOffset;
	private int mScrollPositionOffsetPixels;
	final private int mTouchSlop;

	private float mOverscrollRotation;
	private float mSwipeRotation;
	private int mOverscrollTranslation;
	private int mSwipeTranslation;
	private int mOverscrollAnimationDuration;
	private boolean mAnimateAlpha;
	private Rect mTempTect = new Rect();

	public ViewPager3D(Context context, AttributeSet attrs) {
		super(context, attrs);
		setStaticTransformationsEnabled(true);
		final ViewConfiguration configuration = ViewConfiguration.get(context);
		mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
		super.setOnPageChangeListener(new MyOnPageChangeListener());
		init(attrs);
	}

	private void init(AttributeSet attrs) {
		TypedArray a = getContext().obtainStyledAttributes(attrs,
				R.styleable.ViewPager3D);
		mOverscrollRotation = a.getFloat(
				R.styleable.ViewPager3D_overscroll_rotation,
				DEFAULT_OVERSCROLL_ROTATION);
		mSwipeRotation = a.getFloat(R.styleable.ViewPager3D_swipe_rotation,
				DEFAULT_SWIPE_ROTATION);
		mSwipeTranslation = a.getInt(R.styleable.ViewPager3D_swipe_translation,
				DEFAULT_SWIPE_TRANSLATION);
		mOverscrollTranslation = a.getInt(
				R.styleable.ViewPager3D_overscroll_translation,
				DEFAULT_OVERSCROLL_TRANSLATION);
		mOverscrollAnimationDuration = a.getInt(
				R.styleable.ViewPager3D_overscroll_animation_duration,
				DEFAULT_OVERSCROLL_ANIMATION_DURATION);
		mAnimateAlpha = a.getBoolean(R.styleable.ViewPager3D_animate_alpha,
				DEFAULT_ANIMATE_ALPHA);
		a.recycle();
	}

	public boolean isAnimateAlpha() {
		return mAnimateAlpha;
	}

	public void setAnimateAlpha(boolean mAnimateAlpha) {
		this.mAnimateAlpha = mAnimateAlpha;
	}

	public int getOverscrollAnimationDuration() {
		return mOverscrollAnimationDuration;
	}

	public void setOverscrollAnimationDuration(int mOverscrollAnimationDuration) {
		this.mOverscrollAnimationDuration = mOverscrollAnimationDuration;
	}

	public int getSwipeTranslation() {
		return mSwipeTranslation;
	}

	public void setSwipeTranslation(int mSwipeTranslation) {
		this.mSwipeTranslation = mSwipeTranslation;
	}

	public int getOverscrollTranslation() {
		return mOverscrollTranslation;
	}

	public void setOverscrollTranslation(int mOverscrollTranslation) {
		this.mOverscrollTranslation = mOverscrollTranslation;
	}

	public float getSwipeRotation() {
		return mSwipeRotation;
	}

	public void setSwipeRotation(float mSwipeRotation) {
		this.mSwipeRotation = mSwipeRotation;
	}

	public float getOverscrollRotation() {
		return mOverscrollRotation;
	}

	public void setOverscrollRotation(float mOverscrollRotation) {
		this.mOverscrollRotation = mOverscrollRotation;
	}

	@Override
	public void setOnPageChangeListener(OnPageChangeListener listener) {
		mScrollListener = listener;
	}

	;

	private void invalidateVisibleChilds() {
		for (int i = 0; i < getChildCount(); i++) {
			final View childAt = getChildAt(i);
			childAt.getLocalVisibleRect(mTempTect);
			final int area = mTempTect.width() * mTempTect.height();
			if (area > 0) {
				childAt.invalidate();
			}
		}

		invalidate();
	}

	private class MyOnPageChangeListener implements OnPageChangeListener {

		@Override
		public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
			if (mScrollListener != null) {
				mScrollListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
			}
			mScrollPosition = position;
			mScrollPositionOffset = positionOffset;
			mScrollPositionOffsetPixels = positionOffsetPixels;
			//Log.i(DEBUG_TAG, "mScrollPosition = " + position + " offset = " + String.format("%f.2", positionOffset));
			//Log.i(DEBUG_TAG, "onPageScrolled");

			invalidateVisibleChilds();
		}

		@Override
		public void onPageSelected(int position) {
			if (mScrollListener != null) {
				mScrollListener.onPageSelected(position);
			}
		}

		@Override
		public void onPageScrollStateChanged(final int state) {
			if (mScrollListener != null) {
				mScrollListener.onPageScrollStateChanged(state);
			}
			if (state == SCROLL_STATE_IDLE) {
				mScrollPositionOffset = 0;
			}
		}
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
		switch (action) {
			case MotionEvent.ACTION_DOWN: {
				mLastMotionX = ev.getX();
				mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
				break;
			}
			case MotionEventCompat.ACTION_POINTER_DOWN: {
				final int index = MotionEventCompat.getActionIndex(ev);
				final float x = MotionEventCompat.getX(ev, index);
				mLastMotionX = x;
				mActivePointerId = MotionEventCompat.getPointerId(ev, index);
				break;
			}
		}
		return super.onInterceptTouchEvent(ev);
	}


	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		boolean callSuper = false;

		final int action = ev.getAction();
		switch (action) {
			case MotionEvent.ACTION_DOWN: {
				callSuper = true;
				mLastMotionX = ev.getX();
				mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
				break;
			}
			case MotionEventCompat.ACTION_POINTER_DOWN: {
				callSuper = true;
				final int index = MotionEventCompat.getActionIndex(ev);
				final float x = MotionEventCompat.getX(ev, index);
				mLastMotionX = x;
				mActivePointerId = MotionEventCompat.getPointerId(ev, index);
				break;
			}
			case MotionEvent.ACTION_MOVE: {
				if (mActivePointerId != INVALID_POINTER_ID) {
					// Scroll to follow the motion event
					final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
					final float x = MotionEventCompat.getX(ev, activePointerIndex);
					final float deltaX = mLastMotionX - x;
					final int width = getWidth();
					final int widthWithMargin = width + getPageMargin();
					final int lastItemIndex = getAdapter().getCount() - 1;
					final int currentItemIndex = getCurrentItem();
					final float leftBound = Math.max(0, (currentItemIndex - 1) * widthWithMargin);
					final float rightBound = Math.min(currentItemIndex + 1, lastItemIndex) * widthWithMargin;
					if (mScrollPositionOffset == 0) {
						if (currentItemIndex == 0) {
							if (leftBound == 0) {
								final float over = deltaX + mTouchSlop;
								mOverscrollEffect.setPull(over / width);
							}
						} else if (lastItemIndex == currentItemIndex) {
							if (rightBound == lastItemIndex * widthWithMargin) {
								final float over = deltaX - mTouchSlop;
								mOverscrollEffect.setPull(over / width);
							}
						}
					} else {
						mLastMotionX = x;
					}
				} else {
					mOverscrollEffect.onRelease();
				}
				break;
			}
			case MotionEvent.ACTION_UP:
			case MotionEvent.ACTION_CANCEL: {
				callSuper = true;
				mActivePointerId = INVALID_POINTER_ID;
				mOverscrollEffect.onRelease();
				break;
			}
			case MotionEvent.ACTION_POINTER_UP: {
				final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
				final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
				if (pointerId == mActivePointerId) {
					// This was our active pointer going up. Choose a new
					// active pointer and adjust accordingly.
					final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
					mLastMotionX = ev.getX(newPointerIndex);
					mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
					callSuper = true;
				}
				break;
			}
		}

		if (mOverscrollEffect.isOverscrolling() && !callSuper) {
			return true;
		} else {
			try {
				return super.onTouchEvent(ev);
			} catch (IllegalArgumentException ignore) {
			} catch (ArrayIndexOutOfBoundsException ignore) {
			}
			return false;
		}
	}


	@Override
	protected boolean getChildStaticTransformation(View child, Transformation t) {
		if (child.getWidth() == 0) {
			return false;
		}


		final boolean isFirstOrLast = mScrollPosition == 0 || (mScrollPosition == (getAdapter().getCount() - 1));
		if (mOverscrollEffect.isOverscrolling() && isFirstOrLast) {
			final float dx = getWidth() / 2;
			final int dy = getHeight() / 2;
			t.getMatrix().reset();
			final float translateZ = (float) (mOverscrollTranslation * Math.sin(Math.PI * Math.abs(mOverscrollEffect.mOverscroll)));
			final float degrees = 90 / mOverscrollRotation - (float) ((RADIANS * Math.acos(mOverscrollEffect.mOverscroll)) / mOverscrollRotation);

			mCamera.save();
			mCamera.rotateY(degrees);
			mCamera.translate(0, 0, translateZ);
			mCamera.getMatrix(t.getMatrix());
			mCamera.restore();
			t.getMatrix().preTranslate(-dx, -dy);
			t.getMatrix().postTranslate(dx, dy);

			if (mAnimateAlpha) {
				t.setTransformationType(Transformation.TYPE_BOTH);
				t.setAlpha((FloatMath.sin((float) ((1 - Math.abs(mOverscrollEffect.mOverscroll)) * Math.PI / 2))));
			}
			return true;
		} else if (mScrollPositionOffset > 0) {


			final float dx = getWidth() / 2;
			final float dy = getHeight() / 2;

			double degrees = 0;
			child.getLocalVisibleRect(mTempTect);

			if (mTempTect.left >= mScrollPositionOffsetPixels) {
				if (mAnimateAlpha) {
					t.setTransformationType(Transformation.TYPE_BOTH);
					t.setAlpha((FloatMath.sin((float) (mScrollPositionOffset
							* Math.PI / 2))));
				}
				// right side
				degrees = (90 / mSwipeRotation) - (RADIANS * Math.acos(mScrollPositionOffset)) / mSwipeRotation;
			} else if (mTempTect.left == 0) {
				if (mAnimateAlpha) {
					t.setTransformationType(Transformation.TYPE_BOTH);
					t.setAlpha((FloatMath.sin((float) (mScrollPositionOffset
							* Math.PI / 2 + Math.PI / 2))));
				}
				// left side
				degrees = -(90 / mSwipeRotation) + (RADIANS * Math.acos(1 - mScrollPositionOffset)) / mSwipeRotation;
			}


			final float translateZ = (mSwipeTranslation * FloatMath.sin((float) ((Math.PI) * mScrollPositionOffset)));
			//Log.i(DEBUG_TAG, visibleRect.left+ ", " + mScrollPositionOffsetPixels + ", degress = "+ String.format("%f.2", degrees));

			t.getMatrix().reset();
			mCamera.save();
			mCamera.rotateY((float) degrees);
			mCamera.translate(0, 0, translateZ);
			mCamera.getMatrix(t.getMatrix());
			mCamera.restore();
			// pivot point is center of child
			t.getMatrix().preTranslate(-dx, -dy);
			t.getMatrix().postTranslate(dx, dy);
			//child.invalidate();
			return true;
		}
		return false;
	}
}
(2)配置3DViewPager的adapter

private class AwesomePagerAdapter extends PagerAdapter {

		private Stack<WeakReference<View>> mViews = new Stack<WeakReference<View>>();

		@Override
		public int getCount() {
			return 5;
		}

		/**
		 * Create the page for the given position. The adapter is responsible
		 * for adding the view to the container given here, although it only
		 * must ensure this is done by the time it returns from
		 * {@link #finishUpdate()}.
		 * 
		 * @param container
		 *            The containing View in which the page will be shown.
		 * @param position
		 *            The page position to be instantiated.
		 * @return Returns an Object representing the new page. This does not
		 *         need to be a View, but can be some other container of the
		 *         page.
		 */
		@Override
		public Object instantiateItem(View collection, int position) {
			WeakReference<View> refView = null;
			View v = null;
			while (v == null && mViews.size() > 0) {
				refView = mViews.pop();
				v = refView.get();
			}
			View tv;
			if (v != null) {
				refView.clear();
				tv = v;
			} else {
				tv = mInflater.inflate(R.layout.page_item, null);
			}
			final int r = (int) (Math.random() * 80);
			final int g = (int) (Math.random() * 50);
			final int b = (int) (Math.random() * 100);
			tv.setBackgroundColor(Color.argb(255, r, g, b));
			// tv.setCompoundDrawables(left, top, right, bottom)
			((ViewPager) collection).addView(tv);
			return tv;

		}

		/**
		 * Remove a page for the given position. The adapter is responsible for
		 * removing the view from its container, although it only must ensure
		 * this is done by the time it returns from {@link #finishUpdate()}.
		 * 
		 * @param container
		 *            The containing View from which the page will be removed.
		 * @param position
		 *            The page position to be removed.
		 * @param object
		 *            The same object that was returned by
		 *            {@link #instantiateItem(View, int)}.
		 */
		@Override
		public void destroyItem(View collection, int position, Object view) {
			((ViewPager) collection).removeView((View) view);
			mViews.push(new WeakReference<View>((View) view));

		}

		@Override
		public boolean isViewFromObject(View view, Object object) {
			return view == ((View) object);
		}

		/**
		 * Called when the a change in the shown pages has been completed. At
		 * this point you must ensure that all of the pages have actually been
		 * added or removed from the container as appropriate.
		 * 
		 * @param container
		 *            The containing View which is displaying this adapter's
		 *            page views.
		 */
		@Override
		public void finishUpdate(View arg0) {
		}

		@Override
		public void restoreState(Parcelable arg0, ClassLoader arg1) {
		}

		@Override
		public Parcelable saveState() {
			return null;
		}

		@Override
		public void startUpdate(View arg0) {
		}

	}

(3)在activity中初始化3DViewPager并且绑定数据,记得在布局文件中引用3DViewPager,参考如下

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
		setContentView(R.layout.main);
		mInflater = getLayoutInflater();
		ViewPager3D pager = (ViewPager3D) findViewById(R.id.awesomepager);
		pager.setAdapter(new AwesomePagerAdapter());
	}

    <de.inovex.android.widgets.example.ViewPager3D 
        android:id="@+id/awesomepager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:animate_alpha="false"
        app:overscroll_rotation="6"
        app:overscroll_translation="50"
        app:swipe_rotation="2"
        app:swipe_translation="50">      
    </de.inovex.android.widgets.example.ViewPager3D>


(三)项目实践及其效果

对于开发者来说,往往是因为需要所以学习,这种在实践中学习是最有效率的。因为项目需要,所以才学习了这两个比较实用的开源框架,下面给出自己项目的整合效果

           

   

         附上源码下载链接:http://download.youkuaiyun.com/detail/u010794180/8215411


        希望博文对大家有帮助,也勉励自己,共同进步。

        博文连接:

       主流移动应用开发框架(1)——实现左右滑动隐藏菜单的开源框架AndroidResideMenu二次开发 http://blog.youkuaiyun.com/u010794180/article/details/41575555

       主流移动开发框架(2)——fragment+fragmenttabhost实现底部选项卡导航(可滑动切换) http://blog.youkuaiyun.com/u010794180/article/details/41621003


 


    






评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值