Kotlin自定義菜單控件
本文實(shí)例為大家分享了Kotlin自定義菜單控件的具體代碼,供大家參考,具體內(nèi)容如下
首先貼一下效果圖
思路:菜單控件分兩部分,一是點(diǎn)擊的子按鈕(RecordButton),二是包裹著子按鈕的容器(RecordMenu)。
子按鈕負(fù)責(zé)顯示文字及背景顏色和點(diǎn)擊事件,父容器主要控制子控件的位置和動(dòng)畫(huà)顯示。
實(shí)現(xiàn):
子按鈕,先貼代碼
class RecordButton : RelativeLayout { /** 控件顯示的文本*/ lateinit var textValue: String /** 控件顯示的文本字體大小*/ private var textSize: Float = 18f /** 控件顯示的文本字體顏色*/ private var textColor: Int = Color.BLACK /** 控件按下時(shí)顯示的文本字體顏色*/ private var textColorPress: Int = Color.WHITE /** 控件顯示的背景顏色*/ private var backColorNormal: Int = R.drawable.bg_menu_item /** 控件按下時(shí)顯示的背景顏色*/ private var backColorPress: Int = R.drawable.bg_menu_item_press /** 控件是否是主按鈕*/ var isSwitchMain: Boolean = false /** 按鈕按下時(shí)的時(shí)間*/ var pressBtnTime: Long = 0L /** 按鈕抬起時(shí)的時(shí)間*/ var upBtnTime: Long = 0L /** 事件是否是點(diǎn)擊事件*/ var isClick: Boolean = false /** 點(diǎn)擊事件是否打開(kāi)*/ var isOpen: Boolean = false /** 文本控件*/ private lateinit var textView: TextView /** 監(jiān)聽(tīng)事件*/ 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 } }
這里主要用一個(gè)RelativeLayout包裹著一個(gè)TextView,這么寫(xiě)是為了防止以后擴(kuò)展,需要添加圖片什么的,關(guān)于這個(gè)樣式和顯示沒(méi)什么好說(shuō)的,主要的就是點(diǎn)擊事件,在觸摸事件中判斷按下和抬起的時(shí)間差,如果時(shí)間差小于0.5秒則斷定為點(diǎn)擊。
包裹容器
class RecordMenu : RelativeLayout{ /** 子按鈕半徑*/ private var itemRadius: Int = 0 /*** 按鈕間距*/ private var itemMargin: Int = 0 /** 動(dòng)畫(huà)時(shí)間*/ private var duration: Long = 0 /** 字體大小*/ private var itemFontSize = 18f /** 字體正常顏色*/ private var itemFontColorN = Color.BLACK /** 點(diǎn)擊時(shí)字體顏色*/ private var itemFontColorP = Color.WHITE /** 按鈕正常背景*/ private var itemBackDrawableN = R.drawable.bg_menu_item /** 按鈕點(diǎn)擊背景*/ private var itemBackDrawableP = R.drawable.bg_menu_item_press /** 是否是展開(kāi)狀態(tài)*/ private var isOpen: Boolean = false /** 動(dòng)畫(huà)是否正在運(yùn)行*/ private var isRun: Boolean = false /** 子控件監(jiān)聽(tīng)*/ private var recordListener = RecordListener() /** 上一級(jí)的監(jiān)聽(tīng)事件*/ 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) { /** 畫(huà)出每個(gè)子控件的位置*/ 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) } } /** * 展開(kāi)控件 */ fun openMenu() { isOpen = true isRun = true for (i in 0 until childCount) { buttonItemOpenAnimation(i, getChildAt(i) as RecordButton) } } /** * 關(guān)閉控件 */ fun closeMenu() { isRun = true isOpen = false for (i in 0 until childCount) { buttonItemCloseAnimation(i, getChildAt(i) as RecordButton) } } /** * 展開(kāi)動(dòng)畫(huà) */ 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() } } /** * 關(guān)閉動(dòng)畫(huà) */ 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() } } }
這里面主要就是控制子視圖的大小,位置,動(dòng)畫(huà)。在onLayout方法中遍歷每個(gè)子視圖,通過(guò)layout設(shè)置視圖位置,這里設(shè)置每個(gè)子視圖都在容器的底部。然后在OnMeasure中設(shè)置整個(gè)視圖的大小,這個(gè)根據(jù)子視圖的大小和個(gè)數(shù)來(lái)計(jì)算同時(shí)加上內(nèi)邊距。
最后就是通過(guò)子視圖的點(diǎn)擊事件來(lái)執(zhí)行動(dòng)畫(huà),這里用到的是屬性動(dòng)畫(huà),用的是系統(tǒng)自帶的一個(gè)插值器OvershootInterpolator,這個(gè)插值器實(shí)現(xiàn)的效果就是在線性上先快速的到達(dá)終點(diǎn)然后超出然后仔慢慢回到終點(diǎn),當(dāng)然不想要這種效果自己可以自定義一個(gè)插值器。至于插值器如何用及如何自定義,這里就不在贅述,以后會(huì)專門寫(xiě)一篇文章來(lái)介紹。
以上就是這個(gè)菜單控件的整體實(shí)現(xiàn)過(guò)程,是不是很簡(jiǎn)單。
相關(guān)文章
flutter實(shí)現(xiàn)頭部tabTop滾動(dòng)欄
這篇文章主要為大家詳細(xì)介紹了flutter實(shí)現(xiàn)頭部tabTop滾動(dòng)欄,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03Flutter實(shí)現(xiàn)頁(yè)面切換后保持原頁(yè)面狀態(tài)的3種方法
這篇文章主要給大家介紹了關(guān)于Flutter實(shí)現(xiàn)頁(yè)面切換后保持原頁(yè)面狀態(tài)的3種方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用Flutter具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03android自動(dòng)安裝apk代碼實(shí)例(不使用apk安裝器安裝)
這篇文章主要介紹了android自動(dòng)安裝apk代碼實(shí)例,代碼簡(jiǎn)單,大家參考使用吧2013-11-11Android WebView調(diào)用本地相冊(cè)的方法
這篇文章主要為大家詳細(xì)介紹了Android WebView調(diào)用本地相冊(cè)的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12Android開(kāi)發(fā)實(shí)現(xiàn)圓形圖片功能示例
這篇文章主要介紹了Android開(kāi)發(fā)實(shí)現(xiàn)圓形圖片功能,涉及Android實(shí)現(xiàn)圓形圖片的界面布局與CirImageView組件相關(guān)使用操作技巧,需要的朋友可以參考下2019-04-04Android程序開(kāi)發(fā)之給背景圖加上移動(dòng)的手勢(shì)
這篇文章主要介紹了Android程序開(kāi)發(fā)之給背景圖加上移動(dòng)的手勢(shì) 的相關(guān)資料,需要的朋友可以參考下2016-03-03Android ListView隱藏右側(cè)滾動(dòng)條功能
這篇文章主要介紹了Android ListView隱藏右側(cè)滾動(dòng)條功能,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-03-03