ScaleAnimation in custom View getting clipped - android

I have a custom View where I implement onDraw and it successfully draws the background as well as a small overlay image at the top right. I animate that overlay image (AnimationSet with a ScaleAnimation and Rotate Animation) manually by doing:
final AnimationSet animationSet = new AnimationSet(false);
animationSet.addAnimation(scaleAnimation);
animationSet.addAnimation(rotationAnimation);
animationSet.setFillAfter(true);
post(new Runnable() {
#Override
public void run () {
animationSet.start();
new Handler().post(getAnimationRunnable(animationSet));
}
private Runnable getAnimationRunnable (final AnimationSet animationSet) {
return new Runnable() {
#Override
public void run () {
final Transformation outTransformation = new Transformation();
if (animationSet.getTransformation(System.currentTimeMillis(), outTransformation)) {
// store matrix for applying in onDraw
mMatrix = outTransformation.getMatrix();
// THIS INVALIDATE DOESN'T WORK
parent.invalidate(new Rect(getParentInvalidateRect()));
post(getAnimationRunnable(animationSet));
}
}
};
}
});
If I invalidate the custom View like this ...
// THIS INVALIDATE DOES WORK
invalidate(getOverlayClientRect());
then the animation happens and all is great, except that the animated image now gets clipped - because it's being scaled up, beyond the top and right bounds.
But when I pass a Rect to parent.invalidate() that intersects the custom View's top right corner area, it does not trigger an onDraw for the custom View.
So ...
will this approach work?
if so, is there any other factor than the Rect passed to parent.invalidate() that determines whether it will call the custom Views onDraw method?

After a lot of searching, the answer was obvious. I just had to set the clipChildren property of the parent to false. Didn't need to invalidate the parent, just the custom View.
That worked fine on a 4.0.3 tablet. However, on a Galaxy Nexus (4.2.2), not only did it render outside of the bounds during the animation, it actually only cleared the background of the Rect passed to invalidate. So, when the animation was done some remnants of the image were still visible outside the Rect. The solution there was to pass a Rect large enough to cover the largest of the drawn bitmaps during animation.

Related

How do I animate expansion of a ListView?

I'm looking to implement an animation that will slide open or expand a ListView in android. At the moment, I'm using a scale animation (shown below)
Animation slideDown = new ScaleAnimation(1.0f, 1.0f, 0.0f, 1.0f);
slideDown.setDuration(1000);
but this doesn't work for 2 reasons:
1) I want the first item to fade in, and then the list to drop down. If I do a fade in animation, the entire list shows up and then jumps back up to the top and scales down to full size
2) I'm not really looking to scale, I would like to just reveal the ListView by animating down the bottom (if that makes sense).
Is there a way that I can do a TranslateAnimation on just the bottom margin or something similar? I looked into the drawable Clip resource, but that didn't seem to fit my needs here (I'm not sure how to apply it to a ListView). And I'm using a normal ListView, not an ExpandableListView, because I just have one group of items.
So I started searching for different things and it turns out that animating the height was my solution. Essentially I start with a dummy object in the list (so that it has at least 1 row), then I animate it from the height of that row to the height of the full list. Below is the code for the runnable that I am now calling.
private Runnable slideDownList1 = new Runnable() {
#Override
public void run() {
final ListView detail = (ListView)getView().findViewById(R.id.detail_menu_1);
ValueAnimator va = ValueAnimator.ofInt(detail.getHeight(), detail.getHeight()*mItem.containedObjects.size());
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
Integer value = (Integer) animation.getAnimatedValue();
detail.getLayoutParams().height = value.intValue();
detail.requestLayout();
}
});
va.setDuration(1000);
va.start();
}
};
The XML is just a basic ListView with the id of detail_menu_1.
The new element Recycler View include animations. You can use it with Support Library
https://developer.android.com/training/material/lists-cards.html

Hotspot of onTouchListener does not move along the TranslationAnimation

In the run-time, I added a new ImageView and attached a listener, like so:
image = new ImageView(someAppContext());
relativeLayout.addView(image, someLayoutParam);
// add onTouch listener to the image view:
image.setOnTouchListener(new ImageView.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
makeToast("touched!");
return false; // tried return true, makes no difference
}
});
After that, I used TranslateAnimation with setFillAfter(true), so the ImageView would stay in the new position. However, the onTouch() is triggered if I touch the OLD position of the ImageView, but NOT the new position where the image visually is. How do I make the touch-sensitive hotspot move along with the ImageView itself? I also tried to invalidate(), doesn't seem to help.
Animation does not actually move Views. Translation animation only adds an offset to its coordinates.
I think the best workaround is to clear all the animations, and update layout parameters in order to move the actual ImageView over to the destination of the animation.
You need to store the destination of your translation, say dest_x, dest_y, and after the animation is done, do:
someLayoutParam.leftMargin = dest_x;
someLayoutParam.topMargin = dest_y;
yourImageView.clearAnimation(); // resets its position
yourRelativeLayout.updateViewLayout(yourImageView, someLayoutParam);
In this way, the location of the image view does not change, but the actual view is moved, so the touchable area is moved, too.

Buttons on an animated view respond to clicks even when off screen?

I have a LinearLayout, I'm applying a translation animation to it. I'm filling the animation before and after. Visually it works fine. The animation ends by translating the view off screen. But if I click an x,y coordinate on screen that happens to be where the view was at some point during its animation, a button on the view has its click listener fire.
The only solution I've found is to add an animation listener, and when the animation ends, mark the buttons on the (now out of view) layout to visibility=gone, enabled=false. This seems bizarre - the view is no longer on screen, but it's still responding to click events. Is this a known thing, I'm probably not setting the animation up correctly?
Thanks
----- Update --------
I refactored my animation a little. Instead of using animation.setFillAfter(true), I set the layout's visibility to GONE when the animation is complete. Now it doesn't register clicks when off-screen. Still interested to know if this is a known thing, as it'd be easier to simply not have to add an animation listener etc.
Translate Animations on lower level API( below honey comb) changes where the button is drawn, but not where the button physically exists within the container. So, you are on your own to handle this situation. For more information about this you can refer to this link. One way is to actually change the location of the button in the layout(not by animation). Here is how you can achieve this:
params = (LayoutParams) mBtn.getLayoutParams();
TranslateAnimation animation = new TranslateAnimation(0, 0, 0, 400);
animation.setDuration(2000);
animation.setAnimationListener(mAnimationListener);
mBtn.startAnimation(animation);
....
....
private AnimationListener mAnimationListener = new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
params.topMargin = params.topMargin + 400;
mButton.setLayoutParams(params);
}
};
Here by changing the layout params we are changing the physical position of the button.
In your case as view is going off the screen so you just need to change the visibility of the button(View.GONE) on animation end.

Android Layout Animation won't work

I have a LinearLayout (LayoutContentView) which can contain one or two view (SubView1 and SubView2) (they are eitherTextView or ImageView).
This layout is in another LinearLayout (MyScreenLayout).
I want to make an animation on LayoutContentView so it can move and show just a part of it in MyScreenLayout.
Both of these layouts have setClipChildren(false); so it can let it's children draw outside it-self.
Depending on different parameters, I can change the size of the content, and the size of the content I will show.
Basically, I expend from top to bottom to show the two subviews and unexpend for bottom to top to show only the second subview. Before I expend, I increase the size of the LayoutContentView, so it can show the two subviews, and after I unexpend, I decrease the size of the LayoutContentView, so it can only show the second subview, and it let space on the screen for other elements.
Here is my method for expending and un-expending LayoutContentView :
mLayoutContentView.clearAnimation();
float yFrom = 0.0F;
float yTo = 0.0F;
float xFrom = 0.0F;
float xTo = 0.0F;
if (expend) { // if we expend
// I change the height of my LayoutContentView so it we can show it's two subviews
final android.view.ViewGroup.LayoutParams lp = mLayoutContentView.getLayoutParams();
lp.height = subView1H + subView2H;
setLayoutParams(lp);
invalidate();
// we start the animation so it shows only the second subview
yFrom = -subView1H;
// and we animate from top to bottom until it shows the two subviews
yTo = 0;
} else { // if we un-expend
// we animate from bottom to top starting by showing the two subviews
yFrom = 0;
// and progressively hiding the first subview and showing only the second subview
yTo = -subView1H;
}
Animation anim = new TranslateAnimation(xFrom, xTo, yFrom, yTo);
anim.setDuration(1000);
anim.setFillAfter(true);
anim.setFillEnabled(true);
anim.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
if (!expend) {
// if we un expend at the end of the animation we can set the size of LayoutContentView to the size of the second subview again
final android.view.ViewGroup.LayoutParams lp = mLayoutContentView.getLayoutParams();
lp.height = subView2H;
mLayoutContentView.setLayoutParams(lp);
invalidate();
}
}
});
mLayoutContentView.startAnimation(anim);
The way I made my animation I need it to apply on LayoutContentView, the layout which contain two subview, and with startAnimation() it doesn't do the animation.
I tried to use a LayoutAnimationController, but instead of doing the animation on the LayoutContentView, it does it on each of its children...
I also tried to do the animation on each children myself, but I don't know why, the second subview isn't shown.
Each time I've tried to use HierarchyViewer, but it's does see the change made by the animation.
Does anyone know a solution or have faced the same problem and found a good solution ?
EDIT :
Well it seems that if you set a background color to your TextView and move them with animation, the background move but even if you have set the fill after parameter to your animation, the background moves back to it's original position or something like that, and therefore as I set a background color to both of my SubViews, somewhat one of the SubView's background hide the background of the other...
And there also a problem if after the animation, one of the SubView is still outside the its layout, there is also a problem during the animation, so I add a limitation to what I intended to here too.

Rotating a view in Android

I have a button that I want to put on a 45 degree angle. For some reason I can't get this to work.
Can someone please provide the code to accomplish this?
API 11 added a setRotation() method to all views.
You could create an animation and apply it to your button view. For example:
// Locate view
ImageView diskView = (ImageView) findViewById(R.id.imageView3);
// Create an animation instance
Animation an = new RotateAnimation(0.0f, 360.0f, pivotX, pivotY);
// Set the animation's parameters
an.setDuration(10000); // duration in ms
an.setRepeatCount(0); // -1 = infinite repeated
an.setRepeatMode(Animation.REVERSE); // reverses each repeat
an.setFillAfter(true); // keep rotation after animation
// Aply animation to image view
diskView.setAnimation(an);
Extend the TextView class and override the onDraw() method. Make sure the parent view is large enough to handle the rotated button without clipping it.
#Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.rotate(45,<appropriate x pivot value>,<appropriate y pivot value>);
super.onDraw(canvas);
canvas.restore();
}
I just used the simple line in my code and it works :
myCusstomView.setRotation(45);
Hope it works for you.
One line in XML
<View
android:rotation="45"
... />
Applying a rotation animation (without duration, thus no animation effect) is a simpler solution than either calling View.setRotation() or override View.onDraw method.
// substitude deltaDegrees for whatever you want
RotateAnimation rotate = new RotateAnimation(0f, deltaDegrees,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
// prevents View from restoring to original direction.
rotate.setFillAfter(true);
someButton.startAnimation(rotate);
Rotating view with rotate() will not affect your view's measured size. As result, rotated view be clipped or not fit into the parent layout. This library fixes it though:
https://github.com/rongi/rotate-layout
Joininig #Rudi's and #Pete's answers. I have created an RotateAnimation that keeps buttons functionality also after rotation.
setRotation() method preserves buttons functionality.
Code Sample:
Animation an = new RotateAnimation(0.0f, 180.0f, mainLayout.getWidth()/2, mainLayout.getHeight()/2);
an.setDuration(1000);
an.setRepeatCount(0);
an.setFillAfter(false); // DO NOT keep rotation after animation
an.setFillEnabled(true); // Make smooth ending of Animation
an.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {}
#Override
public void onAnimationRepeat(Animation animation) {}
#Override
public void onAnimationEnd(Animation animation) {
mainLayout.setRotation(180.0f); // Make instant rotation when Animation is finished
}
});
mainLayout.startAnimation(an);
mainLayout is a (LinearLayout) field
As mentioned before, the easiest way it to use rotation available since API 11:
android:rotation="90" // in XML layout
view.rotation = 90f // programatically
You can also change pivot of rotation, which is by default set to center of the view. This needs to be changed programatically:
// top left
view.pivotX = 0f
view.pivotY = 0f
// bottom right
view.pivotX = width.toFloat()
view.pivotY = height.toFloat()
...
In Activity's onCreate() or Fragment's onCreateView(...) width and height are equal to 0, because the view wasn't measured yet. You can access it simply by using doOnPreDraw extension from Android KTX, i.e.:
view.apply {
doOnPreDraw {
pivotX = width.toFloat()
pivotY = height.toFloat()
}
}
if you wish to make it dynamically with an animation:
view.animate()
.rotation(180)
.start();
THATS IT
#Ichorus's answer is correct for views, but if you want to draw rotated rectangles or text, you can do the following in your onDraw (or onDispatchDraw) callback for your view:
(note that theta is the angle from the x axis of the desired rotation, pivot is the Point that represents the point around which we want the rectangle to rotate, and horizontalRect is the rect's position "before" it was rotated)
canvas.save();
canvas.rotate(theta, pivot.x, pivot.y);
canvas.drawRect(horizontalRect, paint);
canvas.restore();
fun rotateArrow(view: View): Boolean {
return if (view.rotation == 0F) {
view.animate().setDuration(200).rotation(180F)
true
} else {
view.animate().setDuration(200).rotation(0F)
false
}
}
That's simple,
in Java
your_component.setRotation(15);
or
your_component.setRotation(295.18f);
in XML
<Button android:rotation="15" />

Categories

Resources