android 自写 ViewPager

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)。



完整项目代码




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值