当前位置: 首页 > news >正文

摇杆控制View

 功能特性:


- 支持360度方向检测
- 摇杆手柄跟随触摸移动
- 手柄限制在摇杆圆盘范围内
- 使用协程实现高频率回调(60fps)
- 提供X/Y偏移百分比、角度、幅度等信息

正文

RobotRocker
package com.example.dsbridge_demoimport android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import kotlinx.coroutines.*
import kotlin.math.*/*** 摇杆控制View* 功能特性:* - 支持360度方向检测* - 摇杆手柄跟随触摸移动* - 手柄限制在摇杆圆盘范围内* - 使用协程实现高频率回调(60fps)* - 提供X/Y偏移百分比、角度、幅度等信息*/
class RobotRocker @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {// ==================== 位置和尺寸 ====================/** 圆盘中心X坐标 */private var centerX = 0f/** 圆盘中心Y坐标 */private var centerY = 0f/** 圆盘半径 */private var baseRadius = 0f/** 手柄半径 */private var hatRadius = 0f/** 手柄当前位置X坐标 */private var currentX = 0f/** 手柄当前位置Y坐标 */private var currentY = 0f// ==================== 画笔 ====================/** 圆盘画笔 */private val basePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {color = Color.LTGRAYstyle = Paint.Style.FILL}/** 手柄画笔 */private val hatPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {color = Color.DKGRAYstyle = Paint.Style.FILL}// ==================== 监听和协程 ====================/** 摇杆监听器 */private var listener: RockerListener? = null/** 协程任务 */private var rockerJob: Job? = null/** 协程作用域 */private val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())/** 回调间隔(毫秒),16ms约60fps */private val callbackInterval = 16L/*** View尺寸改变时调用,重新计算圆盘和手柄的参数*/override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {super.onSizeChanged(w, h, oldw, oldh)// 圆盘中心位于View中心centerX = width / 2fcenterY = height / 2f// 初始化手柄位置为圆盘中心currentX = centerXcurrentY = centerY// 圆盘半径取View宽度和高度的最小值的1/3baseRadius = (minOf(width, height) / 3f)// 手柄半径取View宽度和高度的最小值的1/6hatRadius = (minOf(width, height) / 6f)}/*** 绘制View*/override fun onDraw(canvas: Canvas) {super.onDraw(canvas)// 绘制摇杆圆盘(灰色圆形)canvas.drawCircle(centerX, centerY, baseRadius, basePaint)// 绘制摇杆手柄(深灰色圆形)canvas.drawCircle(currentX, currentY, hatRadius, hatPaint)}/*** 处理触摸事件*/override fun onTouchEvent(event: MotionEvent?): Boolean {event ?: return falsereturn when (event.action) {MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {handleTouch(event.x, event.y, event.action == MotionEvent.ACTION_DOWN)true}MotionEvent.ACTION_UP -> {resetRocker()true}else -> super.onTouchEvent(event)}}/*** 处理触摸位置* @param x 触摸点X坐标* @param y 触摸点Y坐标* @param isActionDown 是否为按下事件*/private fun handleTouch(x: Float, y: Float, isActionDown: Boolean) {// 计算触摸点到圆心的距离val distance = sqrt((x - centerX).pow(2) + (y - centerY).pow(2))// 限制手柄在圆盘范围内if (distance <= baseRadius) {currentX = xcurrentY = y} else {// 触摸点超出范围,将手柄限制在圆盘边缘val angle = atan2(y - centerY, x - centerX)currentX = centerX + cos(angle) * baseRadiuscurrentY = centerY + sin(angle) * baseRadius}invalidate()// 按下时启动协程回调if (isActionDown && listener != null) {startCallbackLoop()}}/*** 启动回调循环*/private fun startCallbackLoop() {rockerJob?.cancel()rockerJob = coroutineScope.launch {while (isActive) {val xPercent = (currentX - centerX) / baseRadiusval yPercent = (currentY - centerY) / baseRadiusval angle = Math.toDegrees(atan2(-yPercent.toDouble(), -xPercent.toDouble())).toFloat()val magnitude = sqrt(xPercent.pow(2) + yPercent.pow(2)) * 100flistener?.onRockerMoved(xPercent, yPercent, angle, magnitude)delay(callbackInterval)}}}/*** 重置摇杆到中心位置*/private fun resetRocker() {currentX = centerXcurrentY = centerYinvalidate()listener?.onRockerMoved(0f, 0f, 0f, 0f)rockerJob?.cancel()rockerJob = null}/*** 设置摇杆监听器* @param listener 摇杆监听器*/fun setRockerListener(listener: RockerListener?) {this.listener = listener}/*** View从窗口分离时调用,清理资源*/override fun onDetachedFromWindow() {super.onDetachedFromWindow()rockerJob?.cancel()coroutineScope.cancel()}/*** 摇杆监听器接口*/interface RockerListener {/*** 摇杆移动回调* @param xPercent X轴偏移百分比(-1到1)* @param yPercent Y轴偏移百分比(-1到1)* @param angle 角度(-180到180度)* @param magnitude 移动幅度(0到100)*/fun onRockerMoved(xPercent: Float, yPercent: Float, angle: Float, magnitude: Float)}
}

DirectionalDiskActivity
package com.example.dsbridge_demoimport android.os.Bundle
import android.util.Log
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity/*** 摇杆控制演示Activity*/
class DirectionalDiskActivity : AppCompatActivity() {private lateinit var robotRocker: RobotRockerprivate lateinit var infoText: TextViewoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_directional_disk)initViews()setupRockerListener()}private fun initViews() {robotRocker = findViewById(R.id.robotRocker)infoText = findViewById(R.id.directionText)}/*** 设置摇杆监听器*/private fun setupRockerListener() {robotRocker.setRockerListener(object : RobotRocker.RockerListener {override fun onRockerMoved(xPercent: Float, yPercent: Float, angle: Float, magnitude: Float) {Log.d("RobotRocker", "X: $xPercent, Y: $yPercent, Angle: $angle, Magnitude: $magnitude")val direction = getDirectionByAngle(angle, magnitude)updateInfoText(xPercent, yPercent, angle, magnitude, direction)}})}/*** 根据角度和幅度获取方向* @param angle 角度(-180到180度)* @param magnitude 幅度(0到100)* @return 方向文字*/private fun getDirectionByAngle(angle: Float, magnitude: Float): String {return when {magnitude < 10f -> "无"angle >= -22.5 && angle < 22.5 -> "右"angle >= 22.5 && angle < 67.5 -> "右上"angle >= 67.5 && angle < 112.5 -> "上"angle >= 112.5 && angle < 157.5 -> "左上"(angle >= 157.5 && angle <= 180) || (angle >= -180 && angle < -157.5) -> "左"angle >= -157.5 && angle < -112.5 -> "左下"angle >= -112.5 && angle < -67.5 -> "下"angle >= -67.5 && angle < -22.5 -> "右下"else -> "无"}}/*** 更新信息显示*/private fun updateInfoText(xPercent: Float,yPercent: Float,angle: Float,magnitude: Float,direction: String) {infoText.text = buildString {appendLine("摇杆状态")appendLine("X偏移: ${String.format("%.2f", xPercent * 100)}%")appendLine("Y偏移: ${String.format("%.2f", yPercent * 100)}%")appendLine("角度: ${String.format("%.1f", angle)}°")appendLine("幅度: ${String.format("%.1f", magnitude)}%")append("方向: $direction")}}
}

activity_directional_disk
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"android:padding="16dp"android:background="#FAFAFA"><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="摇杆控制"android:textSize="24sp"android:textColor="#333333"android:textStyle="bold"android:gravity="center"android:layout_marginBottom="32dp" /><com.example.dsbridge_demo.RobotRockerandroid:id="@+id/robotRocker"android:layout_width="300dp"android:layout_height="300dp"android:layout_gravity="center" /><TextViewandroid:id="@+id/directionText"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="当前方向: 无"android:textSize="18sp"android:textColor="#666666"android:gravity="center"android:layout_marginTop="32dp"android:padding="16dp"android:background="#E0E0E0" /></LinearLayout>

http://www.dtcms.com/a/549986.html

相关文章:

  • 常州网站建设段新浩网站推广公司有哪些
  • TCP三次握手和四次断开
  • 黑马JAVA+AI 加强08 File-IO流
  • (XMODEM协议)自旋锁异常报错
  • 关于光照探针的实验和疑问
  • 校园网站建设的优点wordpress最新文章链接插件
  • 南城网站建设公司策划山东省建设执业资格注册中心网站
  • 【机器学习】模型持久化与部署
  • 「用Python来学微积分」21. 玩转高阶导数
  • 不谈AI模型,只谈系统:SmartMediaKit低延迟音视频技术现实主义路线
  • 哪些证书对学历没硬性要求?高职生必看
  • 公司网站做推广做商城型网站
  • PyQt5 QSet完全指南:深入理解Qt的高性能集合容器
  • 乡村旅游电子商务网站建设有网站怎么做淘宝客
  • 狭小空间难嵌入?这款寻北仪重新定义新标准!
  • 成华区网站建设公司软件工程最好的出路
  • 网站的关键词怎么选择工信部网站登陆
  • Rust 复合类型深度解析:从元组与数组看内存安全与抽象设计
  • ASTMD4169对于医疗冷链包装在空陆联运中的测试验证
  • g++/gcc编译器与自动化构建make/Makefile
  • 高性能人工智能目标检测开山篇----YOLO v1算法详解(上篇)
  • 【文字库】新华字典部分年份出版汇总
  • 个体工商户备案网站备案wordpress推广
  • 设计师网站推荐wordpress换域名安装
  • 搭建 k8s
  • 【MCU控制 初级手札】1.5 化学键(离子键、共价键、金属键)与化合价 【化学基础】
  • Rust与Python完全指南:从零开始理解两门语言的区别与关系
  • 服务器硬盘的作用都有哪些?
  • flash网站源码48快装旧房翻新公司电话
  • 【PID】连续PID和数字PID chapter1(补充) 学习笔记