com.nicodelee.ptr.header.StickinessView.java Source code

Java tutorial

Introduction

Here is the source code for com.nicodelee.ptr.header.StickinessView.java

Source

/**
 * Copyright 2015 bingoogolapple
 * <p/>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p/>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p/>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.nicodelee.ptr.header;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import com.nicodelee.util.Logger;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.ValueAnimator;

/**
 * : :bingoogolapple@gmail.com
 * :15/5/21 22:34
 * ??:?
 */
public class StickinessView extends View {
    private RectF mTopBound;
    private RectF mBottomBound;
    private Rect mRotateDrawableBound;
    private Point mCenterPoint;

    private Paint mPaint;
    private Path mPath;

    private Drawable mRotateDrawable;
    /**
     * ?
     */
    private int mRotateDrawableSize;

    private int mMaxBottomHeight;
    private int mCurrentBottomHeight = 0;

    /**
     * ?
     */
    private boolean mIsRotating = false;
    private boolean mIsRefreshing = false;
    /**
     * ?
     */
    private int mCurrentDegree = 0;

    private int mEdge = 0;
    private int mTopSize = 0;

    public StickinessView(Context context) {
        this(context, null);
    }

    public StickinessView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public StickinessView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initBounds();
        initPaint();
        initSize();
    }

    private void initBounds() {
        mTopBound = new RectF();
        mBottomBound = new RectF();
        mRotateDrawableBound = new Rect();
        mCenterPoint = new Point();
    }

    private void initPaint() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPath = new Path();
    }

    private void initSize() {
        mEdge = dp2px(getContext(), 5);
        mRotateDrawableSize = dp2px(getContext(), 30);
        mTopSize = mRotateDrawableSize + 2 * mEdge;

        mMaxBottomHeight = (int) (2.4f * mRotateDrawableSize);
    }

    public void setStickinessColor(@ColorRes int resId) {
        mPaint.setColor(getResources().getColor(resId));
    }

    public void setRotateImage(@DrawableRes int resId) {
        mRotateDrawable = getResources().getDrawable(resId);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = mTopSize + getPaddingLeft() + getPaddingRight();
        int height = mTopSize + getPaddingTop() + getPaddingBottom() + mMaxBottomHeight;

        setMeasuredDimension(width, height);
        measureDraw();
    }

    private void measureDraw() {
        mCenterPoint.x = getMeasuredWidth() / 2;
        mCenterPoint.y = getMeasuredHeight() / 2;

        mTopBound.left = mCenterPoint.x - mTopSize / 2;
        mTopBound.right = mTopBound.left + mTopSize;
        mTopBound.bottom = getMeasuredHeight() - getPaddingBottom() - mCurrentBottomHeight;
        mTopBound.top = mTopBound.bottom - mTopSize;

        float scale = 1.0f - mCurrentBottomHeight * 1.0f / mMaxBottomHeight;
        scale = Math.min(Math.max(scale, 0.2f), 1.0f);
        int mBottomSize = (int) (mTopSize * scale);

        mBottomBound.left = mCenterPoint.x - mBottomSize / 2;
        mBottomBound.right = mBottomBound.left + mBottomSize;
        mBottomBound.bottom = mTopBound.bottom + mCurrentBottomHeight;
        mBottomBound.top = mBottomBound.bottom - mBottomSize;
    }

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mRotateDrawable == null) {
            return;
        }
        mPath.reset();
        mTopBound.round(mRotateDrawableBound);
        mRotateDrawable.setBounds(mRotateDrawableBound);
        if (mIsRotating) {
            mPath.addOval(mTopBound, Path.Direction.CW);
            canvas.drawPath(mPath, mPaint);
            canvas.save();
            canvas.rotate(mCurrentDegree, mRotateDrawable.getBounds().centerX(),
                    mRotateDrawable.getBounds().centerY());
            mRotateDrawable.draw(canvas);
            canvas.restore();
        } else {
            // drawable
            mPath.moveTo(mTopBound.left, mTopBound.top + mTopSize / 2);
            // drawable?
            mPath.arcTo(mTopBound, 180, 180);
            // ?
            //            mPath.quadTo(mTopBound.right - mTopSize / 8, mTopBound.bottom, mBottomBound.right, mBottomBound.bottom - mBottomBound.height() / 2);

            // mCurrentBottomHeight   0  mMaxBottomHeight
            // scale                  0.2  1
            float scale = Math.max(mCurrentBottomHeight * 1.0f / mMaxBottomHeight, 0.2f);

            float bottomControlXOffset = mTopSize * ((3 + (float) Math.pow(scale, 7) * 16) / 32);
            float bottomControlY = mTopBound.bottom / 2 + mCenterPoint.y / 2;
            // ???
            mPath.cubicTo(mTopBound.right - mTopSize / 8, mTopBound.bottom, mTopBound.right - bottomControlXOffset,
                    bottomControlY, mBottomBound.right, mBottomBound.bottom - mBottomBound.height() / 2);

            mPath.arcTo(mBottomBound, 0, 180);

            //            mPath.quadTo(mTopBound.left + mTopSize / 8, mTopBound.bottom, mTopBound.left, mTopBound.bottom - mTopSize / 2);
            mPath.cubicTo(mTopBound.left + bottomControlXOffset, bottomControlY, mTopBound.left + mTopSize / 8,
                    mTopBound.bottom, mTopBound.left, mTopBound.bottom - mTopSize / 2);

            canvas.drawPath(mPath, mPaint);

            mRotateDrawable.draw(canvas);
        }
    }

    public void setMoveYDistance(int moveYDistance) {
        int bottomHeight = moveYDistance - mTopSize - getPaddingBottom() - getPaddingTop();
        Logger.e(String.format("moveYDistance:%s,bottomHeight:%s", moveYDistance, bottomHeight));
        if (bottomHeight > 0) {
            mCurrentBottomHeight = bottomHeight;
        } else {
            mCurrentBottomHeight = 0;
        }
        postInvalidate();
    }

    /**
     * ???
     */
    public boolean canChangeToRefreshing() {
        return mCurrentBottomHeight >= mMaxBottomHeight * 0.98f;
    }

    public void startRefreshing(final View headView) {
        ValueAnimator animator = ValueAnimator.ofInt(mCurrentBottomHeight, 0);
        animator.setDuration(500);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurrentBottomHeight = (int) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                mIsRefreshing = true;
                Logger.e("mCurrentBottomHeight=" + mCurrentBottomHeight);
                if (mCurrentBottomHeight != 0) {
                    startChangeWholeHeaderViewPaddingTop(headView, mCurrentBottomHeight);
                } else {
                    startChangeWholeHeaderViewPaddingTop(headView,
                            -(mTopSize + getPaddingTop() + getPaddingBottom()));
                }
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                mIsRotating = true;
                startRotating();
            }

            @Override
            public void onAnimationCancel(Animator animation) {
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }
        });
        animator.start();
    }

    public void startChangeWholeHeaderViewPaddingTop(final View mWholeHeaderView, int distance) {
        ValueAnimator animator = ValueAnimator.ofInt(mWholeHeaderView.getPaddingTop(),
                mWholeHeaderView.getPaddingTop() - distance);
        animator.setDuration(5000);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int paddingTop = (int) animation.getAnimatedValue();
                mWholeHeaderView.setPadding(0, paddingTop, 0, 0);
            }
        });
        animator.start();
    }

    private void startRotating() {
        ViewCompat.postOnAnimation(this, new Runnable() {
            @Override
            public void run() {
                mCurrentDegree += 10;
                if (mCurrentDegree > 360) {
                    mCurrentDegree = 0;
                }
                if (mIsRefreshing) {
                    startRotating();
                }
                postInvalidate();
            }
        });
    }

    public void stopRefresh() {
        mIsRotating = true;
        mIsRefreshing = false;
        postInvalidate();
    }

    public void smoothToIdle() {
        ValueAnimator animator = ValueAnimator.ofInt(mCurrentBottomHeight, 0);
        animator.setDuration(500);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurrentBottomHeight = (int) animation.getAnimatedValue();
            }
        });
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                mIsRotating = false;
            }

            @Override
            public void onAnimationCancel(Animator animation) {
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }
        });
        animator.start();
    }

    private int dp2px(Context context, int dpValue) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue,
                context.getResources().getDisplayMetrics());
    }
}