I am developing an application for Android and I am using a popup window when the user clicks a specific menu bar object(consisting of small images lined up horizontally) on the bottom of the screen.
On the click I want the popup window to be anchored to the top-left corner of the view that was clicked and be shown on top.
The only methods that seem to be relevant are showAsDropDown(View anchor, int xoff, int yoff) and showAtLocation(View parent, int gravity, int x, int y). The problem with showAsDropDown is that it is anchored to the bottom-left corner of the view.
Is there another way to implement this?
popupWindow.showAtLocation(...) actually shows the window absolutely positioned on the screen (not even the application). The anchor in that call is only used for its window token. The coordinates are offsets from the given gravity.
What you actually want to use is:
popupWindow.showAsDropDown(anchor, offsetX, offsetY, gravity);
This call is only available in API 19+, so in earlier versions you need to use:
popupWindow.showAsDropdown(anchor, offsetX, offsetY);
These calls show the popup window relative to the specified anchor view. Note that the default gravity (when calling without specified gravity) is Gravity.TOP|Gravity.START so if you are explicitly using Gravity.LEFT in various spots in your app you will have a bad time :)
I wrote a sample Kotlin code which will show a PopupWindow above the anchor view.
private fun showPopupWindow(anchor: View) {
PopupWindow(anchor.context).apply {
isOutsideTouchable = true
val inflater = LayoutInflater.from(anchor.context)
contentView = inflater.inflate(R.layout.popup_layout, null).apply {
measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
)
}
}.also { popupWindow ->
// Absolute location of the anchor view
val location = IntArray(2).apply {
anchor.getLocationOnScreen(this)
}
val size = Size(
popupWindow.contentView.measuredWidth,
popupWindow.contentView.measuredHeight
)
popupWindow.showAtLocation(
anchor,
Gravity.TOP or Gravity.START,
location[0] - (size.width - anchor.width) / 2,
location[1] - size.height
)
}
}
You just needed to move the popupWindow by the height of its anchor using the yoff parameter in the showAsDropDown(View anchor, int xoff, int yoff) syntax.
popupWindow.showAsDropDown(anchor, 0, -anchor.getHeight()+popupView.getHeight);
Also, be aware that if the max height allowed to anchor does not allow for the transformation, the popup might not show up properly.
popupWindow.showAtLocation(anchor, Gravity.BOTTOM, 0, anchor.getHeight());
I have this code: PopupWindow below a specific view (Gravity End) for all sdk version.
// display the popup[![enter image description here][1]][1]
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mPopupWindow.showAsDropDown(v, 0, 0, Gravity.END);
} else {
mPopupWindow.showAsDropDown(v, v.getWidth() - mPopupWindow.getWidth(), 0);
}
Here View v is ImageButton Calendar.
The one you want to use is showAtLocation(...). You specify the anchor view (the one the user clicks), and position it relative to that via the gravity parameter and offsets. Think of the gravity parameter like the PopupWindow is almost like a child view and the parent view is like a container layout.
You should be able to put Gravity.LEFT | Gravity.TOP as the parameter.
you can display the popup always above the anchor by following
popupWindow.showAsDropDown(anchor, 0, -anchor.getHeight()-popupView.getHeight);
Sample example:
ScrollView scrollView = new ScrollView(context);
popupWindow.setContentView(scrollView);
scrollView.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
int he=scrollView.getMeasuredHeight();
popupWindow.showAsDropDown(items,0, -items.getHeight()-he);
After using so many solutions, I got one solution to display the PopupWindow above any View in Kotlin.
To display the popup window above the view, you can use the showAsDropDown() function.
popUp.showAsDropDown(v, 0, (-0.2 * v.height).roundToInt(), Gravity.CENTER)
With:
v: View(Anchor)
0: offSetX
(-0.2 * v.height).roundToInt(): offSetY
Gravity.CENTER: Gravity
For example: https://medium.com/#bhattmeet887/popupwindow-above-the-specific-view-kotlin-ab1a199581eb
Here's using the android-x solution, which should even work in case you have a floating UI using SAW permission (System Alert Window) :
#SuppressLint("InflateParams")
private fun showPopupWindow(anchor: View) {
val popupWindow = PopupWindow(anchor.context)
popupWindow.isFocusable = true
popupWindow.inputMethodMode = PopupWindow.INPUT_METHOD_NOT_NEEDED
popupWindow.contentView = LayoutInflater.from(anchor.context).inflate(R.layout.popup_layout, null)
PopupWindowCompat.showAsDropDown(popupWindow, anchor, 0, 0, Gravity.BOTTOM)
}
This is for alignment of bottom-left.
If you need bottom-center, you could use this, for example:
#SuppressLint("InflateParams")
private fun showPopupWindow(anchor: View) {
val popupWindow = PopupWindow(anchor.context)
popupWindow.isFocusable = true
popupWindow.inputMethodMode = PopupWindow.INPUT_METHOD_NOT_NEEDED
val inflater = LayoutInflater.from(anchor.context)
val contentView = inflater.inflate(R.layout.popup_layout, null)
contentView.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
popupWindow.contentView = contentView
PopupWindowCompat.showAsDropDown(popupWindow, anchor, (anchor.measuredWidth - contentView.measuredWidth) / 2, 0, Gravity.BOTTOM)
}
popupwindow.showAsDropDown(anchor,0, -125);
this thing work for me
Related
I want to slide a PopupWindow from the bottom of the screen to some point above it, say center.
I have the following logic so far
val inflater = getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
val popupView: View = inflater.inflate(R.layout.popup_window, null)
val width = LinearLayout.LayoutParams.WRAP_CONTENT
val height = LinearLayout.LayoutParams.WRAP_CONTENT
val focusable = true // lets taps outside the popup also dismiss it
val popupWindow = PopupWindow(popupView, width, height, focusable)
val slide = Slide()
slide.apply {
duration = 300
slideEdge = Gravity.BOTTOM
}
view?.doOnAttach {
popupWindow.isFocusable = true
popupWindow.enterTransition = slide
popupWindow.exitTransition = slide
popupWindow.showAtLocation(view, Gravity.CENTER, 0, 0)
}
I am able to slide the window between its width. So what this does is that the sliding happens from -w/2 where w is the width of the window and ends at w/2. Which maybe expected since the position of the window is set as 0, 0. However I want the sliding to happen between 2 specific points. Like the sliding should start from the bottom of the screen and it should continue to slide till it reaches the coordinate 0,0.
I found a workaround by expanding the width of the window to the portion below 0,0 but that cause the views below it to not be clickable. So this doesn't work in my case. Is there a way to do this sliding? Thanks!
In my XML layout I define margins / padding as normal:
android:layout_marginBottom="54dp"
I then click a button, and I programatically override this by doing this:
param.setMargins(0, 0, 0, 0)
textInputLayout.layoutParams = param
Now, I need to click a button again, and it should go back to just reading the margin value that I defined in the XML layout. How can I clear / remove the custom margin?
I would expect there to be something like
param.clear()
Is there something like this? Or do I from this point forwards always need to override it programatically?
You have to set margins again after second time button click. Below code might help you.
// Instance variable
private var isSecondTime = false
// I have used FrameLayout as my parent, Replace with your parent layout. E.g LinearLayout
val params = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT
)
And handle button click as per your requirement. Like:
button.setOnClickListener {
if (!isSecondTime) {
isSecondTime = true
params.setMargins(0, 0, 0, 0)
} else {
// setting margin again to 54 dp
//setMargins(left, top, right, bottom)
params.setMargins(0, 0, 0, convertDPToPx(54))
}
textView.layoutParams = params
}
Converting DP to Pixel:
private fun convertDPToPx(dip: Int): Int {
val r: Resources = resources
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dip.toFloat(),
r.displayMetrics
).toInt()
}
I am using constraint layout in which I have arranged views using flow. These views are added dynamically.
On clicking any of view a popup window should open like shown in the illustration below:
I have managed to show the popup window but it is not taking complete width of anchor view. This is how it is looking right now:
This is the snippet from my code which shows the popup window
anchorView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
val width = anchorView.measuredWidth
val height = ViewGroup.LayoutParams.WRAP_CONTENT
val focusable = true
val popupWindow = PopupWindow(popupCardBinding.root, width, height, focusable)
popupWindow.showAsDropDown(anchorView)
How can I make it's width same as anchorView's width?
I'm not sure where you're calling measure for the anchor view, but try measuring after the view has been drawn - using UNSPECIFIED doesn't always return the correct values, if you use measure too early:
anchorView.post {
anchorView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
val width = anchorView.measuredWidth
val height = ViewGroup.LayoutParams.WRAP_CONTENT
val focusable = true
val popupWindow = PopupWindow(popupCardBinding.root, width, height, focusable)
popupWindow.showAsDropDown(anchorView)
}
If it doesn't work, try showing more code about how and when anchorView is created
I had the exact same problem and couldn't find a good answer after searching for a while.
I ended up passing the Anchor View to the PopupWindow's constructor.
public TestPopupWindow(Context context, View anchorView) {
super(context);
this.context = context;
this.anchorView = anchorView;
createView();
}
And setting the width during the view inflation.
private void createView() {
View view = View.inflate(context, R.layout.popup_window_layout, null);
this.setWidth(anchorView.getWidth());
setContentView(view);
}
Simple and works for me
From the click on a button vi, I passed the same coordinates (vi.x and vi.y) for a popup dialog criation (dialog dial), associated a layout lay, and under it has several buttons.
When I've positioned the popup, with the same x, y of the button, it does it correctly, but with a slight right and down shift, that I can't find any explanation.
PS: I positioned exactly the same place as the button, just to make the below image clear.
Here the dummy code. (It's working in a real app, but I've reduced for clarity):
fun openDialog(xPos: Float, yPos:Float) {
...
val dial = Dialog(this) // this is the context
val win = dial.window
val wlp = win.attributes
wlp.apply {
gravity = Gravity.TOP or Gravity.LEFT
x = xPos
y = yPos
}
win.attributes = wlp
var lay = RelativeLayout(cx)
.....
dial.setContentView(
lay, ViewGroup.LayoutParams(150,500))
dial.show()
}
I've tried many things: horizontalMargin and verticalMargin in window attributes (dial.window.attributes), padding, x, y and gravity in layout lay
Nothing works!
PS 1: My main layout is full screen. Even the battery status is hidden. So the screen has the exact size of screen size.
PS 2: All my views and widgets are programatically created without XML.
Here is the scheme:
I need to show PopupWindow under one Views shown on the screen.
How can I calculate coordinates of needed View and place PopupWindow under it? Code example are more than welcome. Thanks.
Locating an already displayed view is fairly easy - here's what I use in my code:
public static Rect locateView(View v)
{
int[] loc_int = new int[2];
if (v == null) return null;
try
{
v.getLocationOnScreen(loc_int);
} catch (NullPointerException npe)
{
//Happens when the view doesn't exist on screen anymore.
return null;
}
Rect location = new Rect();
location.left = loc_int[0];
location.top = loc_int[1];
location.right = location.left + v.getWidth();
location.bottom = location.top + v.getHeight();
return location;
}
You could then use code similar to what Ernesta suggested to stick the popup in the relevant location:
popup.showAtLocation(parent, Gravity.TOP|Gravity.LEFT, location.left, location.bottom);
This would show the popup directly under the original view - no guarantee that there would be enough room to display the view though.
you have getLeft() and getBottom() to get the exact position of the view in the layout. You also have getWidth() and getHeight() to know the exact space occupied by the view. If you want to position your popup window below a view.
You setLeft() and setTop() methods of the view to position the new popup Window.
To get size of the main application screen without stuff like title and notification bars, override the following method in the class generating the screen in question (sizes are measured in pixels):
#Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
}
To get the bottom coordinate of the view under which you want to show the popup:
View upperView = ...
int coordinate = upperView.getBottom();
Now as long as height - coordinate is large enough for your popup view, you can simply place the popup like this:
PopupWindow popup = new PopupWindow();
Button button = new Button(this);
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
popup.showAtLocation(parent, Gravity.CENTER, 0, coordinate);
}
});
Here, showAtLocation() takes the parent view as an argument together with gravity and location offsets.