如何像在使用ContextMenu时一样在触摸位置附近显示自定义的Popup窗口? [英] How to show a customized Popup window near the touch location, like what we have when we use ContextMenu?
问题描述
背景
看到正式使用上下文菜单的行自定义视图甚至图标是不可能的(
但是,我找不到如何做到这一点的方法
我尝试过的事情
显示PopupWindow可以通过长时间触摸来完成,因为例如:
onCreateViewHolder(parent:ViewGroup,viewType:Int)的替代乐趣:ViewHolder {
val inflater = LayoutInflater。 from(context)
valholder = 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);
真实的
}
返回持有者
}
而且,如果您想要一个好的背景而不是透明的,可以使用
问题
-
如何将弹出窗口设置为显示在屏幕上的触摸位置附近,而无需甚至处理onTouch事件,就像使用ContextMenu在示例上所做的一样?
-
上下文菜单(或类似菜单)是否具有我可以获取的一些属性,可以将其设置为显示的最大大小(简而言之:默认最大宽度)?如果可以,该如何使用?如何设置宽度和高度以考虑膨胀视图之一?
如何设置弹出窗口显示在屏幕上的触摸位置附近?
为此,您需要在用户触摸视图的位置找到精确的协调,因此需要使用 setOnTouchListener()
尝试这种方式
您这个 PopupWindowHelper
< blockquote>
PopupWindowHelper
import android.view.Gravity
导入android.graphics.drawable.BitmapDrawable
导入android.content.Context
导入android.graphics.Rect
导入android.view.LayoutInflater
导入android.view.MotionEvent
导入android.view.View
导入android.widget.LinearLayout
导入android.widget.PopupWindow
class PopupWindowHelper(priv val ctx:Context ){
private val tipWindow:PopupWindow?
私有val内容查看器:查看
私有val充气器:LayoutInflater
内部val isTooltipShown:布尔值
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)
}
内部乐趣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)
锚点。 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)
}
内部乐趣dismissTooltip(){
if(tipWindow!= null& tipWindow.isShowing)
tipWindow.dismiss()
}
}
MainActivity
导入androidx.appcompat.app.AppCompatActivity
导入android.os.Bundle
导入androidx.recyclerview.widget.LinearLayoutManager
导入kotlinx.android.synthetic.main.activity_main。*
类MainActivity:AppCompatActivity(){
重写fun onCreate(savedInstanceState:Bundle?){
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main )
myRecyclerView.layoutManager = LinearLayoutManager(this)
myRecyclerView.setHasFixedSize(true)
myRecyclerView.adapter = DataAdapter(this)
}
}
DataAdapter
导入android.content.Context
导入android.util.Log
导入andro id.view.LayoutInflater
导入android.view.View
导入android.view.ViewGroup
导入androidx.recyclerview.widget.RecyclerView
导入kotlinx.android.synthetic.main。 row_layout.view。*
导入android.view.MotionEvent
导入android.view.View.OnTouchListener
类DataAdapter(context:Context):
RecyclerView.Adapter< ; DataAdapter.ViewHolder>(){
val mContext = context
private var lastTouchDown:Long = 0
private val CLICK_ACTION_THRESHHOLD = 200
覆盖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->如果(System.currentTimeMillis()-lastTouchDown< CLICK_ACTION_THRESHHOLD){
val popupWindowHelper = PopupWindowHelper(mContext)
myView?.let {
popupWindowHelper.showToolTip(
it
,事件
)
}
}
}
真
}
return ViewHolder(view)
}
重写fun getItemCount():Int {
return 30
}
重写fun onBindViewHolder(holder:ViewHolder,position:Int){
holder.tvDescription.text =行描述$ position
holder.tvTitle.text =行标题$ position
}
内部类ViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){
val tvTitle = itemView.tvTitle
val tvDescription = itemView.tv的描述
}
}
Background
Seeing that it's not officially possible to have a context menu which has a customized view or even icons for its rows (here), I decided to create my own solution (of custom view that acts like it).
The problem
When using a context menu on a RecyclerView, the touch position matters, so if you long touch an item, the context menu will try to appear near the touch location (sample taken from here), and without me giving this information (meaning via OnClickListener or onLongClickListener ) :
However, I can't find how to do this in the more basic classes.
What I've tried
Showing a PopupWindow can be done via long touch, as such:
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
}
And, if you want to have a nice background for it instead of being transparent, you could use a workaround, of ListPopupWindow, and if you don't want a list, you can just set its promptView , as such (code available here) :
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
}
I'm not sure about the max width that I've calculated, because I can't find what's the maximum size that a popup can have. I know that the context menu has some maximum and then it just truncates the text for some reason. Maybe it's the same as of Dialog? Except that for dialog I could find a maximum width, yet I've found a minimal : windowMinWidthMajor
and windowMinWidthMinor
.
But back to the issue: I can't find any function here that's related to putting the popup near the touch location.
So this is what I get, for example:
The questions
How to set the popup window to appear near the touch location on the screen, without even handling onTouch event, as done on the sample using ContextMenu ?
Does the context menu (or similar) have some attribute that I can get, to set as the max size for what I show (in short: a default max width) ? If so, how do I use it? How can I set the width&height to consider the one of the inflated view?
How to set the popup window to appear near the touch location on the screen?
For this purpose, you need to find exact coordination where the user has touch the view so you need to use setOnTouchListener()
Try this way
You this PopupWindowHelper
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)
}
}
DataAdapter
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
}
}
You can find complete code from my GitHub repo
这篇关于如何像在使用ContextMenu时一样在触摸位置附近显示自定义的Popup窗口?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!