I'm working on a simple sliding-drawer component. It's a similar to the deprecated SlidingDrawer component.
I'm extending LinearLayout (because that suits what I need to do). I'm not doing anything very fancy, just a handle that you click on to open and close the drawer.
My drawers are contained in a RelativeLayout, and for opening and closing it uses a TranslateAnimation to reposition the view mostly off-screen (with just the handle showing).
The problem I am having is that when the drawer is closed, the child components inside the drawer are responding to touch events at the same screen position as if the drawer was open - even though they are clearly not in that position.
For my right-hand drawer this is particularly bad because the area where you should be able to click the handle does not work, as the event is being handled instead by the component that would be in that position if the drawer were open.
It's really odd. I tried a requestLayout on the drawer in the onAnimationEnd event, but that did not help (in desperation I also tried invalidate and forceLayout - also did not work). I've also tried calling the requestLayout on the parent view.
Any thoughts on what else I could try?
It seems that the weird touch events occur because the view is translated, and for whatever reason this translation is not also applied to the touch region - go figure.
I resolved the problem by clearing the animation (which removes the translation), and then repositioning the view using layout params. Here's a snippet...
#Override
public void onAnimationEnd(Animation a) {
drawer.clearAnimation();
MarginLayoutParams lp = (MarginLayoutParams) drawer.getLayoutParams();
switch (drawer.mAlign) {
case ALIGN_LEFT:
lp.leftMargin = x2;
break;
case ALIGN_RIGHT:
lp.rightMargin = -x;
break;
case ALIGN_TOP:
lp.topMargin = y2;
break;
case ALIGN_BOTTOM:
lp.bottomMargin = -y2;
break;
}
drawer.setLayoutParams(lp);
}
x2 and y2 are the final x and y positions calculated earlier for use in the TranslateAnimation, and then reused here.
Note: On the animation I use setFillAfter(true). You can potentially leave this out, and then you also don't need the clearAnimation() later, but I found that doing this caused a flicker.
I hope this helps others. I'm a bit of a noob to Android, so very happy for any corrections or better ways of handling this!
The pre-3.0 animation system only updates the drawing for a given view, not its actual bounds including touchable areas.
You can use the newer animation properties (you'll need 9OldAndroids if you want to support pre honeycomb), or set the draw view to gone after the animation to slide it off has finished.
You could also try playing with offsetLeftAndRight
Related
I implemented a View.OnDragListener for a simple drag and drop feature, and added additional support for scrolling the parent view while dragging the target. This works fine if I release the drag at a valid target view (i.e. if onDragEvent returns false for DragEvent.ACTION_DROP). For example here I start dragging folder L, scroll up and drop it to inside folder A, and the animation is working correctly:
This also works fine if I don't scroll while dragging and nothing handles the drop (i.e. if onDragEvent returns true for DragEvent.ACTION_DROP and the position of the dragged view hasn't changed since starting the drag). For example here I start dragging folder L, drag it up to blank space which isn't a valid drop target, and release. The animation moves it back down to where L's original position, looking good.
But as soon as I scroll the parent view while dragging and nothing handles the drop, the animation becomes completely wrong because Android apparently ignores the fact that the view is now sitting at different coordinates than before. It just sets the coordinates at the point of calling startDragAndDrop and never updates them. For example here I start dragging folder L, scroll up, drag it to blank space which isn't a valid drop target, and release. The animation moves it up because that's where L's original location was relative to the screen, not the scrolling content:
What I need is for L to move down to where it is at the end, not at the start. How can I achieve this? I know the final coordinates, but I don't know how to fix the animation accordingly. I haven't even found any Android SDK code where this release animation is handled (Google seems to handle this magically via SurfaceSession and a system-private IWindowSession.aidl).
The code I use for the drag and drop is very simple, it's just this:
val shadow = View.DragShadowBuilder(v)
#Suppress("DEPRECATION")
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
v.startDrag(null, shadow, item, 0)
else
v.startDragAndDrop(null, shadow, item, 0)
The code to make it support scrolling is a bit more complicated, something like the solutions to this question: Make a scrollView autoscroll with drag and drop in Android.
But that part isn't essential to reproduce it. You basically just need a ScrollView or RecyclerView and scroll it programmatically or manually after starting the drag. Nothing fancy.
Please note that I'm showing examples which might lead to the assumption that ItemTouchHelper would solve my problem. In normal situations ItemTouchHelper would be the go-to solution, but in fact, I'm actually using the drag and drop to move views between RecyclerViews (and across fragments), which is not supported by ItemTouchHelper, so it's not applicable in this case.
OK, I have an app that needs access to basic swipe gestures. I've implemented a GestureDetector using code scraped from StackOverflow and then modified (to make it work at all). For it to work, it needs to steal Touch events.
Originally, I was detecting onScroll events and such and trying to pass them on to elements that needed to scroll, but this was really jumpy and never scrolled by the amount I wanted, so I started to simply pass the touch events on to the super-class after reading them. This caused really strange behavior and some elements never scrolled at all (no idea why), so I started passing events in a 3 way split ... some to the super-class, some to the gesture-detector, and some to the on-screen view that needed to scroll! Yeah, a mess, but everything is finally working smoothly. Except ...
If you touch the bottom of an EditText (it's full-screen except for the AppBar), then as the keyboard appears and scrolls the EditText up, the appbar scrolls off the screen (right under the statusbar icons ... looks weird because they overlay each other) and I can't make it scroll back. Also means I can't get to my Menu or the Cut/Copy icons. Hitting the menu key when it has scrolled off crashes the app.
Any idea how to stop the AppBar from scrolling off the screen?
#Override
public boolean dispatchTouchEvent(MotionEvent me) {
int top = dm.heightPixels - myEditText.getMeasuredHeight();
MotionEvent mod = MotionEvent.obtain(me.getDownTime(), me.getEventTime(),
me.getAction(), me.getX(), me.getY()-top, me.getMetaState());
if (me.getY() <= top)
return super.dispatchTouchEvent(me);
myEditText.onTouchEvent(mod);
return this.detector.onTouchEvent(me);
}
OK, this seems to fix it! In the activity manifest ...
android:windowSoftInputMode="adjustResize"
Bamn! No more problems!
I have a list view with each list item containing a button (with a text on it) and an image. I am trying to implement swiping a swiping effect when the user touches the listitem and moves his finger to the left or right. This is my code in onIntercetTouchEvent
case MotionEvent.ACTION_DOWN:
mDownX=motionEvent.getX();
Log.i(TAG, "onInterceptTouchEvent.ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
float deltaX=motionEvent.getX()-mDownX;
int mSwipingSlop=deltaX>0?mSlop:-mSlop;
this.setTranslationX(deltaX-mSwipingSlop);
Log.i(TAG, "onInterceptTouchEvent.ACTION_MOVE");
break;
It works. But the problem is that there is a blurring effect when the swiping is happening. Even though I am swiping in one direction, button in the view seems to move back and forth very fast and thus giving a blurring effect. Can anyone tell what is the problem here and a possible solution?
Printing value of deltaX in for ACTION_MOVE
﹕
onInterceptTouchEvent.ACTION_DOWN
\ onInterceptTouchEvent.ACTION_MOVE---13.481277
onInterceptTouchEvent.ACTION_MOVE---4.9546204
\onInterceptTouchEvent.ACTION_MOVE---16.477112
onInterceptTouchEvent.ACTION_MOVE---30.629227
onInterceptTouchEvent.ACTION_MOVE---33.33777
onInterceptTouchEvent.ACTION_MOVE---53.111282
onInterceptTouchEvent.ACTION_MOVE---53.15001
onInterceptTouchEvent.ACTION_MOVE---74.20006
onInterceptTouchEvent.ACTION_MOVE---75.70354
onInterceptTouchEvent.ACTION_MOVE---94.25169
onInterceptTouchEvent.ACTION_MOVE---95.33977
onInterceptTouchEvent.ACTION_MOVE---116.58623
onInterceptTouchEvent.ACTION_MOVE---117.084274
onInterceptTouchEvent.ACTION_MOVE---140.37877
onInterceptTouchEvent.ACTION_MOVE---137.01903
onInterceptTouchEvent.ACTION_MOVE---157.82243
onInterceptTouchEvent.ACTION_MOVE---152.6262
onInterceptTouchEvent.ACTION_MOVE---172.87059
onInterceptTouchEvent.ACTION_MOVE---165.39714
onInterceptTouchEvent.ACTION_MOVE---182.45793
onInterceptTouchEvent.ACTION_MOVE---174.23326
onInterceptTouchEvent.ACTION_MOVE---190.94212
onInterceptTouchEvent.ACTION_MOVE---182.86662
onInterceptTouchEvent.ACTION_MOVE---196.75847
I have reproduced the problem, and this seems to work: instead of setTranslationX(deltaX-mSwipingSlop), use setX(getX() + deltaX-mSwipingSlop).
Using getRawX() instead of getX() works as well, but needs some extra calculations to accommodate for second gestures.
I am not entirely sure why this behavior occurs though.
I have to implement slidable menu in my app. User should be able to show/hide it by tapping on it and move left/right.
To implement this, I`ve created custom SlidableFrameLayout that extends FrameLayout.
By overriding onTouchEvent method I calculate slide position and call
private void setLeftLayout(int left) {
layout(left, getTop(), getRight(), getBottom());
}
to change position for SlidableFrameLayout. Everything works fine, but when I keyboard shows (after focusing EditText), SlidableFrameLayout changes its left value to 0, so if menu was closed, it becomes opened. The same thing happens when I hide the keyboard (generally, it appears in all actions that prevents calling layout methods, I think).
I can not invent how to fix it. Could you help me? If any additional info is required, I`ll attach it. Thanks
I think your issue is related to calling layout directly. When the container view changes size as a result of the keyboard being shown/hidden onMeasure/onLayout will be called with the view's default bounds. In your case I'm guessing the left bound is 0, so layout is being called with 0 for left every time your window resizes.
Instead you probably want to use something like setTranslationX or scrollTo ..
Good luck!
As a result, I changed my logic:
private void setLeftLayout(int left) {
FrameLayout.LayoutParams params = getFrameLayoutParams();
params.leftMargin = left;
params.rightMargin = -left;
requestLayout();
}
In my case, view is a parent of FrameLayout, so I haven`t added any conditions in 1st line of the method. This method works pretty fine.
For my current project I'm building a pulldown view with a handle and a content.
When I move the handle by dragging it everything is fine. Then I let it go and start an animation to fully open or close the pulldown.
First: without the animation in it everything is fine, but off course not that smooth...
So I've added this for the animation:
ObjectAnimator animHandle = ObjectAnimator.ofFloat(mHandleContainer, "y", newHandlePosition);
ObjectAnimator animContent = ObjectAnimator.ofFloat(mContent, "y", newContentPosition);
final AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(animHandle, animContent);
animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
animatorSet.setDuration(MAX_ANIMATION_DURATION);
animatorSet.start();
Everything is just fine utill here.
But then, when I press the handle (mHandleContainer) again, I want to know where the handle is positioned so I do a
mHandleContainer.getTop()
And that is where it all goes wrong.
When I initialize the custom component I set the top of the handle to 0.
So far so good, then I start dragging, when I let go I see that I will animate to a position of 1134 pixels. Which is fine (meaning the pull down will be entirely open).
But then I press the handle again and get the top (getTop()) and it still returns me 0.
What I want is that my view's getTop() function returns me 1134 again.
Is that possible?
Or should I do something in the onAnimationEnd method of my AnimatorListener?
I already tried to do a mHandleContainer.layout(..) in there and then my getTop() is ok, but then my layout is screwed...
And tricks here for letting the animation end on the correct position (as it does) and persist the position in the view?
I already had a look on SO (and Google) but didn't find something useful. The only thing close to my problem that I could find was this: NineOldAndroids, not clickable view after rotation or move
But didn't help either...
This is because your are animating the "y" property while trying to read the property top, and they aren't the same.
Top is the top position of the view relative to its parent.
Y is the visual y position of the view which is equivalent to translationY + the current top property.
So in your case:
Either animate "top" like this:
ObjectAnimator animHandle = ObjectAnimator.ofFloat(mHandleContainer, "top", 200);
And read it via
mHandleContainer.getTop()
Or "y":
ObjectAnimator animHandle = ObjectAnimator.ofFloat(mHandleContainer, "y", 200);
And read it via
mHandleContainer.getY();
I did solve the issue using the basic ValueAnimator which each time called my custom drawing method for moving all my views.