112

仿即刻的点赞滚动放大波纹图标

 6 years ago
source link: https://juejin.im/post/5a28f45451882540f36371cc
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

仿即刻的点赞滚动放大波纹图标

2017年12月07日 09:50 ·  阅读 3358

首先感谢关于仿写者刘金伟:

https://github.com/arvinljw/ThumbUpSample的作者,从中收到了启发。

先来看一张效果图(没图说个蛋蛋)

1602ffbd23b5b556~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.image

大体思路:

上面这个控件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对你有帮助的话顺手给个星吧!

码字不易,期待各位的赞赏!!!

168f4290930633ab~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.image
    
168f4247331ed4ef~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.image

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK