本篇博客代码下载地址: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