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

news2024/10/18 14:44:04

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

源代码剖析

首先从MenuPanelItemRoot讲起。

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,它们分别代表什么呢?
截屏2024-05-21 17.png
title就是红圈圈出来的地方。title span就是标题的间隙,你直接当成margins比较容易理解。

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

接下来来看MenuPanel。

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()进行刷新。

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中是如何生成代码的,你就大概知道怎么使用了。

/*
 * 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布局。

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

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

修改菜单数据

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

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
                }
            }
        }
    }
}
设置菜单点击事件
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 。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1703691.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第16篇:JTAG UART IP应用<三>

Q&#xff1a;如何通过HAL API函数库访问JTAG UART&#xff1f; A&#xff1a;Quartus硬件工程以及Platform Designer系统也和第一个Nios II工程--Hello_World的Quartus硬件工程一样。 Nios II软件工程对应的C程序调用HAL API函数&#xff0c;如open用于打开和创建文件&#…

链表经典题目—相交链表和链表倒数第k个节点

&#x1f389;&#x1f389;&#x1f389;欢迎莅临我的博客空间&#xff0c;我是池央&#xff0c;一个对C和数据结构怀有无限热忱的探索者。&#x1f64c; &#x1f338;&#x1f338;&#x1f338;这里是我分享C/C编程、数据结构应用的乐园✨ &#x1f388;&#x1f388;&…

网络编程基础(一)

目录 前言 一、网络体系架构 1.1 OSI 1.2 TCP/IP协议簇体系架构 二、TCP和UDP的不同 1.TCP 2.UDP 三、网络编程基础相关概念 1.字节序 1.在计算机中有关多字节整数的存储方式&#xff0c;根据主机CPU处理数据的方式不同&#xff0c;我们将主机分为大端存储和小端存储…

一招解决Redis缓存穿透,缓存雪崩,缓存击穿问题【超详细版】

文章目录 小故事一、为什么要使用缓存?二、什么是缓存穿透&#xff1f;怎么解决&#xff1f;2.1解决方案2.2代码实现 三、什么是缓存击穿&#xff1f;怎么解决&#xff1f;3.1解决方案3.2代码实现 四、什么是缓存雪崩&#xff1f;怎么解决&#xff1f;4.1解决方案 五、Redis缓…

气泡水位计的安装方法详解(二)

气泡水位计的安装方法详解&#xff08;二&#xff09; 产品简介 气泡式水位计ZL-BWL-013是一款适用于水文、水利信息化建设领域的新一代水位测量类设备&#xff0c;产品执行GB/T 11828.2-2022标准。ZL-BWL-013气泡水位计&#xff0c;具有安装方便、易于操作&#xff0c;高精度…

VM中Ubuntu16.04的下载以及ROS—kinetic的版本下载

一、Ubuntu镜像地址 转载备份一下&#xff1b; 官方下载地址&#xff08;不推荐&#xff09; https://www.ubuntu.com/downloadhttps://www.ubuntu.com/download 中科大源 Index of /ubuntu-releases/16.04/http://mirrors.ustc.edu.cn/ubuntu-releases/16.04/ 阿里云开…

音视频开发9 FFmpeg 解复用框架说明,重要知识点

一&#xff0c;播放器框架 二 常用音视频术语 容器&#xff0f;文件&#xff08;Conainer/File&#xff09;&#xff1a; 即特定格式的多媒体文件&#xff0c; 比如mp4、flv、mkv等。 媒体流&#xff08;Stream&#xff09;&#xff1a; 表示时间轴上的一段连续数据&#xff0…

JVM学习-javap解析Class文件

解析字节码的作用 通过反编译生成字节码文件&#xff0c;可以深入了解Java工作机制&#xff0c;但自己分析类文件结构太麻烦&#xff0c;除了第三方的jclasslib工具外&#xff0c;官方提供了javapjavap是jdk自带的反解析工具&#xff0c;它的作用是根据class字节码文件&#x…

【GateWay】自定义RoutePredicateFactory

需求&#xff1a;对于本次请求的cookie中&#xff0c;如果userType不是vip的身份&#xff0c;不予访问 思路&#xff1a;因为要按照cookie参数进行判断&#xff0c;所以根据官方自带的CookieRoutePredicateFactory进行改造 创建自己的断言类&#xff0c;命名必须符合 xxxRout…

在virtualbox中ubuntu如何利用mobaxterm来拖拽文件

首先得先利用ssh、ubuntu的ip 一、开启ssh 安装 openssh-server sudo apt-get install openssh-server 检查 ssh 服务是否启动成功 sudo ps -e | grep ssh 如果有 sshd 则说明 ssh 服务已启动&#xff0c;如果没有启动&#xff0c;输入下边命令启动 ssh 服务 sudo servi…

东子哥:从来不拼搏的人,不是我的兄弟!新一轮裁员潮即将来临!

今年初&#xff0c;包括微软、亚马逊、谷歌母公司Alphabet等在内的巨头先后宣布裁员计划&#xff0c;曾掀起了一轮裁员潮。 进入年中阶段&#xff0c;特斯拉、理想汽车、TikTok、安德玛等知名巨头&#xff0c;也先后宣布裁员计划&#xff0c;难道&#xff0c;新一轮裁员潮已经…

长江电力:“你们随意,我躺赢”

“只要长江不断流&#xff0c;我们就躺着挣钱。”这是某股股吧里的股东们喊出的。 今天说的这个公司“没什么意思”&#xff0c;十年来股价一直涨一直涨&#xff0c;涨了5倍&#xff0c; &#xff08;最&#xff09;重要的是&#xff0c;持有体验特别好&#xff0c;几乎没有什…

HTTP -- HTTP概述

HTTP概述 HTTP使用的是可靠的数据传输协议。 web内容都是存储在web服务器上的&#xff0c;web服务器所使用的是http协议&#xff0c;故被称为http服务器。 web服务器是web资源的宿主&#xff0c;web资源是web内容的源头。 因特网上有数以千种的数据类型&#xff0c;http仔细的…

在Bash中解析命令行参数的两种样例脚本

文章目录 问题回答以空格分隔选项和参数以等号分隔选项和参数 参考 问题 假设&#xff0c;我有一个脚本&#xff0c;它会被这样一行调用: ./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile或者这个&#xff1a; ./myscript -v -f -d -o /fizz/someOtherFile ./fo…

C++ ─── string的模拟实现

本博客将简单实现来模拟实现string类&#xff0c;最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数。 下期我们继续讲解完整版string的模拟实现&#xff08;将不再会是浅拷贝了&#xff09; 说明&#xff1a;下述string类没有显式定义其拷贝构造函数与赋值运…

30秒学会一个ChatGpt-4o小技巧 --- 照片漫画化

文章目录 选择照片修改图片 选择照片 先选择自己的一张照片 当然首先你得能够访问ChatGpt-4o, 图片生成能力只有ChatGpt-4才有 所以我们先登录到 国内能够使用的ChatGpt网站: 我要超级GPT 然后把图片上传&#xff0c;再写提示词&#xff1a;请帮我把这种照片按照日系动漫风…

【quarkus系列】构建可执行文件native image

目录 序言为什么选择 Quarkus Native Image&#xff1f;性能优势便捷的云原生部署 搭建项目构建可执行文件方式一&#xff1a;配置GraalVM方式二&#xff1a;容器运行错误示例构建过程分析 创建docker镜像基于可执行文件命令式构建基于dockerfile构建方式一&#xff1a;构建mic…

fpga系列 HDL 00 : 可编程逻辑器件原理

一次性可编程器件&#xff08;融保险丝实现&#xff09; 一次性可编程器件&#xff08;One-Time Programmable Device&#xff0c;简称 OTP&#xff09;是一种在制造后仅能编程一次的存储设备。OTP器件在编程后数据不可更改。这些器件在很多应用场景中具有独特的优势和用途。 …

重生之我要精通JAVA--第六周笔记

File 路径 相对路径 路径1&#xff1a;“a.txt” 路径2&#xff1a;“abc\\a.txt” 绝对路径 路径1&#xff1a;“c:\\a.txt” 路径2&#xff1a;“c:\\abc\\a.txt” File对象就表示一个路径&#xff0c;可以是文件的路径、也可以是文件夹的路径这个路径可以是存在的&…

c++ (命名空间 字符串)

思维导图&#xff1a; 定义自己得命名空间myspace,在myspace中定义string类型变量s1,再定义一个函数完成字符串逆置 #include <iostream> #include <cstring> //定义自己得命名空间myspace,在myspace中定义string类型变量s1,再定义一个函数完成字符串逆置 using n…