NumberProgressBar开源项目学习

1、概述

多看多学涨姿势, github真是个宝库

这个项目主要是实现数字进度条效果

github地址在https://github.com/daimajia/NumberProgressBar

感谢开源作者!

梳理主要知识点:

【1】熟悉自定义view的流程

【2】实现原理

【3】android中的view坐标系使用

【4】onMeasure优雅的方法书写

【5】canvas中drawText方法注意点

【6】代码的可读性非常强

2、项目要点分析

【熟悉自定义view的流程】

自定义view需要多多看别写的精彩代码,不过流程基本都是一致的在我的自定义View入门中有详细介绍按照这个思路去分析自定义view即可

【本项目实现原理】

该项目比较基础,适合作为入门学习项目,作者主要将自定义控件分为3大区域

mReachedRectF——Text区域(可以选择没有)——mUnreachedRectF

(该控件支持没有text区域),主要是通过控制mReachedRectF和mUnreachedRectF的坐标来不断地刷新ui来实现移动效果,没有使用到动画

自定义view 步骤 之获取自定义属性

这里作者直接写到其中一个构造方法中

  public NumberProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        default_reached_bar_height = dp2px(1.5f);
        default_unreached_bar_height = dp2px(1.0f);
        default_text_size = sp2px(10);
        default_progress_text_offset = dp2px(3.0f);

        //load styled attributes.
        final TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.NumberProgressBar,
                defStyleAttr, 0);

        mReachedBarColor = attributes.getColor(R.styleable.NumberProgressBar_progress_reached_color, default_reached_color);
        mUnreachedBarColor = attributes.getColor(R.styleable.NumberProgressBar_progress_unreached_color, default_unreached_color);
        mTextColor = attributes.getColor(R.styleable.NumberProgressBar_progress_text_color, default_text_color);
        mTextSize = attributes.getDimension(R.styleable.NumberProgressBar_progress_text_size, default_text_size);

        mReachedBarHeight = attributes.getDimension(R.styleable.NumberProgressBar_progress_reached_bar_height, default_reached_bar_height);
        mUnreachedBarHeight = attributes.getDimension(R.styleable.NumberProgressBar_progress_unreached_bar_height, default_unreached_bar_height);
        mOffset = attributes.getDimension(R.styleable.NumberProgressBar_progress_text_offset, default_progress_text_offset);

        int textVisible = attributes.getInt(R.styleable.NumberProgressBar_progress_text_visibility, PROGRESS_TEXT_VISIBLE);
        if (textVisible != PROGRESS_TEXT_VISIBLE) {
            mIfDrawText = false;
        }

        setProgress(attributes.getInt(R.styleable.NumberProgressBar_progress_current, 0));
        setMax(attributes.getInt(R.styleable.NumberProgressBar_progress_max, 100));

        attributes.recycle();
        initializePainters();
    }

这里作者开始初始化自定义的属性,通常我们可以单独使用一个函数放在每个构造器下面

然后就是onMeasure,这里比较优雅,

其中EXACTLY主要针对match_parent/具体参数

ATMOST主要针对wrap_content情况这里做了处理,有时候我们如果把布局文件的宽和高写成wrap_content,若此时父布局也为AT_MOST此时显示的就是父布局的PraentSize

因此我们支持设置wrap_content时候需要重写onMeasure方法,下面也是做了处理(AT_MOST)

鸿洋大神的自定义view博文对此也做了说明http://blog.csdn.net/lmj623565791/article/details/24252901

而UNSPECIFIED往往用于系统内部的测量通常只需要关注ATMOST和EXACTLY

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measure(widthMeasureSpec, true), measure(heightMeasureSpec, false));
    }

    private int measure(int measureSpec, boolean isWidth) {
        int result;
        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);
        int padding = isWidth ? getPaddingLeft() + getPaddingRight() : getPaddingTop() + getPaddingBottom();
        if (mode == MeasureSpec.EXACTLY) {
            result = size;
        } else {
            result = isWidth ? getSuggestedMinimumWidth() : getSuggestedMinimumHeight();
            result += padding;
            if (mode == MeasureSpec.AT_MOST) {
                if (isWidth) {
                    result = Math.max(result, size);
                } else {
                    result = Math.min(result, size);
                }
            }
        }
        return result;
    }

onDrawer方法的可读性更是6666

有时候我们也要注意写出“可以说话的代码”,注意函数的封装

  @Override
    protected void onDraw(Canvas canvas) {
        if (mIfDrawText) {
            calculateDrawRectF();
        } else {
            calculateDrawRectFWithoutProgressText();
        }

        if (mDrawReachedBar) {
            canvas.drawRect(mReachedRectF, mReachedBarPaint);
        }

        if (mDrawUnreachedBar) {
            canvas.drawRect(mUnreachedRectF, mUnreachedBarPaint);
        }

        if (mIfDrawText)
            canvas.drawText(mCurrentDrawText, mDrawTextStart, mDrawTextEnd, mTextPaint);
    }

这段代码我们可以清晰的看出作者的逻辑 要是设置了数字显示执行calculateDrawRectF()否则执行calculateDrawRectFWithoutProgressText()

这俩个函数就是开始处理前文提到的mReachedRectF和mUnreachedRectF俩个矩形的位置变化,这里需要熟悉一下android中的坐标系和点击位置的获取

看一下有文字时候计算俩个区域的情况主要集中处理了开始阶段和最终阶段的文字未知的特殊情况

    private void calculateDrawRectF() {

        mCurrentDrawText = String.format("%d", getProgress() * 100 / getMax());
        mCurrentDrawText = mPrefix + mCurrentDrawText + mSuffix;//转换成字符串
        mDrawTextWidth = mTextPaint.measureText(mCurrentDrawText);//测量出文字的长度

        if (getProgress() == 0) {
            mDrawReachedBar = false;
            mDrawTextStart = getPaddingLeft();//起始位置(右)
        } else {
            mDrawReachedBar = true;
            mReachedRectF.left = getPaddingLeft();
            mReachedRectF.top = getHeight() / 2.0f - mReachedBarHeight / 2.0f;
            mReachedRectF.right = (getWidth() - getPaddingLeft() - getPaddingRight()) / (getMax() * 1.0f) * getProgress() - mOffset + getPaddingLeft();
            mReachedRectF.bottom = getHeight() / 2.0f + mReachedBarHeight / 2.0f;
            mDrawTextStart = (mReachedRectF.right + mOffset);//实际中右位置
        }
        //mTextPaint.descent() + mTextPaint.ascent() baseLine到字体最高+baseLine到字体最低=实际字体高度
        mDrawTextEnd = (int) ((getHeight() / 2.0f) - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f));

        if ((mDrawTextStart + mDrawTextWidth) >= getWidth() - getPaddingRight()) {
            //文字终点位置重置文字起始位置和mReachedRectF矩形的right
            mDrawTextStart = getWidth() - getPaddingRight() - mDrawTextWidth;
            mReachedRectF.right = mDrawTextStart - mOffset;
        }

        float unreachedBarStart = mDrawTextStart + mDrawTextWidth + mOffset;
        if (unreachedBarStart >= getWidth() - getPaddingRight()) {
            //没有到最终点
            mDrawUnreachedBar = false;
        } else {
            mDrawUnreachedBar = true;
            mUnreachedRectF.left = unreachedBarStart;
            mUnreachedRectF.top = getHeight() / 2.0f -mUnreachedBarHeight / 2.0f;
            mUnreachedRectF.right = getWidth() - getPaddingRight();
            mUnreachedRectF.bottom = getHeight() / 2.0f + mUnreachedBarHeight / 2.0f;
        }
    }

这里主要提下mTextPaint.descent() + mTextPaint.ascent()的这里参照的距离都是baseline,这样可以计算出整个字体的高度,具体可以参考

http://mikewang.blog.51cto.com/3826268/871765/ 详细讲解了androd的字体属性和测量

还有canvas.drawText方法中xy坐标其实是baseline的位置,这在校准字体位置的时候很有用!

时间: 2024-10-07 01:32:36

NumberProgressBar开源项目学习的相关文章

转:从开源项目学习 C 语言基本的编码规则

从开源项目学习 C 语言基本的编码规则 每个项目都有自己的风格指南:一组有关怎样为那个项目编码约定.一些经理选择基本的编码规则,另一些经理则更偏好非常高级的规则,对许多项目而言则没有特定的编码规则,项目中的每个开发者使用他自己的风格. 所有代码都保持一致风格的大型库,更容易让人理解. 有许多资源是关于能让人采取的更好的编码规则的,我们可以通过以下方式学到好的编码规则: 阅读书或杂志 浏览网站 与同事交流 参加培训 另一个更有趣的方法是通过研究一个成熟的知名开源项目来得知其开发者是怎样编写代码的.

android开源项目学习

FBReaderJ FBReaderJ用于Android平台的电子书阅读器,它支持多种电子书籍格式包括:oeb.ePub和fb2.此外还支持直接读取zip.tar和gzip等压缩文档. 项目地址:http://www.fbreader.org/FBReaderJ/ Angle Angle是一款专为Android平台设计的,适合快速开发的2D游戏引擎,基于OpenGL ES技术开发.该引擎全部用Java代码编写,并且可以根据自己的需要替换里面的实现. 项目地址:http://code.google

ASP.NET MVC 开源项目学习之ProDinner (二)

下面我们来看第二层:Data   这一层相对来说是对Core层的具体实现了. 从命名可以看出来,这和数据库相关. 1.Db.cs CodeFirst模式的本地数据库类,继承了DbContext. protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Dinner>().HasMany(r => r.Meals).WithMany(o => o.Dinner

ASP.NET MVC 开源项目学习之ProDinner (一)

首先在github上面将ProDinner项目通过 Git Bash 克隆到本地,接下来我们开始分析这个项目吧~ 系统采用.Net 4.5, Asp.net Mvc 5,VS2012,Sql server,系统的整体设计非常轻量级,却做到了整体架构分层明显,模块耦合度低的架构思想,很适合新手学习. Core层实现了本地化Model和EF模型化需要的model数据,另外,Core层还为低耦合的业务逻辑和低耦合的数据访问做好了接口准备. 1.Model: 以下是表关系的大致内容: 我们在分析数据库表

ASP.NET MVC 开源项目学习之ProDinner (三)

第四层:Resources   这一层里面主要是几个资源文件. 资源文件知识小杂烩: 几乎每一个生产性应用程序都需要使用资源.资源是在逻辑上由应用程序部署的任何非可执行数据.资源可以在应用程序中作为错误信息显示,或者作为用户 界面的一部分显示.资源可以包含多种形式的数据,包括字符串.图像和持久的对象.通过在资源文件中存储数据,无需重新编译整个应用程序即可更改数据.在.net中,有文本文件.resx 文件和 .resources 文件三种资源文件.如果资源将只包含字符串数据,则文本文件是最简单的选

SpringBoot开源项目学习总结

一.实现日期格式数据类型的转换 首先,定义DateConverter实现Converter<String, Date>接口: 1 package com.stevlu.common; 2 3 import org.springframework.core.convert.converter.Converter; 4 5 import java.text.ParseException; 6 import java.text.SimpleDateFormat; 7 import java.util

java开源项目学习

http://jeecg-boot.mydoc.io/ 在线文档已切换至新地址: http://doc.jeecg.com Jeecg-Boot 是一款基于SpringBoot+代码生成器的快速开发平台!采用前后端分离架构:SpringBoot,Mybatis,Shiro,JWT,Vue&Ant Design.强大的代码生成器让前端和后台代码一键生成,不需要写任何代码,保持jeecg一贯的强大,绝对是全栈开发福音!! JeecgBoot在提高UI能力的同时,降低了前后分离的开发成本,JeecgB

Jetsever开源项目学习(五)Concurrent学习

首先梳理一下整体的架构,总的来说就是一个生产—消费者的形式,建立在Executor framework上: 1.每一个Lane包含一个名字string和线程池(ExecutorService),线程池其实就相当于worker集合2.每一个Lane有一个计数器(AtomicInteger),用来记录进入这个Lane的session的数量,session集合其实就相当于相当于任务队列3.每一个GameRoom属于一个Lane(也就是一个Lane中可以有多个GameRoom).每当添加一个新的sess

Jetsever开源项目学习(三)Session学习

package org.menacheri.jetserver.app; import java.util.List; import org.menacheri.jetserver.communication.MessageSender; import org.menacheri.jetserver.communication.MessageSender.Fast; import org.menacheri.jetserver.communication.MessageSender.Reliab