Kotlin自定义菜单控件

发布时间:2026/10/28 23:21:45

Kotlin自定义菜单控件 本文实例为大家分享了Kotlin自定义菜单控件的具体代码供大家参考具体内容如下首先贴一下效果图**思路**菜单控件分两部分一是点击的子按钮RecordButton二是包裹着子按钮的容器RecordMenu。子按钮负责显示文字及背景颜色和点击事件父容器主要控制子控件的位置和动画显示。实现子按钮先贴代码classRecordButton:RelativeLayout{/** 控件显示的文本*/lateinitvartextValue:String/** 控件显示的文本字体大小*/privatevartextSize:Float18f/** 控件显示的文本字体颜色*/privatevartextColor:IntColor.BLACK/** 控件按下时显示的文本字体颜色*/privatevartextColorPress:IntColor.WHITE/** 控件显示的背景颜色*/privatevarbackColorNormal:IntR.drawable.bg_menu_item/** 控件按下时显示的背景颜色*/privatevarbackColorPress:IntR.drawable.bg_menu_item_press/** 控件是否是主按钮*/varisSwitchMain:Booleanfalse/** 按钮按下时的时间*/varpressBtnTime:Long0L/** 按钮抬起时的时间*/varupBtnTime:Long0L/** 事件是否是点击事件*/varisClick:Booleanfalse/** 点击事件是否打开*/varisOpen:Booleanfalse/** 文本控件*/privatelateinitvartextView:TextView/** 监听事件*/varonRecordItemClickListener:OnRecordItemClickListener?nullconstructor(context:Context,textValue:String,textSize:Float,textColor:Int,backColorNormal:Int,textColorPress:Int,backColorPress:Int):this(context){this.textValuetextValuethis.textSizetextSizethis.textColortextColorthis.backColorNormalbackColorNormalthis.isSwitchMainisSwitchMainthis.textColorPresstextColorPressthis.backColorPressbackColorPresssetBackgroundResource(backColorNormal)textViewTextView(context)textView.texttextValue textView.gravityCENTERtextView.setTextColor(textColor)textView.textSizetextSizevarllLayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT)ll.addRule(CENTER_IN_PARENT)addView(textView,ll)}constructor(context:Context):this(context,null){}constructor(context:Context,attrs:AttributeSet?):this(context,attrs,0){}constructor(context:Context,attrs:AttributeSet?,defStyleAttr:Int):super(context,attrs,defStyleAttr){}override funonTouchEvent(event:MotionEvent?):Boolean{when(event?.action){MotionEvent.ACTION_DOWN-{pressBtnTimeSystem.currentTimeMillis()setBackgroundResource(backColorPress)textView.setTextColor(textColorPress)returntrue}MotionEvent.ACTION_MOVE-{}MotionEvent.ACTION_UP-{upBtnTimeSystem.currentTimeMillis()setBackgroundResource(backColorNormal)textView.setTextColor(textColor)isClick(upBtnTime-pressBtnTime)/10000.5}}if(isClick){onRecordItemClickListener?.onClick(isSwitchMain,textValue,isOpen)isOpen!isOpen}returntrue}}这里主要用一个RelativeLayout包裹着一个TextView这么写是为了防止以后扩展需要添加图片什么的关于这个样式和显示没什么好说的主要的就是点击事件在触摸事件中判断按下和抬起的时间差如果时间差小于0.5秒则断定为点击。包裹容器classRecordMenu:RelativeLayout{/** 子按钮半径*/privatevaritemRadius:Int0/*** 按钮间距*/privatevaritemMargin:Int0/** 动画时间*/privatevarduration:Long0/** 字体大小*/privatevaritemFontSize18f/** 字体正常颜色*/privatevaritemFontColorNColor.BLACK/** 点击时字体颜色*/privatevaritemFontColorPColor.WHITE/** 按钮正常背景*/privatevaritemBackDrawableNR.drawable.bg_menu_item/** 按钮点击背景*/privatevaritemBackDrawablePR.drawable.bg_menu_item_press/** 是否是展开状态*/privatevarisOpen:Booleanfalse/** 动画是否正在运行*/privatevarisRun:Booleanfalse/** 子控件监听*/privatevarrecordListenerRecordListener()/** 上一级的监听事件*/varonRecordItemClickListener:OnRecordItemClickListener?nullconstructor(context:Context):this(context,null){}constructor(context:Context,attrs:AttributeSet?):this(context,attrs,0){}constructor(context:Context,attrs:AttributeSet?,defStyleAttr:Int):super(context,attrs,defStyleAttr){init(context,attrs)}override funonLayout(change:Boolean,l:Int,t:Int,r:Int,b:Int){/** 画出每个子控件的位置*/for(iin0until childCount){varrecordButtongetChildAt(i)asRecordButtonvarleft:Int0varright:IntitemRadius*2vartop:Int(childCount-1)*(itemRadius*2itemMargin)itemRadiusvarbottom:InttopitemRadius*2recordButton.layout(left,top,right,bottom)}}override funonMeasure(widthMeasureSpec:Int,heightMeasureSpec:Int){varwidthitemRadius*2varheight(childCount-1)*(itemRadius*2itemMargin)itemRadius*2itemRadius widthpaddingLeftpaddingRight heightpaddingToppaddingBottom val countchildCountfor(iin0until count){getChildAt(i).measure(width,width)if(icount-1){varrecordButtongetChildAt(i)asRecordButton recordButton.isSwitchMaintrue}}setMeasuredDimension(width,height)}privatefuninit(context:Context,attrs:AttributeSet?){val typedArraycontext.obtainStyledAttributes(attrs,R.styleable.RecordMenu)itemRadiustypedArray.getDimension(R.styleable.RecordMenu_itemRadius,30f).toInt()itemMargintypedArray.getDimension(R.styleable.RecordMenu_itemMargin,10f).toInt()durationtypedArray.getInteger(R.styleable.RecordMenu_animDuration,2000).toLong()itemFontSizetypedArray.getDimension(R.styleable.RecordMenu_itemFontSize,18f)itemFontColorNtypedArray.getColor(R.styleable.RecordMenu_itemFontColorN,Color.BLACK)itemFontColorPtypedArray.getColor(R.styleable.RecordMenu_itemFontColorP,Color.WHITE)itemBackDrawableNtypedArray.getResourceId(R.styleable.RecordMenu_itemBackDrawableN,R.drawable.bg_menu_item)itemBackDrawablePtypedArray.getResourceId(R.styleable.RecordMenu_itemBackDrawableP,R.drawable.bg_menu_item_press)}funaddItemView(textValue:String){varrecordButtonRecordButton(context,textValue,itemFontSize,itemFontColorN,itemBackDrawableN,itemFontColorP,itemBackDrawableP)varl1LayoutParams(itemRadius*2,itemRadius*2)addView(recordButton,l1)recordButton.onRecordItemClickListenerrecordListener}funaddItemView(textValue:String,itemBackDrawableN:Int,itemBackDrawableP:Int){varrecordButtonRecordButton(context,textValue,itemFontSize,itemFontColorN,itemBackDrawableN,itemFontColorP,itemBackDrawableP)varl1LayoutParams(itemRadius*2,itemRadius*2)addView(recordButton,l1)recordButton.onRecordItemClickListenerrecordListener}innerclassRecordListener:OnRecordItemClickListener{override funonClick(isSwitch:Boolean,textValue:String,isOpen1:Boolean){if(!isRun){if(!isOpen){openMenu()}else{closeMenu()}}onRecordItemClickListener?.onClick(isSwitch,textValue,isOpen1)}}/** * 展开控件 */funopenMenu(){isOpentrueisRuntruefor(iin0until childCount){buttonItemOpenAnimation(i,getChildAt(i)asRecordButton)}}/** * 关闭控件 */funcloseMenu(){isRuntrueisOpenfalsefor(iin0until childCount){buttonItemCloseAnimation(i,getChildAt(i)asRecordButton)}}/** * 展开动画 */privatefunbuttonItemOpenAnimation(index:Int,view:RecordButton){if(!view.isSwitchMain){val propertyAnimatorview.animate().alpha(1f).setInterpolator(OvershootInterpolator()).setDuration(duration/3)propertyAnimator.y((itemRadius*2*indexitemMargin*indexitemRadius).toFloat())if(isOpen){view.visibilityView.VISIBLE}propertyAnimator.setListener(object:Animator.AnimatorListener{override funonAnimationRepeat(p0:Animator?){}override funonAnimationCancel(p0:Animator?){}override funonAnimationEnd(p0:Animator?){if(indexchildCount-2){isRunfalse}}override funonAnimationStart(p0:Animator?){}})propertyAnimator.start()}}/** * 关闭动画 */privatefunbuttonItemCloseAnimation(index:Int,view:RecordButton){if(!view.isSwitchMain){val propertyAnimatorview.animate().alpha(0f).setDuration(duration/3)propertyAnimator.y(((itemRadius*2itemMargin)*(childCount-1)itemRadius).toFloat())propertyAnimator.setListener(object:Animator.AnimatorListener{override funonAnimationStart(animation:Animator){}override funonAnimationEnd(animation:Animator){if(indexchildCount-2){isRunfalse}if(!isOpen){view.visibilityView.GONE}}override funonAnimationCancel(animation:Animator){}override funonAnimationRepeat(animation:Animator){}})propertyAnimator.start()}}}这里面主要就是控制子视图的大小位置动画。在onLayout方法中遍历每个子视图通过layout设置视图位置这里设置每个子视图都在容器的底部。然后在OnMeasure中设置整个视图的大小这个根据子视图的大小和个数来计算同时加上内边距。最后就是通过子视图的点击事件来执行动画这里用到的是属性动画用的是系统自带的一个插值器OvershootInterpolator这个插值器实现的效果就是在线性上先快速的到达终点然后超出然后仔慢慢回到终点当然不想要这种效果自己可以自定义一个插值器。更多Android进阶指南 可以扫码 解锁《Android十大板块文档》1.Android车载应用开发系统学习指南附项目实战2.Android Framework学习指南助力成为系统级开发高手3.2024最新Android中高级面试题汇总解析告别零offer4.企业级Android音视频开发学习路线项目实战附源码5.Android Jetpack从入门到精通构建高质量UI界面6.Flutter技术解析与实战跨平台首要之选7.Kotlin从入门到实战全方面提升架构基础8.高级Android插件化与组件化含实战教程和源码9.Android 性能优化实战360°全方面性能调优10.Android零基础入门到精通高手进阶之路敲代码不易关注一下吧。ღ( ´ᴗ )
延伸阅读

更多相关文章

2026/10/25 22:22:38

原生PHP网站如何实现从零开始的微信支付?底

底层原理用户选择微信支付:用户在你的网站上购物,并选择微信支付作为支付方式。创建订单:你的网站需要为用户购买的商品创建一个订单,并生成一个唯一的订单号。调用微信支付API:你的网站将这个订单信息发送给微信支付A…

2026/10/28 8:26:44

EV 驱动电机零转速满转矩起步(Align→OpenLoop→FOC)Simulink 建模

目录 一、为什么“零转速满转矩起步”要单独做 二、起步控制原理简述 2.1 预定位(Align / Pre‑Excite) 2.2 开环旋转(Open‑Loop Voltage‑Oriented) 2.3 切闭环 FOC 三、关键参数 四、Simulink 建模(手把手) 4.1 Step 1️⃣ —— PMSM + Inverter + 坐标变换(同…

2026/10/28 23:18:08

基于IPSO-LSTM的电力负荷预测模型优化实践

1. 项目概述电力负荷预测是电力系统运行调度和电力市场交易的重要基础。传统预测方法在面对复杂非线性负荷数据时往往表现不佳,而基于深度学习的预测模型正在成为行业新宠。这个项目结合改进粒子群算法(IPSO)和长短期记忆网络(LSTM),构建了一个高精度的短…

2026/10/28 23:18:08

电热冷综合能源系统优化调度技术与实践

1. 电热冷综合能源系统的基本概念现代能源系统正在经历一场深刻的变革,从传统的单一能源供应模式向多能互补、协同优化的方向发展。电热冷综合能源系统(Integrated Electricity-Heat-Cooling Energy System)正是这一趋势下的产物,…

2026/10/28 23:18:08

智能通风系统在矿井安全中的关键技术与应用

1. 矿井通风系统为何成为矿企生命线在地下300米深的巷道里,空气变得粘稠而沉重。去年参与某煤矿安全评估时,我亲眼目睹了通风系统故障后瓦斯浓度在23分钟内从0.5%飙升到2.8%的全过程——这个数字已经超过了1.5%的爆炸下限。正是这次经历让我深刻理解到&a…

2026/10/28 23:18:08

OpenFASTv3.4.0与Simulink联合仿真实现IPC-MPC风电载荷控制

简介:本资源面向风能控制方向的研究生、科研工程师及风电系统仿真开发者,提供一套基于OpenFAST v3.4.0与Simulink联合仿真的独立变桨-模型预测控制(IPC-MPC)完整实现方案,解决高精度风电机组多变量协同控制建模难、实时…

2026/10/28 23:18:08

基于Hadoop+Spark的交通拥堵预测系统设计与优化

1. 项目背景与核心价值交通拥堵预测系统是智慧城市建设中的关键组成部分。这个基于HadoopSparkHive技术栈的毕业设计项目,实际上构建了一个完整的城市交通大数据分析平台。我在实际交通管理部门参与过类似系统的部署,发现这类系统能有效提升城市交通管理…

2026/10/28 23:13:03

ponytail 插件怎么用?从核心思路到实操落地的完整指南

1. 从“ponytail”这个热词说起:它到底是什么第一次看到“ponytail”被当成一个技术词条刷上热搜的时候,我其实愣了一下。马尾辫?这跟代码、插件、工具链有什么关系?后来翻了一圈社区讨论和几个开发者群的聊天记录才反应过来&…

2026/10/28 14:42:12

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/28 15:28:56

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/28 3:32:29

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/28 0:02:13

Python股票自动交易系统源码解析:从行情获取到风控的完整链路

简介:这份资源是面向高校计算机、金融相关专业毕业设计场景的完整项目源码包,主题为基于Python的股票自动交易系统,适合需要完成毕设选题、课程设计或自学金融量化交易开发的学生与开发者参考。压缩包共收录687个文件,整体约14.93…

2026/10/28 0:02:13

视酷酷信IM源码部署指南:Java 17+MySQL 8.0.33实战配置

简介:这是一套2024年亲测可部署的视酷酷信IM即时通讯系统源码,面向具备Java、OC、C#及Vue开发能力的技术人员,解决私有化即时通讯平台快速搭建与二次定制需求,尤其适用于需集成商城、保障数据主权的中小型企业或独立开发者。资源共…

2026/10/28 0:02:13

Python爬取百度迁徙数据:接口解析、批量采集与避坑指南

简介:这份资源是一个用Python爬取百度迁徙数据的脚本压缩包,面向具备Python基础、希望入门网络爬虫与数据解析的开发者,解决按日期获取全国热门城市人口迁徙数据的问题。包内共1个文件,为单个py脚本,压缩包约1KB&#…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑