/MoneyView

A Custom money view to show different style Android 自定义View金额、价格样式显示MoneyView

Primary LanguageJava

License Download

效果图:

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