仿即刻的點贊滾動放大波紋圖示

VIjolie發表於2017-12-07

首先感謝關於仿寫者劉金偉:

https://github.com/arvinljw/ThumbUpSample的作者,從中收到了啟發。

先來看一張效果圖(沒圖說個蛋蛋)

仿即刻的點贊滾動放大波紋圖示

大體思路:

上面這個控制元件PraiseView我把它拆成了兩部分:一個左邊的ImageView這個點選的時候會有放大的動畫,比較簡單。右邊的那個控制元件ScrollTextView複製數字加減進位,文字的滾動。這樣的好處是避免複雜的尺寸計算以及繪製邏輯,同時拆成兩個程式碼不會顯得過於冗長,便於理解。

關鍵程式碼解析:

public class PraiseView extends LinearLayout implements View.OnClickListener {
    private static final int DIP_8 = DisplayUtil.dip2px(8);
    /**
     * 預設的padding為縮放動畫留出空間
     */
    private final static int PADDING = DIP_8;

    private ImageView mImageView;
    private ScrollTextView mScrollTextView;
    private Drawable mPraiseDrawable;
    private Drawable mUnPraiseDrawable;
    private int mTextSize;
    private int mTextColor;
    public boolean mCanClick = true;
    private AnimatorSet mAnimatorSet;
    private int mLikeCount;
    private boolean mIsLiked;
    //圓的半徑
    private int mCircleMaxRadius;
    //園的顏色
    private int mCircleColor = Color.parseColor("#E73256");
    private Paint mCirclePaint = new Paint();
    private int mCurrentRadius = 0;
    private IPraiseListener mIPraiseListener;
    private ValueAnimator valueAnimator;


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

    public PraiseView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PraiseView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context, attrs);
    }

    private void initView(Context context, @Nullable AttributeSet attrs) {
        View.inflate(context, R.layout.layout_praise_view, this);
        setOrientation(HORIZONTAL);
        setGravity(Gravity.CENTER_VERTICAL);
        setPadding(PADDING, PADDING, PADDING, PADDING);
        setOnClickListener(this);
        mImageView = findViewById(R.id.iv_praise);
        mScrollTextView = findViewById(R.id.scroll_text_praise);

        TypedArray attrArray = context.obtainStyledAttributes(attrs, R.styleable.PraiseView);
        mTextSize = attrArray.getDimensionPixelSize(R.styleable.PraiseView_pv_textSize, DisplayUtil.sp2px(12));
        mTextColor = attrArray.getColor(R.styleable.PraiseView_pv_textColor, Color.parseColor("#757575"));
        mPraiseDrawable = attrArray.getDrawable(R.styleable.PraiseView_pv_praise_imageSrc);
        mUnPraiseDrawable = attrArray.getDrawable(R.styleable.PraiseView_pv_unPraise_imageSrc);
        attrArray.recycle();

        initView();
    }

    private void initView() {
        if (mPraiseDrawable == null) {
            mPraiseDrawable = getResources().getDrawable(R.mipmap.icon_praise_orange);
        }
        if (mUnPraiseDrawable == null) {
            mUnPraiseDrawable = getResources().getDrawable(R.mipmap.icon_un_praise_gray);
        }
        mImageView.setImageDrawable(mIsLiked ? mPraiseDrawable : mUnPraiseDrawable);
        mScrollTextView.setTextColorAndSize(mTextColor, mTextSize);
        mCirclePaint.setAntiAlias(true);
        mCirclePaint.setStyle(Paint.Style.STROKE);
        mCirclePaint.setStrokeWidth(DisplayUtil.dip2px(2));
    }


    public void bindData(IPraiseListener praiseListener, boolean isLike, int likeCount) {
        mLikeCount = likeCount;
        mIPraiseListener = praiseListener;
        setLiked(isLike);
        refreshText(likeCount);
    }

    void refreshText(int likeCount) {
        mScrollTextView.bindData(likeCount > 0 ? likeCount : 0);

    }

    public void setLiked(boolean isLike) {
        mIsLiked = isLike;
        mImageView.setImageDrawable(isLike ? mPraiseDrawable : mUnPraiseDrawable);
    }


    public void clickLike() {
        setLiked(!mIsLiked);

        if (mAnimatorSet == null) {
            mAnimatorSet = generateScaleAnim(mImageView, 1f, 1.3f, 0.9f, 1f);
        } else {
            mAnimatorSet.cancel();
        }
        mAnimatorSet.start();
        if (mIsLiked) {
            mLikeCount++;
        } else if (mLikeCount > 0) {
            mLikeCount--;
        }
        mIPraiseListener.like(mIsLiked, mLikeCount);
        mScrollTextView.bindDataWithAnim(mLikeCount);


    }


    @Override
    public void onClick(View v) {
        if (!mCanClick) return;
        clickLike();
        generateCircleAnim();

    }

    /**
     * 生成一個縮放動畫 X軸和Y軸
     *
     * @param view       需要播放動畫的View
     * @param scaleValue 縮放軌跡
     * @return AnimatorSet 動畫物件
     */
    public static AnimatorSet generateScaleAnim(View view, float... scaleValue) {
        AnimatorSet animatorSet = new AnimatorSet();
        ObjectAnimator animatorX = ObjectAnimator.ofFloat(view, View.SCALE_X, scaleValue);
        animatorX.setDuration(600);

        ObjectAnimator animatorY = ObjectAnimator.ofFloat(view, View.SCALE_Y, scaleValue);
        animatorY.setDuration(600);

        List<Animator> animatorList = new ArrayList<>(2);
        animatorList.add(animatorX);
        animatorList.add(animatorY);
        animatorSet.playTogether(animatorList);
        return animatorSet;
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, mCurrentRadius, mCirclePaint);
    }

    /**
     * 計算波紋動畫的最大半徑
     */
    private void calculateRadius() {
        mCircleMaxRadius = Math.min(getWidth(), getHeight()) / 2 - DIP_8;
    }

    public interface IPraiseListener {
        void like(boolean isPraise, int praiseCount);
    }

    /***
     * 波紋動畫
     */
    private void generateCircleAnim() {
        calculateRadius();
        if (valueAnimator != null && valueAnimator.isRunning()) {
            valueAnimator.cancel();
        }
        valueAnimator = ValueAnimator.ofInt(0, mCircleMaxRadius);
        valueAnimator.setDuration(400);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurrentRadius = (int) animation.getAnimatedValue();
                if (mCurrentRadius >= mCircleMaxRadius) {
                    mCurrentRadius = 0;
                }
                mCirclePaint.setColor(ColorUtils.setAlphaComponent(mCircleColor, (int) ((mCircleMaxRadius - mCurrentRadius) * 1.0f / mCircleMaxRadius * 255)));
                invalidate();
            }
        });
        valueAnimator.start();
    }
}複製程式碼

可以看到PraiView繼承了LinearLayout,因此不需要進行復雜的尺寸和繪製,使用預設的就好了。/***

     * 波紋動畫
     */
    private void generateCircleAnim() {
        calculateRadius();
        if (valueAnimator != null && valueAnimator.isRunning()) {
            valueAnimator.cancel();
        }
        valueAnimator = ValueAnimator.ofInt(0, mCircleMaxRadius);
        valueAnimator.setDuration(400);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurrentRadius = (int) animation.getAnimatedValue();
                if (mCurrentRadius >= mCircleMaxRadius) {
                    mCurrentRadius = 0;
                }
                mCirclePaint.setColor(ColorUtils.setAlphaComponent(mCircleColor, (int) ((mCircleMaxRadius - mCurrentRadius) * 1.0f / mCircleMaxRadius * 255)));
                invalidate();
            }
        });
        valueAnimator.start();
    }
}複製程式碼

通過ValueAnimator不斷改變圓的半徑,進行不斷的重繪,形成了點選波紋擴散的效果,注意的是:

 @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, mCurrentRadius, mCirclePaint);
    }複製程式碼

畫波紋的程式碼一定要放在super.dispatchDraw(canvas);操作的後面,也就是說波紋是前景,這樣會更加美觀,否則就成了背景,另外隨州波紋的擴撒波紋的顏色逐漸透明這裡用了ColorUtils.setAlphaComponent()。

      接下來看下ScrollTextView這個控制元件,有兩個比較重要的點:

  1. 怎麼處理進位退位?/**
     * 計算不變,原來,和改變後各部分的數字
     * 這裡是只針對加一和減一去計算的演算法,因為直接設定的時候沒有動畫
     */
    private void calculateChangeNum(int change) {
        mChange = change;
        if (change == 0) {
            mChangeNumbers[0] = String.valueOf(mOriginValue);
            mChangeNumbers[1] = "";
            mChangeNumbers[2] = "";
            return;
        }
        toBigger = change > 0;
        String oldNum = String.valueOf(mOriginValue);
        String newNum = String.valueOf(mOriginValue + change);
    
        int oldNumLen = oldNum.length();
    
        if (isLengthDifferent(mOriginValue, mOriginValue + change)) {
            mChangeNumbers[0] = "";
            mChangeNumbers[1] = oldNum;
            mChangeNumbers[2] = newNum;
        } else {
            for (int i = 0; i < oldNumLen; i++) {
                char oldC1 = oldNum.charAt(i);
                char newC1 = newNum.charAt(i);
                if (oldC1 != newC1) {
                    if (i == 0) {
                        mChangeNumbers[0] = "";
                    } else {
                        mChangeNumbers[0] = newNum.substring(0, i);
                    }
                    mChangeNumbers[1] = oldNum.substring(i);
                    mChangeNumbers[2] = newNum.substring(i);
                    break;
                }
            }
        }
        mOriginValue = mOriginValue + change;
    
    
    }複製程式碼

這裡採用一個長度為3的陣列存放不變的數字、原來的數字、變化後的數字。例如:

87到88,那麼陣列的元素為"8","7","8";99到100,那麼陣列的元素為"","99","100"。不變的數字在draw的時候直接花一次就好了,原來的數字和變化後的數字需要不斷改變Y值形成滾動的動畫。

private void drawText(Canvas canvas) {
    Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();
    float y = (getHeight() - fontMetrics.bottom - fontMetrics.top) / 2;
    canvas.drawText(String.valueOf(mChangeNumbers[0]), mStartX, y, mTextPaint);
    if (mChange != 0) {
        //字型滾動
        float fraction = (mTextSize - Math.abs(mOldOffsetY)) / mTextSize;
        Log.e("drawText", "drawText" + fraction);
        mTextPaint.setColor(ColorUtils.setAlphaComponent(mTextColor, (int) (fraction * 255)));
        canvas.drawText(String.valueOf(mChangeNumbers[1]), mSingleTextWidth * mChangeNumbers[0].length() + mStartX, y + mOldOffsetY, mTextPaint);
        mTextPaint.setColor(ColorUtils.setAlphaComponent(mTextColor, (int) ((1 - fraction) * 255)));
        canvas.drawText(String.valueOf(mChangeNumbers[2]), mSingleTextWidth * mChangeNumbers[0].length() + mStartX, y + mNewOffsetY, mTextPaint);
    }

}複製程式碼

值得注意的是這裡:

private int getContentWidth() {
    /**
     * 加1為了防止進位時寬度不夠顯示不下
     */
    return (int) (getPaddingRight() + getPaddingLeft() + mSingleTextWidth * (String.valueOf(mOriginValue).length() + 1));
}複製程式碼

控制元件的寬度為當前字元的寬度再加一個字元寬度,避免發生進位時顯示不全的問題。

程式碼github對你有幫助的話順手給個星吧!

碼字不易,期待各位的讚賞!!!

仿即刻的點贊滾動放大波紋圖示    仿即刻的點贊滾動放大波紋圖示


相關文章