OK, so I am starting to get a hang of building Android apps, well at least as much a programmer can after a few days - I am proud of what I have learned so far.
Anyways, I want to force login on the main activity - this I am doing by fetching a SharedPrefernece and than checking if that piece of information is null and than getting a PopupWindow which holds the "login" fields and options.
This PopupWindow has a Flipper inside, which is fine and I got working fine when the certain options are choosen.
I am having problems displaying this PopupWindow to just be the size of the content (wrap_content) as when I set the PopupWindow.setAtLocation()
Now, here is what I have been trying to do to get the size of the popup - as mentioned a few times on here:
popup.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
final PopupWindow pw = new PopupWindow(popup,popup.getMeasuredHeight(),popup.getMeasuredWidth(), true);
Note that popup is the inflator of the actual layout of the Popup, pw is the actual PopupWindow object.
Now, I want to get the actual size of just the popup window (so that way it isn't streched out over the page, but rather just in the center looking like a normal popup should.
Also, with the ViewFlipper. I want it to update the size of the popup when it switch pages (so the page should be sizing up and down per page) is there a way to make this work as well? I tried pw.update() but that didn't work out very well.
You want to measure the actual layout you will be adding to your PopupWindow. I just solved a similar problem of putting a ListView inside a PopupWindow. The trick is to override onMeasure() in your View. Remeasure that ViewFlipper everytime it changes.
#Override
public void onMeasure(int x, int y)
{
super.onMeasure(x, y);
if(x == View.MeasureSpec.UNSPECIFIED && y == View.MeasureSpec.UNSPECIFIED)
{
//measure your child views here
//tell the view it has been measured
this.setMeasuredDimension(width, height);
}
}
Related
I have a fairly complicated situation where I need to either process events in a custom view, which is added via WindowManager, or pass them to the underlying window if it is outside of the wanted area. The wanted area is the containerView where it can be smaller from the root view itself, or may have equal width / height.
The view has a size of 28x28, but it can grow up until 60x60. The growing part is done with ValueAnimator where current width and target width is determined by the ValueAnimator.getAnimatedValue() (in this case, between 28 and 60). The window needs to consume the event if it has been clicked on, or the target view which may be smaller than the window itself, is clicked.
An example of the layout looks like this:
<FrameLayout android:layout_width="wrap_content"
android:layout_height="wrap_content">
<FrameLayout android:id="#+id/containerView"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_gravity="center">
<!-- rest of the view, not important -->
<!-- the containerView can have 28x28 size or
60x60 size -->
</FrameLayout>
</FrameLayout>
The animated view is the one that is defined with android:id="#+id/containerView".
I've tried to attach the view using regular layout params, like this, to make the window layout dynamic:
WindowManager manager = context.getSystemService(WindowManager.class);
View rootView = LayoutInflater.from(context).inflate(resId, null, false);
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
params.flags = FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH;
manager.addView(rootView, params);
And this similar code block adds the view with 28x28 size, that's not a problem. However, while animating to 60x60 size depending on a state change (on containerView), the animation flickers a lot. I guess it happens because both view itself and the window needs to be re-sized. I've tried to use setLayerType(HARDWARE, null) but that didn't seem to work. Then I've found another workaround, which is increasing the size of the window directly, before starting the animation, by giving it fixed width-height values, like this:
params.width = dpToPx(60);
params.height = dpToPx(60);
manager.updateViewLayout(rootView, params);
And after this, I start the growing animation, which changes the containerView width and height gradually. With this way, animation is smooth, even on lower-end devices so I think it's a good optimization.
The problem begins with the window size change. You see, containerView has to have the attribute android:layout_gravity="center" to position the view to window's center. But, increasing the window width and height changes the view's position. To overcome that, I've decided to write another approach by doing something like this:
// This method is inside the root view, which contains
// the WindowManager.LayoutParams as its layout params.
private void setWindowSize(int widthPx, int heightPx)
{
WindowManager.LayoutParams params = getLayoutParams(); // ignore cast
int oldWidth = params.width;
int oldHeight = params.height;
int differenceWidth = widthPx - oldWidth;
int differenceHeight = heightPx - oldHeight;
// Position the view relatively to the window so
// it should look like its position is not changed
// due to containerView's center layout_gravity.
params.x -= differenceWidth / 2;
params.y -= differenceHeight / 2;
params.width = widthPx;
params.height = heightPx;
// Update itself since this is already the root view.
manager.updateViewLayout(this, params);
}
The code above was causing the position change happening with animation. Hence, I've searched if this animation can be disabled, and found an answer here which seems to be working with Android 10 emulator. However, I don't think this is a reliable approach, as most manufacturers change source codes of framework classes to implement their own themes etc. so I'm looking for a more reliable approach. The change also cause a flicker due to the containerView.onLayout() operation, presumably happening after manager.updateViewLayout() is executed, where it appears on top-left for one frame and on center on the 2nd frame, visible to the eyes.
At this point, I can only think of some ways to prevent these bugs:
1) Process touch events only on certain states (such as the coordinates intercepting the containerView)
2) Make the view non-touchable after receiving MotionEvent.ACTION_OUTSIDE which will indicate a touch event happened outside of the view's boundaries.
1st one has a flaw: If the view is clickable in all cases, it becomes clickable starting from the root view, and once the touch event is received from that view, it is not transferred to other windows (a.k.a underlying applications) which cause an issue.
2nd one seemed a good approach for me, but the event MotionEvent.ACTION_OUTSIDE does not contain any specific x or y coordinates so it is impossible to tell if the event occurred in window's boundaries. If this was possible, I'd add FLAG_NOT_TOUCHABLE to layout params and updated the view, and removed that flag if the touch is to be processed.
So, my question is:
Can a custom view, that has been added with a WindowManager choose to deliver the events further based on, i don't know, returning false from dispatchTouchEvent() or something? Or, is there a way to receive all touch events even outside our application with the specific screen coordinates so I can change the window flags depending on it?
Any help is appreciated, thank you very much.
I was able to resolve the issue by applying an ugly hack. I've used a second window, where the window itself is full screen and contains flags FLAG_NOT_FOCUSABLE and FLAG_NOT_TOUCHABLE and since the touch is disabled the events are passed below window.
The window-resize flickering depending on the animation was the cause, so I've thought about using a temporary view, added the view to a second window, by getting the cache of the view itself using a bitmap and a canvas (the states are cached and recycled by the way), and making the image view visible, setting the view on the original window as INVISIBLE and after making sure it became invisible (by using ViewTreeObserver.addOnDrawListener because the draw function is called) changing window size.
With this approach, the view becomes already invisible while the window size is changed, and translated accordingly, which eliminated the possibility of the buggy view.
Then, after the layout is complete (I've also made sure by using ViewTreeObserver.addOnGlobalLayoutListener() and waiting for the view to be placed on the target coordinates relative to parent), switched the views. Extra memory is used because of the extra added window and image view and bitmap, but the issue seems to be resolved.
The only remaining thing is how to disable window animations with the call windowManager.updateViewLayout() because the flag that the other question mentioned is apparently added in API 18, whereas this app targets to API 16. On the rest of the emulators and the devices that I've tested on seem to have this flag consistently, and the window translate animations seem to be disabled successfully.
This question is very specific, What I am trying to do (with a list view) is described in great detail in the following article: http://www.pushing-pixels.org/2011/07/18/android-tips-and-tricks-synchronized-scrolling.html
Thanks #kaushal trivedi for the link
Details:
I have an android application I am working on that uses a list view with a custom adapter. The Listview Contains a Custom header of a non-fixed height. Also please note that the list items are also of variable height. My goal is to mimic the effect produced in the latest gmail app (as an example) where when you are viewing an email, and scroll past the header, it sticks to the top of the screen just under the action bar and the content continues to scroll under it. What I would like to do, is stick the bottom half of my header to the top of the screen.
My initial reasoning was to create an invisible view fixed in the desired location, and when the user scrolled to or past that location, make the view visible. The issue in this logic, is I need the exact pixel scroll height, which after many attempts I have determined very difficult to do. The exact issue I ran into is, it is not possible from what I can gather to retrieve the pixel level Y-scroll in an onScroll event, I have only been able to retrieve the value in the onScrollStateChanged event. Which as described above will not achieve the desired functionality.
Working with the onScroll event "int firstVisibleItem, int visibleItemCount, int totalItemCount" parameters is also not an option because of the fact that the content I want to "stick" is not the size of a list item, but a fraction of the size of the variable height header.
Is there a correct way to accomplish this effect? My current minSDK level is 10.
Update 10/10/13
I made some progress. The following code syncs the Y position floating view I have on the screen with the list view. b is the view I am setting just as an example.
NOTE: This is used in the onScroll event of the list view.
View c = view.getChildAt(0);
if (c != null) {
int currY = c.getTop();
int diffY = currY - lastY;
lastY = currY;
b.setTop(b.getTop() + diffY);
}
Now the issue is, the header of my List is a non fixed height as I said earlier. So I need to get the height of the header and apply an offset to "b" to place it at the bottom of the list header floating above the list.
This is the code I've tried so far.
header.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
The issue here us header.getMeasuredHeight(); always resolves to the same value no matter how tall the actual height is.
I understand I cannot get the height until after it is displayed. Is there a way I can get that value and set the offset after it is rendered?
Update 10/11/13
I Answered my last question as soon as I woke up this morning.
While the View.measure() code was returning a height. It appears to be the default height of the view, assuming there was no text (that would ultimately stretch the view). So I used the below event to listen for when the view is displayed, and then record its actual height (which works exactly as I had hoped :) )
ViewTreeObserver vto = header.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
b.setY(header.getMeasuredHeight() - 80); //80 is a temp offset
}
});
I have to go to work soon and being that I have still not fully achieved the desired effect, I will not mark this as answered yet. Hopefully I will be able to sit down and finish this in the next day or two. I am still open to suggestions on better ways of doing this.
Okay, so after a lot of time and research, I have found an answer to my question.
First off, Thank you #kaushal for this link: http://www.pushing-pixels.org/2011/07/18/android-tips-and-tricks-synchronized-scrolling.html
My solution ended up being somewhat complex. So instead of trying to describe it here, I made an example app and posted it here: https://github.com/gh123man/Partial-Header-ListView-Scroll-Sync
The specific file containing the code for the solution is here: https://github.com/gh123man/Partial-Header-ListView-Scroll-Sync/blob/master/src/com/example/partialheaderlistviewscrollsync/MainActivity.java
I'm currently extending the SlidingDrawer class and want to resize the width of the content and can't quite get it to work right. Right now I'm setting the width of the whole view (handle and content) and it works for resizing purposes but also introduced a visual glitch when I move the handle it jumps to the new size for a split second and then returns to the handles position. I'm thinking that the problem is stemming from the onMeasure() or onLayout() calls that are happening in the base SlidingDrawer that are preventing the content area to be resized but am not completely sure.
I'm using getLayoutParams().width = newWidth; to resize the whole view but would like to use something like mContent.getLayoutParams().width = newWidth;.
The source code for the onMeasure() is here and for the onLayout() here.
Any insight into why the content area can't be resized would be great. Thanks!
So I finally figured it out if anyone else was having an issue with this. Basically when you want to resize the layout you need to measure() the layouts after the size change. Without the offsetLeftAndRight() call the handle will "jump" to the new size for a split second so setting the offset eliminates that "jump".
A simplified version of what I did was essentially:
public void resize() {
int previousPosition = mHandle.getLeft();
//Set the new size of the content area
mContent.getLayoutParams().width = width;
//Measure the newly sized content area and adjust the layout
mContent.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getBottom() - getTop(), MeasureSpec.EXACTLY));
mContent.layout(handleWidth + mTopOffset, 0, mTopOffset + handleWidth + content.getMeasuredWidth(), content.getMeasuredHeight());
/* Remeasure any other views that were resized also here */
//Not required but helps position the handle correctly
mHandle.offsetLeftAndRight(previousPosition);
}
Is there a way the this problem can be fixed? I tried invalidate() but, it still displays the same problem. What happens is that, after opening the page/ the Activity, the images behaves like the one in Figure A. It only renders to my desired layout (Figure B) after scrolling it back and forth.
What I'm trying to do is set the width and heigth of the image during runtime. So this is also in relation to my previous questions : Images in my HorizontalListView changes it size randomly and ImageView dynamic width and height which received a very little help.
Any tips regarding this matter, please?
EDIT: btw, my classes are:
MyCustomAdapter (extends baseadapter, this calls the displayimage() from ImageLoader ),
MyActivity and
ImageLoader (this is where my image url are loaded, decoded, displayed asynchronously)
Im also confused as to where i will set the height and width of the imageView. For now, i set it at ImageLoader. It was okay. but i dont know if i did the right thing.
If you want to set the width and height manually at runtime, grab a reference to the ImageView's LayoutParams AFTER the View has been measured by the layout system. If you do this too early in the rendering phase, your view's width and height as well as its parent view and so on will be 0.
I have some code in an open source library that might help you. The process is two parts:
Set up an OnPreDrawListener attached to the ViewTreeObserver for your control. My example does this inside of a custom control, but you can do this in your activity as well.
Inside the onPreDraw method, your image and it's parent will now have their width and height values assigned to them. You can make your calculations and then set your width and/or height manually to the LayoutParams object of your view (don't forget to set it back).
Check out this example where I'm applying an aspect ratio to a custom ImageView just before it's rendered to the screen. I don't know if this exactly fits your use case, but this will demonstrate how to add an OnPreDrawListener to a ViewTreeObserver, removing it when you're done, and applying dynamic sizing to a View at runtime
https://github.com/aguynamedrich/beacon-utils/blob/master/Library/src/us/beacondigital/utils/RemoteImageView.java#L78
Here's a modified version that removes my particular resizing logic. It also grabs the ViewTreeObserver from the imageView, which is a more likely scenario if you're not implementing a custom control and you only want to do this in the Activity
private void initResizeLogic() {
final ViewTreeObserver obs = imageView.getViewTreeObserver();
obs.addOnPreDrawListener(new OnPreDrawListener() {
public boolean onPreDraw() {
dynamicResize();
obs.removeOnPreDrawListener(this);
return true;
}
});
}
protected void dynamicResize() {
ViewGroup.LayoutParams lp = imageView.getLayoutParams();
// resize logic goes here...
// imageView.getWidth() and imageView.getHeight() now return
// their initial layout values
lp.height = someCalculatedHeight;
lp.width = someCalculatedWidth;
imageView.setLayoutParams(lp);
}
}
I would like to make a curved on-screen keyboard for Android. I have examined the softkeyboard, and various other on screen keyboards on google code. None that I have found involve a keyboard shape that is anything other than a rectangle. Ideally, I would like to create a keyboard that consists of keys distributed across two semi-circles on opposite sides of the screen (i.e., imagine holding a tablet by the sides and being able to hit the keys with your thumbs).
Of the code I have examined, onscreen keyboards are created as views (usually extending KeyboardView) and appear as a continuous bar across the bottom of the screen. As an approximation to my goal, I have tried to alter code I found on google code (dotdash-keyboard-android) to only draw its keys in the lower left-hand corner and leave the lower-right hand corner transparent. I have been able to override onMeasure to affect the dimensions of the view (see below), but this only seems to alter the positions of the keys and not the positions of the container. In other words, there is still a black bar filling the bottom of the screen.
//Located within KeyboardView
#Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
this.setMeasuredDimension(200, 200);
}
Is what I want to do even possible? Is there a more correct term for this? Are there projects I can use as examples?
I have also tried to set the view's dimensions using this.setLayoutParams -- but, these calls seem to have no effect. I have also tried to use this.getParent to access the parent view (if one even exists) and change it's dimensions but this approach does not work (or, I'm just doing it wrong). Any help would be much appreciated.
Thank you
UPDATE: 12/21/2012 - I think I need to override the parent class's onDraw method. Looking here , it looks like KeyboardView's onDraw method draws to a canvas that is equal to the size of the screen using the following code:
final int width = Math.max(1, getWidth());
final int height = Math.max(1, getHeight());
mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBuffer);
I think I can override onDraw and draw whatever I want on the canvas.
UPDATE: 12/21/2012 - I've overridden onDraw and it's now clear that the keyboardView is the dimensions I'm setting it to (200x200). Using Hierarchyview, I can see that the keyboardview is inside a framelayout with the id InputArea. So, the horizontal bar that fills the entire width is this framelayout. But, I'm not creating it -- where does it come from and how can I alter its dimensions?
UPDATE: 12/22/2012 - After more testing, it seems like the behavior (dimensions) of a keyboardview are in-part determined by the activity that calls it. In the browser, I get the behavior I've been describing: the height of the browser window shrinks to accommodate a bar across the bottom of the screen that holds the keyboard, even if the width of the keyboard is less than the width of the screen. In the calendar app, the keyboard size appears as I have set it (as a square in the lower-left hand corner) with the calendar appearing unchanged beneath it. So, it seems impossible to reach my goal with most apps using this approach. An alternative approach might be to have the IME service create a popupwindow or dialog. One problem is that popupwindows need a parent view or anchor to attach to and I don't think it's possible to find the top-most view from the IME service. Perhaps I can create a transparent view over the current activity and place the popup on top of that?
UPDATE: 12/23/2012 - Progress. I've figured out how to display a popup from the keyboard IME. The next step is to figure out how to make the popups a little round/organic. Here's a screenshot of what I accomplished followed by source.
Source. The following method is in the service IME class and called by the child (of the service) view's onMeasure method so that the popups open at the same time the keyboard is drawn. I've set the dimensions of the keyboard to 1x1 so it isn't visible. The log statements are there to help me figure out how to position the popups.
public void initiatePopupWindow()
{
try {
WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
DisplayMetrics dm = new DisplayMetrics();
display.getMetrics(dm);
//display.getSize(p);
Log.i("dotdashkeyboard","initiatePopupWindow (from IME service)");
LayoutInflater inflater = (LayoutInflater) this.getBaseContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View layoutLeft = inflater.inflate(R.layout.popup_layout_left,null);
View layoutRight = inflater.inflate(R.layout.popup_layout_right, null);
// create a 300px width and 470px height PopupWindow
int popupHeight = 300;
int popupWidth = 200;
if (popUpLeft == null) popUpLeft = new PopupWindow(layoutLeft, popupWidth, popupHeight, false);
if (popUpRight == null) popUpRight = new PopupWindow(layoutRight, popupWidth, popupHeight, false);
int ypos = 0;
int xposRight = 0;
if (display.getRotation() == Surface.ROTATION_0) {
ypos = -(dm.heightPixels / 2 + popupHeight/2);
xposRight = (dm.widthPixels - popupWidth);
Log.i("dotdashkeyboard","test rotation=normal");
} else if (display.getRotation() == Surface.ROTATION_90) {
ypos = -(dm.heightPixels / 2 + popupHeight/2)/2;
xposRight = (dm.widthPixels - popupWidth)*2-popupWidth;
Log.i("dotdashkeyboard","test rotation=90-degrees");
} else {
Log.i("dotdashkeyboard","test rotation=unknown=" + display.getRotation());
}
popUpLeft.showAtLocation(inputView, Gravity.NO_GRAVITY, 0, ypos);
popUpRight.showAtLocation(inputView, Gravity.NO_GRAVITY, xposRight, ypos);
Log.i("dotdashkeyboard","test created popup at ypos="+ypos + " xposRight=" + xposRight);
Log.i("dotdashkeyboard","test screenWidth=" + dm.widthPixels + " screenHeight=" + dm.heightPixels);
Button cancelButton = (Button) layoutLeft.findViewById(R.id.popup_cancel_button);
//cancelButton.setOnClickListener(inputView.cancel_button_click_listener);
cancelButton.setOnClickListener(cancel_button_click_listener);
} catch (Exception e) {
e.printStackTrace();
}
}
You seem to be on the right track here. As you've noted, most activities will shrink their views to provide space for the keyboard window, so if you want the calling activity to fill the screen you need to use a secondary window, such as a PopupWindow. You can set the dimensions of your main window to 0x0.
Have you tried calling popUpLeft.setBackgroundDrawable(null) / popUpRight.setBackgroundDrawable(null)? This should remove the semi-transparent background and show only whatever you draw on top.