Android低代码开发 - MenuPanel的源码剖析和基本使用

看了我上篇文章Android低代码开发 - 像启蒙和乐高玩具一样的MenuPanel 之后,本篇开始讲解代码。

源代码剖析

首先从MenuPanelItemRoot讲起。

kt 复制代码
package dora.widget.panel

interface MenuPanelItemRoot {

    /**
     * 菜单的标题。
     *
     * @return
     */
    var title: String?

    fun hasTitle(): Boolean

    /**
     * 获取标题四周的间距。
     *
     * @return
     */
    fun getTitleSpan(): Span

    fun setTitleSpan(titleSpan: Span)

    /**
     * 菜单的上边距。
     *
     * @return
     */
    var marginTop: Int

    class Span {
        var left = 0
        var top = 0
        var right = 0
        var bottom = 0

        constructor()

        /**
         * 根据水平间距和垂直间距设置四周的间距,常用。
         *
         * @param horizontal
         * @param vertical
         */
        constructor(horizontal: Int, vertical: Int) : this(
            horizontal,
            vertical,
            horizontal,
            vertical
        )

        constructor(left: Int, top: Int, right: Int, bottom: Int) {
            this.left = left
            this.top = top
            this.right = right
            this.bottom = bottom
        }
    }
}

无论是菜单还是菜单组,都要实现这个接口,这是什么模式啊?对,这是组合模式的应用。树枝节点可以添加若干树叶节点,且它们不会直接产生依赖,而是同时依赖其抽象。这个类里面看到,有title、title span和margin top,它们分别代表什么呢?

title就是红圈圈出来的地方。title span就是标题的间隙,你直接当成margins比较容易理解。

红框标出来的为margin top。如果有title的情况下,即title不为空以及空字符串,hasTitle()方法会检测出有标题。marginTop是指标题上面的区域。

接下来来看MenuPanel。

kt 复制代码
package dora.widget.panel

import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.ScrollView
import android.widget.TextView
import java.util.LinkedList
import java.util.UUID

/**
 * 通用功能菜单,类似于RecyclerView。
 */
open class MenuPanel : ScrollView, View.OnClickListener {

    /**
     * 面板的背景颜色,一般为浅灰色。
     */
    private var panelBgColor = DEFAULT_PANEL_BG_COLOR
    protected var menuPanelItems: MutableList<MenuPanelItem> = ArrayList()
    protected var viewsCache: MutableList<View> = ArrayList()
    private var onPanelMenuClickListener: OnPanelMenuClickListener? = null
    private var onPanelScrollListener: OnPanelScrollListener? = null
    private val groupInfoList: MutableList<GroupInfo> = ArrayList()
    private val listenerInfo = LinkedList<ListenerDelegate>()

    lateinit var panelRoot: FrameLayout

    /**
     * 存放Menu和Custom View。
     */
    lateinit var container: LinearLayout

    constructor(context: Context) : super(context) {
        init(context)
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        init(context)
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ) {
        init(context)
    }

    fun removeItem(item: MenuPanelItem): MenuPanel {
        val position = seekForItemPosition(item)
        if (position != SEEK_FOR_ITEM_ERROR_NOT_FOUND &&
            position != SEEK_FOR_ITEM_ERROR_MISS_MENU_NAME
        ) {
            removeItem(position)
        } else {
            Log.e(TAG, "failed to seekForItemPosition,$position")
        }
        return this
    }

    private fun init(context: Context) {
        isFillViewport = true
        addContainer(context)
    }

    fun setOnPanelMenuClickListener(l: OnPanelMenuClickListener) {
        onPanelMenuClickListener = l
    }

    fun setOnPanelScrollListener(l: OnPanelScrollListener?) {
        onPanelScrollListener = l
    }

    @JvmOverloads
    fun parseItemView(item: MenuPanelItem?, isLoadData: Boolean = false): View {
        val menuView = item!!.inflateView(context)
        if (isLoadData) {
            item.initData(menuView)
        }
        return menuView
    }

    val items: List<MenuPanelItem>
        get() = menuPanelItems

    fun getItem(position: Int): MenuPanelItem? {
        if (position < 0 || position > menuPanelItems.size - 1) {
            return null
        }
        return menuPanelItems[position]
    }

    val itemViewsCache: List<View>
        get() = viewsCache

    fun getGroupInfo(item: MenuPanelItem): GroupInfo? {
        for (groupInfo in groupInfoList) {
            if (groupInfo.hasItem(item)) {
                return groupInfo
            }
        }
        return null
    }

    /**
     * 根据item的position移除一个item,此方法被多处引用,修改前需要理清布局层级结构。
     *
     * @param position
     * @return
     */
    fun removeItem(position: Int): MenuPanel {
        val item = menuPanelItems[position]
        val groupInfo = getGroupInfo(item)
        val belongToGroup = groupInfo != null
        val view = getCacheViewFromPosition(position)
        if (!belongToGroup) {
            container.removeView(view)
        } else {
            // 属于一个组
            val menuGroupCard = groupInfo!!.groupMenuCard
            menuGroupCard.removeView(view)
            groupInfo.removeItem(item)
            // 一个组内的item全部被移除后,也移除掉这个组
            if (groupInfo.isEmpty) {
                // 连同title一起移除
                container.removeView(menuGroupCard)
                groupInfoList.remove(groupInfo)
            }
        }
        menuPanelItems.removeAt(position)
        viewsCache.removeAt(position)
        listenerInfo.removeAt(position)
        return this
    }

    /**
     * 清空所有item和相关view。
     */
    fun clearAll(): MenuPanel {
        if (menuPanelItems.size > 0) {
            menuPanelItems.clear()
        }
        container.removeAllViews()
        viewsCache.clear()
        groupInfoList.clear()
        listenerInfo.clear()
        return this
    }

    /**
     * 移除连续的item。
     *
     * @param start 第一个item的下标,包括
     * @param end   最后一个item的下标,包括
     * @return
     */
    fun removeItemRange(start: Int, end: Int): MenuPanel {
        for (i in start until end + 1) {
            removeItem(start)
        }
        return this
    }

    /**
     * 从某个位置移除到最后一个item。
     *
     * @param start 第一个item的下标,包括
     * @return
     */
    fun removeItemFrom(start: Int): MenuPanel {
        val end = menuPanelItems.size - 1
        if (start <= end) {
            // 有就移除
            removeItemRange(start, end)
        }
        return this
    }

    /**
     * 从第一个item移除到某个位置。
     *
     * @param end 最后一个item的下标,包括
     * @return
     */
    fun removeItemTo(end: Int): MenuPanel {
        val start = 0
        removeItemRange(start, end)
        return this
    }

    val itemCount: Int
        get() = menuPanelItems.size

    fun addMenuGroup(itemGroup: MenuPanelItemGroup): MenuPanel {
        val hasTitle = itemGroup.hasTitle()
        val items = itemGroup.items
        val titleView = TextView(context)
        titleView.setPadding(
            itemGroup.getTitleSpan().left, itemGroup.getTitleSpan().top,
            itemGroup.getTitleSpan().right, itemGroup.getTitleSpan().bottom
        )
        titleView.text = itemGroup.title
        titleView.textSize = 15f
        titleView.setTextColor(DEFAULT_TITLE_COLOR)
        val menuGroupCard = LinearLayout(context)
        menuGroupCard.orientation = LinearLayout.VERTICAL
        val lp = LinearLayout.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.WRAP_CONTENT
        )
        lp.topMargin = itemGroup.marginTop
        menuGroupCard.layoutParams = lp
        if (hasTitle) {
            menuGroupCard.addView(titleView)
        }
        for (item in items) {
            // 清除组内item的边距等
            applyDefault(item)
            addMenuToCard(item, menuGroupCard)
        }
        container.addView(menuGroupCard)
        // 保存菜单组信息
        groupInfoList.add(GroupInfo(items, menuGroupCard))
        return this
    }

    override fun addView(child: View) {
        if (child !is FrameLayout) {
            return
        }
        if (childCount > 1) {
            return
        }
        super.addView(child)
    }

    private fun addContainer(context: Context) {
        panelRoot = FrameLayout(context)
        container = LinearLayout(context)
        container.orientation = LinearLayout.VERTICAL
        container.setBackgroundColor(panelBgColor)
        panelRoot.addView(container)
        addView(panelRoot)
    }

    fun addMenu(item: MenuPanelItem): MenuPanel {
        val menuView = bindItemListener(item)
        if (!item.hasTitle()) {
            container.addView(menuView)
        } else {
            val titleView = TextView(context)
            titleView.setPadding(
                item.getTitleSpan().left, item.getTitleSpan().top,
                item.getTitleSpan().right, item.getTitleSpan().bottom
            )
            titleView.text = item.title
            titleView.textSize = 15f
            titleView.setTextColor(DEFAULT_PANEL_BG_COLOR)
            val menuCard = LinearLayout(context)
            menuCard.orientation = LinearLayout.VERTICAL
            menuCard.addView(titleView)
            menuCard.addView(menuView)
            container.addView(menuCard)
        }
        return this
    }

    private fun addMenuToCard(item: MenuPanelItem, container: LinearLayout) {
        val menuView = bindItemListener(item)
        val lp = LinearLayout.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.WRAP_CONTENT
        )
        lp.topMargin = item.marginTop
        menuView.layoutParams = lp
        container.addView(menuView)
    }

    fun seekForItemPosition(item: MenuPanelItem): Int {
        for (i in menuPanelItems.indices) {
            val mpi = menuPanelItems[i]
            val menu = mpi.menuName
            if (menu == "" || item.menuName == "") {
                return SEEK_FOR_ITEM_ERROR_MISS_MENU_NAME //失去菜单名称
            }
            if (menu == item.menuName) {
                return i
            }
        }
        return SEEK_FOR_ITEM_ERROR_NOT_FOUND
    }

    /**
     * 获取MenuPanel中条目布局中的子控件,推荐使用。
     *
     * @param position
     * @param viewId
     * @return
     */
    fun getCacheChildView(position: Int, viewId: Int): View? {
        val menuView = getCacheViewFromPosition(position)
        return menuView?.findViewById(viewId)
    }

    /**
     * 获取item的view,用于修改item的数据。
     *
     * @param item
     * @return
     */
    fun getCacheViewFromItem(item: MenuPanelItem): View? {
        val position = seekForItemPosition(item)
        return if (position != SEEK_FOR_ITEM_ERROR_NOT_FOUND &&
            position != SEEK_FOR_ITEM_ERROR_MISS_MENU_NAME
        ) {
            getCacheViewFromPosition(position)
        } else null
    }

    /**
     * 获取item的view,用于修改item的数据。
     *
     * @param position item的位置,从0开始
     * @return
     */
    fun getCacheViewFromPosition(position: Int): View? {
        return if (position < viewsCache.size) {
            viewsCache[position]
        } else null
    }

    protected fun getCacheViewFromTag(tag: String): View? {
        for (delegate in listenerInfo) {
            val dtag = delegate.tag
            if (dtag == tag) {
                val position = delegate.position
                return getCacheViewFromPosition(position)
            }
        }
        return null
    }

    /**
     * 绑定item的点击事件。
     *
     * @param item
     * @return 绑定成功后返回item的view
     */
    private fun bindItemListener(item: MenuPanelItem): View {
        menuPanelItems.add(item)
        //解析Item所对应的布局,并调用item的initData
        val menuView = parseItemView(item, true)
        viewsCache.add(menuView)
        val tag = UUID.randomUUID().toString().substring(0, 16)
        menuView.tag = tag
        val delegate = getListenerInfo(tag)
        menuView.setOnClickListener(delegate)
        listenerInfo.add(delegate)
        return menuView
    }

    private fun applyDefault(item: MenuPanelItem) {
        // item的上边距修改为1dp
        item.marginTop =
            TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, 1f,
                resources.displayMetrics
            ).toInt()
        // item去掉标题
        item.title = ""
        // item去掉标题边距
        item.setTitleSpan(MenuPanelItemRoot.Span())
    }

    /**
     * 不是菜单,所以不会影响菜单的点击事件位置,但需要自己处理控件内部的点击事件。
     *
     * @param view
     * @param <T>
     */
    fun <T : View> addCustomView(view: T): MenuPanel {
        container.addView(view)
        return this
    }

    fun <T : View> addCustomView(view: T, index: Int): MenuPanel {
        container.addView(view, index)
        return this
    }

    fun removeCustomViewAt(position: Int): MenuPanel {
        if (container.childCount > position) {
            // 有就移除
            container.removeViewAt(position)
        }
        return this
    }

    /**
     * 样式等参数改变才需要更新,只有类似于addItem、removeItem这样的,不需要调用此方法。
     */
    open fun updatePanel() {
        requestLayout()
    }

    fun getListenerInfo(tag: String): ListenerDelegate {
        return ListenerDelegate(tag, menuPanelItems.size - 1, this)
    }

    class GroupInfo(
        private var items: MutableList<MenuPanelItem>,
        var groupMenuCard: LinearLayout
    ) {
        fun hasItem(item: MenuPanelItem): Boolean {
            return items.contains(item)
        }

        val itemCount: Int
            get() = items.size

        fun addItem(item: MenuPanelItem) {
            items.add(item)
        }

        fun removeItem(item: MenuPanelItem?) {
            items.remove(item)
        }

        val isEmpty: Boolean
            get() = items.size == 0

        fun getItems(): MutableList<MenuPanelItem> {
            return items
        }
    }

    override fun onClick(v: View) {
        val tag = v.tag as String
        for (delegate in listenerInfo) {
            if (delegate.tag == tag) {
                val clickPos = delegate.position
                menuPanelItems[clickPos].menuName?.let {
                    onPanelMenuClickListener?.onMenuClick(clickPos, v, it)
                }
                break
            }
        }
    }

    fun setPanelBgColor(color: Int): MenuPanel {
        panelBgColor = color
        container.setBackgroundColor(panelBgColor)
        return this
    }

    interface OnPanelMenuClickListener {
        fun onMenuClick(position: Int, view: View, menuName: String)
    }

    override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
        super.onScrollChanged(l, t, oldl, oldt)
        if (scrollY == 0) {
            onPanelScrollListener?.onScrollToTop()
        } else if (panelRoot.measuredHeight == scrollY + height) {
            onPanelScrollListener?.onScrollToBottom()
        }
    }

    interface OnPanelScrollListener {
        fun onScrollToTop()
        fun onScrollToBottom()
    }

    class ListenerDelegate(
        val tag: String,
        val position: Int,
        private val listener: OnClickListener
    ) : OnClickListener {
        override fun onClick(v: View) {
            listener.onClick(v)
        }
    }

    companion object {
        private const val TAG = "MenuPanel"
        private const val DEFAULT_PANEL_BG_COLOR = -0xa0a07
        private const val DEFAULT_TITLE_COLOR = -0x666667
        private const val SEEK_FOR_ITEM_ERROR_NOT_FOUND = -1
        private const val SEEK_FOR_ITEM_ERROR_MISS_MENU_NAME = -2
    }
}

由于它仿RecyclerView的布局,它可以实现少量固定数量的item的高效创建,但不适应于大量item的场景。本来这个控件设计之初就是用在菜单上面的,而业务功能不可能无限多,所以这个问题可以忽略。根据代码我们可以得知,它是一个ScrollView,通常我们宽高都设置成match_parent,上面放一个titlebar,这样就填满了整个内容视图。这里面有addMenu()、addMenuGroup()和addCustomView()三种添加子控件的方法,只有前两种会受框架的约束。也就是说,如果你调用addCustomView()添加非菜单的视图,那么不会有OnPanelMenuClickListener面板菜单点击事件的回调,需要自己处理自身的事件。通过getCacheChildView()getCacheViewFromPosition()这两个方法都是用来更新菜单数据的,它们的区别在于前者是拿item的具体某一个子控件,后者是拿item本身。删除菜单和回调菜单的点击事件会使用到menuName这个属性,所以你在addMenu()的时候,务必保证menuName不重复。无论是添加还是移除菜单,最后都需要调用updatePanel()进行刷新。

kt 复制代码
package dora.widget.panel

import android.content.Context
import android.util.AttributeSet
import android.util.TypedValue
import android.view.Gravity
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView

/**
 * 自动给最后加一行提示信息,如共有几条记录的菜单面板。
 */
class TipsMenuPanel : MenuPanel {

    private var tips: String? = ""
    private var tipsColor = -0x666667
    private var tipsView: TextView? = null

    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )

    fun setEmptyTips(): TipsMenuPanel {
        setTips("")
        return this
    }

    fun setTips(tips: String?): TipsMenuPanel {
        this.tips = tips
        return this
    }

    fun setTipsColor(color: Int): TipsMenuPanel {
        tipsColor = color
        return this
    }

    override fun updatePanel() {
        if (tipsView != null) {
            container.removeView(tipsView)
        }
        if (tips != null && tips!!.isNotEmpty()) {
            tipsView = TextView(context)
            val lp = LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
            )
            lp.topMargin = dp2px(context, 5f)
            lp.bottomMargin = dp2px(context, 5f)
            tipsView!!.gravity = Gravity.CENTER_HORIZONTAL
            tipsView!!.setTextColor(tipsColor)
            tipsView!!.layoutParams = lp
            tipsView!!.text = tips
            // 增加了底部的tips
            container.addView(tipsView)
        }
        super.updatePanel()
    }

    private fun dp2px(context: Context, dpVal: Float): Int {
        return TypedValue.applyDimension(
            TypedValue.COMPLEX_UNIT_DIP,
            dpVal, context.resources.displayMetrics
        ).toInt()
    }
}

另外更新其子类TipsMenuPanel的底部提示信息的布局也需要调用updatePanel()方法。

开始使用

先给你们看一下dora-studio-plugin中是如何生成代码的,你就大概知道怎么使用了。

kt 复制代码
/*
 * Copyright (C) 2022 The Dora Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.dorachat.templates.recipes.app_package.res.layout

fun menuPanelActivityXml(
        packageName: String,
  activityClass: String
) = """
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context="${packageName}.${activityClass}">

    <data>
    
    </data>
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <dora.widget.DoraTitleBar
            android:id="@+id/titleBar"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            app:dview_title="@string/app_name"
            android:background="@color/colorPrimary"/>

        <dora.widget.panel.MenuPanel
            android:id="@+id/menuPanel"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </LinearLayout>
</layout>
"""

以上为生成xml布局。

kt 复制代码
/*
 * Copyright (C) 2022 The Dora Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.dorachat.templates.recipes.app_package.src

fun menuPanelActivityKt(
        applicationPackage: String,
  packageName: String,
        activityClass: String,
  bindingName: String,
  layoutName: String
) = """
package ${packageName}

import android.os.Bundle

import dora.BaseActivity

import ${applicationPackage}.R
import ${applicationPackage}.databinding.${bindingName}

class ${activityClass} : BaseActivity<${bindingName}>() {

   override fun getLayoutId(): Int {
          return R.layout.${layoutName}
   }

   override fun initData(savedInstanceState: Bundle?, binding: ${bindingName}) {
          TODO("Not yet implemented")
   }
}
"""

fun menuPanelActivity(
        applicationPackage: String,
        packageName: String,
        activityClass: String,
        bindingName: String,
        layoutName: String
) = """
package ${packageName};

import android.os.Bundle;
import androidx.annotation.Nullable;

import dora.BaseActivity;

import ${applicationPackage}.R;
import ${applicationPackage}.databinding.${bindingName};

public class ${activityClass} extends BaseActivity<${bindingName}> {

   @Override
    protected int getLayoutId() {
        return R.layout.${layoutName};
    }

   @Override
    public void initData(@Nullable Bundle savedInstanceState, ${bindingName} binding) {
        // TODO: Not yet implemented
        // For Example:
        // binding.menuPanel.addMenuGroup(
        //     MenuPanelItemGroup(
        //         DensityUtils.dp2px(10f),
        //         NormalMenuPanelItem("menuName", "text", true, "arrowText")
        //     )
        // )
   }
}
"""

以上为生成activity。

Gradle依赖配置
groovy 复制代码
// 添加以下代码到项目根目录下的build.gradle
allprojects {
    repositories {
        maven { url "https://jitpack.io" }
    }
}
// 添加以下代码到app模块的build.gradle
dependencies {
    implementation 'com.github.dora4:dview-menu-panel:1.0'
}
添加菜单和菜单组
添加菜单
kt 复制代码
binding.menuPanel.addMenu(NormalMenuPanelItem("menuName", "text", true, "arrowText"))
添加菜单组
kt 复制代码
binding.menuPanel.addMenuGroup(
             MenuPanelItemGroup(
                 DensityUtils.dp2px(10f),
                 NormalMenuPanelItem("menuName", "text", true, "arrowText")
             )
         )

不要无脑copy,参数请自行更换。

修改菜单数据

例如:更新颜色选择菜单的标签颜色。

kt 复制代码
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (resultCode == Activity.RESULT_OK) {
        if (requestCode == 0) {
            data?.let {
                val tagColor = it.getStringExtra(KEY_PICKED_COLOR)
                tagColor?.let {
                    groupTitleColor = tagColor
                }
                val tvTag = mBinding.menuPanel.getCacheChildView(1, R.id.tv_menu_panel_color_picker_tag)
                val color = Color.parseColor(tagColor)
                val drawable = TagDrawable(
                    color, 0, 0,
                    DensityUtils.dp2px(this, 20f),
                    DensityUtils.dp2px(this, 10f),
                )
                if (tvTag != null) {
                    tvTag.background = drawable
                }
            }
        }
    }
}
设置菜单点击事件
kt 复制代码
binding.menuPanel.setOnPanelMenuClickListener(object : MenuPanel.OnPanelMenuClickListener {
    override fun onMenuClick(position: Int, view: View, menuName: String) {
        when (menuName) {
            "newMsgNotice" -> {
                // 新消息通知
                spmSelectContent("点击新消息通知")
                val intent = Intent(this@SettingsActivity, NewMsgNoticeActivity::class.java)
                startActivity(intent)
            }
            "switchLanguage" -> {
                // 切换语言
                spmSelectContent("点击切换语言")
                val intent = Intent(this@SettingsActivity, SetLanguageActivity::class.java)
                startActivity(intent)
            }
            "chatFont" -> {
                IntentUtils.startActivityWithString(
                    this@SettingsActivity,
                    ChatFontActivity::class.java,
                    KEY_USER_ID,
                    userId
                )
            }
            "chatBg" -> {
                spmSelectContent("点击聊天背景")
                IntentUtils.startActivityWithString(
                    this@SettingsActivity,
                    ChatBackgroundActivity::class.java,
                    KEY_USER_ID,
                    userId
                )
            }
            "cacheClear" -> {
                spmSelectContent("点击缓存清理")
                IntentUtils.startActivityWithString(
                    this@SettingsActivity,
                    CacheCleanActivity::class.java,
                    KEY_USER_ID,
                    userId
                )
            }
            "aboutUs" -> {
                spmSelectContent("点击关于我们")
                IntentUtils.startActivityWithString(
                    this@SettingsActivity,
                    AboutActivity::class.java,
                    KEY_USER_ID,
                    userId
                )
            }
            "superUser" -> {
                spmSelectContent("点击超级管理员")
                IntentUtils.startActivityWithString(
                    this@SettingsActivity,
                    SuperUserActivity::class.java,
                    KEY_USER_ID,
                    userId
                )
            }
            "logout" -> {
                spmSelectContent("点击注销登录")
                // 注销登录
                dialog!!.show(
                    "logout",
                    getString(R.string.are_you_sure_logout)
                )
            }
        }
    }
})

这里注意一点,尽量使用menuName去判断具体是哪一个菜单,而不建议使用position。因为在有删除菜单的情况下,position会错位。spm埋点统计的代码你无需关心。

总结

本篇详细讲解了MenuPanel的核心代码实现及其使用方式,下篇我们演示IDE插件的操作流程。最后不忘点个star支持一下,https://github.com/dora4/dview-menu-panel

相关推荐
似霰2 小时前
安卓adb shell串口基础指令
android·adb
fatiaozhang95274 小时前
中兴云电脑W102D_晶晨S905X2_2+16G_mt7661无线_安卓9.0_线刷固件包
android·adb·电视盒子·魔百盒刷机·魔百盒固件
CYRUS_STUDIO5 小时前
Android APP 热修复原理
android·app·hotfix
鸿蒙布道师6 小时前
鸿蒙NEXT开发通知工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
鸿蒙布道师6 小时前
鸿蒙NEXT开发网络相关工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
大耳猫6 小时前
【解决】Android Gradle Sync 报错 Could not read workspace metadata
android·gradle·android studio
ta叫我小白6 小时前
实现 Android 图片信息获取和 EXIF 坐标解析
android·exif·经纬度
dpxiaolong7 小时前
RK3588平台用v4l工具调试USB摄像头实践(亮度,饱和度,对比度,色相等)
android·windows
tangweiguo030519878 小时前
Android 混合开发实战:统一 View 与 Compose 的浅色/深色主题方案
android
老狼孩111229 小时前
2025新版懒人精灵零基础及各板块核心系统视频教程-全分辨率免ROOT自动化开发
android·机器人·自动化·lua·脚本开发·懒人精灵·免root开发