Below my ListView, I have a bar with a trash can on it.
When some action is performed, suppose it is simply clicking on the item, I would like the item to animate down to the trash can. This means both moving downward and shrinking horizontally to a width of 0.
How can this be achieved? I suspect it involves creating a bitmap of the item and then animating that bitmap downward and ever smaller. This also means, depending on where the list item is relative to the screen, it may need to shrink faster than other times (ie: if the list item is already close to the bottom of the screen instead of the top). I'm just not sure how to achieve any of this.
The only animation I've done involves presenting a bar from the bottom of the screen. I've no experience with moving around a free-floating object and altering its size.
Could someone provide me with some good direction?
Follow these steps to achieve that target:
First, you need to create a "hoovered" copy of your list item. To do that, you need to inflate a new view, which has the same structure as the list items in your listview. After that, you need to add that to the windows manager, like this:
WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams();
windowParams.gravity = Gravity.TOP | Gravity.RIGHT;
windowParams.x = item.getLeft();
windowParams.y = item.getTop();
windowParams.height = item.getHeight();
windowParams.width = item.getWidth();
windowParams.flags =
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
windowParams.format = PixelFormat.TRANSLUCENT;
windowParams.windowAnimations = 0;
View hooveredView = getClonedView(item);
// Add the hoovered view to the window manager, as a new view in the screen
WindowManager mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mWindowManager.addView(hooveredView, windowParams);
Now, create new Animation class which gets the WindowParams, and change the x and y coordinates, in order to move the hoovered item to the lower trash bar. Update the new view position by using this code:
mWindowManager.updateViewLayout(mViewToAnimate, mWindowParams);
You can also play with the width and height in order to simulate shrinking of the view.
In order to animate the empty spot left on the list, you can use my ExpandAnimation explained on my blog:
http://udinic.wordpress.com/2011/09/03/expanding-listview-items/
That's pretty much it. If you need more details - just ask.
As far as performing the animation, take a look at Animation. It looks like you will want to build an AnimationSet comprised of a ScaleAnimation, AlphaAnimation, and TranslateAnimation. Then you apply that animation to whatever you create to move around over the list view. Off the top of my head, I would probably approach that aspect by creating a copy of the item that will be deleted, animating it to the 'trash' then deleting the original item. But that approach may be harder/less efficient than creating a Bitmap.
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.
I am trying to create a screen, on some operation it is required to slide a layout from bottom to the height to match it's length, with a translation animation kind of slide.
Have a look at the screen example:
As you can see the initial screen layout, the new view 5 slides in from bottom, pushing the entire layout upwards with an animation.
I have tried this: https://stackoverflow.com/a/19766034/1085742
Using the above link, the new view is visible with animation but screen is not sliding down to show the new view 5.
Any idea how to do this!!
Thanks in advance.
I also tested this. Apparently an animation with translateY or Y does not change the layout bounds. In other words, the other views do not move up. Neither when using an Animation nor an Animator.
One solution is to animate the margins, if the animated view is in a layout that supports margins. In Kotlin:
val collapse = ValueAnimator.ofInt(0, -someLayoutAndViews1.height)
collapse.addUpdateListener {
val params = someLayoutAndViews1.layoutParams as ViewGroup.MarginLayoutParams
params.topMargin = it.animatedValue as Int
someLayoutAndViews1.layoutParams = params
}
collapse.start()
Basically what I'm trying to do is prevent users from interacting with part of the screen in Android by covering that area with a transparent View. Looking at other SO posts, I was able to come up with a solution that partially works. For example, to cover the left half of the screen I could do this:
Context mContext = getApplicationContext();
WindowManager mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Display display = mWindowManager.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int mScreenWidth = size.x;
int mScreenHeight = size.y;
View leftBox = new View(mContext);
WindowManager.LayoutParams leftBoxParams = new WindowManager.LayoutParams();
leftBoxParams.height = mScreenHeight;
leftBoxParams.width = mScreenWidth/2;
leftBoxParams.gravity = Gravity.LEFT;
leftBoxParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
leftBoxParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
leftBoxParams.alpha = 0.0f;
leftBox.setBackgroundColor(Color.TRANSPARENT);
mWindowManager.addView(leftBox, leftBoxParams);
The above works correctly for most cases: If there is a button on the left side of the screen, the user can't touch it, and a button on the right is still accessible. However, in the case that the user touches the accessible button on the right while also touching anywhere on the left side of the screen, the button will never receive a click event. This means that if the user is resting his hand on the left half of the screen, the right side becomes basically unusable.
It's important to note that I'm creating the transparent View within an AsyncTask, and things underneath the overlay may be part of other applications, not just mine.
From the Android developer site, it seems like FLAG_NOT_TOUCH_MODAL would be what I'm looking for:
Window flag: even when this window is focusable (its FLAG_NOT_FOCUSABLE is not set), allow any pointer events outside of the window to be sent to the windows behind it.
However, neither this flag nor FLAG_NOT_FOCUSABLE fix the behavior I'm seeing. Any suggestion about why I'm seeing this behavior or how I might fix it is greatly appreciated.
I have started working on an app that uses an overlay service to display a utility sidebar on the phone.
What I did was to add a small arrow handle to the right side of the screen, and when the user swipes over it, the sidebar shows up.
This worked without issue up to Jelly Bean. Starting with JB, the entire process of showing the sidebar started to become animated. Which would be very cool, if it were not animating all over the place.
What I did to show and hide the sidebar was a simple updateViewLayout command as below.
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
handlesize, WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.RIGHT|Gravity.CENTER_VERTICAL;
wm.updateViewLayout(mOverlay, params);
handleButton.setImageResource(R.drawable.handle);
isCompact=true;
This piece of code hides the sidebar. The handlesize variable is the width of the handle. The wm variable is the window manager. So basically, what I do is only show the width of the handle, so anything else that might be next to the handle(the sidebar is not visible). I also use WRAP_CONTENT for the height, to make sure the only part that absorbs touch events is the area used by the handle, so as to not block off the entire right hand side of the screen from registering touches.
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.RIGHT|Gravity.CENTER_VERTICAL;
wm.updateViewLayout(mOverlay, params);
handleButton.setImageResource(R.drawable.handle_back);
isCompact=false;
This is the code that displays the sidebar. The width of the whole view is set to WRAP_CONTENT, so it shows all the content, and the height is set to MATCH_PARENT, because the sidebar is full height when it's visible.
Now normally, this code should instantly switch between one width and height set to the other width and height set, nothing in between. But since Jelly Bean, this has started doing this through some weird animation. I'm not sure, but it may have something to do with Project Butter.
In the link below, you can see some frames of the animation.
http://i.stack.imgur.com/QzZGO.png
If I set the height of the view, even when closed, to MATCH_PARENT, then the animation is just on the horizontal plane, so right to left, which does look nice. The problem with that is I don't want to block the entire right hand side of the screen. There are a lot of uses for sliding from the right in other apps that might run under the service, like switching tabs in Chrome.
So my question is, do you have any idea what I should do to stop this behavior, or, even better, to use it to my advantage in order to make the sidebar do some smooth animations, but in a way that works on Android 2.2 and above?
Thanks a lot!
Since JB Android has added window animations to layout changes. If you disable window animation in Developer settings, the behavior will stop. Also, you cannot disable/enable this programmatically (requires system permission if I remember correctly), so the solution is to remove the view completely and attach it again. Instead of wm.updateViewLayout(view,params) you have to wm.removeView(view) and then wm.addView(view,params).
is there a way to move the whole screen content outside the screen bounds, with titlebar and all, on the left for example, i tried
content = ((LinearLayout) act.findViewById(android.R.id.content)
.getParent());
to get the screen content and add a margin to it
FrameLayout.LayoutParams pr = (android.widget.FrameLayout.LayoutParams) content
.getLayoutParams();
pr.rightMargin = xOffset;
content.setLayoutParams(pr);
but it doesnt work, the margin appears but the content is not moved just resized to fit the screen, anyone know how to do this the simple way?
you can set the margin in negative(-) values.
for example if your want the layout to move upper use this:
android:marginTop = "-50dip or more u can put"
Are you trying to flip between views? As in, moving that layout off the screen to show another one? If so you should look at using a widget called ViewFlipper which will do exactly as you need. It can also be animated etc.
http://developer.android.com/reference/android/widget/ViewFlipper.html