如何在触摸位置附近显示定制的弹出窗口,就像我们使用ContextMenu时看到的那样?

4

背景

由于官方不支持自定义视图或图标的上下文菜单行(这里),我决定创建自己的解决方案(即类似其功能的自定义视图)。

问题

在RecyclerView上使用上下文菜单时,触摸位置很重要。 因此,如果你长按一个项目,则上下文菜单将尝试出现在接近触摸位置的位置(取自这里示例),而我没有通过OnClickListener或onLongClickListener提供此信息:

enter image description here

但是,我找不到如何在更基本的类中完成此操作。

我尝试过的

可以通过长按触发PopupWindow的显示,如下所示:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    val inflater = LayoutInflater.from(context)
    val holder = ViewHolder(inflater.inflate(R.layout.list_item_main, parent, false))
    holder.itemView.setOnLongClickListener {
        val contextMenuView=inflater.inflate(R.layout.context_menu,null)
        val popupWindow = PopupWindow(contextMenuView, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, true)
        popupWindow.showAsDropDown(holder.itemView,0,0);
        true
    }
    return holder
}

如果你想要一个漂亮的背景而不是透明的,你可以使用ListPopupWindow的解决方法。如果你不需要列表,你只需要设置它的promptView ,代码如下(可在这里找到):

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    val inflater = LayoutInflater.from(context)
    val holder = ViewHolder(inflater.inflate(R.layout.list_item_main, parent, false))
    val maxAllowedPopupWidth = context.resources.displayMetrics.widthPixels * 90 / 100
    holder.itemView.setOnLongClickListener {
        val contextMenuView = inflater.inflate(R.layout.context_menu, null)
        val listPopupWindow = ListPopupWindow(context)
        contextMenuView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
        val width = min(maxAllowedPopupWidth, contextMenuView.measuredWidth)
        listPopupWindow.setPromptView(contextMenuView)
        listPopupWindow.setContentWidth(width)
        listPopupWindow.anchorView = it
        listPopupWindow.show()
        true
    }
    return holder
}

我对我计算出的最大宽度不确定,因为我找不到弹出窗口的最大尺寸。我知道上下文菜单有一些最大值,然后由于某种原因它截断了文本。也许它和Dialog是一样的?除了对话框我找到了一个最大宽度,但我只找到了一个最小宽度:windowMinWidthMajorwindowMinWidthMinor

但回到问题:我在这里找不到任何与将弹出窗口放置在接触位置附近有关的函数。

所以这就是我得到的例子:

enter image description here

问题

  1. 如何设置弹出窗口出现在屏幕接触位置附近,而无需处理onTouch事件,就像使用ContextMenu时所做的那样?

  2. 上下文菜单(或类似菜单)是否有一些属性可以获取,以设置为我显示的最大大小(简而言之:默认最大宽度)?如果有,我该如何使用它?如何设置宽度和高度来考虑充气视图的尺寸?

2个回答

2

我已经有一段时间没有做这个了,但我认为我们遇到了同样的问题。让我看看能否回答。

无法为EditText创建自定义上下文菜单是我最终决定创建一个用于蒙古语的自定义组件库的主要原因之一。虽然垂直的蒙古语部分对你来说可能没有用处,但其他自定义弹出窗口的概念应该是相同的。

以下是我拥有的几个截图:

这是一个使用自定义弹出菜单的自定义EditText。它使用用户触摸位置来放置弹出位置。

enter image description here

下一个页面是关于不同设置弹出窗口位置的更一般演示。

enter image description here

这两个演示都包含在mongol-library demo app中。

我的自定义菜单是PopupWindow的子类。您可以在这里找到源代码。

我将其放置在特定位置的方法是使用showAtLocation方法,据我回忆,这只是PopupWindow上的普通方法:

private void showMongolContextMenu(MongolMenu menu, int xTouchLocation, int yTouchLocation) {
    float paddingPx = CONTEXT_MENU_TOUCH_PADDING_DP * getResources().getDisplayMetrics().density;
    Rect menuSize = menu.getDesiredSize();
    int y = yTouchLocation - menuSize.height() - (int) paddingPx;
    menu.showAtLocation(this, Gravity.NO_GRAVITY, xTouchLocation, y);
}

那段代码来自这里
哦,对了,我还在自定义键盘中使用了它:

enter image description here

查看以下类以获取更多信息:


不错,但是你能否将所需内容提取到单个存储库中,并使用一个示例进行演示。这个项目的目的是处理语言...我还注意到你可能也支持按住并选择(称为“拖动选择”:https://www.androidpolice.com/2013/11/16/getting-to-know-android-4-4-kitkat-edition-android-gets-cleaner-brighter-more-useful/#design-tidbits-and-other-stuff)。这是真的吗? - android developer
@androiddeveloper,我引用的代码对我的目的来说工作得足够好,所以我没有必要将其提取到一个单独的仓库中,但是如果您愿意可以这样做。它不支持拖动选择。 - Suragch

0
如何将弹出窗口设置为出现在屏幕上触摸位置附近?
为此,您需要找到用户触摸视图的确切坐标。因此,您需要使用 `setOnTouchListener()` 方法。
请尝试使用以下方法:
使用 `PopupWindowHelper` 辅助类。
import android.view.Gravity
import android.graphics.drawable.BitmapDrawable
import android.content.Context
import android.graphics.Rect
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.widget.LinearLayout
import android.widget.PopupWindow

class PopupWindowHelper(private val ctx: Context) {
    private val tipWindow: PopupWindow?
    private val contentView: View
    private val inflater: LayoutInflater

    internal val isTooltipShown: Boolean
        get() = tipWindow != null && tipWindow.isShowing


    init {
        tipWindow = PopupWindow(ctx)

        inflater = ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
        contentView = inflater.inflate(R.layout.popup_window, null)
    }

    internal fun showToolTip(anchor: View, event: MotionEvent) {

        tipWindow!!.height = LinearLayout.LayoutParams.WRAP_CONTENT
        tipWindow.width = LinearLayout.LayoutParams.WRAP_CONTENT

        tipWindow.isOutsideTouchable = true
        tipWindow.isTouchable = true
        tipWindow.isFocusable = true
        tipWindow.setBackgroundDrawable(BitmapDrawable())

        tipWindow.contentView = contentView

        val screenPos = IntArray(2)
        anchor.getLocationOnScreen(screenPos)

        val anchorRect =
            Rect(screenPos[0], screenPos[1], screenPos[0] + anchor.width, screenPos[1] + anchor.height)

        contentView.measure(
            LinearLayout.LayoutParams.WRAP_CONTENT,
            LinearLayout.LayoutParams.WRAP_CONTENT
        )

        val contentViewHeight = contentView.measuredHeight
        val contentViewWidth = contentView.measuredWidth

        val positionX = anchorRect.centerX() - contentViewWidth / 2
        val positionY = anchorRect.bottom - anchorRect.height() / 2

        tipWindow.showAtLocation(anchor, Gravity.NO_GRAVITY, event.x.toInt(), positionY)

    }

    internal fun dismissTooltip() {
        if (tipWindow != null && tipWindow.isShowing)
            tipWindow.dismiss()
    }


}

MainActivity

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        myRecyclerView.layoutManager=LinearLayoutManager(this)
        myRecyclerView.setHasFixedSize(true)
        myRecyclerView.adapter=DataAdapter(this)
    }
}

数据适配器
import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.row_layout.view.*
import android.view.MotionEvent
import android.view.View.OnTouchListener

class DataAdapter(context: Context) :
    RecyclerView.Adapter<DataAdapter.ViewHolder>() {
    val mContext = context
    private var lastTouchDown: Long = 0
    private val CLICK_ACTION_THRESHHOLD = 200

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view =
            LayoutInflater.from(mContext)
                .inflate(R.layout.row_layout, parent, false)

        view.setOnTouchListener { myView, event ->
            when (event?.action) {
                MotionEvent.ACTION_DOWN -> lastTouchDown = System.currentTimeMillis()
                MotionEvent.ACTION_UP -> if (System.currentTimeMillis() - lastTouchDown < CLICK_ACTION_THRESHHOLD) {
                    val popupWindowHelper = PopupWindowHelper(mContext)
                    myView?.let {
                        popupWindowHelper.showToolTip(
                            it
                            , event
                        )
                    }
                }
            }
            true
        }
        return ViewHolder(view)
    }

    override fun getItemCount(): Int {
        return 30
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {

        holder.tvDescription.text = "Row Description $position"
        holder.tvTitle.text = "Row Title $position"

    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val tvTitle = itemView.tvTitle
        val tvDescription = itemView.tvDescription
    }
}

你可以在我的GitHub代码库中找到完整的代码。


GIF动画看起来是你做的,但我无法导入该项目以进行尝试。请检查一下。 - android developer
1
@androiddeveloper,请检查一下,我已经更新了GitHub存储库。 - AskNilesh
看起来工作得非常好,但我发现(很遗憾我没有表达清楚)Context菜单所提供的功能并不需要你拥有onTouchListener。它知道用户触摸的位置,而无需我告诉它,这意味着只需使用onClickListener或onLongClickListener即可。你知道这是如何实现的吗? - android developer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接