Pager 作为android中常用的控件,难免遇到不一样的需求,由于android 提供的ViewPager 难于满足项目需求,自定义了一个pager,此文用以记录写该控件的所有过程。
一 、明确该控件的作用(为什么要去编写这个控件):
1.用户操作可以多样性(单向滑动、双向滑动、水平滑动、垂直滑动 甚至任意方向的滑动)
2.可以启动自动轮播
3.可以无限循环 (系统提供的 ViewPager会导致回滚效果)
4.数据量必须大于一定数量了才支持无限循环
相信满足以上功能的控件现在已经不少了,然而很多都需要我们拿过来修修改改(到目前为止,个人还没有找到满足所有要求拿来即用的控件)。
二、设计过程
最好的学习方法也就是临摹,在编写代码之前狠狠地研究了哈系统提供的 ViewPager 源码,发现太复杂..... 以下是设计思路:
1. 提供三个重叠容器,一个用于显示当前view , 一个用户缓存左上view , 一个用于缓存右下view。
2. 所有显示内容的位置都是相对于当前的view,并且只关注当前view的前后两个view。
3. 在循环模式下,通过任意单向操作可以回到当前视图。
三、 代码结构
在android中,View 、 Adapter 、data几乎成了标配,该控件的实现也采用了这个模式。
InfinitePager 通过 PageViewAdapter 创建所有的View 。 Pager的动画效果由用户实现AbsTransformer提供。
四、关键代码
处理滑动手势:
<pre name="code" class="java"> private void handleMotionEvent(float dx, float dy) {
if (transformer == null) {
return;
}
if ((internalSlideMode & SLIDE_MODE_BOTTOM_TO_TOP) != 0 && dy < 0) {
slideBottomToTop(dx, dy);
}
if ((internalSlideMode & SLIDE_MODE_TOP_TO_BOTTOM) != 0 && dy > 0) {
slideTopToBottom(dx, dy);
}
if ((internalSlideMode & SLIDE_MODE_LEFT_TO_RIGHT) != 0 && dx > 0) {
slideLeftToRight(dx, dy);
}
if ((internalSlideMode & SLIDE_MODE_RIGHT_TO_LEFT) != 0 && dx < 0) {
slideRightToLeft(dx, dy);
}
}
private void slideBottomToTop(float dx, float dy) {
if (isLoopEnabled() || (!isLoopEnabled() && !isPagerLastItem())) {
visibleTopLeft(false);
transformer.doTransform(current, currentPort, bottomRight, bottomRightPort, dx, dy, false);
}
}
private void slideTopToBottom(float dx, float dy) {
if (isLoopEnabled() || (!isLoopEnabled() && !isPagerFirstItem())) {
visibleTopLeft(true);
transformer.doTransform(current, currentPort, topLeft, topLeftPort, dx, dy, true);
}
}
private void slideLeftToRight(float dx, float dy) {
if (isLoopEnabled() || (!isLoopEnabled() && !isPagerFirstItem())) {
visibleTopLeft(true);
transformer.doTransform(current, currentPort, topLeft, topLeftPort, dx, dy, true);
}
}
private void slideRightToLeft(float dx, float dy) {
if (isLoopEnabled() || (!isLoopEnabled() && !isPagerLastItem())) {
visibleTopLeft(false);
transformer.doTransform(current, currentPort, bottomRight, bottomRightPort, dx, dy, false);
}
}
手势离开view:
<pre name="code" class="java"> private void animBottomToTop(float dy, float vy) {
// filter out invalid animation request
if (dy > 0 || isViewInSrcPos(bottomRight)) {
return;
}
animator = null; // reset animator
if ((isLoopEnabled() || (!isLoopEnabled() && !isPagerLastItem()))
&& (vy < -DEFAULT_FLING_TRIGGER || Math.abs(dy) >= getHeight() / 2)) {
animator = transformer.doForwardAnimation(this, current,
currentPort, bottomRight, bottomRightPort, false);
} else {
animator = transformer.doBacwardAnimation(current,
currentPort, bottomRight, bottomRightPort, false);
}
playAnimation(animator);
}
private void animTopToBottom(float dy, float vy) {
if (dy < 0 || isViewInSrcPos(topLeft)) {
return;
}
animator = null;
if ((isLoopEnabled() || (!isLoopEnabled() && !isPagerFirstItem()))
&& (vy > DEFAULT_FLING_TRIGGER || dy >= getHeight() / 2)) {
animator = transformer.doForwardAnimation(this, current,
currentPort, topLeft, topLeftPort, true);
} else {
animator = transformer.doBacwardAnimation(current,
currentPort, topLeft, topLeftPort, true);
}
playAnimation(animator);
}
private void playAnimation(Animator anim) {
if (anim != null) {
anim.start();
}
}
private void animLeftToRight(float dx, float vx) {
if (dx < 0 || isViewInSrcPos(topLeft)) {
return;
}
animator = null;
if ((isLoopEnabled() || (!isLoopEnabled() && !isPagerFirstItem()))
&& (vx > DEFAULT_FLING_TRIGGER || Math.abs(dx) >= getWidth() / 2)) {
animator = transformer.doForwardAnimation(this, current,
currentPort, topLeft, topLeftPort, true);
} else {
animator = transformer.doBacwardAnimation(current,
currentPort, topLeft, topLeftPort, true);
}
playAnimation(animator);
}
private void animRightToLeft(float dx, float vx) {
// filter out invalid animation request
if (dx > 0 || isViewInSrcPos(bottomRight)) {
return;
}
animator = null; // reset animator
if ((isLoopEnabled() || (!isLoopEnabled() && !isPagerLastItem()))
&& (vx < -DEFAULT_FLING_TRIGGER || Math.abs(dx) >= getWidth() / 2)) {
animator = transformer.doForwardAnimation(this, current,
currentPort, bottomRight, bottomRightPort, false);
} else {
animator = transformer.doBacwardAnimation(current,
currentPort, bottomRight, bottomRightPort, false);
}
playAnimation(animator);
}
private void handleFlingEvent(float dx, float dy, float vx, float vy) {
if (transformer == null) {
return;
}
if ((internalSlideMode & SLIDE_MODE_BOTTOM_TO_TOP) != 0) {
animBottomToTop(dy, vy);
}
if ((internalSlideMode & SLIDE_MODE_TOP_TO_BOTTOM) != 0) {
animTopToBottom(dy, vy);
}
if ((internalSlideMode & SLIDE_MODE_LEFT_TO_RIGHT) != 0) {
animLeftToRight(dx, vx);
}
if ((internalSlideMode & SLIDE_MODE_RIGHT_TO_LEFT) != 0) {
animRightToLeft(dx, vx);
}
}
手势拦截处理(决定Pager何时抢占手势焦点)
public boolean onInterceptTouchEvent(MotionEvent ev) {
autoLoopHandler.removeCallbacks(transformTask);
float tx = ev.getX();
float ty = ev.getY();
switch (ev.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
downX = tx;
downY = ty;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (isIntervalLoop) {
enableIntervalLoop(intervalTime);
}
break;
}
if (isEnableScroll) {
downX = ev.getX();
downY = ev.getY();
initViewPosition();
return true;
}
isEnableScroll = !isTouchPointConsumed(ev)
//enable scroll in touchable children views
|| ((((internalSlideMode & SLIDE_MODE_BOTTOM_TO_TOP) != 0)
|| ((internalSlideMode & SLIDE_MODE_TOP_TO_BOTTOM) != 0))
&& Math.abs(ty - downY) > touchSlop)
|| ((((internalSlideMode & SLIDE_MODE_LEFT_TO_RIGHT) != 0)
|| ((internalSlideMode & SLIDE_MODE_RIGHT_TO_LEFT) != 0))
&& Math.abs(tx - downX) > touchSlop);
if (isEnableScroll) {
ViewParent vp = getParent();
if (vp != null) {
vp.requestDisallowInterceptTouchEvent(true);
}
}
return super.onInterceptTouchEvent(ev);
}
AbsTransformer.java
package com.edwin.infinitepager.transformer;
import android.animation.Animator;
import android.graphics.Rect;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import com.edwin.infinitepager.InfinitePager;
/**
* Created by chen xue yu on 2016/1/26.
* @author chenxueyu
*/
public abstract class AbsTransformer {
public static final int DEFAULT_ANIMATION_DURATION = 300;
protected final int animDuration;
protected Interpolator interpolator = new DecelerateInterpolator();
public void setInterpolator(Interpolator interpolator) {
if (interpolator == null) {
interpolator = new DecelerateInterpolator();
}
this.interpolator = interpolator;
}
protected AbsTransformer() {
this(DEFAULT_ANIMATION_DURATION);
}
protected AbsTransformer(int duration) {
if (duration < 0) {
duration = 0;
}
animDuration = duration;
}
/**
*/
/**
* control the current view and the next view transform when user touch on the screen
*
* @param current the view is showing
* @param next the view will be shown
* @param dx the total distance of current touch point and 'touch down point' in X-axis
* @param dy the total distance of current touch point and 'touch down point' in Y-axis
* @param topLeft indicate whether the next view is top-left of current view.
*/
public abstract void doTransform(View current, Rect currentSrcRect,
View next, Rect nextSrcRect,
float dx, float dy, boolean topLeft);
/**
* animate to the next view , method {@link InfinitePager#scrollToNext()}
* must be called after animation complete and invoke {@link View#setVisibility(int)}
*
* @param pager
* @param current
* @param currentSrcRect
* @param next
* @param nextSrcRect
* @param topLeft indicate whether the next view is top-left of current view.
* @return
*/
public abstract Animator doForwardAnimation(InfinitePager pager, View current,
Rect currentSrcRect, View next,
Rect nextSrcRect, boolean topLeft);
/**
* restore views state
*
* @param current
* @param currentSrcRect
* @param next
* @param nextSrcRect
* @param topLeft indicate whether the next view is top-left of current view.
* @return
*/
public abstract Animator doBacwardAnimation(View current, Rect currentSrcRect,
View next, Rect nextSrcRect, boolean topLeft);
}
五、使用示例代码
psv = (InfinitePager) findViewById(R.id.page_psv);
psv.setSlideMode(InfinitePager.SlideMode.HORIZONTAL); // 设置滑动模式
psv.reverseContainer(false); // 反转容器(栈动画模式时设置为true , 其它为 false)
psv.enableIntervalLoop(3000); // 启动轮播模式,3000 ,轮播间隔时间
psv.setTransformer(new HorizontalScroll(500)); // 设置动画模式
psv.setMinmumLoopCount(10); // 设置最小循环数量(当数据量小于该值时,循环模式设置为true 无效)
六、与系统ViewPager比较分析(狠狠地鄙视一把,哈哈 O(∩_∩)O哈哈~)
系统提供的ViewPager无疑是很强大的,至少除了在无线循环效果不很满意,其他的都不错,下面主要谈谈自定义的 InfinitePager 优缺点:
优点: 功能统一、使用简单、各种模式之间任意切换、增加缓存网络大图加载时动画不会出现空白等。
缺点:由于丢失了部分位置信息,所以开发者必须在动画中控制View的移动(是滚动到前一个View,还是滚动到下一个View)。