前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Kotlin自定义菜单控件

Kotlin自定义菜单控件

作者头像
砸漏
发布2020-11-05 15:15:24
1K0
发布2020-11-05 15:15:24
举报
文章被收录于专栏:恩蓝脚本

本文实例为大家分享了Kotlin自定义菜单控件的具体代码,供大家参考,具体内容如下

首先贴一下效果图

思路:菜单控件分两部分,一是点击的子按钮(RecordButton),二是包裹着子按钮的容器(RecordMenu)。

子按钮负责显示文字及背景颜色和点击事件,父容器主要控制子控件的位置和动画显示。

实现:

子按钮,先贴代码

代码语言:javascript
复制
class RecordButton : RelativeLayout {
/** 控件显示的文本*/
lateinit var textValue: String
/** 控件显示的文本字体大小*/
private var textSize: Float = 18f
/** 控件显示的文本字体颜色*/
private var textColor: Int = Color.BLACK
/** 控件按下时显示的文本字体颜色*/
private var textColorPress: Int = Color.WHITE
/** 控件显示的背景颜色*/
private var backColorNormal: Int = R.drawable.bg_menu_item
/** 控件按下时显示的背景颜色*/
private var backColorPress: Int = R.drawable.bg_menu_item_press
/** 控件是否是主按钮*/
var isSwitchMain: Boolean = false
/** 按钮按下时的时间*/
var pressBtnTime: Long = 0L
/** 按钮抬起时的时间*/
var upBtnTime: Long = 0L
/** 事件是否是点击事件*/
var isClick: Boolean = false
/** 点击事件是否打开*/
var isOpen: Boolean = false
/** 文本控件*/
private lateinit var textView: TextView
/** 监听事件*/
var onRecordItemClickListener: OnRecordItemClickListener? = null
constructor(context: Context,
textValue: String,
textSize: Float,
textColor: Int,
backColorNormal: Int,
textColorPress: Int,
backColorPress: Int) : this(context) {
this.textValue = textValue
this.textSize = textSize
this.textColor = textColor
this.backColorNormal = backColorNormal
this.isSwitchMain = isSwitchMain
this.textColorPress = textColorPress
this.backColorPress = backColorPress
setBackgroundResource(backColorNormal)
textView = TextView(context)
textView.text = textValue
textView.gravity = CENTER
textView.setTextColor(textColor)
textView.textSize = textSize
var ll = LayoutParams(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 fun onTouchEvent(event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_DOWN -  {
pressBtnTime = System.currentTimeMillis()
setBackgroundResource(backColorPress)
textView.setTextColor(textColorPress)
return true
}
MotionEvent.ACTION_MOVE -  {
}
MotionEvent.ACTION_UP -  {
upBtnTime = System.currentTimeMillis()
setBackgroundResource(backColorNormal)
textView.setTextColor(textColor)
isClick = (upBtnTime - pressBtnTime) / 1000 < 0.5
}
}
if (isClick) {
onRecordItemClickListener?.onClick(isSwitchMain, textValue,isOpen)
isOpen = !isOpen
}
return true
}
}

这里主要用一个RelativeLayout包裹着一个TextView,这么写是为了防止以后扩展,需要添加图片什么的,关于这个样式和显示没什么好说的,主要的就是点击事件,在触摸事件中判断按下和抬起的时间差,如果时间差小于0.5秒则断定为点击。

包裹容器

代码语言:javascript
复制
class RecordMenu : RelativeLayout{
/** 子按钮半径*/
private var itemRadius: Int = 0
/*** 按钮间距*/
private var itemMargin: Int = 0
/** 动画时间*/
private var duration: Long = 0
/** 字体大小*/
private var itemFontSize = 18f
/** 字体正常颜色*/
private var itemFontColorN = Color.BLACK
/** 点击时字体颜色*/
private var itemFontColorP = Color.WHITE
/** 按钮正常背景*/
private var itemBackDrawableN = R.drawable.bg_menu_item
/** 按钮点击背景*/
private var itemBackDrawableP = R.drawable.bg_menu_item_press
/** 是否是展开状态*/
private var isOpen: Boolean = false
/** 动画是否正在运行*/
private var isRun: Boolean = false
/** 子控件监听*/
private var recordListener = RecordListener()
/** 上一级的监听事件*/
var onRecordItemClickListener: OnRecordItemClickListener? = null
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) {
init(context, attrs)
}
override fun onLayout(change: Boolean, l: Int, t: Int, r: Int, b: Int) {
/** 画出每个子控件的位置*/
for (i in 0 until childCount) {
var recordButton = getChildAt(i) as RecordButton
var left: Int = 0
var right: Int = itemRadius * 2
var top: Int = (childCount - 1) * (itemRadius * 2 + itemMargin) + itemRadius
var bottom: Int = top + itemRadius * 2
recordButton.layout(left, top, right, bottom)
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var width = itemRadius * 2
var height = (childCount - 1) * (itemRadius * 2 + itemMargin) + itemRadius * 2 + itemRadius
width += paddingLeft + paddingRight
height += paddingTop + paddingBottom
val count = childCount
for (i in 0 until count) {
getChildAt(i).measure(width, width)
if(i == count-1){
var recordButton = getChildAt(i) as RecordButton
recordButton.isSwitchMain = true
}
}
setMeasuredDimension(width, height)
}
private fun init(context: Context, attrs: AttributeSet?) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.RecordMenu)
itemRadius = typedArray.getDimension(R.styleable.RecordMenu_itemRadius, 30f).toInt()
itemMargin = typedArray.getDimension(R.styleable.RecordMenu_itemMargin, 10f).toInt()
duration = typedArray.getInteger(R.styleable.RecordMenu_animDuration, 2000).toLong()
itemFontSize = typedArray.getDimension(R.styleable.RecordMenu_itemFontSize,18f)
itemFontColorN = typedArray.getColor(R.styleable.RecordMenu_itemFontColorN,Color.BLACK)
itemFontColorP = typedArray.getColor(R.styleable.RecordMenu_itemFontColorP,Color.WHITE)
itemBackDrawableN = typedArray.getResourceId(R.styleable.RecordMenu_itemBackDrawableN,R.drawable.bg_menu_item)
itemBackDrawableP = typedArray.getResourceId(R.styleable.RecordMenu_itemBackDrawableP,R.drawable.bg_menu_item_press)
}
fun addItemView(textValue: String){
var recordButton = RecordButton(context,textValue,itemFontSize,itemFontColorN,itemBackDrawableN,itemFontColorP,itemBackDrawableP)
var l1 = LayoutParams(itemRadius * 2, itemRadius * 2)
addView(recordButton, l1)
recordButton.onRecordItemClickListener = recordListener
}
fun addItemView(textValue: String,itemBackDrawableN:Int,itemBackDrawableP:Int){
var recordButton = RecordButton(context,textValue,itemFontSize,itemFontColorN,itemBackDrawableN,itemFontColorP,itemBackDrawableP)
var l1 = LayoutParams(itemRadius * 2, itemRadius * 2)
addView(recordButton, l1)
recordButton.onRecordItemClickListener = recordListener
}
inner class RecordListener : OnRecordItemClickListener {
override fun onClick(isSwitch: Boolean, textValue: String,isOpen1:Boolean) {
if (!isRun) {
if (!isOpen) {
openMenu()
} else {
closeMenu()
}
}
onRecordItemClickListener?.onClick(isSwitch,textValue,isOpen1)
}
}
/**
* 展开控件
*/
fun openMenu() {
isOpen = true
isRun = true
for (i in 0 until childCount) {
buttonItemOpenAnimation(i, getChildAt(i) as RecordButton)
}
}
/**
* 关闭控件
*/
fun closeMenu() {
isRun = true
isOpen = false
for (i in 0 until childCount) {
buttonItemCloseAnimation(i, getChildAt(i) as RecordButton)
}
}
/**
* 展开动画
*/
private fun buttonItemOpenAnimation(index: Int, view: RecordButton) {
if (!view.isSwitchMain) {
val propertyAnimator = view.animate().alpha(1f).setInterpolator(OvershootInterpolator()).setDuration(duration / 3)
propertyAnimator.y((itemRadius * 2 * index + itemMargin * index + itemRadius).toFloat())
if (isOpen) {
view.visibility = View.VISIBLE
}
propertyAnimator.setListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(p0: Animator?) {
}
override fun onAnimationCancel(p0: Animator?) {
}
override fun onAnimationEnd(p0: Animator?) {
if (index == childCount - 2) {
isRun = false
}
}
override fun onAnimationStart(p0: Animator?) {
}
})
propertyAnimator.start()
}
}
/**
* 关闭动画
*/
private fun buttonItemCloseAnimation(index: Int, view: RecordButton) {
if (!view.isSwitchMain) {
val propertyAnimator = view.animate().alpha(0f).setDuration(duration / 3)
propertyAnimator.y(((itemRadius * 2 + itemMargin) * (childCount - 1) + itemRadius).toFloat())
propertyAnimator.setListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator) {}
override fun onAnimationEnd(animation: Animator) {
if (index == childCount - 2) {
isRun = false
}
if (!isOpen) {
view.visibility = View.GONE
}
}
override fun onAnimationCancel(animation: Animator) {}
override fun onAnimationRepeat(animation: Animator) {}
})
propertyAnimator.start()
}
}
}

这里面主要就是控制子视图的大小,位置,动画。在onLayout方法中遍历每个子视图,通过layout设置视图位置,这里设置每个子视图都在容器的底部。然后在OnMeasure中设置整个视图的大小,这个根据子视图的大小和个数来计算同时加上内边距。

最后就是通过子视图的点击事件来执行动画,这里用到的是属性动画,用的是系统自带的一个插值器OvershootInterpolator,这个插值器实现的效果就是在线性上先快速的到达终点然后超出然后仔慢慢回到终点,当然不想要这种效果自己可以自定义一个插值器。至于插值器如何用及如何自定义,这里就不在赘述,以后会专门写一篇文章来介绍。

以上就是这个菜单控件的整体实现过程,是不是很简单。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-09-11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档