背景

之前写过的App里有评分的功能,而显示评分一般使用系统的RatingBar再加自定义,一切都很完美,但是产品提了一个需求,例如4.6、4.7、5.8分,不要显示为4个星星加一个半星(4.5分),而是能显示出区别。(系统的RatingBar必须满正整数才可以满星,如果没满,还是显示一半的效果)

这时系统的RatingBar就不满足需求了,需要我们自定义控件,当时需求太赶了,需求先放下来了,既然要绘制星星,我们就从绘制单个星星开始吧,后续只要对星星做一层PorterDuffXfermode处理即可。

原理

我们以星星的中心为圆心,对五角星的5个端点画一个外接圆。以五角星的内部5个小角画一个内接圆,所以有2个圆。

  1. 五角星的5个顶点,将360的圆平分5份,平均角度为72度。

  1. 取一个90度直角为参考,90度直角将右上角的部分分为2个角,分别是大的角和小的角,大的角为72度,所以小角的角度为90度减去72度,为18度。

  1. 我们再计算出一半的平均角度,72除以2,为36度。

  1. 而内角,就是凹进去的那个小角的角度就可以计算出来,36度加18度,为54度。

  1. 知道2个角的角度,以及外接圆和内接圆的半径,就可以用三角函数计算出坐标点。

Canvas会经过0,0点,所以第一个点我们要先将Path调用moveTo(x,y),移动到第一个点,再继续lineTo(x,y)下一个点。最后调用close()闭合Path。

完整代码

自定义属性

<declare-styleable name="StarsView"><!-- 星星的颜色 --><attr name="stv_color" format="color" /><!-- 星星的边数 --><attr name="stv_num" format="integer|dimension|reference" /><!-- 边的线宽 --><attr name="stv_edge_line_width" format="float|dimension|reference" /><!-- 填充风格 --><attr name="stv_style" format="enum"><!-- 填满 --><enum name="fill" value="1" /><!-- 描边 --><enum name="stroke" value="2" /></attr>
</declare-styleable>

JAVA代码

public class StarsView extends View {/*** View默认最小宽度*/private static final int DEFAULT_MIN_WIDTH = 100;/*** 风格,填满*/private static final int STYLE_FILL = 1;/*** 风格,描边*/private static int STYLE_STROKE = 2;/*** 控件宽*/private int mViewWidth;/*** 控件高*/private int mViewHeight;/*** 外边大圆的半径*/private float mOutCircleRadius;/*** 里面小圆的的半径*/private float mInnerCircleRadius;/*** 画笔*/private Paint mPaint;/*** 多少个角的五角星*/private int mAngleNum;/*** 星星的路径*/private Path mPath;/*** 星星的颜色*/private int mColor;/*** 边的线宽*/private float mEdgeLineWidth;/*** 填充风格*/private int mStyle;public StarsView(Context context) {this(context, null);}public StarsView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public StarsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context, attrs, defStyleAttr);}private void init(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {initAttr(context, attrs, defStyleAttr);//取消硬件加速setLayerType(LAYER_TYPE_SOFTWARE, null);//画笔mPaint = new Paint();mPaint.setAntiAlias(true);if (mStyle == STYLE_FILL) {mPaint.setStyle(Paint.Style.FILL);} else if (mStyle == STYLE_STROKE) {mPaint.setStyle(Paint.Style.STROKE);}mPaint.setColor(mColor);mPaint.setStrokeWidth(mEdgeLineWidth);}private void initAttr(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {int defaultColor = Color.argb(255, 0, 0, 0);int defaultNum = 5;int mineNum = 2;float defaultEdgeLineWidth = dip2px(context, 1f);int defaultStyle = STYLE_STROKE;if (attrs != null) {TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.StarsView, defStyleAttr, 0);mColor = array.getColor(R.styleable.StarsView_stv_color, defaultColor);int num = array.getInt(R.styleable.StarsView_stv_num, defaultNum);mAngleNum = num <= mineNum ? mineNum : num;mEdgeLineWidth = array.getDimension(R.styleable.StarsView_stv_edge_line_width, defaultEdgeLineWidth);mStyle = array.getInt(R.styleable.StarsView_stv_style, defaultStyle);array.recycle();} else {mColor = defaultColor;mAngleNum = defaultNum;mEdgeLineWidth = defaultEdgeLineWidth;mStyle = defaultStyle;}}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);mViewWidth = w;mViewHeight = h;//计算外边大圆的半径mOutCircleRadius = (Math.min(mViewWidth, mViewHeight) / 2f) * 0.95f;//计算里面小圆的的半径mInnerCircleRadius = (Math.min(mViewWidth, mViewHeight) / 2f) * 0.5f;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//将画布中心移动到中心点canvas.translate(mViewWidth / 2, mViewHeight / 2);//画星星drawStars(canvas);}/*** 画星星*/private void drawStars(Canvas canvas) {//计算平均角度,例如360度分5份,每一份角都为72度float averageAngle = 360f / mAngleNum;//计算大圆的外角的角度,从右上角为例计算,90度的角减去一份角,得出剩余的小角的角度,例如90 - 72 = 18 度float outCircleAngle = 90 - averageAngle;//一份平均角度的一半,例如72 / 2 = 36度float halfAverageAngle = averageAngle / 2f;//计算出小圆内角的角度,36 + 18 = 54 度float internalAngle = halfAverageAngle + outCircleAngle;//创建2个点Point outCirclePoint = new Point();Point innerCirclePoint = new Point();if (mPath == null) {mPath = new Path();}mPath.reset();for (int i = 0; i < mAngleNum; i++) {//计算大圆上的点坐标//x = Math.cos((18 + 72 * i) / 180f * Math.PI) * 大圆半径//y = -Math.sin((18 + 72 * i)/ 180f * Math.PI) * 大圆半径outCirclePoint.x = (int) (Math.cos(angleToRadian(outCircleAngle + i * averageAngle)) * mOutCircleRadius);outCirclePoint.y = (int) -(Math.sin(angleToRadian(outCircleAngle + i * averageAngle)) * mOutCircleRadius);//计算小圆上的点坐标//x = Math.cos((54 + 72 * i) / 180f * Math.PI ) * 小圆半径//y = -Math.sin((54 + 72 * i) / 180 * Math.PI ) * 小圆半径innerCirclePoint.x = (int) (Math.cos(angleToRadian(internalAngle + i * averageAngle)) * mInnerCircleRadius);innerCirclePoint.y = (int) -(Math.sin(angleToRadian(internalAngle + i * averageAngle)) * mInnerCircleRadius);//第一次,先移动到第一个大圆上的点if (i == 0) {mPath.moveTo(outCirclePoint.x, outCirclePoint.y);}//坐标连接,先大圆角上的点,再到小圆角上的点mPath.lineTo(outCirclePoint.x, outCirclePoint.y);mPath.lineTo(innerCirclePoint.x, innerCirclePoint.y);}mPath.close();canvas.drawPath(mPath, mPaint);}/*** 角度转弧度,由于Math的三角函数需要传入弧度制,而不是角度值,所以要角度换算为弧度,角度 / 180 * π** @param angle 角度* @return 弧度*/private double angleToRadian(float angle) {return angle / 180f * Math.PI;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);setMeasuredDimension(handleMeasure(widthMeasureSpec), handleMeasure(heightMeasureSpec));}/*** 处理MeasureSpec*/private int handleMeasure(int measureSpec) {int result = DEFAULT_MIN_WIDTH;int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);if (specMode == MeasureSpec.EXACTLY) {result = specSize;} else {//处理wrap_content的情况if (specMode == MeasureSpec.AT_MOST) {result = Math.min(result, specSize);}}return result;}public static int dip2px(Context context, float dipValue) {final float scale = context.getResources().getDisplayMetrics().density;return (int) (dipValue * scale + 0.5f);}
}

基本使用

<com.zh.cavas.sample.widget.StarsViewandroid:id="@+id/stars"android:layout_width="30dp"android:layout_height="30dp"android:layout_margin="10dp"app:stv_color="#0000FF"app:stv_edge_line_width="1dp"app:stv_num="5"app:stv_style="stroke" /><com.zh.cavas.sample.widget.StarsViewandroid:id="@+id/stars2"android:layout_width="30dp"android:layout_height="30dp"android:layout_margin="10dp"app:stv_color="#0000FF"app:stv_edge_line_width="1dp"app:stv_num="5"app:stv_style="fill" />

Android 自定义View 绘制五角星相关推荐

  1. android自定义弧度按钮,Android 自定义View 绘制六边形设置按钮

    今天逛酷安的时候,发现酷安的设置按钮(截图的右上角),是一个六边形 + 中心圆的图标,所以又是一个自定义View练习对象了.画圆很简单,知道半径即可,而重点就在画出六边形. 酷安截图.png 最终效果 ...

  2. Android自定义View绘制闹钟

    Android自定义View绘制闹钟 本文简单实现了一个闹钟,扩展View,Canvas绘制 效果如下: 代码如下: package com.gaofeng.mobile.clock_demo;imp ...

  3. Android 自定义View绘制电池图标

    /*** @anthor GrainRain* @funcation 自定义View绘制电池* @date 2019/8/27*/ public class DrawBatteryView exten ...

  4. Android自定义View绘制流程

    Android视图层次结构简介 在介绍View绘制流程之前,咱们先简单介绍一下Android视图层次结构以及DecorView,因为View的绘制流程的入口和DecorView有着密切的联系. 我们平 ...

  5. android 自定义View绘制电池电量(电池内带数字显示)

    最新公司需要一个电池内带数字的显示电池电量需求,百度了一下.参考下面这篇文章写的Android自定义View之电池电量显示. 增加了里面电池电量数字显示,还有就是一个屏幕适配.不管屏幕分辨率基本都能适 ...

  6. android画a4矩形,Android自定义View绘制原理:画多大?画在哪?画什么?(三)

    View绘制就好比画画,抛开Android概念,如果要画一张图,首先会想到哪几个基本问题: 画多大? 画在哪? 怎么画? Android绘制系统也是按照这个思路对View进行绘制,上面这些问题的答案分 ...

  7. Android自定义View绘制闪闪发光的文字

    如何实现类似网页效果中闪闪发光的文字,通过自定义View可以实现这一炫酷效果 1.自定义View public class FlickTextView extends TextView {privat ...

  8. Android 自定义View绘制的基本开发流程 Android自定义View(二)

    1 View绘制的过程 View的测量--onMeasure() View的位置确定--onLayout() View的绘制--onDraw() 2 View的测量--onMeasure() Andr ...

  9. Android 自定义View 绘制正N边形

    从我的 "慕课-手记" 中搬过来   作者: stone86  链接:http://www.imooc.com/article/14599 来源:慕课网 支付宝芝麻信用分-分析中, ...

最新文章

  1. 【Android 电量优化】JobScheduler 源码分析 ( JobServiceContext 源码分析 | 闭环操作总结 | 用户提交任务 | 广播接收者接受相关广播触发任务执行 )★
  2. 每个人都有自己的秘密
  3. 使用Maven搭建Struts2框架的开发环境
  4. TabLayout基本属性全解
  5. 让ubuntu开机快一点:记开机出现Waiting for network configuration...
  6. java mock void_如何使用Mockito模拟void方法 - How to mock void methods with Mockito
  7. Office365—Exchange管理4—通讯组和安全组
  8. Bower和npm有什么区别?
  9. freertos nand flash 读取错误_Flash失效小谈
  10. 【转】JavaScript常用代码书写规范
  11. matlab中的turbo码,基于Matlab的Turbo码仿真研究
  12. 动手学Pytorch深度学习建模与应用
  13. Mac OS读写NTFS
  14. 当跳槽遇到互联网公司裁员寒潮,该如何应对?
  15. CC攻击(Challenge Collapsar)原理及防范方法
  16. golang学习之五:error、painc、recover
  17. 陀螺世界脚本合集分享,autojs弹窗代码、autojs多选勾选代码
  18. NB物联网之天翼物联(7)——一次性通过电信NB平台自助测试
  19. ST推出 28nm MCU ,NXP更狠,推出16nm MCU
  20. Dcloud H5+微信登录功能

热门文章

  1. tp5.1 配置虚拟域名
  2. 第一章 FreeBSD之系统安装
  3. 进程状态--生活中的小例子
  4. # Alpha冲刺7
  5. 傻瓜式安装Kubernetes集群
  6. windows10密钥激活失败 0x80072efe
  7. Retained Messages - MQTT Essentials: Part 8
  8. 入职自我介绍怎么做?
  9. MATLAB对于自控题目的解答实用代码
  10. 呼叫中心系统的强大功能