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.rating

import 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.R


fun 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 = 5
        set(value) {
            field = value
            invalidate()
        }
    var upRating: Int = 0
        set(value) {
            field = value
            animateRating()
        }
    var leftRating: Int = 0
        set(value) {
            field = value
            animateRating()
        }
    var rightRating: Int = 0
        set(value) {
            field = value
            animateRating()
        }
    private var strokeColor: Int = Color.GRAY
    private var strokeWidth: Float = context.dpToPx(1.5f)
    private var ratingStrokeColor: Int = Color.RED
    private var ratingStrokeWidth: Float = context.dpToPx(2.5f)
    private var animatedUpRating = 0
    private var animatedLeftRating = 0
    private var animatedRightRating = 0

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.STROKE
        color = strokeColor
        strokeWidth = this@TriangleRatingAnimView.strokeWidth
    }

    private val outerPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.STROKE
        color = ratingStrokeColor
        strokeWidth = this@TriangleRatingAnimView.ratingStrokeWidth
    }

    private val fillPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.FILL
        color = ColorUtils.setAlphaComponent(ratingStrokeColor, (0.3 * 255).toInt())
    }
    private val circlePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.STROKE
        color = ratingStrokeColor
        strokeWidth = context.dpToPx(1.5f)
    }
    private val circleFillPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.FILL
        color = 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 = 300
            interpolator = LinearOutSlowInInterpolator()
            addUpdateListener { animation ->
                val animatedValue = animation.animatedValue as Float
                animatedUpRating = (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 padding
        val p2 = padding to height - padding
        val 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) / 3
        val 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.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center">

        <TextView
            android: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.TriangleRatingAnimView
            android: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" />

        <TextView
            android: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"
            />

        <TextView
            android: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>

    <Button
        android: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.rating

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.yxlh.androidxy.databinding.ActivityRatingBinding
import kotlin.random.Random

class RatingActivity : AppCompatActivity() {

    private var binding: ActivityRatingBinding? = null

    override 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 = maxRating
            this.upRating = upRating
            this.leftRating = leftRating
            this.rightRating = rightRating
            invalidate()
        }
    }
}

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

前端项目使用docker编译发版和gitlab-cicd发版方式

项目目录 app/ ├── container/ │ ├── init.sh │ ├── nginx.conf.template ├── src/ ├── .gitlab-ci.yml └── deploy.sh └── Dockerfile └── Makefilecontainer目录是放nginx的配置文件&#xff0c;给nginx镜像使用 .gitlab-ci.yml和Makefile是c…

【工具使用】搜狗输入法如何输入希腊字母等特殊字符

步骤&#xff1a; 1&#xff0c;点击悬浮框的输入方式&#xff0c;选择“符号大全”&#xff1a; 2&#xff0c;根据自己需要选择对应的符号即可&#xff1a;

QT7_视频知识点笔记_4_文件操作,Socket通信:TCP/UDP

1.事件分发器&#xff0c;事件过滤器&#xff08;重要程度&#xff1a;一般&#xff09; event函数 2.文件操作&#xff08;QFile&#xff09; 实现功能&#xff1a;点击按钮&#xff0c;弹出对话框&#xff0c;并且用文件类读取出内容输出显示在控件上。 #include <QFi…

第八课,分支语句嵌套、随机数函数、初识while循环

一&#xff0c;分支结构的嵌套语法 在 Python 中&#xff0c;分支结构可以嵌套&#xff0c;这意味着你可以在一个条件语句中包含另一个条件语句。嵌套的分支结构可以让你更灵活地控制程序的逻辑流程。 怎么理解呢&#xff1f;打个比方&#xff1a;放学后&#xff0c;请三年级…

深度学习之基于Tensorflow卷积神经网络(CNN)实现猫狗识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 在人工智能和深度学习的热潮中&#xff0c;图像识别是一个备受关注的领域。猫狗识别作为图像识…

AcWing 217:绿豆蛙的归宿 ← 搜索算法

【题目来源】https://www.acwing.com/problem/content/219/【题目描述】 给出一个有向无环的连通图&#xff0c;起点为 1&#xff0c;终点为 N&#xff0c;每条边都有一个长度。 数据保证从起点出发能够到达图中所有的点&#xff0c;图中所有的点也都能够到达终点。 绿豆蛙从起…

DDR5—新手入门学习(一)【1-5】

目录 1、DDR背景 &#xff08;1&#xff09;SDR SDRAM时代 &#xff1a; &#xff08;2&#xff09;DDR SDRAM的创新 &#xff1a; &#xff08;3&#xff09;DDR技术的演进 &#xff1a; &#xff08;4&#xff09;需求推动&#xff1a; 2、了解内存 &#xff08;1&…

k8s笔记 | Prometheus安装

kube-prometheus 基于github安装 选择对应的版本 这里选择 https://github.com/prometheus-operator/kube-prometheus/tree/release-0.11 下载修改为国内镜像源 image: quay.io 改为 quay.mirrors.ustc.edu.cn image: k8s.gcr.io 改为 lank8s.cn 创建 prometheus-ingres…

教师专属的成绩发布小程序

还在为成绩发布而烦恼&#xff1f;还在担心家长无法及时获得孩子的学习反馈&#xff1f;是否想要一个既安全又高效的工具来简化你的教学工作&#xff1f;那么&#xff0c;易查分小程序可能是你一直在寻找的答案。 现在的老师们有了超多的工具来帮助我们减轻负担&#xff0c;提高…

Harmony学习笔记一——项目创建及配置

文章基于Harmony Next Preview2 进行学习&#xff0c;其他版本可能会稍有不同 准备工作 由于目前Harmony Next仅有Preview版本&#xff0c;想要进行Harmony Next开发需要向华为申请权限&#xff0c;具体操作参考: https://developer.huawei.com/consumer/cn/forum/topic/02081…

YOLOV8 如何训练自己的数据

1、git code 项目 地址 2、数据标注&#xff1a;使用yolov8官方推荐的roboflow 地址 2.1 上传数据 2.2 标注 2.3 生成数据集 2.4 导出数据 3 训练 3.1 建.yaml 文件 建立.yaml 文件 3.2 修改.yaml文件里面的内容 1.这是roboflow 网站下下来的数据&#xff0c;只需要把.…

常见算法(2)

1.冒泡排序 定义&#xff1a;相邻的数据两两比较&#xff0c;小的放前面&#xff0c;大的放后面。 public class test {public static void main(String [] arg) {int [] arr {2,4,5,3,6,1};//冒泡排序&#xff0c;排序次数arr.length-1for(int i0;i<arr.length-1;i) {f…

Blazor入门-简单svg绘制+导出图像

参考&#xff1a; SVG 教程 | 菜鸟教程 https://www.runoob.com/svg/svg-tutorial.html 本地环境&#xff1a;win10, visual studio 2022 community 注意&#xff1a;本文只给出思路和框架&#xff0c;对于具体的计算细节&#xff0c;考虑到日后会写入软件著作权和专利文书&am…

visio生成pdf文件有黑边(边框),插入latex输出有边框

解决办法&#xff1a; 1 文件-导出pdf-点击“选项” 2 选择取消勾选

HTML静态网页成品作业(HTML+CSS)——利物浦足球俱乐部介绍网页设计制作(5个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;共有5个页面。 二、作品演示 三、代码目录 四、网站代码 HTML部分代…

TikTok矩阵管理系统:品牌增长的新引擎

随着社交媒体的快速发展&#xff0c;TikTok已成为全球最受欢迎的短视频平台之一。品牌和企业纷纷涌入这个平台&#xff0c;寻求新的增长机会。然而&#xff0c;随着内容的激增和用户群体的多样化&#xff0c;管理TikTok账号变得越来越复杂。这时&#xff0c;TikTok矩阵管理系统…

LLaMa系列模型详解(原理介绍、代码解读):LLaMA 3

LLaMA 3 2024年4月18日&#xff0c;Meta 重磅推出了Meta Llama 3&#xff0c;Llama 3是Meta最先进开源大型语言模型的下一代&#xff0c;包括具有80亿和700亿参数的预训练和指令微调的语言模型&#xff0c;能够支持广泛的应用场景。这一代Llama在一系列行业标准基准测试中展示…

数据仓库实验四:聚类分析实验

目录 一、实验目的二、实验内容和要求三、实验步骤1、建立数据表2、建立数据源视图3、建立挖掘结构Student.dmm4、部署项目并浏览结果5、挖掘模型预测 四、实验结果分析五、实验总结体会 一、实验目的 通过本实验&#xff0c;进一步理解基于划分的、基于层次的、基于密度的聚类…

Python 渗透测试:GhostScript 沙箱绕过.(CVE-2018-16509)

什么是 GhostScript 沙箱绕过 GhostScript 沙箱是一种安全机制,用于在受控环境中运行 GhostScript 解释器,以防止恶意代码的执行。GhostScript 是一个广泛使用的 PDF 和 PostScript 解释器,通常用于在服务器上处理和渲染这些文件格式。Tavis Ormandy 通过公开邮件列表&#xf…

[Algorithm][动态规划][路径问题][不同路径][不同路径Ⅱ][珠宝的最高价值]详细讲解

目录 1.不同路径1.题目链接2.算法原理详解3.代码实现 2.不同路径 II1.题目链接2.算法原理详解3.代码实现 3.珠宝的最高价值1.题目链接2.算法原理详解3.代码实现 1.不同路径 1.题目链接 不同路径 2.算法原理详解 思路&#xff1a; 确定状态表示 -> dp[i][j]的含义 走到dp[…