Android自定义view实现评分控件

作者: Codebearsh | 来源:发表于2017-10-08 10:36 被阅读2293次

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

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

    如图:

    image image

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

    先上效果图:

    image
    image
    image
    image

    gif效果图:

    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,至于为什么是这样计算,这我就没有去探究了(赶项目要紧),这段代码绘制出来的图案如下:

    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就是两个星星之间的间距

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

    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的画笔,即可得到填充了进度的星星效果,如图:

    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

    相关文章

      网友评论

        本文标题:Android自定义view实现评分控件

        本文链接:https://www.haomeiwen.com/subject/qjreyxtx.html