I'm programmatically adding a PopupView which contains an EditText field to my Activity, which is vertically and horizontally centered on the screen. When the keyboard opens, I want the PopupView to move up, so it is still centered on the visible screen/activity part.
My code:
EditText e = new EditText(super.getContext());
PopupWindow popup = new PopupWindow(e, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
popup.setBackgroundDrawable(new ColorDrawable(Color.WHITE));
popup.setOutsideTouchable(true);
popup.setFocusable(true);
popup.showAtLocation(this, Gravity.CENTER, 0, 0);
I've tried many things with windowSoftInputMode for the Activity; I've tried to setSoftInputMode(mode) on the popup - but none of my approaches have worked. Neither my layout nor the Popup change their position when the keyboard opens. (I only want my popup but not the layout to change, though, just pointing it out).
Also the code is placed in a LinearLayout class, in case you are wondering why I'm using this as a View.
Easier to get Android to do all the heavy lifting for you.
Just use:
popup.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
So after much research, I finally found a way to accomplish that.
The code for creating the PopupWindow and making it being displayed in the vertical and horizontal center stays the same:
PopupWindow popup = new PopupWindow(
popupView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
/** ... **/
popup.showAtLocation(this, Gravity.CENTER, 0, 0);
Then the only thing you need is a Listener for the Keyboard (or more general: For Window Height changes). This was actually easier than I thought - and it didn't require any special access like an Activity-object or similar. Even in my independent View-class which only knows the Context (which I didn't want to cast), I was able to accomplish that. Everything you need is only one View-object which has already been added to the layout.
// You can call this method on any view that is added to the layout:
final View root = this.getRootView();
root.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
Rect r = new Rect();
root.getWindowVisibleDisplayFrame(r);
// Calculate the difference between the original height and the new height
int heightDiff = r.height() - root.getHeight();
// Now update the Popup's position
// The first value is the x-axis, which stays the same.
// Second value is the y-axis. We still want it centered, so move it up by 50% of the height
// change
// The third and the fourth values are default values to keep the width/height
popup.update(0, heightDiff / 2, -1, -1);
}
});
For reference:
Listening to window height changes
Only downside:
This solution may not work when you add a PopupView while the Keyboard is already opened. But in my case, this isn't an expectable scenario anyway.
Related
I have a DialogFragment with two EditText fields and another field with an ImageView to increment its value underneath these, they all live in a ScrollView.
The problem is neither adjust mode for the soft keyboard shows my entire DialogFragment at once, despite there being space.
adjustResize causes the ScrollView to resize and hide the bottom row. adjustpan keeps the ScrollView size intact but the soft keyboard overlaps the bottom row.
Removing the ScrollView means either option causes the keyboard to overlap.
What I would like is for the DialogFragment to move up the screen without resizing. Can I make that happen? Ideally I'd like to keep the ScrollView in my Layout to better support very small screens.
The only solution I found was to change the window options for the dialog fragment itself. Obviously on a smaller screen this will still be an issue so I am still looking for a better answer.
#Override
#NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) {
//I was using android.R.style.Theme_Translucent_NoTitleBar for another issue
//but I don't think the theme makes any difference to how the window is laid out
//that is relevant to the below code
Dialog dialog = new Dialog(getActivity(), android.R.style.Theme_Translucent_NoTitleBar);
... //Do your usual stuff here
dialog.getWindow().setContentView(...);
final WindowManager.LayoutParams params = dialog.getWindow().getAttributes();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.gravity = Gravity.TOP; //this is the important part
dialog.setCanceledOnTouchOutside(false);
return dialog;
}
It's easy to add layout transitions with this attribute:
android:animateLayoutChanges="true"
However, the animation you get does not create a pleasing user experience. When elements are added to the layout (I'm using a simple vertical LinearLayout) or change from gone to visible there's a 2-stage process that I think is rather annoying. First, room is prepared for the new element (everything else is pushed down). Then when there's enough room, the new view fades into existence. Likewise, when a view is removed or changes from visible to gone, first it fades out, then the room claimed by it gradually shrinks to zero.
I would really like a way to change the animation to what I really think is the natural way to do it: When adding a view its height gradually changes from zero to its full size, so that first you see just the top, without ever changing the alpha. When removing a view its height gradually changes to its full size to zero, so that near the end of the animation you see just the top, without ever changing the alpha.
How can I accomplish this in Android? (Note: the user can tap on several buttons together and cause several elements to appear / disappear in quick succession, before the animation for the other views ended - or even make something appear while it's still appearing).
Another question that this is perhaps not the place to ask: why isn't this the default?
(And if it's possible, can a slightly different behavior be specified in which first just the bottom of the view appears, rather than the top, like the new view slides down from under the one above it?)
You have to write your own animator and set it.
Code:
final ViewGroup profileParent = (ViewGroup) view.findViewById(R.id.profileParent);
LayoutTransition transition = new LayoutTransition();
Animator appearingAnimation = ObjectAnimator.ofFloat(null, "translationY", 600/*profileParent.getHeight()*/, 0);
appearingAnimation.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator anim) {
View view = (View) ((ObjectAnimator) anim).getTarget();
view.setTranslationY(0f);
}
});
Animator disappearingAnimation = ObjectAnimator.ofFloat(null, "translationY", 0, 600/*profileParent.getHeight()*/);
appearingAnimation.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator anim) {
View view = (View) ((ObjectAnimator) anim).getTarget();
view.setTranslationY(0f);
}
});
transition.setAnimator(LayoutTransition.APPEARING, appearingAnimation);
transition.setDuration(LayoutTransition.APPEARING, 300);
transition.setStartDelay(LayoutTransition.APPEARING, 0);
transition.setAnimator(LayoutTransition.DISAPPEARING, disappearingAnimation);
transition.setDuration(LayoutTransition.DISAPPEARING, 300);
transition.setStartDelay(LayoutTransition.DISAPPEARING, 0);
profileParent.setLayoutTransition(transition);
I'm working on an Android game.
I'm wondering if drag and drop is simply the wrong approach to tackle this. The effect I'm looking for is to have a button that when the user long-presses, will initiate a drag and drop effect with the drop-shadow being a target.
This custom drag shadow would persist until the user releases the target shadow. I have the custom shadow working and am responding to drag events. What I am not sure about is how to make this whole thing work without actually moving the button they are initiating the drag from.
Is it as simple as somehow not passing the originating View (button) to the shadow builder?
EDIT
Added definition of touch listener I'm using to initiate the drag.
private final class MyTouchListener implements OnTouchListener {
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
ClipData data = ClipData.newPlainText("", "");
//DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
view.startDrag(data, new TargetDragShadowBuilder(view), view, 0);
view.setVisibility(View.INVISIBLE);
return true;
} else {
return false;
}
}
}
I am setting the visibility here, though to be honest I'm frankly trying to follow the tutorial here:
http://www.vogella.com/tutorials/AndroidDragAndDrop/article.html
Perhaps this is the wrong approach. I'm basically looking to do the following steps:
1) Detect a drag event starting with a button
2) Without altering the button in any way, I want a custom drop shadow of a picture I specify (with a transparency) to appear under the user's finger as they drag across the screen.
Eventually I need to figure out how to make the shadow flash, or linger for a few seconds before disappearing and also to get the location where the user released their finger. It looks like from the docs I can get that from ACTION_DRAG_LOCATION...?
There is an official API Guide on how to implement Drag and Drop in Android. Unfortunately solution proposed by bonnyz does not comply to this guideline - adding a temporary view to indicate a drag operation is somewhat a heavy-weight solution.
Answering your question:
Is it as simple as somehow not passing the originating View (button) to the shadow builder?
The documentation says:
View.DragShadowBuilder(View)
This constructor accepts any of your application's View objects. The constructor stores the View object in the View.DragShadowBuilder object, so during the callback you can access it as you construct your drag shadow. It doesn't have to be associated with the View (if any) that the user selected to start the drag operation.
So, the view you pass there is not used to represent the shadow. The shadow is what you draw on Canvas within onDrawShadow() method implementation.
Basically, the shadow is just a graphics that follows the finger while being dropped. It is not a view and does not have to look like any view in the activity. It is arbitrary graphics that provides an idea to the user what is being dragged and dropped.
Here's my suggestion:
1) When user longPress on the button, get the absolute coordinates of the button:
int coord[] = new int[2];
button.getLocationOnScreen(coord);
2) Create the "ShadowView" which need to be shown under the finger while the user drags (as you like)
3) Attach the ShadowView to the Window (leaving others layout inalterated):
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
View shadowView = /..shadow view.../
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.TOP | Gravity.LEFT;
params.x = coord[0]; //add offset if needed
params.y = coord[1];
windowManager.addView(shadowView, params);
4) Update params using the user dragging position.
params.x = // drag X - halfsize of the view's width
params.y = // drag Y - halfsize of the view's height
windowManager.updateViewLayout(shadowView, params);
5) When the user stop dragging, remove the ShadowView from the Window:
windowManager.removeViewImmediate(shadowView);
Issue was that I was setting the view invisible (facepalm) in response to the touch listener. Furthermore my shadow was passing the view to super() making the view come along for the ride. Combined effect was the appearance of the view being dragged away and lost forever. Fixed by removing visibility call and removing the view from super class in customer dropshadowbuilder.
Basic question regarding setting the text of a programatically created button. As seen in my code below I've done the basics in terms of creating the button but my button appears as seen in my attached image. Basically the text in the button doesn't appear as expected. Any ideas why?
Note: I've declared button as a public instance variable right above my onCreate() and has been added correctly to my relative layout using addView();
// Create User button
btnUserAdmin = new Button(this);
// Customise the UserAdmin button
btnUserAdmin.setBackgroundColor(Color.BLUE);
btnUserAdmin.setTextSize(13.7f);
btnUserAdmin.setTextColor(Color.parseColor("#FFCC00"));
btnUserAdmin.setText("USER ADMINISTRATION");
btnUserAdmin.setGravity(Gravity.LEFT);
Thanks.
You should specify the dimensions of the button, otherwise the size could be unexpected. For instance
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.MATCH_PARENT );
btnUserAdmin.setLayoutParams(lp);
also, you can directly set them when you add the buttom
yourRelativeLatout.addView(btnUserAdmin, lp);
Also remember that numeric values for the dimensions (of the bottom or the layout) usually are evil. As you can, use only WRAP_CONTENT and MATCH_PARENT
I have a FrameLayout (all the screen is the FL) wich haves a openGLview and a header image on the top of the screen. Now i want to display a menu of two buttons, created with a LinearLayout.
My LL Menu must be floating on the framelayout, 100px below the top of the screen.
How can i achieve that? i tryed with this code, but is not working properly, the Menu is being displayed 100px below the top of the screen but it is painting the upper part of the menu, and i dont want that, i need that the upper part of the menu it's not painted with the colour of the menu. Must be a floating menu.
I'm sure that there is another way to draw the menu 100px below the top of the screen without painting the upper part of the menu with the colour of the menu.
My code (with the upper part colour problem):
///////////////sub menu de shareit////////////////
LinearLayout sharellContainer = new LinearLayout(this);
sharellContainer.setOrientation(LinearLayout.HORIZONTAL);
LinearLayout sharell = new LinearLayout(this);
sharell.setOrientation(LinearLayout.VERTICAL);
sharell.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
//LinearLayout.LayoutParams sharellParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
//sharellParams.gravity=Gravity.CENTER;
sharell.setPadding(10, shareit.getHeight()+80, 10, 10);
sharell.setBackgroundColor(0xFF383838);
//sharell.setLayoutParams(sharellParams);
share= new ImageButton(this);
selector(share, R.drawable.but_share_up,R.drawable.but_share_down);
LinearLayout.LayoutParams shareParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
shareParams.setMargins(0, 0, 0, 10); //dejo un espacio entre este botón y el siguiente
share.setLayoutParams(shareParams);
sharell.addView(share);
web= new ImageButton(this);
selector(web, R.drawable.but_web_up,R.drawable.but_web_down);
sharell.addView(web);
sharellContainer.addView(sharell);
sharellContainer.setGravity(Gravity.RIGHT);
//////////////////////////////////////////////////
.
.
.
fl.addView(squareGLSurfaceView);
fl.addView(rl);
fl.addView(sharellContainer);
setContentView(fl);
The problem is that you are using padding rather than margins. Any padding gets the background color of the view, margins do not.
You will have to add the margins to to the LayoutParams that you give to your view.
This will be very easy if you use an XML layout. You can also view what you are creating and set individual properties. This also allows you to separate your logic from your views and adhere to the MVVM design pattern so future updates are easier to perform, giving you a more flexible system.