Android-自定义三角形评分控件

效果图

序言

在移动应用开发中,显示数据的方式多种多样,直观的图形展示常常能带给用户更好的体验。本文将介绍如何使用Flutter创建一个自定义三角形纬度评分控件,该控件可以通过动画展示评分的变化,让应用界面更加生动。

实现思路及步骤

  1. 定义控件属性:首先需要定义控件的基本属性,如宽度、高度、最大评分以及每个顶点的评分值。
  2. 自定义绘制:使用自定义View绘制三角形和评分三角形,并在顶点处绘制空心圆点。
  3. 实现动画效果:使用属性动画ValueAnimator来控制评分动画,使每个顶点的评分从0逐渐增加到对应的评分值。

代码实现

定义自定义属性和布局文件

在res/values/attrs.xml中定义自定义属性:

   <declare-styleable name="TriangleRatingAnimView"><attr name="maxRating" format="integer" /><attr name="upRating" format="integer" /><attr name="leftRating" format="integer" /><attr name="rightRating" format="integer" /><attr name="strokeColor" format="color" /><attr name="strokeWidth" format="dimension" /><attr name="ratingStrokeColor" format="color" /><attr name="ratingStrokeWidth" format="dimension" /></declare-styleable>
创建自定义View类

首先,创建一个自定义View类TriangleRatingAnimView,用于绘制三角形和动画效果。

package com.yxlh.androidxy.demo.ui.ratingimport android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.util.AttributeSet
import android.util.TypedValue
import android.view.View
import androidx.core.content.withStyledAttributes
import androidx.core.graphics.ColorUtils
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
import com.yxlh.androidxy.Rfun Context.dpToPx(dp: Float): Float {return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.displayMetrics)
}/*** 三角形评分控件* https://github.com/yixiaolunhui/AndroidXY*/
class TriangleRatingAnimView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,
) : View(context, attrs, defStyleAttr) {var maxRating: Int = 5set(value) {field = valueinvalidate()}var upRating: Int = 0set(value) {field = valueanimateRating()}var leftRating: Int = 0set(value) {field = valueanimateRating()}var rightRating: Int = 0set(value) {field = valueanimateRating()}private var strokeColor: Int = Color.GRAYprivate var strokeWidth: Float = context.dpToPx(1.5f)private var ratingStrokeColor: Int = Color.REDprivate var ratingStrokeWidth: Float = context.dpToPx(2.5f)private var animatedUpRating = 0private var animatedLeftRating = 0private var animatedRightRating = 0private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {style = Paint.Style.STROKEcolor = strokeColorstrokeWidth = this@TriangleRatingAnimView.strokeWidth}private val outerPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {style = Paint.Style.STROKEcolor = ratingStrokeColorstrokeWidth = this@TriangleRatingAnimView.ratingStrokeWidth}private val fillPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {style = Paint.Style.FILLcolor = ColorUtils.setAlphaComponent(ratingStrokeColor, (0.3 * 255).toInt())}private val circlePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {style = Paint.Style.STROKEcolor = ratingStrokeColorstrokeWidth = context.dpToPx(1.5f)}private val circleFillPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {style = Paint.Style.FILLcolor = Color.WHITE}init {context.withStyledAttributes(attrs, R.styleable.TriangleRatingAnimView) {maxRating = getInt(R.styleable.TriangleRatingAnimView_maxRating, 5)upRating = getInt(R.styleable.TriangleRatingAnimView_upRating, 0)leftRating = getInt(R.styleable.TriangleRatingAnimView_leftRating, 0)rightRating = getInt(R.styleable.TriangleRatingAnimView_rightRating, 0)strokeColor = getColor(R.styleable.TriangleRatingAnimView_strokeColor, Color.GRAY)strokeWidth = context.dpToPx(getDimension(R.styleable.TriangleRatingAnimView_strokeWidth, 2f))ratingStrokeColor = getColor(R.styleable.TriangleRatingAnimView_ratingStrokeColor, Color.RED)ratingStrokeWidth = context.dpToPx(getDimension(R.styleable.TriangleRatingAnimView_ratingStrokeWidth, 4f))}}private fun animateRating() {val animator = ValueAnimator.ofFloat(0f, 1f).apply {duration = 300interpolator = LinearOutSlowInInterpolator()addUpdateListener { animation ->val animatedValue = animation.animatedValue as FloatanimatedUpRating = (upRating * animatedValue).toInt()animatedLeftRating = (leftRating * animatedValue).toInt()animatedRightRating = (rightRating * animatedValue).toInt()invalidate()}}animator.start()}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)val width = measuredWidth.toFloat()val height = measuredHeight.toFloat()val circleRadius = context.dpToPx(5f)val padding = circleRadius + context.dpToPx(2f)val p1 = width / 2 to paddingval p2 = padding to height - paddingval p3 = width - padding to height - padding// 绘制外部三角形val path = Path().apply {moveTo(p1.first, p1.second)lineTo(p2.first, p2.second)lineTo(p3.first, p3.second)close()}canvas.drawPath(path, paint)val centroidX = (p1.first + p2.first + p3.first) / 3val centroidY = (p1.second + p2.second + p3.second) / 3// 绘制顶点到重心的连线canvas.drawLine(p1.first, p1.second, centroidX, centroidY, paint)canvas.drawLine(p2.first, p2.second, centroidX, centroidY, paint)canvas.drawLine(p3.first, p3.second, centroidX, centroidY, paint)val dynamicP1 =centroidX + (p1.first - centroidX) * (animatedUpRating / maxRating.toFloat()) to centroidY + (p1.second - centroidY) * (animatedUpRating / maxRating.toFloat())val dynamicP2 =centroidX + (p2.first - centroidX) * (animatedLeftRating / maxRating.toFloat()) to centroidY + (p2.second - centroidY) * (animatedLeftRating / maxRating.toFloat())val dynamicP3 =centroidX + (p3.first - centroidX) * (animatedRightRating / maxRating.toFloat()) to centroidY + (p3.second - centroidY) * (animatedRightRating / maxRating.toFloat())// 绘制内部动态三角形val ratingPath = Path().apply {moveTo(dynamicP1.first, dynamicP1.second)lineTo(dynamicP2.first, dynamicP2.second)lineTo(dynamicP3.first, dynamicP3.second)close()}canvas.drawPath(ratingPath, outerPaint)canvas.drawPath(ratingPath, fillPaint)// 绘制动态点上的空心圆canvas.drawCircle(dynamicP1.first, dynamicP1.second, circleRadius, circlePaint)canvas.drawCircle(dynamicP1.first, dynamicP1.second, circleRadius - context.dpToPx(1.5f), circleFillPaint)canvas.drawCircle(dynamicP2.first, dynamicP2.second, circleRadius, circlePaint)canvas.drawCircle(dynamicP2.first, dynamicP2.second, circleRadius - context.dpToPx(1.5f), circleFillPaint)canvas.drawCircle(dynamicP3.first, dynamicP3.second, circleRadius, circlePaint)canvas.drawCircle(dynamicP3.first, dynamicP3.second, circleRadius - context.dpToPx(1.5f), circleFillPaint)}
}

定义Activity界面xml文件

在res/layout/activity_rating.xml中使用自定义View:

<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_gravity="center_horizontal"android:gravity="center"android:orientation="vertical"><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_gravity="center"><TextViewandroid:id="@+id/upText"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="时间管理"android:textColor="@color/black"android:textSize="13sp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /><com.yxlh.androidxy.demo.ui.rating.TriangleRatingAnimViewandroid:id="@+id/triangleRatingAnimView"android:layout_width="300dp"android:layout_height="200dp"android:layout_centerInParent="true"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toBottomOf="@+id/upText"app:leftRating="3"app:maxRating="10"app:ratingStrokeColor="@android:color/holo_red_dark"app:ratingStrokeWidth="4dp"app:rightRating="8"app:strokeColor="@android:color/darker_gray"app:strokeWidth="3dp"app:upRating="5" /><TextViewandroid:id="@+id/leftText"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="成本控制"android:textColor="@color/black"android:textSize="13sp"app:layout_constraintTop_toBottomOf="@+id/triangleRatingAnimView"app:layout_constraintLeft_toLeftOf="@+id/triangleRatingAnimView"/><TextViewandroid:id="@+id/rightText"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="质量保证"android:textColor="@color/black"android:textSize="13sp"app:layout_constraintTop_toBottomOf="@+id/triangleRatingAnimView"app:layout_constraintRight_toRightOf="@+id/triangleRatingAnimView"/></androidx.constraintlayout.widget.ConstraintLayout><Buttonandroid:id="@+id/randomizeButton"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/triangleRatingAnimView"android:layout_centerHorizontal="true"android:layout_marginTop="20dp"android:text="更改数据" /></androidx.appcompat.widget.LinearLayoutCompat>
定义RatingActivity
package com.yxlh.androidxy.demo.ui.ratingimport android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.yxlh.androidxy.databinding.ActivityRatingBinding
import kotlin.random.Randomclass RatingActivity : AppCompatActivity() {private var binding: ActivityRatingBinding? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityRatingBinding.inflate(layoutInflater)setContentView(binding?.root)binding?.randomizeButton?.setOnClickListener {randomizeRatings()}}private fun randomizeRatings() {val random = Random(System.currentTimeMillis())val maxRating = 5 + random.nextInt(6)val upRating = 1 + random.nextInt(maxRating)val leftRating = 1 + random.nextInt(maxRating)val rightRating = 1 + random.nextInt(maxRating)binding?.triangleRatingAnimView?.apply {this.maxRating = maxRatingthis.upRating = upRatingthis.leftRating = leftRatingthis.rightRating = rightRatinginvalidate()}}
}

通过以上步骤和代码,我们可以创建一个带动画效果的三角形纬度评分控件,使评分展示更加生动和直观。

详情可见:github.com/yixiaolunhui/AndroidXY

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/331119.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue3实战easypan(六):回收站+设置

一、回收站 src/views/recycle/Recycle.vue <template><!-- 上方两个按钮 --><div class"top"><el-button type"success" :disabled"selectFileIdList.length 0" click"revertBatch"><span class"ic…

[保姆式教程]使用目标检测模型YOLO V8 OBB进行旋转目标的检测:训练自己的数据集(基于卫星和无人机的农业大棚数据集)

最近需要做基于卫星和无人机的农业大棚的旋转目标检测&#xff0c;基于YOLO V8 OBB的原因是因为尝试的第二个模型就是YOLO V8&#xff0c;后面会基于YOLO V9模型做农业大棚的旋转目标检测。YOLO V9目前还不能进行旋转目标的检测&#xff0c;需要修改代码 PS:欢迎大家分享农业大…

Plotly库利用滑块创建数据可视化

使用了Plotly库来创建一个数据可视化图表&#xff0c;并使用滑块来控制显示哪些数据 import plotly.graph_objects as go from plotly.subplots import make_subplots# 示例数据 x [1, 2, 3, 4, 5] y1 [1, 2, 3, 4, 5] y2 [5, 4, 3, 2, 1] y3 [2, 3, 1, 5, 4]# 创建子图 f…

12306技术内幕

公司内部做的一次技术分享 文章目录 12306的成就12306系统特点12306系统难点解决思路产品角度技术角度余票库存的表如何设计&#xff1f; 抢票软件推荐巨人的肩膀 对于未公开的技术部分&#xff0c;只能结合已公开的信息&#xff0c;去做大胆的猜想。 本文提到的一些解决方案&…

【车载开发系列】Autosar中的VFB

【车载开发系列】Autosar中的VFB # 【车载开发系列】Autosar中的VFB 【车载开发系列】Autosar中的VFB一. 什么是VFB二. VFB的优点与缺点1&#xff09;VFB的缺点2&#xff09;VFB的好处 三. RTE与VFB之间关系四. 总线架构模式 一. 什么是VFB Virtual Functional Bus。它就是虚拟…

Python函数、类和方法

大家好&#xff0c;当涉及到编写可维护、可扩展且易于测试的代码时&#xff0c;Python提供了一些强大的工具和概念&#xff0c;其中包括函数、类和方法。这些是Python编程中的核心要素&#xff0c;可以帮助我们构建高效的测试框架和可靠的测试用例。 本文将探讨Python中的函数、…

Vue3实战笔记(43)—Vue3组合式API下封装可复用ECharts图表组件

文章目录 前言一、封装echart图标钩子二、使用步骤总结 前言 接上文&#xff0c;已经安装好了ECharts&#xff0c;开始封装组件方便使用。 一、封装echart图标钩子 首先应用我们之前学习的钩子方式&#xff0c;在hooks目录下创建一个名为 useECharts.js 的文件&#xff0c;用…

从零起航,Python编程全攻略

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、Python入门之旅 二、Python进阶之道 三、Python爬虫实战 四、Python数据分析利器 五…

linux系统——终止进程命令

linux进程&#xff0c;有所谓进程树的概念&#xff0c;在此之上&#xff0c;有父进程与子进程 pgrep进程名可以查看进程信息 同时&#xff0c;此命令也可以使用参数进行调节 关于kill有一系列命令参数 echo $?可以输出上次命令执行的情况

【Spring Boot】深度复盘在开发搜索引擎项目中重难点的整理,以及遇到的困难和总结

&#x1f493; 博客主页&#xff1a;从零开始的-CodeNinja之路 ⏩ 收录文章&#xff1a;【Spring Boot】深度复盘在开发搜索引擎项目中重难点的整理&#xff0c;以及遇到的困难和总结 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 什么是搜索引…

Ajax异步删除

在页面上定义一个按钮 <button type"button" class"btn"><a href"JavaScript:;" class"id" b_id"{{$attachment[id]}}">删除</a></button> js代码 <script>$(.id).click(function (){va…

[读论文]精读Self-Attentive Sequential Recommendation

论文链接&#xff1a;https://arxiv.org/abs/1808.09781 其他解读文章&#xff1a;https://mp.weixin.qq.com/s/cRQi3FBi9OMdO7imK2Y4Ew 摘要 顺序动态是许多现代推荐系统的一个关键特征&#xff0c;这些系统试图根据用户最近执行的操作来捕获用户活动的“上下文”。为了捕捉…

ES基础概念

本文不介绍如何使用ES&#xff08;使用ES见&#xff1a;&#xff09; 1.ES生态圈 ES&#xff1a; Logstash&#xff1a;数据处理服务程序&#xff0c;解析转换加工数据&#xff1b; Kibana&#xff1a;数据展示、集群管理&#xff0c;数据可视化、ES管理与监控、报表等&#xf…

区块链钱包如果丢失了私钥或助记词,资产还能恢复吗?

如果你丢失了区块链钱包的私钥或助记词&#xff08;通常是用于恢复钱包的短语或种子&#xff09;&#xff0c;那么你的资产在大多数情况下是无法恢复的。私钥是访问和控制你在区块链上资产的唯一凭证&#xff0c;而助记词&#xff08;如BIP39标准中的12、18、24个单词的短语&am…

【数据分析面试】53.推送消息的分布情况(SQL)

题目 我们有两个表&#xff0c;一个是 notification_deliveries 表&#xff0c;另一个是包含 created 和购买 conversion dates 的 users 表。如果用户没有购买&#xff0c;那么 conversion_date 列为 NULL。 编写一个查询&#xff0c;以获取用户转换前的推送通知总数的分布情…

无人机监测系统:天空之眼,精准掌握地球脉动

在当今信息化快速发展的时代&#xff0c;无人机技术以其独特的优势&#xff0c;正在成为资源调查、环境监测和规划支持的重要工具。无人机监测系统通过搭载多种传感器和设备&#xff0c;能够快速、高效地获取地表信息&#xff0c;为决策提供科学依据。 项目背景 随着全球环境…

SpringMVC接收请求参数的方式:

接收简单变量的请求参数 直接使用简单变量作为形参进行接收&#xff08;这里简单变量名称需要与接收的参数名称保持一致&#xff0c;否则需要加上RequestParam注解&#xff09;&#xff1a; 细节&#xff1a; 1&#xff1a;SpringMVC会针对常见类型&#xff08;八种基本类型及…

二叉排序树的创建

二叉排序树就是节点经过排序构建起的二叉树&#xff0c;其有以下性质&#xff1a; 1. 若它的左子树不为空&#xff0c;则左子树上所有节点的值均小于它的根节点的值。 2. 若它的右子树不为空&#xff0c;则右子树上所有节点的值均大于它的根节点的值。 3. 它的左、右子树也分…

python期末作业:批量爬取站长之家的网站排行榜数据并保存,数据分析可视化

爬虫作业,含python爬取数据和保存文件,数据分析使用pyecharts做数据可视化 整体上分析网站的排名,直观看各个网站的热度。 数据分析之后大致的效果: 整个项目分为两个大的部分,第一部分就是抓取网站排名数据,然后保存为Excel、csv等格式,其次就是从文件中…

下一代Docker会让部署更丝滑吗

下一代Docker会让部署更丝滑吗 如何通俗易懂的理解DockerDocker有什么缺点Docker与AI结合&#xff0c;会让部署更加丝滑吗 随着互联网技术的不断发展&#xff0c;单机系统已经无法满足日益正常的用户量以及正常处理用户请求&#xff0c;这个时候就需要进行多机部署&#xff0c;…