Android源码解析--超好看的下拉刷新动画

superopen 9年前

来自: http://blog.csdn.net//lyhhj/article/details/48064001


本篇博客代码下载地址:https://github.com/Yalantis/Taurus

最近在github上看到了好多高端、大气、上档次的动画效果,如果给你的项目中加上这些动画,相信你的app一定很优秀,今天给大家分析一下来自Yalantis的一个超好看的下拉刷新动画。

首先我们看一下效果如何:



怎么样?是不是很高大上?接下来我们看一下代码:

一、首先我们需要自定义刷新的动态RefreshView(也就是下拉时候的头)

1.初始化头所占用的Dimens

private void initiateDimens() {          mScreenWidth = mContext.getResources().getDisplayMetrics().widthPixels;          mJetTopOffset = mParent.getTotalDragDistance() * 0.5f;          mTop = -mParent.getTotalDragDistance();      }

2.为头填充图片,设置图片的大小

分别为左边的云彩,右边的云彩,中间的云彩还有中间的飞机,飞机是带有动画的,下面会介绍飞机的动画
private void createBitmaps() {          mLeftClouds = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.clouds_left);          mRightClouds = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.clouds_right);          mFrontClouds = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.clouds_center);          mJet = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.airplane);            mJetWidthCenter = mJet.getWidth() / 2;          mJetHeightCenter = mJet.getHeight() / 2;          mFrontCloudWidthCenter = mFrontClouds.getWidth() / 2;          mFrontCloudHeightCenter = mFrontClouds.getHeight() / 2;            mRightCloudsWidthCenter = mRightClouds.getWidth() / 2;          mRightCloudsHeightCenter = mRightClouds.getHeight() / 2;          mLeftCloudsWidthCenter = mLeftClouds.getWidth() / 2;          mLeftCloudsHeightCenter = mLeftClouds.getHeight() / 2;      }

3.然后我们来画这个头

public void draw(@NonNull Canvas canvas) {          final int saveCount = canvas.save();            // DRAW BACKGROUND.          canvas.drawColor(mContext.getResources().getColor(R.color.sky_background));            if (isRefreshing) {              // Set up new set of wind              while (mWinds.size() < WIND_SET_AMOUNT) {                  float y = (float) (mParent.getTotalDragDistance() / (Math.random() * RANDOM_Y_COEFFICIENT));                  float x = random(MIN_WIND_X_OFFSET, MAX_WIND_X_OFFSET);                    // Magic with checking interval between winds                  if (mWinds.size() > 1) {                      y = 0;                      while (y == 0) {                          float tmp = (float) (mParent.getTotalDragDistance() / (Math.random() * RANDOM_Y_COEFFICIENT));                            for (Map.Entry<Float, Float> wind : mWinds.entrySet()) {                              // We want that interval will be greater than fifth part of draggable distance                              if (Math.abs(wind.getKey() - tmp) > mParent.getTotalDragDistance() / RANDOM_Y_COEFFICIENT) {                                  y = tmp;                              } else {                                  y = 0;                                  break;                              }                          }                      }                  }                    mWinds.put(y, x);                  drawWind(canvas, y, x);              }                // Draw current set of wind              if (mWinds.size() >= WIND_SET_AMOUNT) {                  for (Map.Entry<Float, Float> wind : mWinds.entrySet()) {                      drawWind(canvas, wind.getKey(), wind.getValue());                  }              }                // We should to create new set of winds              if (mInverseDirection && mNewWindSet) {                  mWinds.clear();                  mNewWindSet = false;                  mWindLineWidth = random(MIN_WIND_LINE_WIDTH, MAX_WIND_LINE_WIDTH);              }                // needed for checking direction              mLastAnimationTime = mLoadingAnimationTime;          }            drawJet(canvas);          drawSideClouds(canvas);          drawCenterClouds(canvas);            canvas.restoreToCount(saveCount);      }

/**       * Draw wind on loading animation       *       * @param canvas  - area where we will draw       * @param y       - y position fot one of lines       * @param xOffset - x offset for on of lines       */      private void drawWind(Canvas canvas, float y, float xOffset) {          /* We should multiply current animation time with this coefficient for taking all screen width in time          Removing slowing of animation with dividing on {@LINK #SLOW_DOWN_ANIMATION_COEFFICIENT}          And we should don't forget about distance that should "fly" line that depend on screen of device and x offset          */          float cof = (mScreenWidth + xOffset) / (LOADING_ANIMATION_COEFFICIENT / SLOW_DOWN_ANIMATION_COEFFICIENT);          float time = mLoadingAnimationTime;            // HORRIBLE HACK FOR REVERS ANIMATION THAT SHOULD WORK LIKE RESTART ANIMATION          if (mLastAnimationTime - mLoadingAnimationTime > 0) {              mInverseDirection = true;              // take time from 0 to end of animation time              time = (LOADING_ANIMATION_COEFFICIENT / SLOW_DOWN_ANIMATION_COEFFICIENT) - mLoadingAnimationTime;          } else {              mNewWindSet = true;              mInverseDirection = false;          }            // Taking current x position of drawing wind          // For fully disappearing of line we should subtract wind line width          float x = (mScreenWidth - (time * cof)) + xOffset - mWindLineWidth;          float xEnd = x + mWindLineWidth;            canvas.drawLine(x, y, xEnd, y, mWindPaint);      }        private void drawSideClouds(Canvas canvas) {          Matrix matrixLeftClouds = mMatrix;          Matrix matrixRightClouds = mAdditionalMatrix;          matrixLeftClouds.reset();          matrixRightClouds.reset();            // Drag percent will newer get more then 1 here          float dragPercent = Math.min(1f, Math.abs(mPercent));            boolean overdrag = false;            // But we check here for overdrag          if (mPercent > 1.0f) {              overdrag = true;          }            float scale;          float scalePercentDelta = dragPercent - SCALE_START_PERCENT;          if (scalePercentDelta > 0) {              float scalePercent = scalePercentDelta / (1.0f - SCALE_START_PERCENT);              scale = SIDE_CLOUDS_INITIAL_SCALE + (SIDE_CLOUDS_FINAL_SCALE - SIDE_CLOUDS_INITIAL_SCALE) * scalePercent;          } else {              scale = SIDE_CLOUDS_INITIAL_SCALE;          }            // Current y position of clouds          float dragYOffset = mParent.getTotalDragDistance() * (1.0f - dragPercent);            // Position where clouds fully visible on screen and we should drag them with content of listView          int cloudsVisiblePosition = mParent.getTotalDragDistance() / 2 - mLeftCloudsHeightCenter;            boolean needMoveCloudsWithContent = false;          if (dragYOffset < cloudsVisiblePosition) {              needMoveCloudsWithContent = true;          }            float offsetRightX = mScreenWidth - mRightClouds.getWidth();          float offsetRightY = (needMoveCloudsWithContent                  ? mParent.getTotalDragDistance() * dragPercent - mLeftClouds.getHeight()                  : dragYOffset)                  + (overdrag ? mTop : 0);            float offsetLeftX = 0;          float offsetLeftY = (needMoveCloudsWithContent                  ? mParent.getTotalDragDistance() * dragPercent - mLeftClouds.getHeight()                  : dragYOffset)                  + (overdrag ? mTop : 0);            // Magic with animation on loading process          if (isRefreshing) {              if (checkCurrentAnimationPart(AnimationPart.FIRST)) {                  offsetLeftY += getAnimationPartValue(AnimationPart.FIRST) / Y_SIDE_CLOUDS_SLOW_DOWN_COF;                  offsetRightX -= getAnimationPartValue(AnimationPart.FIRST) / X_SIDE_CLOUDS_SLOW_DOWN_COF;              } else if (checkCurrentAnimationPart(AnimationPart.SECOND)) {                  offsetLeftY += getAnimationPartValue(AnimationPart.SECOND) / Y_SIDE_CLOUDS_SLOW_DOWN_COF;                  offsetRightX -= getAnimationPartValue(AnimationPart.SECOND) / X_SIDE_CLOUDS_SLOW_DOWN_COF;              } else if (checkCurrentAnimationPart(AnimationPart.THIRD)) {                  offsetLeftY -= getAnimationPartValue(AnimationPart.THIRD) / Y_SIDE_CLOUDS_SLOW_DOWN_COF;                  offsetRightX += getAnimationPartValue(AnimationPart.THIRD) / X_SIDE_CLOUDS_SLOW_DOWN_COF;              } else if (checkCurrentAnimationPart(AnimationPart.FOURTH)) {                  offsetLeftY -= getAnimationPartValue(AnimationPart.FOURTH) / X_SIDE_CLOUDS_SLOW_DOWN_COF;                  offsetRightX += getAnimationPartValue(AnimationPart.FOURTH) / Y_SIDE_CLOUDS_SLOW_DOWN_COF;              }          }            matrixRightClouds.postScale(scale, scale, mRightCloudsWidthCenter, mRightCloudsHeightCenter);          matrixRightClouds.postTranslate(offsetRightX, offsetRightY);            matrixLeftClouds.postScale(scale, scale, mLeftCloudsWidthCenter, mLeftCloudsHeightCenter);          matrixLeftClouds.postTranslate(offsetLeftX, offsetLeftY);            canvas.drawBitmap(mLeftClouds, matrixLeftClouds, null);          canvas.drawBitmap(mRightClouds, matrixRightClouds, null);      }        private void drawCenterClouds(Canvas canvas) {          Matrix matrix = mMatrix;          matrix.reset();          float dragPercent = Math.min(1f, Math.abs(mPercent));            float scale;          float overdragPercent = 0;          boolean overdrag = false;            if (mPercent > 1.0f) {              overdrag = true;              // Here we want know about how mach percent of over drag we done              overdragPercent = Math.abs(1.0f - mPercent);          }            float scalePercentDelta = dragPercent - SCALE_START_PERCENT;          if (scalePercentDelta > 0) {              float scalePercent = scalePercentDelta / (1.0f - SCALE_START_PERCENT);              scale = CENTER_CLOUDS_INITIAL_SCALE + (CENTER_CLOUDS_FINAL_SCALE - CENTER_CLOUDS_INITIAL_SCALE) * scalePercent;          } else {              scale = CENTER_CLOUDS_INITIAL_SCALE;          }            float parallaxPercent = 0;          boolean parallax = false;          // Current y position of clouds          float dragYOffset = mParent.getTotalDragDistance() * dragPercent;          // Position when should start parallax scrolling          int startParallaxHeight = mParent.getTotalDragDistance() - mFrontCloudHeightCenter;            if (dragYOffset > startParallaxHeight) {              parallax = true;              parallaxPercent = dragYOffset - startParallaxHeight;          }            float offsetX = (mScreenWidth / 2) - mFrontCloudWidthCenter;          float offsetY = dragYOffset                  - (parallax ? mFrontCloudHeightCenter + parallaxPercent : mFrontCloudHeightCenter)                  + (overdrag ? mTop : 0);            float sx = overdrag ? scale + overdragPercent / 4 : scale;          float sy = overdrag ? scale + overdragPercent / 2 : scale;            if (isRefreshing && !overdrag) {              if (checkCurrentAnimationPart(AnimationPart.FIRST)) {                  sx = scale - (getAnimationPartValue(AnimationPart.FIRST) / LOADING_ANIMATION_COEFFICIENT) / 8;              } else if (checkCurrentAnimationPart(AnimationPart.SECOND)) {                  sx = scale - (getAnimationPartValue(AnimationPart.SECOND) / LOADING_ANIMATION_COEFFICIENT) / 8;              } else if (checkCurrentAnimationPart(AnimationPart.THIRD)) {                  sx = scale + (getAnimationPartValue(AnimationPart.THIRD) / LOADING_ANIMATION_COEFFICIENT) / 6;              } else if (checkCurrentAnimationPart(AnimationPart.FOURTH)) {                  sx = scale + (getAnimationPartValue(AnimationPart.FOURTH) / LOADING_ANIMATION_COEFFICIENT) / 6;              }              sy = sx;          }            matrix.postScale(sx, sy, mFrontCloudWidthCenter, mFrontCloudHeightCenter);          matrix.postTranslate(offsetX, offsetY);            canvas.drawBitmap(mFrontClouds, matrix, null);      }        private void drawJet(Canvas canvas) {          Matrix matrix = mMatrix;          matrix.reset();            float dragPercent = mPercent;          float rotateAngle = 0;            // Check overdrag          if (dragPercent > 1.0f && !mEndOfRefreshing) {              rotateAngle = (dragPercent % 1) * 10;              dragPercent = 1.0f;          }            float offsetX = ((mScreenWidth * dragPercent) / 2) - mJetWidthCenter;            float offsetY = mJetTopOffset                  + (mParent.getTotalDragDistance() / 2)                  * (1.0f - dragPercent)                  - mJetHeightCenter;            if (isRefreshing) {              if (checkCurrentAnimationPart(AnimationPart.FIRST)) {                  offsetY -= getAnimationPartValue(AnimationPart.FIRST);              } else if (checkCurrentAnimationPart(AnimationPart.SECOND)) {                  offsetY -= getAnimationPartValue(AnimationPart.SECOND);              } else if (checkCurrentAnimationPart(AnimationPart.THIRD)) {                  offsetY += getAnimationPartValue(AnimationPart.THIRD);              } else if (checkCurrentAnimationPart(AnimationPart.FOURTH)) {                  offsetY += getAnimationPartValue(AnimationPart.FOURTH);              }          }            matrix.setTranslate(offsetX, offsetY);            if (dragPercent == 1.0f) {              matrix.preRotate(rotateAngle, mJetWidthCenter, mJetHeightCenter);          }            canvas.drawBitmap(mJet, matrix, null);      }

动画效果已经画好了,下面我们来看看怎么结合下拉刷新来调用吧?

二、我们还需要自定义一个PullToRefreshView(下拉刷新)

1.我们的PullToRefreshView这里需要继承ViewGroup

我们先把刚才定义的刷新时的动画加进来
private RefreshView mRefreshView;
<pre name="code" class="java">private ImageView mRefreshImageView;
<pre name="code" class="java">mRefreshImageView = new ImageView(context);          mRefreshView = new RefreshView(getContext(), this);          mRefreshImageView.setImageDrawable(mRefreshView);            addView(mRefreshImageView);
 

2.然后我们设置OntouchEvent()事件

@Override      public boolean onTouchEvent(@NonNull MotionEvent ev) {            if (!mIsBeingDragged) {              return super.onTouchEvent(ev);          }            final int action = MotionEventCompat.getActionMasked(ev);            switch (action) {              case MotionEvent.ACTION_MOVE: {                  final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);                  if (pointerIndex < 0) {                      return false;                  }                    final float y = MotionEventCompat.getY(ev, pointerIndex);                  final float yDiff = y - mInitialMotionY;                  final float scrollTop = yDiff * DRAG_RATE;                  mCurrentDragPercent = scrollTop / mTotalDragDistance;                  if (mCurrentDragPercent < 0) {                      return false;                  }                  float boundedDragPercent = Math.min(1f, Math.abs(mCurrentDragPercent));                  float extraOS = Math.abs(scrollTop) - mTotalDragDistance;                  float slingshotDist = mTotalDragDistance;                  float tensionSlingshotPercent = Math.max(0,                          Math.min(extraOS, slingshotDist * 2) / slingshotDist);                  float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow(                          (tensionSlingshotPercent / 4), 2)) * 2f;                  float extraMove = (slingshotDist) * tensionPercent / 2;                  int targetY = (int) ((slingshotDist * boundedDragPercent) + extraMove);                    mRefreshView.setPercent(mCurrentDragPercent);                  setTargetOffsetTop(targetY - mCurrentOffsetTop, true);                  break;              }              case MotionEventCompat.ACTION_POINTER_DOWN:                  final int index = MotionEventCompat.getActionIndex(ev);                  mActivePointerId = MotionEventCompat.getPointerId(ev, index);                  break;              case MotionEventCompat.ACTION_POINTER_UP:                  onSecondaryPointerUp(ev);                  break;              case MotionEvent.ACTION_UP:              case MotionEvent.ACTION_CANCEL: {                  if (mActivePointerId == INVALID_POINTER) {                      return false;                  }                  final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);                  final float y = MotionEventCompat.getY(ev, pointerIndex);                  final float overScrollTop = (y - mInitialMotionY) * DRAG_RATE;                  mIsBeingDragged = false;                  if (overScrollTop > mTotalDragDistance) {                      setRefreshing(true, true);                  } else {                      mRefreshing = false;                      animateOffsetToPosition(mAnimateToStartPosition);                  }                  mActivePointerId = INVALID_POINTER;                  return false;              }          }            return true;      }

三、最后我们看怎样在Activity中使用这个下拉刷新控件

1.先看一下布局文件

这里是我们的下拉刷新空间嵌套着我们的ListView,然后我们再给ListView填充数据即可
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"      xmlns:tools="http://schemas.android.com/tools"      android:layout_width="match_parent"      android:layout_height="match_parent"      tools:context=".PullToRefreshActivity">        <com.hankkin.AnimationPullToRefreshDemo.PullToRefreshView          android:id="@+id/pull_to_refresh"          android:layout_width="match_parent"          android:layout_height="match_parent">            <ListView              android:id="@+id/list_view"              android:divider="@null"              android:dividerHeight="0dp"              android:fadingEdge="none"              android:layout_width="match_parent"              android:layout_height="match_parent" />        </com.hankkin.AnimationPullToRefreshDemo.PullToRefreshView>    </RelativeLayout>

2.为ListView填充数据

为了我们的效果比较好看,这里我们给ListView的每一个item填充不同的颜色,看起来会比较高大上。
Map<String, Integer> map;          List<Map<String, Integer>> sampleList = new ArrayList<Map<String, Integer>>();              int[] colors = {                  R.color.saffron,                  R.color.eggplant,                  R.color.sienna};            int[] tripNames = {                  R.string.trip_to_india,                  R.string.trip_to_italy,                  R.string.trip_to_indonesia};            for (int i = 0; i < tripNames.length; i++) {              map = new HashMap<String, Integer>();              map.put(SampleAdapter.KEY_NAME, tripNames[i]);              map.put(SampleAdapter.KEY_COLOR, colors[i]);              sampleList.add(map);          }            ListView listView = (ListView) findViewById(R.id.list_view);          listView.setAdapter(new SampleAdapter(this, R.layout.list_item, sampleList));

3.最后,我们再设置一下下拉刷新的监听事件就OK了

mPullToRefreshView = (PullToRefreshView) findViewById(R.id.pull_to_refresh);          mPullToRefreshView.setOnRefreshListener(new PullToRefreshView.OnRefreshListener() {              @Override              public void onRefresh() {                  mPullToRefreshView.postDelayed(new Runnable() {                      @Override                      public void run() {                          mPullToRefreshView.setRefreshing(false);                      }                  }, REFRESH_DELAY);              }          });
怎么样?有没有很高大上啊?
说明:
自定义View里面的一些动画效果,包括飞机的动画效果,风的动画效果和一些方法没有详细介绍,有兴趣的小伙伴可以到github上下载源码仔细研究一下,作者写的还是比较不错的,很佩服。如果一些小伙伴还没有用惯AndroidStudio,这里也有Idea版本的,用Eclise同样可以打开运行看效果的。
下载地址:

http://www.eoeandroid.com/thread-905093-1-1.html