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

news2024/9/17 8:33:04

效果图

序言

在移动应用开发中,显示数据的方式多种多样,直观的图形展示常常能带给用户更好的体验。本文将介绍如何使用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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Pantera 合伙人简谈 Morpho:更高效、适应性更强的 DeFi 解决方案

原文标题&#xff1a;《Pioneering Peer-to-Peer Lending in the DeFi Revolution》撰文&#xff1a;Pantera Capital 合伙人 Paul Veradittakit编译&#xff1a;Chris&#xff0c;Techub News 文章来源&#xff1a;香港Web3媒体Techub News Morpho 正在超越 Compound 等传统…

linux系统安全加固

目录 1、账户安全基本措施 1&#xff09;系统账户清理 2&#xff09;密码安全控制 3&#xff09;命令历史限制 2、用户切换及提权 1&#xff09;使用 su命令切换用户 2&#xff09;使用sudo机制提升权限 3、系统引导和安全登录控制 1&#xff09;开机安全控制 2&…

亚马逊卖家账号注册复杂吗?需要什么辅助工具吗?

在当今数字化的商业世界中&#xff0c;亚马逊作为全球最大的电商平台之一&#xff0c;吸引着无数的卖家和买家。对于想要进入亚马逊销售市场的卖家来说&#xff0c;首先要完成的一项重要任务就是注册亚马逊卖家账号。本文将详细介绍亚马逊注册的步骤、所需时间&#xff0c;以及…

Advanced Installer 使用教程-注册表的读写

一、写入 1、注册表的写入&#xff1a;点击左侧“注册表”&#xff0c;在配置单元注册表的条目上右击选择“新建项”&#xff0c;填入新建项名称&#xff0c;如下图新建了一个“InstallerManager”项 2、在刚才的新建项上右击选择“新建值”&#xff0c;在弹出的窗口中填入名称…

微服务框架Go-kit 01 - 基础示例

一、Go kit简介 Go kit 是一个用于构建可扩展、灵活和可维护微服务的框架和工具集合。它提供了一系列库和组件&#xff0c;涵盖了微服务开发的各个方面&#xff0c;包括服务发现、负载均衡、通信、日志记录、请求跟踪、限流、熔断等。 Go kit 构建微服务时遵循一种类似于传统…

java/php/node.js/python农业技术学习平台【2024年毕设】

本系统带文档lw万字以上 文末可领取本课题的JAVA源码参考 开发环境 开发语言&#xff1a;Java 框架&#xff1a;ssm 技术&#xff1a;ssmvue JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7或8.0 数据库工具&#xff1a;Navicat11 …

AI绘画Stable Diffusion 一键打造网红黏土风格图片,本地部署免费使用!

大家好&#xff0c;我是画画的小强 最近社交平台正在被一股全新的创作潮流所席卷&#xff0c;这就是“黏土AI”的狂潮。这种丑陋但又可爱的风格已经成为网民们热议的话题。无论是在小红书还是其他社交平台上&#xff0c;诸如#黏土、#我的黏土世界等标签都被这种新奇的创作风格…

Flink-cdc更好的流式数据集成工具

What’s Flink-cdc? Flink CDC 是基于Apache Flink的一种数据变更捕获技术&#xff0c;用于从数据源&#xff08;如数据库&#xff09;中捕获和处理数据的变更事件。CDC技术允许实时地捕获数据库中的增、删、改操作&#xff0c;将这些变更事件转化为流式数据&#xff0c;并能够…

客厅落地台灯怎么摆放好?五款精品护眼落地灯分享

客厅落地台灯怎么摆放好&#xff1f;在追求品质生活的今天&#xff0c;许多人都选择入手了落地台灯&#xff0c;它不仅能够帮助补充光线&#xff0c;还能够提供敞亮的照明效果&#xff0c;特别在采光不是很好的地方&#xff0c;而客厅落地台灯怎么摆放好&#xff1f;其实大路灯…

SQL刷题笔记day1

1题目 我的代码&#xff1a; select * from employees order by hire_date desc limit 2,1 标准代码&#xff1a; select * from employees where hire_date (select distinct hire_date from employees order by hire_date desc limit 2,1) 复盘&#xff1a;因为按照入…

DOS学习-目录与文件应用操作经典案例-copy

欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一.前言 二.使用 三.案例 一.前言 copy命令的功能是复制一个或多个已经存在的文件到新的位置&#xff0c;或者将多个文件的内容整合后保存为一个单独的文件&#xff0c;亦或者用于创建批…

docker部署kafka实战

目录 一、部署kafaka、zookeeper 二、测试信息发送与接收 三、kafka进阶 一、部署kafaka、zookeeper 请提前安装docker、docker-compose 安装docker&#xff1a;docker--安装docker-ce-CSDN博客 安装docker-compose&#xff1a; 安装docker-compose_安装 docker-compose-CSD…

URL化00

题目链接 URL化 题目描述 注意点 字符串长度在 [0, 500000] 范围内假定该字符串尾部有足够的空间存放新增字符 解答思路 因为该字符串尾部有足够的空间存放新增字符&#xff0c;所以直接使用大小为s.length()的char数组进行操作&#xff0c;使用idx记录当前操作char数组的…

Vue学习笔记2——创建一个Vue项目

Vue项目 1、创建一个Vue项目2、Vue项目的目录结构3、模版语法4、属性绑定5、条件渲染 1、创建一个Vue项目 vue官方文档&#xff1a; https://cn.vuejs.org/打开命令行界面&#xff08; “winR"再输入"cmd”&#xff09;&#xff0c;切换位置到指定的位置创建vue项目…

Z缓冲技术在AI去衣中的关键角色

引言&#xff1a; 人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;为图像处理领域带来了革命性的变化。其中&#xff0c;AI去衣技术作为一种新兴的应用&#xff0c;引起了广泛关注。它不仅在多媒体内容的编辑、虚拟现实和增强现实等领域具有重要的应用价值&…

【B站 heima】小兔鲜Vue3 项目学习笔记Day03

文章目录 Home1.Home整体结构搭建和分类实现2. banner轮播图功能3. Home 面板组件封装4.新鲜好物和人气推荐实现5. 图片懒加载指令实现6. Home- product产品列表实现7. Home-GoodsItem 组件封装 一级路由1. 整体认识和路由配置2. 面包屑导航3. 一级分类 - 轮播图的实现4. 激活状…

MacPro中Ubuntu安装GNOME桌面

第一步&#xff0c;先在MacPro中安装UTM虚拟机。 查看另一文章&#xff1a; https://blog.csdn.net/qq_38382925/article/details/139157877?spm1001.2014.3001.5502 第二步&#xff0c;在虚拟机中安装Ubuntu ARM64 server 查看另一文章&#xff1a; https://blog.csdn.net/qq…

Golang | Leetcode Golang题解之第100题相同的树

题目&#xff1a; 题解&#xff1a; func isSameTree(p *TreeNode, q *TreeNode) bool {if p nil && q nil {return true}if p nil || q nil {return false}queue1, queue2 : []*TreeNode{p}, []*TreeNode{q}for len(queue1) > 0 && len(queue2) > …

uni-app 微信 支付宝 小程序 使用 longpress 实现长按删除功能,非常简单 只需两步

1、先看效果 2、直接上代码 ui结构 <view class"bind" longpress"deleteImage" :data-index"index"><view class"bind_left">绑定设备</view><view class"bind_right"><view class"bind_t…

三方登录- iOS Twitter登录

背景 在现代移动应用中&#xff0c;集成第三方登录已经成为一种常见的需求&#xff0c;它不仅能提高用户体验&#xff0c;还能减少用户注册的阻力。我们选择了 Twitter 作为示例&#xff0c;但类似的步骤和逻辑也适用于其他第三方登录服务。希望这篇博客能为你提供清晰的指导&…