Android自定义view实现评分控件

首先贴上源码地址:https://github.com/CB-ysx/CBRatingBar

最近需要做一个星星的评分控件(可以调整进度,进度颜色渐变)

如图:

《Android自定义view实现评分控件》 image
《Android自定义view实现评分控件》 image

一开始想到的是用系统自带的RatingBar做,但发现了一个问题,实现颜色渐变有点复杂,而且有好几个页面都用了这个,总不能都这样写吧。刚好这段时间看了HenCoder写的Android自定义view系列文章,于是就想自己尝试下实现一个评分控件,可以实现图案的替换,渐变颜色,进度背景,图案个数,大小等参数的自己控制,经过几天的折腾,终于完成了这个控件CBRatingBar

先上效果图:

《Android自定义view实现评分控件》 image

《Android自定义view实现评分控件》 image

《Android自定义view实现评分控件》 image

《Android自定义view实现评分控件》 image

gif效果图:

《Android自定义view实现评分控件》 image

如何使用:

Gradle

  • 在项目的build.gradle中添加如下代码:
    allprojects {
        repositories {
            maven { url 'https://jitpack.io' }
        }
    }
  • 在项目的build.gradle中引入该库:
    dependencies {
        compile 'com.github.CB-ysx:CBRatingBar:2.0.1'
    }

使用方法

  • xml
    <com.cb.ratingbar.CBRatingBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <com.cb.ratingbar.CBRatingBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:starSize="20dp"
        app:starCount="5"
        app:starSpace="10dp"
        app:starStrokeWidth="1dp"
        app:starCanTouch="true"
        app:starMaxProgress="120"
        app:starProgress="60"
        app:starShowStroke="true"
        app:starUseGradient="true"
        app:starStartColor="#0000ff"
        app:starEndColor="#00ff00"
        app:starCoverColor="#ff0000"
        app:starFillColor="#666666"
        app:starPointCount="5"
        app:starStrokeColor="#0f0f0f"
        app:pathData="@string/bird"
        app:pathDataId="@string/bird"/>
  • java
cbRatingBar.setStarSize(20) //大小
        .setStarCount(5) //数量
        .setStarSpace(10) //间距
        .setStarPointCount(5) //角数(n角星)
        .setShowStroke(true) //是否显示边框
        .setStarStrokeColor(Color.parseColor("#00ff00")) //边框颜色
        .setStarStrokeWidth(5) //边框大小
        .setStarFillColor(Color.parseColor("#00ff00")) //填充的背景颜色
        .setStarCoverColor(Color.parseColor("#00ff00")) //填充的进度颜色
        .setStarMaxProgress(120) //最大进度
        .setStarProgress(50) //当前显示的进度
        .setUseGradient(true) //是否使用渐变填充(如果使用则coverColor无效)
        .setStartColor(Color.parseColor("#000000")) //渐变的起点颜色
        .setEndColor(Color.parseColor("#ffffff")) //渐变的终点颜色
        .setCanTouch(true) //是否可以点击
        .setPathData(getResources().getString(R.string.pig))//传入path的数据
        .setPathDataId(R.string.pig)//传入path数据id
        .setDefaultPath()//设置使用默认path
        .setPath(path)//传入path
        .setOnStarTouchListener(new CBRatingBar.OnStarTouchListener() { //点击监听
            @Override
            public void onStarTouch(int touchCount) {
                Toast.makeText(MainActivity.this, "点击第" + touchCount + "个星星", Toast.LENGTH_SHORT).show();
            }
        });

说明

pathData为svg文件中的path数据,如下:

<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
    "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" style="" class="icon" height="16" p-id="2384" t="1506306007922"
     version="1.1" viewBox="0 0 1137 1024" width="17.765625" xmlns="http://www.w3.org/2000/svg">
    <defs>
        <style type="text/css"></style>
    </defs>
    <path
        d="M800.653373 60.04085c-92.533023 0-174.90483 42.957261-228.601405 109.913074-53.696576-66.955813-136.068383-109.913074-228.601406-109.913074-161.838292 0-293.037297 131.199004-293.037297 293.037297 0 34.485875 6.284982 67.452386 17.216998 98.202847 82.149461 249.939217 470.047002 495.868793 504.429116 517.265896 34.374702-21.397103 422.272244-267.326679 504.421705-517.265896 10.932015-30.750461 17.216997-63.716972 17.216997-98.202847-0.007412-161.838292-131.206416-293.037297-293.044708-293.037297z"
        fill="#E24B44" p-id="2385">
    </path>
</svg>

以上path中”M800.653373 … -293.037297z”这部分数据就是要提交给控件的pathData。

如何实现

为了有更好的扩展性,这我用了path数据来绘制图案,而非单独实现绘制星星,当然,一开始确实只是实现了绘制星星,而且是没有圆角效果的星星(设计图有圆角,先凑合着用吧,哈哈)。于是在网上找到了星星的绘制方法:

/**
     * 获取星星的path
     *
     * @return
     */
    private Path getStarPath(int dx) {
        Path path = new Path();
        float radius;
        if (starPointCount % 2 == 0) {
            radius = starSize * (cos(360.0 / starPointCount / 2) / 2 - sin(360.0 / starPointCount / 2) * sin(90 -
                    360.0 / starPointCount) / cos(90 - 360.0 / starPointCount));
        } else {
            radius = starSize * sin(360.0 / starPointCount / 4) / 2 / sin(180 - 360.0 / starPointCount / 2 - 360.0 /
                    starPointCount / 4);
        }
        for (int i = 0; i < starPointCount; ++i) {
            if (i == 0) {
                path.moveTo(starSize * cos(360 / starPointCount * i) / 2, starSize * sin(360 / starPointCount * i) /
                        2 + dx);
            } else {
                path.lineTo(starSize * cos(360.0 / starPointCount * i) / 2, starSize * sin(360.0 / starPointCount *
                        i) / 2 + dx);
            }
            path.lineTo(radius * cos(360.0 / starPointCount * i + 360.0 / starPointCount / 2), radius * sin(360.0 /
                    starPointCount * i + 360.0 / starPointCount / 2) + dx);
        }
        path.close();

        return path;
    }

其中dx可以先不用管,这是后面需要绘制多个星星用到的,这段代码就可以得到星星的path,至于为什么是这样计算,这我就没有去探究了(赶项目要紧),这段代码绘制出来的图案如下:

《Android自定义view实现评分控件》 image

没错,只能绘制一个星星,那要如何绘制多个星星呢,这里就用到了dx了,dx是星星的偏移量(星星宽度+两个星星之间的间距),通过这个偏移量获取不同位置的星星path,再绘制到画布上就能实现多个星星了,代码如下:

canvas.translate(dx, dy);
canvas.rotate(-90);

int x = 0;
for (int i = 0; i < starCount; ++i) {
    Path path = getStarPath(x);
    canvas.drawPath(patpaint);
    x += (starSize + starSpace);
}

canvas.rotate(90);
canvas.translate(-dx, -dy);

starCount就是星星的个数
starSize就是星星的大小
starSpace就是两个星星之间的间距

此时绘制出来的星星是填充了颜色的星星,但是还没有进度条,更没有渐变的进度条效果。
绘制效果如图:

《Android自定义view实现评分控件》 image

接下来就是实现进度条,先实现纯色的进度条,本来绘制进度很简单的,但是由于星星属于不太规则的图案(虽然已经很规则了,但相对于矩形、圆形这些来说还是不规则的,哈哈,不要吐槽我),加上考虑到扩展性(可能使用的是其他真正不规则的图案),这里用了另一种方法来填充不规则图形。
代码如下:

//将星星绘制到star上
Bitmap star = Bitmap.createBitmap(width, starSizeBitmap.Config.ARGB_8888);
Canvas starCanvas = neCanvas(star);
drawStar(starCanvas, starFillPaint);

//在star上填充颜色
Bitmap finalStar = Bitmap.createBitmap(width, starSizeBitmap.Config.ARGB_8888);
Canvas canvas = neCanvas(finalStar);
canvas.save();
canvas.clipRect(0, 0, dx, starSize);
canvas.drawRect(0, 0, widthstarSize, starCoverPaint);
canvas.restore();

canvas.drawBitmap(star, 0, 0, starPaint);

先创建一个宽为width(由星星个数及间距计算得出)高为starSize(单个星星的大小)的Bitmap,然后把星星以未选中时的背景色绘制到Bitmap上,这时得到的就是没有任何进度的图。接下来是绘制进度,我们需要再创建一个Bitmap,然后使用clipRect方法裁剪高度为starSize,宽度为dx(进度)的矩形局域,然后填充进度的颜色(可以是纯色也可以是渐变色),最后把绘制了星星的bitmap绘制到这个画布上,采用PorterDuffXfermode的画笔,即可得到填充了进度的星星效果,如图:

《Android自定义view实现评分控件》 image

设置画笔:

starPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));

关于画笔的设置,可参考HenCoder的这篇文章HenCoder Android 开发进阶: 自定义 View 1-2 Paint 详解,不得不说,这几篇文章都写得很不错,对我来说,收获真的很多。

这样就完成了CBRatingBar的第一个版本。用在app上,又方便效果也还不错,不足的是只有一个图案,没办法自定义,于是就有了第二个版本,开发2.0.0版本虽然只是加多了自定义path数据,但是这一过程也是挺坎坷的。

研究png图片提取path数据—无果;
研究svg图片中的path数据—有点希望;
于是开始学习svg语法,自己实现将svg中的pathData转为Android绘图中的path数据,到了画弧线这些命令就卡住了,还有网上看了一些svg数据,发现其实挺坑的,有的用‘,’分割,有的用空格,感觉我的算法并不能很好地识别,又纠结了一段时间。最后在github发现了RichPath这个库,发现了该库中有实现从svg提取path的算法,于是就拿过来用了,在此感谢tarek360提供的算法。

有了这个剩下的就简单多了,加多几个方法,传入pathData数据,将之前获取星星的path方法,改为可以从pathData中提取,这样就实现了可以自定义图案,具体代码如下:

/**
* 初始化path
*
* @return
*/
private void initPath() {
    if (pathData != null && !"".equals(pathData.trim().replace(" ", ""))) {
        mPath = PathParserCompat.createPathFromPathData(pathData);
        isSelfPath = true;
    } else if (pathDataId != -1) {
        mPath = PathParserCompat.createPathFromPathData(getResources().getString(pathDataId));
        isSelfPath = true;
    } else {
        isSelfPath = false;
    }
    if (isSelfPath) {
        resizePath(mPath, starSize, starSize);
    }
}

其中的pathData就是原始数据,PathParserCompat就是用来将数据转为path的。由于提取出来的path是svg本来的大小,所以需要将它缩减为我们设置的大小,也就是用resizePath这个方法:

public void resizePath(Path path, float width, float height) {
    RectF bounds = new RectF(0, 0, width, height);
    RectF src = new RectF();
    path.computeBounds(src, true);
    Matrix resizeMatrix = new Matrix();
    resizeMatrix.setRectToRect(src, bounds, Matrix.ScaleToFit.FILL);
    path.transform(resizeMatrix);
}

这样我们就可以很方便地使用我们自己的图案来绘制了。
最后再贴上源码地址:https://github.com/CB-ysx/CBRatingBar
欢迎star~

关于svg语法,可查看W3C School

    原文作者:Codebearsh
    原文地址: https://www.jianshu.com/p/67e8c0555d8d
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞