Android 自定义View金额、价格样式显示MoneyView

效果图:

layout-2016-09-01-182947.png

使用方式

app的build.gradle 添加依赖

compile 'com.github.cchao:moneyview:1.0.1'

XML布局文件 中引用

<com.github.cchao.MoneyView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  money:money_text="789456.123"/>

显示效果就是效果图的第一个。

balabala

故事是这样的:
不懂设计从哪里搞得效果图,要求实现金额文本里元的字体较大,分的字体较小,而且前面的前缀样式还不定。各个页面的样式还不一致,为了满足妹子特别的需求。
就哒哒哒码了个MoneyView。

思路

根据妹子已给出的图,我猜,没错,我是猜的。鬼懂她什么时候想想又乱改了。so,从以下几个方面去规定这个MoneyView应该具备怎么样的样式:

  • 元和分能自定义大小
  • 前缀能自定义文本、颜色和大小
  • 前缀与文本能自定义padding
  • 小数点能自定义左右间隔
  • 金额能自定义颜色
  • 允许设置千分符

attrs

其实还可能有更多的,不过她如果这么过分的话,我就继承ViewGroup的子类了,哼。
好了,根据上述规定,程序员写出了下列attr:

<!--金额 样式  元大 角分小 $25.33-->
<declare-styleable name="MoneyView">
    <!--金额-->
    <attr name="money_text" format="string"/>
    <!--金额颜色-->
    <attr name="money_color" format="color"/>
    <!--元大小-->
    <attr name="yuan_size" format="dimension"/>
    <!--分大小-->
    <attr name="cent_size" format="dimension"/>
    <!--前缀文本-->
    <attr name="prefix_text" format="string"/>
    <!--前缀大小-->
    <attr name="prefix_size" format="dimension"/>
    <!--前缀颜色-->
    <attr name="prefix_color" format="color"/>
    <!--前缀右边距-->
    <attr name="prefix_padding" format="dimension"/>
    <!--小数点左边距-->
    <attr name="point_padding_left" format="dimension"/>
    <!--小数点右边距-->
    <attr name="point_padding_right" format="dimension"/>
    <!--是否使用千分符-->
    <attr name="grouping" format="boolean"/>
</declare-styleable>

constructor

没错,各位看官也可以有这样的思路去自定义View,先想好可能的拓展,再列出attr,最后才开始写代码。
好,既然我们写完了attr,就开始去学代码了。先new 一个Class 名字叫做MoneyView,然后复写其三个构造方法:

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

public MoneyView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public MoneyView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init(context, attrs, defStyle);
}

private void init(Context context, AttributeSet attrs, int defStyle) {
    TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MoneyView, defStyle, 0);

    mMoneyText = typedArray.getString(R.styleable.MoneyView_money_text);
    mMoneyColor = typedArray.getColor(R.styleable.MoneyView_money_color, mMoneyColor);
    mYuanSize = typedArray.getDimensionPixelSize(R.styleable.MoneyView_yuan_size, mYuanSize);
    mCentSize = typedArray.getDimensionPixelSize(R.styleable.MoneyView_cent_size, mCentSize);

    mPrefix = typedArray.getString(R.styleable.MoneyView_prefix_text);
    mPrefixSize = typedArray.getDimensionPixelSize(R.styleable.MoneyView_prefix_size, mPrefixSize);
    mPrefixColor = typedArray.getColor(R.styleable.MoneyView_prefix_color, mPrefixColor);
    mPrefixPadding = typedArray.getDimensionPixelSize(R.styleable.MoneyView_prefix_padding, mPrefixPadding);

    mPointPaddingLeft = typedArray.getDimensionPixelSize(R.styleable.MoneyView_point_padding_left, mPointPaddingLeft);
    mPointPaddingRight = typedArray.getDimensionPixelSize(R.styleable.MoneyView_point_padding_right, mPointPaddingRight);
    mIsGroupingUsed = typedArray.getBoolean(R.styleable.MoneyView_grouping, false);
    typedArray.recycle();

    // 获得绘制文本的宽和高
    mPaint = new Paint();
    mPaint.setAntiAlias(true); // 消除锯齿
    mPaint.setFlags(Paint.ANTI_ALIAS_FLAG); // 消除锯齿
    mYuanBound = new Rect();
    mCentBound = new Rect();
    mPointBound = new Rect();
    mPrefixBound = new Rect();

    if (TextUtils.isEmpty(mPrefix)) {
        mPrefix = "¥";
    }
}

通过TypedArray 获取我们刚才写的attr
public int getDimensionPixelSize(int index, int defValue) {
用户不输入我们就给予默认值。默认在声明成员属性处已经给出。

onMeasure

那,现在我们也获取到用户设置的属性了,现在就要调用onMeasure去计算这个自定义MoneyView占据的宽高了。(代码有删减)

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width;
    int height;

    int pointPosition = mMoneyText.indexOf(POINT);
    if (!mMoneyText.contains(POINT)) {
        pointPosition = mMoneyText.length();
    }
    //获取元的文本
    mYuan = mMoneyText.substring(0, pointPosition);
    //如果使用千分符
    if (mIsGroupingUsed) {
        mYuan = NumberFormat.getInstance().format(Long.valueOf(mYuan));
    }
    //获取分的文本
    mCent = mMoneyText.substring(pointPosition + 1, mMoneyText.length());
    //获取元小数点、的占据宽高
    mPaint.setTextSize(mYuanSize);
    mPaint.getTextBounds(mYuan, 0, mYuan.length(), mYuanBound);
    mPaint.getTextBounds(POINT, 0, POINT.length(), mPointBound);
    //获取分占据宽高
    mPaint.setTextSize(mCentSize);
    mPaint.getTextBounds(mCent, 0, mCent.length(), mCentBound);
    //获取前缀占据宽高
    mPaint.setTextSize(mPrefixSize);
    mPaint.getTextBounds(mPrefix, 0, mPrefix.length(), mPrefixBound);
    //文本占据的宽度
    mTextWidth = mYuanBound.width() + mCentBound.width() + mPrefixBound.width() + mPointBound.width()
        + mPointPaddingLeft + mPointPaddingRight + mPrefixPadding;
    // 设置宽度
    int specMode = MeasureSpec.getMode(widthMeasureSpec);
    int specSize = MeasureSpec.getSize(widthMeasureSpec);

    if (specMode == MeasureSpec.EXACTLY) {
        width = specSize + getPaddingLeft() + getPaddingRight();
    } else {
        width = mTextWidth + getPaddingLeft() + getPaddingRight();
    }
    // 设置高度
    // 获取最大字号
    int maxSize = Math.max(mYuanSize, mCentSize);
    maxSize = Math.max(maxSize, mPrefixSize);
    mPaint.setTextSize(maxSize);
    // 获取基线距离
    maxDescent = mPaint.getFontMetrics().descent;
    int maxHeight = Math.max(mYuanBound.height(), mCentBound.height());
    maxHeight = Math.max(maxHeight, mPrefixBound.height());
    // 文本占据的高度 (给顶线和底线留间距)
    mTextHeight = maxHeight + (int) (maxDescent * 2 + 0.5f);

    specMode = MeasureSpec.getMode(heightMeasureSpec);
    specSize = MeasureSpec.getSize(heightMeasureSpec);
    if (specMode == MeasureSpec.EXACTLY) {
        height = specSize;
    } else {
        height = mTextHeight;
    }
    setMeasuredDimension(width, height);
}

我们分别计算前缀,元,分占据的宽高,然后对比,取最高的一个作为MoneyView的高。
这里获取了最大文本的基线值maxDescent。这里需要注意一点,canvas.drawText是根据 baseLine(基线) 绘制的,这和我们小时候用四线纸去写字母一个意思,如下图:

e038aae657b1832ecc32c336c6075ffc.jpg

onDraw

所以我们在 onDraw()时要在Y轴上加上基线距离底部的值,才是我们需要绘制的, 绘制过程会将文本居中。

@Override
protected void onDraw(Canvas canvas) {
    //绘制X坐标
    int drawX = (getMeasuredWidth() - mTextWidth) / 2;
    float drawY = (getMeasuredHeight() + mTextHeight) / 2 - maxDescent;

    //绘制前缀
    mPaint.setColor(mPrefixColor);
    mPaint.setTextSize(mPrefixSize);
    canvas.drawText(mPrefix, drawX, drawY, mPaint);
    //绘制元
    drawX += mPrefixBound.width() + mPrefixPadding;
    mPaint.setColor(mMoneyColor);
    mPaint.setTextSize(mYuanSize);
    canvas.drawText(mYuan, drawX, drawY, mPaint);
    //绘制小数点
    drawX += mYuanBound.width() + mPointPaddingLeft;
    canvas.drawText(POINT, drawX, drawY, mPaint);
    //绘制分
    drawX += mPointPaddingRight;
    mPaint.setTextSize(mCentSize);
    canvas.drawText(mCent, drawX, drawY, mPaint);
}

OK,那这个MoneyView 就可以拿来用了,这是全部代码的地址:
github:https://github.com/cchao1024/MoneyView

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

推荐阅读更多精彩内容