自定义View - 仿QQ运动步数进度效果

我blog也写过,欢迎各位看我博客,https://blog.csdn.net/qq_24675479
首先看下效果图

效果图.gif

在做这个项目之前先了解下文字获取
我上一篇 自定义View入门 - 自定义TextView 有写过——https://www.jianshu.com/p/7b5e4f5abb74

分析.png

今天详细讲解一下baseLine 基线(参考文章:文淑大神的自定义View之绘图篇(四)http://blog.csdn.net/u012551350/article/details/51361778
FontMetrics

基线.png

top:可绘制的最高高度所在线
bottom:可绘制的最低高度所在线
ascent :系统建议的,绘制单个字符时,字符应当的最高高度所在线
descent:系统建议的,绘制单个字符时,字符应当的最低高度所在线

获取实例

Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
Paint.FontMetricsInt fm=  mPaint.getFontMetricsInt();

成员变量

        float ascent = fontMetrics.ascent;
        float descent = fontMetrics.descent;
        float top = fontMetrics.top;
        float bottom = fontMetrics.bottom;
        float leading = fontMetrics.leading;

这里的ascent,descent,top,bottom,leading指的是到基线baseline的位置

[图片上传失败...(image-4386fb-1530855738868)]

文字的高度

         float top = fontMetrics.top + baseLineY;
        float bottom = fontMetrics.bottom + baseLineY;
        //文字高度
        float height= bottom - top; //注意top为负数
        //文字中点y坐标
        float center = (bottom - top) / 2;

即中线是(bottom-top)/2,实际呢是bottom+top只是因为这里的top是负的(图片上的top到baseline的距离)

已知中线求baseline
[图片上传失败...(image-9ea4bc-1530855738868)]
结论
baseline=centerY+A-fm.bottom;

centerY呢实际就是getHeight/2,整体高度的一半,然后求基线的y坐标,实际就是(top-bottom)/2-fontMetrics.bottom;再次强调:这里的ascent,descent,top,bottom,leading指的是到基线baseline的位置。最后相加getHeight+(top-bottom)/2-fontMetrics.bottom**

接下来开始做项目:

  • 第一步自定义属性
    一共需要:内部圆环颜色,外部圆环颜色,圆环宽度,文字颜色,文字大小五种
<declare-styleable name="QQStepView">
        <attr name="outerColor" format="color" />
        <attr name="innerColor" format="color" />
        <attr name="borderWidth" format="dimension" />
        <attr name="stepTextSize" format="dimension" />
        <attr name="stepTextColor" format="color" />
    </declare-styleable>
  • 自定义view获取属性,设置属性等
public class QQStepView extends View {
    private int mOuterColor = Color.RED;
    private int mInnerColor = Color.BLUE;
    private int mBorderWidth = 20;
    private int mStepTextSize;
    private int mStepTextColor;
    private Paint mOuterPaint, mInnerPaint, mTextPaint;
    private int mStepMax;//总的步数
    private int mCurrentStep;//当前步数

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

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

    public QQStepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.QQStepView);
        mOuterColor = ta.getColor(R.styleable.QQStepView_outerColor, mOuterColor);
        mInnerColor = ta.getColor(R.styleable.QQStepView_innerColor, mInnerColor);
        mBorderWidth = (int) ta.getDimension(R.styleable.QQStepView_borderWidth, mBorderWidth);
        mStepTextSize = ta.getDimensionPixelOffset(R.styleable.QQStepView_stepTextSize, mStepTextSize);
        mStepTextColor = ta.getColor(R.styleable.QQStepView_stepTextColor, mStepTextColor);
        ta.recycle();
        //内弧
        mOuterPaint = new Paint();
        mOuterPaint.setAntiAlias(true);
        mOuterPaint.setStrokeWidth(mBorderWidth);
        mOuterPaint.setColor(mOuterColor);
        mOuterPaint.setStrokeCap(Paint.Cap.ROUND);//设置下方为圆形
        mOuterPaint.setStyle(Paint.Style.STROKE);//设置内部为空心
        //外弧
        mInnerPaint = new Paint();
        mInnerPaint.setAntiAlias(true);
        mInnerPaint.setStrokeWidth(mBorderWidth);
        mInnerPaint.setColor(mInnerColor);
        mInnerPaint.setStrokeCap(Paint.Cap.ROUND);//设置下方为圆形
        mInnerPaint.setStyle(Paint.Style.STROKE);//设置内部为空心

        //文字
        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextSize(mStepTextSize);
        mTextPaint.setColor(mStepTextColor);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //调用者在布局文件中可能 wrap_content导致宽高不一致
        //确保是正方形
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(width > height ? height : width, width > height ? height : width);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //1.画外圆弧   边缘没有显示完整
        //RectF recf = new RectF(0, 0, getWidth(), getHeight());

        int center = getWidth() / 2;
        int radius = getWidth() / 2 - mBorderWidth / 2;
        //mBorderWidth/2,mBorderWidth/2,getWidth()-mBorderWidth/2,getWidth()-mBorderWidth/2
        RectF recf = new RectF(center - radius,
                center - radius,
                center + radius,
                center + radius);
        canvas.drawArc(recf, 135, 270, false, mOuterPaint);
        //2.绘制内圆弧
        float sweepAngle = (float) mCurrentStep / mStepMax;
        canvas.drawArc(recf, 135, sweepAngle * 270, false, mInnerPaint);

        //3.绘制文字
        String stepText = mCurrentStep + "";
        Rect rect = new Rect();
        mTextPaint.getTextBounds(stepText,0,stepText.length(),rect);
        int dx = getWidth() / 2 - rect.width() / 2;
        //第一种方式获取高度
        //int dy = getWidth() / 2 + rect.width()/2;
        //第二种表达方式获取高度
        Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();
        //获取中心(fontMetrics.bottom - fontMetrics.top) / 2
        int dy = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
        int baseLine = getHeight() / 2 + dy;
        canvas.drawText(stepText, dx, baseLine, mTextPaint);

    }
    //其他,写几个方法让他动起来

    public void setStepMax(int mStepMax) {
        this.mStepMax = mStepMax;
    }

    public void setCurrentStep(int mCurrentStep) {
        this.mCurrentStep = mCurrentStep;
        //不断绘制 onDraw()
        invalidate();
    }
}

在MainActivity中设置动画

final QQStepView qqStepView = (QQStepView) findViewById(R.id.step_view);
        qqStepView.setStepMax(5000);
        //属性动画
        ValueAnimator animator = ValueAnimator.ofFloat(0, 3000);//0到3000的变化
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float currentStep = (float) animation.getAnimatedValue();//获取当前值
                qqStepView.setCurrentStep((int) currentStep);
            }
        });
        animator.setDuration(2000);
        animator.start();

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容