GratuityView: 虎扑体育打赏控件

前言

NBA全明星周末!重要事情说一遍!
作为一个经常在虎扑上看NBA直播的小球迷,老早就留意到了打赏加油的小控件,今天赶紧的,让我们来实现它吧~搞起!

虎扑原型和效果图图

先看下虎扑原型:

GratuityView: 虎扑体育打赏控件_第1张图片
原型.png

挺好看的一个控件嘛有木有~,看下我们的效果图:

GratuityView: 虎扑体育打赏控件_第2张图片
原始效果.gif

GratuityView: 虎扑体育打赏控件_第3张图片
点击效果.gif

GratuityView: 虎扑体育打赏控件_第4张图片
色彩效果.gif



基本效果就是这样了。



开始我们的自定义

1.分析原型

先来分析一波原型图

GratuityView: 虎扑体育打赏控件_第5张图片
原型

对view的元素分析。这个view有两个主要的元素: 圆以及灰色背景框。
圆有两个元素:text 以及背景颜色 color;
背景框:某个颜色绘制出来的圆弧图形,并且该圆弧所在圆的半径与大圆相等。

对view的结构分析。
view是多个圆组成的结构,以大圆为基圆,左边展开的小圆都包含在一个基圆的空间内并且与该基圆圆心重合。背景框可以拆解成左边一个半基圆的弧形,加上右边一个矩形。
用图来演示一下:

GratuityView: 虎扑体育打赏控件_第6张图片
结构图

ok,分析完毕~

2.动手
1 )先绘制圆

圆是这个控件里面很重要的一个元素,大圆和小圆都应该使用到同一个控件。因为大圆是基圆,我们先来画个圆的控件,就叫BaseView。
定义一个BaseView,让它继承自view,再在onDraw( )方法内绘制圆和文本text,比较简单,直接上代码:
绘制圆

canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2,
Math.min(getMeasuredHeight(), getMeasuredWidth()) / 2, mCirclePaint);

绘制文本

mTextPaint.getTextBounds(fstText, 0, fstText.length(), fstRect);
canvas.drawText(fstText, getMeasuredWidth() / 2 - fstRect.width() / 2,
getMeasuredHeight() / 2 + fstRect.height() / 2, mTextPaint);

绘制文本无非多一点对文本位置的修正,恩,不难~

2 )绘制控件

从原型可以看出,基圆处在了父控件的最右边。或者可以说,当前控件假若从左边展开,BaseView在最右;控件从右边展开,BaseView位于父控件最左。因此我们可以自定义一个view去继承RelativeLayout,这样BaseView的处置就比较容易实现了。
定义一个GratuityView,继承自RelativeLayout.

第一步,将我们写好的BaseView添加到最右边;
重写onLayout( )方法,添加一个BaseView, 并添加上BaseView的点击事件。贴上代码:

private void addBaseView(int widthSize, int heightSize) {
        if (added || widthSize < 1 || heightSize < 1) return;
        added = !added;
        BaseView                    baseView = new BaseView(mContext);
        RelativeLayout.LayoutParams params   = new            RelativeLayout.LayoutParams(size, size);
        params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
        baseView.setLayoutParams(params);
        if (isNewLine)
            baseView.setText(fstText, secText);
        else baseView.setText(fstText);
        baseView.setTextSize(mBaseTextSize);
        baseView.setTextColor(mBaseTextColor);
        baseView.setCircleColor(mBasegroundColor);
        addView(baseView);
        baseView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                //TODO
            }
        });

    }



第二步,完成BaseView的点击事件
BaseView的点击事件有两种,一种是展开事件Expand, 一种是收缩事件 collapse.
当BaseView被点击时,展开动画开始,获取当前动画进度不断地调用onDraw( )方法去绘制背景框(包括一个矩形和一个弧形),贴上代码:

private void startExpandAnimation() {
        if (collapseAnimation != null && collapseAnimation.isRunning()) {
            return;
        }
        expand = !expand;
        animated = true;
        if (mWidthMode == AT_MOST)
            AnimLength = (mContainedCount + 1) * size - radius * 2;
        else
            AnimLength = mWidth - radius * 2;      //总运动长度
        expandAnimation = ValueAnimator.ofInt(AnimLength);
        expandAnimation.setDuration(mAnimDuration);
        expandAnimation.start();
        expandAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
        expandAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mCurrentValue = (int) valueAnimator.getAnimatedValue();
                invalidate();
            }
        });
    }

这里有一个点需要注意的:当前动画的距离。
假如,当前展开后只有3个子view,那么控件完整显示需要的宽度就是
mWidth=(3+1)*基圆直径。当getMeasureWidth()比所需宽度mWidth大,动画距离

AnimLength = mWidth - radius * 2

当getMeasureWidth( )比所需宽度mWidth小,动画距离

AnimLength = (mContainedCount + 1) * size - radius * 2

中间灰色部分为动画运动的距离


GratuityView: 虎扑体育打赏控件_第7张图片
ggv.png

接下来在onDraw( )内绘制背景框~
这里使用了xformode里面的模式,将灰色矩形与BaseView相交部分产生的颜色区域忽略掉。

//宽度过长
int diff    = getMeasuredWidth() - mWidth;
int layerId = canvas.saveLayer(diff, top, right, bottom, mPaint, Canvas.ALL_SAVE_FLAG);
//绘制弧形
fstRectF.left = diff + AnimLength - mCurrentValue;                
fstRectF.right = diff + AnimLength - mCurrentValue + radius * 2;
fstRectF.top = top;
fstRectF.bottom = bottom;
canvas.drawArc(fstRectF, 90, 180, true, backgroundPaint);
//绘制矩形
canvas.drawRect(diff + mWidth - radius - mCurrentValue, top, right - radius, bottom, backgroundPaint);
//绘制xformode源图像
fstRectF.left = right - size;
fstRectF.right = right;
fstRectF.top = top;
fstRectF.bottom = bottom;
canvas.drawArc(fstRectF, 90, 180, true, backAlphaPaint);
canvas.restoreToCount(layerId);

据说收缩的方法和展开类似哦~



第三步,添加子BaseView
在展开的方法 startExpandAnimation( )内添加子view。添加的位置很好计算,因为子view都添加到基圆的中心,基圆大小

size = Math.min(getMeasuredHeight(), getMeasuredWidth());

添加子view

private void addRewardView() {
        for (int i = 0; i < mContainedCount; i++) {
            int                         rewardSize = (int) (2 * 1.0f / 3 * Math.min(getMeasuredHeight(), getMeasuredWidth()));
            BaseView                    rewardView = new BaseView(mContext);
            RelativeLayout.LayoutParams params     = new RelativeLayout.LayoutParams(rewardSize, rewardSize);
            params.rightMargin = radius - rewardSize / 2;
            params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
            params.addRule(RelativeLayout.CENTER_VERTICAL);
            rewardView.setLayoutParams(params);
            addView(rewardView);
            mRewardViewList.add(rewardView);
    }



第四步,子view添加动画和点击事件
子view开始出现的位置为BaseView圆心,往左展开包括了一个水平向左的平移动画,逆时针方向的旋转动画,出现和消失时的淡入淡出动画;贴出展开动画吧

private void startChildExpandAnimation() {
        for (int i = 0; i < mContainedCount; i++) {
            final BaseView view = mRewardViewList.get(i);
            view.setVisibility(VISIBLE);
            final int      desX           = size * (mContainedCount - i);
            ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, "translationX", 0, -desX);
            objectAnimator.setDuration(mAnimDuration);
            objectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
            objectAnimator.start();
            objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float value = (float) animation.getAnimatedValue();
                    value = Math.abs(value);
                    //淡出
                    if (value <= radius * 1.0f * 33 / 20) {
                        float alpha = value * 1.0f / radius - 0.65f;
                        view.setAlpha(alpha);
                    } else view.setAlpha(1.0f);
                    //旋转
                    float ratation = -value * 1.0f / desX * 90;
                    view.setRotation(ratation);
                }
            });
        }
    }



第五步,完善代码
完善部分包括完善自定义的属性,GratuityView对外提供的方法,以及一些特殊情况的处理。比如展开和收缩时屏蔽子view的点击事件,当getMeasureWidth()小于所需宽度时对动画绘制的位置要重新计算,如何保存不同子view各自的文字设置以及设置双行文本 等等。


end~

终于搞完咯!德玛西亚!

转载请注明出处哦谢谢

下载

https://github.com/dengzq/GratuityView

你可能感兴趣的:(GratuityView: 虎扑体育打赏控件)