Android: Set popup window width same as achor view width - android

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

Related

How programmatically make a custom dialog be 100% of screen height?

I have a relative layout that occupies 100% of the screen. I call many custom dialogs and always works well. I get positioning and correct height and width with no problems.
However, now I need a dialog that is 100% of screen height and 100% of screen width. I get 100% width but I don't get 100% height. The maximum is around 95 of height.
I've reduced my code to a minimum for better clarity:
var lay = RelativeLayout(this)
var dial = Dialog(cx)
val win = dial.window
val wlp = win.attributes
win.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
wlp.apply {
gravity = Gravity.TOP or Gravity.LEFT
x = 0
y = 0
}
win.attributes = wlp
// Here are my widgets linked to lay with lay.addView(widget).
dial.setContentView( // Connection between layout and dialog
lay, ViewGroup.LayoutParams(
screen_width, // variable with screen width in pixels
screen_height )) // variable with screen heigth in pixels
I've read many questions in Stackoverflow. What I have tried:
a) I've used MATCH_PARENT as height.
b) I've tried some windows flags:
win.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
win.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN)
c) I've tried windows attribute property:
wlp.verticalMargin = 0F
No success.
PS: I also have a strange shade border at right and bottom side of dialog that I cannot get rid of it.
Update:
Rahul Khurana solution has killed the problem.
It just use
val dial = Dialog(cx,android.R.style.Theme_Black_NoTitleBar_Fullscreen)
It also eliminates the shade in other no fullscreen dialogs
val dial = Dialog(cx,
android.R.style.Theme_DeviceDefault_Dialog_NoActionBar)
Change the constructor like this:
val dialog=Dialog(this,android.R.style.Theme_Black_NoTitleBar_Fullscreen);
Initialize and show your dialog as below in onStart
override fun onStart() {
super.onStart()
val dial = Dialog(this);
var width = ViewGroup.LayoutParams.MATCH_PARENT;
var height = ViewGroup.LayoutParams.MATCH_PARENT;
dial.getWindow().setLayout(width, height);
dial.show()
}
Use this snippet of code, it will work.
Activity activity = ...;
AlertDialog dialog = ...;
// retrieve display dimension
Rect displayRectangle = new Rect();
Window window = activity.getWindow();
window.getDecorView().getWindowVisibleDisplayFrame(displayRectangle);
// inflate layout
LayoutInflater inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.your_layout, null);
layout.setMinimumWidth((int)(displayRectangle.width() * 0.95f));
layout.setMinimumHeight((int)(displayRectangle.height() * 0.95f));
dialog.setView(layout);
dialog?.window?.apply {
requestFeature(Window.FEATURE_NO_TITLE); setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
setLayout(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT)
}

Why can’t I resize and position my dynamic dialog (with no XML associated)?

I have studied many questions and answers in Stackoverflow, then I made a dummy function, that I run after a click in my app.
Here is the code:
fun openDialog(cx: Context) {
val alertDialog = Dialog(cx)
var linLayout = LinearLayout(cx)
linLayout.setOrientation(LinearLayout.VERTICAL);
// Set width, height and weight
linLayout.layoutParams = LinearLayout.LayoutParams(500,1000,1F)
// top and left position
linLayout.x = 0F
linLayout.y = 0F
val title = TextView(cx); // dummy view 1
title.setText("Custom Dialog 1")
title.setTextColor(Color.BLACK)
title.setTextSize(20F)
linLayout.addView(title) // add in layout
val msg = TextView(cx) // dummy view 2
msg.setText("Custom Dialog Box 2")
msg.setTextColor(Color.RED)
msg.setTextSize(10F)
linLayout.addView(msg) // add in layout
alertDialog.setContentView(linLayout) // Add the layout in Dialog
alertDialog.show(); // Show the dialog with layout
}
What I get? A proper dialog, but in the middle of the screen with width and height defined by the content. I also try to use the windows linked to the custom dialog without success.
val wlp = win.attributes
wlp.apply {
x = 0
y = 0
height = 1000
width = 500
}
win.attributes = wlp
No changes. However wlp.gravity = Gravity.BOTTOM works, but it's not enough for me. A also try to use win.setLayout(1000,500) without success
The Android documentation states:
Set the width and height layout parameters of the window. The default
for both of these is MATCH_PARENT; you can change them to WRAP_CONTENT
or an absolute value to make a window that is not full-screen.
Why can’t I resize and position the layout that I assign to my dialog?
Has somebody a hint?
The cell phone screen:
Update
I've got to change the position of dialog using
wlp.gravity = Gravity.TOP or Gravity.LEFT
wlp.x = 100 // Relative to left
wlp.y = 200 // relative to top
I keep trying to figure out how to change the width and height.
Set the layout params for the view you're inflating when you set the content :
this line :
alertDialog.setContentView(linLayout)
should be :
alertDialog.setContentView(linLayout, LinearLayout.LayoutParams(500,1000,1F))
You can also remove the explicit setting of the params for the LinearLayout

How to get the height of recyclerview item in "onBindViewHolder"

The height of RecyclerView item is not fixed,i need to set the background image for every item,so I want to get the height of recyclerview's item to resize the Image,but the itemView.getHeight() always return 0 in onBindViewHolder.
I have try to search many questions or articles,but i still cant get a good soluation.
Short
Measure the View manually
view.measure(
View.MeasureSpec.makeMeasureSpec(recyclerViewWidth, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
and get the height with view.getMeasuredHeight()
Background
A view will return a valid value for View.getHeight()only after it has been measured. The measuring itself will automatically happen by the system when the view is about to be displayed on screen.
When Android wants to display the layout, it will recursively call the view.layout() function for each view in the view tree. Each Parent tells its children the constraints they might have (width/height) and ask them to view.measure() themselves. As a result, the view will store the measured values BASED on the constraints in designated members (MeasuredHeight/Width). Note that at this point view.getMeasuredHeight() will hold the value while view.getHeight() will still be invalid. view.getHeight() will only return a valid value once the view has an actual height in the UI hierarchy.
Recap
So, to know the height of a view element, before it has been measured and laid out by the system, we will need to invoke the view.measure() function manually.
The measure function expects 2 parameters which derived from the view LayoutParams + the parent constraints.
In the above code sample, we are measuring the view forcing its width to be EXACTLY the width of the parent (the RecycleView), and the height is not limited.
I suggest that you define multiple layout files with the expected heights and inflate them according to some criteria in your data set.
ViewHolder onCreateViewHolder (ViewGroup parent, int viewType){
if(some condition){
//inflate layout 1
}else{
//inflate layout 2
}
or as answered here: you can get the measurements while initializing the view holder
itemView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
int width = itemView.getMeasuredWidth();
int height = itemView.getMeasuredHeight();
How about this:
view.post(() -> {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
using this code to get recycler view's item height:
view.getViewTreeObserver().addOnGlobalLayoutListener(new
ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
//don't forget remove this listener
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
//get item height here
int itemHeight = v.getHeight();
}
});
MyViewHolder.kt
With the addOnGlobalLayoutListener() method, the height value is obtained before the TextView is drawn. And then save it in a member variable.
The key is to modify the UI inside and outside the implementation of the listener so that there are no rendering problems (when the views are redrawn).
That is, you shoud use the getter inside the listener and; the setter inside and outside the listener.
companion object {
var maxHeight: Int = 0
fun create(mContext: Context): MyViewHolder {
val view = LayoutInflater.from(mContext).inflate(R.layout.item_answer, null)
updateLayout(view)
view.viewTreeObserver.addOnGlobalLayoutListener {
if (view.myTextView.height > maxHeight)
maxHeight = view.myTextView.height
updateLayout(view)
}
return MyViewHolder(mContext, view).apply {
setIsRecyclable(false)
}
}
fun updateLayout(view: View) {
if (maxHeight != 0 && view.myTextView.height != maxHeight)
view.myTextView.height = maxHeight
}
}
Source

Android - PopupWindow above a specific view

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

How to get or compute the width/height of an inflated view

How do I compute the width and height of an inflated View if the parent is a PopupWindow, not a ViewGroup? I cannot use LayoutInflator.inflate(int resId, ViewGroup parent, attachToRoot boolean) because PopupWindow is not a ViewGroup, so I use LayoutInflator.inflate(int resId) instead, but after that I getWidth() and getHeight() return zero :(
I need to resize the PopupWindow to fit the View, but cannot do so until the View has a parent. Do I have a chicken-and-egg problem?
By the way the View is a subclass of RelativeView so calculating it manually is essentially out of the question.
Thanks in advance,
Barry
Actually popupWindow supports "wrap content" constans, so if you want popup be exact as your view - use this:
popup = new PopupWindow(context);
popup.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
popup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
--other options--
getWidth() and getHeight() returns zero, because view have size only after drawing on the screen. You can try to get values from LayoutParams of inflated view.
If there is fill_parent value - you are in egg-chicken situation.
If there is values in px or dp you can manually calculate size in pixels:
Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpSize, context.getResources().getDisplayMetrics()));
If there is wrap_content value - you can use view.measure() method - all children and view itself will be measured, and you can get view.getMeasuredHeight() and view.getMeasuredWidth().
As Jin35 said, your problem was that the view's width and height haven't been calculated yet...the dimensions aren't calculated until after the layout pass.
Using a fixed width and height from dimension resources (e.g. from a values/dimens.xml file) is one workaround, since then you don't need to wait for the view's onMeasure to occur -- you can get the value of the same dimension resource you used for the view you're interested in, and use that.
A better solution is to just delay your calculations until after the onMeasure happens. You can do that by overriding onMeasure, but a more elegant solution is to use a temporary OnGlobalLayoutListener like this:
View popup = LayoutInflator.inflate(int resId);
if(popup != null) {
// set up an observer that will be called once the listView's layout is ready
android.view.ViewTreeObserver viewTreeObserver = listView.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(new android.view.ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// This will be called once the layout is finished, prior to displaying.
View popup = findViewById(resId);
if(popup != null) {
int width = popup.getMeasuredWidth();
int height = popup.getMeasuredHeight();
// don't need the listener any more
popup.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
});
}
}
Please try this:
Display display = getWindowManager().getDefaultDisplay();
Log.e("", "" + display.getHeight() + " " + display.getWidth());

Categories

Resources