[关闭]
@TryLoveCatch 2017-04-20T16:03:27.000000Z 字数 7590 阅读 1804

ViewPager背景渐变

android 自定义View 实例 ViewPager


需求

实现ViewPager滑动的时候,背景渐变的过程。

实现1.0

主要需要用到一下的知识点

ArgbEvaluator

Android提供的Color属性估值器,它可以估算两个颜色值之间,任意部分的色值。所以背景的颜色渐变,可以用这个来实现。

ValueAnimator.setCurrentPlayTime

这个方法,我的理解就是,会根据传入的时间,来得到这个时间对应的Value,并且会触发onAnimationUpdate()回调,然后就会停止,除非再次调用setCurrentPlayTime()方法。

ViewPager.OnPageChangeListener

  1. @Override
  2. public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
  3. }
  4. @Override
  5. public void onPageSelected(int position) {
  6. }
  7. @Override
  8. public void onPageScrollStateChanged(int state) {
  9. }

先说一下onPageScrolled()方法:
假设有A和B两个pager,A-->B,打印如下:

  1. onPageScrolled====position: 0, positionOffset: 0.05185185, positionOffsetPixels: 55
  2. onPageScrolled====position: 0, positionOffset: 0.13703704, positionOffsetPixels: 148
  3. onPageScrolled====position: 0, positionOffset: 0.25277779, positionOffsetPixels: 273
  4. onPageScrolled====position: 0, positionOffset: 0.37222221, positionOffsetPixels: 402
  5. onPageScrolled====position: 0, positionOffset: 0.43333334, positionOffsetPixels: 468
  6. onPageScrolled====position: 0, positionOffset: 0.46944445, positionOffsetPixels: 507
  7. onPageScrolled====position: 0, positionOffset: 0.63055557, positionOffsetPixels: 681
  8. onPageScrolled====position: 0, positionOffset: 0.9990741, positionOffsetPixels: 1079
  9. onPageScrolled====position: 1, positionOffset: 0.0, positionOffsetPixels: 0

然后,B-->A,打印如下:

  1. onPageScrolled====position: 0, positionOffset: 0.962963, positionOffsetPixels: 1040
  2. onPageScrolled====position: 0, positionOffset: 0.9, positionOffsetPixels: 972
  3. onPageScrolled====position: 0, positionOffset: 0.83518517, positionOffsetPixels: 902
  4. onPageScrolled====position: 0, positionOffset: 0.79907405, positionOffsetPixels: 863
  5. onPageScrolled====position: 0, positionOffset: 0.7814815, positionOffsetPixels: 844
  6. onPageScrolled====position: 0, positionOffset: 0.64074075, positionOffsetPixels: 692
  7. onPageScrolled====position: 0, positionOffset: 0.5277778, positionOffsetPixels: 570
  8. onPageScrolled====position: 0, positionOffset: 0.42037037, positionOffsetPixels: 454
  9. onPageScrolled====position: 0, positionOffset: 9.259259E-4, positionOffsetPixels: 1
  10. onPageScrolled====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、positionOffsetposition对应页面已经滑动了多少,也可以理解为,该页面现在看不见的地方所占的比例,取值范围[0~1],当为0的时候,表明显示出来了,当为1的时候,表示完全看不见了。
5、positionOffsetPixels:同positionOffset,只是单位变成了像素。

再来看一下onPageScrollStateChanged()方法:

A->B和B->A都是一样的,也无论翻页成功与否,基本上打印如下:

  1. onPageScrollStateChanged====state: 1
  2. onPageScrollStateChanged====state: 2
  3. onPageScrollStateChanged====state: 0

参数state有三个取值:
SCROLL_STATE_IDLE,即0,表示页面处于闲置、稳定状态,即没被拖动也没惯性滑动;
SCROLL_STATE_DRAGGING,即1,表示页面正在被用户拖动,即手指正在拖动状态,还未抬起;
SCROLL_STATE_SETTLING,即2,表示页面处于即将到达最终状态的过程,即手指松开后惯性滑动状态。

最后说一下onPageSelected()方法:

A->B和B->A都是一样的,完整打印如下:

  1. onPageScrollStateChanged====state: 1
  2. onPageScrolled====position: 0, positionOffset: 0.01759255, positionOffsetPixels: 18
  3. onPageScrolled====position: 0, positionOffset: 0.1907407, positionOffsetPixels: 205
  4. onPageScrollStateChanged====state: 2
  5. onPageSelected====position: 1
  6. onPageScrolled====position: 0, positionOffset: 0.2805556, positionOffsetPixels: 303
  7. onPageScrolled====position: 0, positionOffset: 0.9990741, positionOffsetPixels: 1079
  8. onPageScrolled====position: 1, positionOffset: 0.0, positionOffsetPixels: 0
  9. onPageScrollStateChanged====state: 0

1、只有在翻页成功才会调用
2、在onPageScrollStateChanged()SCROLL_STATE_SETTLING之后调用,当符合1的时候。
3、onPageSelected()调用之后,onPageScrolled()依然会执行一段时间
4、onPageScrollStateChanged()最先执行,而且也会在最后再执行一次。
5、只有在手指抬起,才会调用onPageScrollStateChanged()SCROLL_STATE_SETTLINGonPageSelected()

主要代码

  1. public void seek(long seekTime) {
  2. colorAnim.setCurrentPlayTime(seekTime);
  3. }
  4. private void createAnimation(Object[] pColors) {
  5. if (colorAnim == null) {
  6. colorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), pColors);
  7. colorAnim.setDuration(DURATION);
  8. colorAnim.addUpdateListener(this);
  9. }
  10. }
  11. @Override
  12. public void onAnimationUpdate(ValueAnimator animation) {
  13. mCurrentColor = (int) animation.getAnimatedValue();
  14. invalidate();
  15. }
  16. @Override
  17. protected void onDraw(Canvas canvas) {
  18. super.onDraw(canvas);
  19. if(mCurrentColor == -1){
  20. return;
  21. }
  22. setBackgroundColor(mCurrentColor);
  23. }

根据传入的colors创建ValueAnimator,其中用到了ArgbEvaluatorColor属性估值器,注意我们并没有start这个动画,我们通过外部调用seek()方法,来触发ValueAnimator.setCurrentPlayTime()方法,然后这个方法会调用onAnimationUpdate()回调,我们就刷新一次,重新画出背景。调用代码如下:

  1. mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
  2. @Override
  3. public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
  4. handleBgAnim(position, positionOffset);
  5. }
  6. });
  7. private void handleBgAnim(int pPosition, float pPositionOffset) {
  8. int count = mArrViews.size() - 1;
  9. if (count != 0) {
  10. float length = (pPosition + pPositionOffset) / count;
  11. int progress = (int) (length * ColorAnimationView.DURATION);
  12. mColorAnimationView.seek(progress);
  13. }
  14. }

seek()方法需要传入一个时间进度,所以我们通过onPageScrolled()方法的参数除以pager的个数,得到一个比值,然后乘以动画持续时间,得到当前的时间进度。

实现2.0

后来需求更改了,背景改成渐变色了,不再是纯色,所以就不能用ArgbEvaluator了。其实,渐变色无非就是起始色和结束色,多了一个颜色而已,我们可以自定义TypeEvaluator来实现,代码如下:

  1. public static class Color {
  2. public int startColor;
  3. public int endColor;
  4. }
  5. private class MyEvaluator implements TypeEvaluator<Color> {
  6. public Color evaluate(float fraction, Color startValue, Color endValue) {
  7. Color tColor = new Color();
  8. tColor.startColor = createColor(fraction, startValue.startColor, endValue.startColor);
  9. tColor.endColor = createColor(fraction, startValue.startColor, endValue.startColor);
  10. return tColor;
  11. }
  12. private int createColor(float fraction, int pStartColor, int pEndColor) {
  13. int startA = (pStartColor >> 24) & 0xff;
  14. int startR = (pStartColor >> 16) & 0xff;
  15. int startG = (pStartColor >> 8) & 0xff;
  16. int startB = pStartColor & 0xff;
  17. int endA = (pEndColor >> 24) & 0xff;
  18. int endR = (pEndColor >> 16) & 0xff;
  19. int endG = (pEndColor >> 8) & 0xff;
  20. int endB = pEndColor & 0xff;
  21. return (int) ((startA + (int) (fraction * (endA - startA))) << 24)
  22. | (int) ((startR + (int) (fraction * (endR - startR))) << 16)
  23. | (int) ((startG + (int) (fraction * (endG - startG))) << 8)
  24. | (int) ((startB + (int) (fraction * (endB - startB))));
  25. }
  26. }

MyEvaluator类其实就是ArgbEvaluator的代码,只是ArgbEvaluator返回一个color,我们返回了一个对象,具体逻辑一模一样。

然后修改原来的使用逻辑:

  1. private void createAnimation(Object[] pColors) {
  2. if (colorAnim == null) {
  3. colorAnim = ValueAnimator.ofObject(new MyEvaluator(), pColors);
  4. colorAnim.setDuration(DURATION);
  5. colorAnim.addUpdateListener(this);
  6. }
  7. }
  8. @Override
  9. public void onAnimationUpdate(ValueAnimator animation) {
  10. mCurrentColor = (Color) animation.getAnimatedValue();
  11. invalidate();
  12. }
  13. @Override
  14. protected void onDraw(Canvas canvas) {
  15. super.onDraw(canvas);
  16. if (mCurrentColor == null) {
  17. return;
  18. }
  19. mDrawable = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM,
  20. new int[] {mCurrentColor.startColor, mCurrentColor.endColor});
  21. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
  22. setBackground(mDrawable);
  23. } else {
  24. setBackgroundDrawable(mDrawable);
  25. }
  26. }

Evaluator&Interpolator

关于这两个,我后面会专门写动画相关的文章来详细展开介绍。

问题

编译的时候有一个警告:

警告: 最后一个参数使用了不准确的变量类型的 varargs 方法的非 varargs 调用;
对于 varargs 调用, 应使用 Object
对于非 varargs 调用, 应使用 Object[], 这样也可以抑制此警告

虽然不影响运行,但是确实很不爽,报错的位置如下:

  1. private void createAnimation(Color[] pColors) {
  2. if (colorAnim == null) {
  3. colorAnim = ValueAnimator.ofObject(new MyEvaluator(), pColors);
  4. colorAnim.setDuration(DURATION);
  5. colorAnim.addUpdateListener(this);
  6. }
  7. }

因为ValueAnimator.ofObject第二参数是可变参数,我传入的是Color[]数组,所以就报错了,其实,修改办法也很简单,改为Object[]即可,如下:

  1. private void createAnimation(Object[] pColors) {
  2. if (colorAnim == null) {
  3. colorAnim = ValueAnimator.ofObject(new MyEvaluator(), pColors);
  4. colorAnim.setDuration(DURATION);
  5. colorAnim.addUpdateListener(this);
  6. }
  7. }

效果如下

代码地址

ViewPagerDemo

Demo下载

Demo下载

参考

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

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注