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

来源:互联网 发布:快手上的软件 编辑:程序博客网 时间:2024/04/29 14:31

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








2 0
原创粉丝点击