@TryLoveCatch
2017-04-20T08:03:27.000000Z
字数 7590
阅读 2088
android 自定义View 实例 ViewPager
实现ViewPager滑动的时候,背景渐变的过程。
主要需要用到一下的知识点
Android提供的Color属性估值器,它可以估算两个颜色值之间,任意部分的色值。所以背景的颜色渐变,可以用这个来实现。
这个方法,我的理解就是,会根据传入的时间,来得到这个时间对应的Value,并且会触发onAnimationUpdate()回调,然后就会停止,除非再次调用setCurrentPlayTime()方法。
@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}@Overridepublic void onPageSelected(int position) {}@Overridepublic void onPageScrollStateChanged(int state) {}
先说一下onPageScrolled()方法:
假设有A和B两个pager,A-->B,打印如下:
onPageScrolled====position: 0, positionOffset: 0.05185185, positionOffsetPixels: 55onPageScrolled====position: 0, positionOffset: 0.13703704, positionOffsetPixels: 148onPageScrolled====position: 0, positionOffset: 0.25277779, positionOffsetPixels: 273onPageScrolled====position: 0, positionOffset: 0.37222221, positionOffsetPixels: 402onPageScrolled====position: 0, positionOffset: 0.43333334, positionOffsetPixels: 468onPageScrolled====position: 0, positionOffset: 0.46944445, positionOffsetPixels: 507onPageScrolled====position: 0, positionOffset: 0.63055557, positionOffsetPixels: 681onPageScrolled====position: 0, positionOffset: 0.9990741, positionOffsetPixels: 1079onPageScrolled====position: 1, positionOffset: 0.0, positionOffsetPixels: 0
然后,B-->A,打印如下:
onPageScrolled====position: 0, positionOffset: 0.962963, positionOffsetPixels: 1040onPageScrolled====position: 0, positionOffset: 0.9, positionOffsetPixels: 972onPageScrolled====position: 0, positionOffset: 0.83518517, positionOffsetPixels: 902onPageScrolled====position: 0, positionOffset: 0.79907405, positionOffsetPixels: 863onPageScrolled====position: 0, positionOffset: 0.7814815, positionOffsetPixels: 844onPageScrolled====position: 0, positionOffset: 0.64074075, positionOffsetPixels: 692onPageScrolled====position: 0, positionOffset: 0.5277778, positionOffsetPixels: 570onPageScrolled====position: 0, positionOffset: 0.42037037, positionOffsetPixels: 454onPageScrolled====position: 0, positionOffset: 9.259259E-4, positionOffsetPixels: 1onPageScrolled====position: 0, positionOffset: 0.0, positionOffsetPixels: 0
根据以上打印,我们可以推断出来:
1、滑动过程中不断被调用
2、无论从左到右,还是从右到左滑动,onPageScrolled的参数都表示前一页的数据。A->B还是B->A都表述的是A的数据,只有在A->B翻页成功并且结束的时候,会打印B的数据:position: 1, positionOffset: 0.0, positionOffsetPixels: 0
3、position: 表示后面两个参数对应的是那一页。并不一定是当前的页面。例如,A->B的时候,position打印的是0,但是B->A的时候,position打印的依然是0。
4、positionOffset:position对应页面已经滑动了多少,也可以理解为,该页面现在看不见的地方所占的比例,取值范围[0~1],当为0的时候,表明显示出来了,当为1的时候,表示完全看不见了。
5、positionOffsetPixels:同positionOffset,只是单位变成了像素。
再来看一下onPageScrollStateChanged()方法:
A->B和B->A都是一样的,也无论翻页成功与否,基本上打印如下:
onPageScrollStateChanged====state: 1onPageScrollStateChanged====state: 2onPageScrollStateChanged====state: 0
参数
state有三个取值:
SCROLL_STATE_IDLE,即0,表示页面处于闲置、稳定状态,即没被拖动也没惯性滑动;
SCROLL_STATE_DRAGGING,即1,表示页面正在被用户拖动,即手指正在拖动状态,还未抬起;
SCROLL_STATE_SETTLING,即2,表示页面处于即将到达最终状态的过程,即手指松开后惯性滑动状态。
最后说一下onPageSelected()方法:
A->B和B->A都是一样的,完整打印如下:
onPageScrollStateChanged====state: 1onPageScrolled====position: 0, positionOffset: 0.01759255, positionOffsetPixels: 18onPageScrolled====position: 0, positionOffset: 0.1907407, positionOffsetPixels: 205onPageScrollStateChanged====state: 2onPageSelected====position: 1onPageScrolled====position: 0, positionOffset: 0.2805556, positionOffsetPixels: 303onPageScrolled====position: 0, positionOffset: 0.9990741, positionOffsetPixels: 1079onPageScrolled====position: 1, positionOffset: 0.0, positionOffsetPixels: 0onPageScrollStateChanged====state: 0
1、只有在翻页成功才会调用
2、在onPageScrollStateChanged()为SCROLL_STATE_SETTLING之后调用,当符合1的时候。
3、onPageSelected()调用之后,onPageScrolled()依然会执行一段时间
4、onPageScrollStateChanged()最先执行,而且也会在最后再执行一次。
5、只有在手指抬起,才会调用onPageScrollStateChanged()为SCROLL_STATE_SETTLING和onPageSelected()
public void seek(long seekTime) {colorAnim.setCurrentPlayTime(seekTime);}private void createAnimation(Object[] pColors) {if (colorAnim == null) {colorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), pColors);colorAnim.setDuration(DURATION);colorAnim.addUpdateListener(this);}}@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mCurrentColor = (int) animation.getAnimatedValue();invalidate();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if(mCurrentColor == -1){return;}setBackgroundColor(mCurrentColor);}
根据传入的colors创建ValueAnimator,其中用到了ArgbEvaluatorColor属性估值器,注意我们并没有start这个动画,我们通过外部调用seek()方法,来触发ValueAnimator.setCurrentPlayTime()方法,然后这个方法会调用onAnimationUpdate()回调,我们就刷新一次,重新画出背景。调用代码如下:
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {handleBgAnim(position, positionOffset);}});private void handleBgAnim(int pPosition, float pPositionOffset) {int count = mArrViews.size() - 1;if (count != 0) {float length = (pPosition + pPositionOffset) / count;int progress = (int) (length * ColorAnimationView.DURATION);mColorAnimationView.seek(progress);}}
seek()方法需要传入一个时间进度,所以我们通过onPageScrolled()方法的参数除以pager的个数,得到一个比值,然后乘以动画持续时间,得到当前的时间进度。
后来需求更改了,背景改成渐变色了,不再是纯色,所以就不能用ArgbEvaluator了。其实,渐变色无非就是起始色和结束色,多了一个颜色而已,我们可以自定义TypeEvaluator来实现,代码如下:
public static class Color {public int startColor;public int endColor;}private class MyEvaluator implements TypeEvaluator<Color> {public Color evaluate(float fraction, Color startValue, Color endValue) {Color tColor = new Color();tColor.startColor = createColor(fraction, startValue.startColor, endValue.startColor);tColor.endColor = createColor(fraction, startValue.startColor, endValue.startColor);return tColor;}private int createColor(float fraction, int pStartColor, int pEndColor) {int startA = (pStartColor >> 24) & 0xff;int startR = (pStartColor >> 16) & 0xff;int startG = (pStartColor >> 8) & 0xff;int startB = pStartColor & 0xff;int endA = (pEndColor >> 24) & 0xff;int endR = (pEndColor >> 16) & 0xff;int endG = (pEndColor >> 8) & 0xff;int endB = pEndColor & 0xff;return (int) ((startA + (int) (fraction * (endA - startA))) << 24)| (int) ((startR + (int) (fraction * (endR - startR))) << 16)| (int) ((startG + (int) (fraction * (endG - startG))) << 8)| (int) ((startB + (int) (fraction * (endB - startB))));}}
MyEvaluator类其实就是ArgbEvaluator的代码,只是ArgbEvaluator返回一个color,我们返回了一个对象,具体逻辑一模一样。
然后修改原来的使用逻辑:
private void createAnimation(Object[] pColors) {if (colorAnim == null) {colorAnim = ValueAnimator.ofObject(new MyEvaluator(), pColors);colorAnim.setDuration(DURATION);colorAnim.addUpdateListener(this);}}@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mCurrentColor = (Color) animation.getAnimatedValue();invalidate();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (mCurrentColor == null) {return;}mDrawable = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM,new int[] {mCurrentColor.startColor, mCurrentColor.endColor});if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {setBackground(mDrawable);} else {setBackgroundDrawable(mDrawable);}}
关于这两个,我后面会专门写动画相关的文章来详细展开介绍。
Interpolator时间插值器,作用是根据时间流逝的百分比来计算当前属性值改变的百分比
Evaluator 类型估值算法,也叫估值器.作用是根据当前属性改变的百分比来计算改变后的属性值.系统预置的估值器有:
编译的时候有一个警告:
警告: 最后一个参数使用了不准确的变量类型的 varargs 方法的非 varargs 调用;
对于 varargs 调用, 应使用 Object
对于非 varargs 调用, 应使用 Object[], 这样也可以抑制此警告
虽然不影响运行,但是确实很不爽,报错的位置如下:
private void createAnimation(Color[] pColors) {if (colorAnim == null) {colorAnim = ValueAnimator.ofObject(new MyEvaluator(), pColors);colorAnim.setDuration(DURATION);colorAnim.addUpdateListener(this);}}
因为ValueAnimator.ofObject第二参数是可变参数,我传入的是Color[]数组,所以就报错了,其实,修改办法也很简单,改为Object[]即可,如下:
private void createAnimation(Object[] pColors) {if (colorAnim == null) {colorAnim = ValueAnimator.ofObject(new MyEvaluator(), pColors);colorAnim.setDuration(DURATION);colorAnim.addUpdateListener(this);}}

Android特效专辑(二)——ViewPager渲染背景颜色渐变(引导页)
Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法
Android-开发艺术探索》-07-Andriod动画深入分析
[Parallax Animation]实现知乎 Android 客户端启动页视差滚动效果