Can someone please explain to a noob the correct way to animate a View so its touch area and image actually move together?!
I have read lots of posts and questions and tutorials, but none explains what moves the layout and what moves the image such that I can animate a view and then leave it at its new position.
This is an example method I'm working with, trying lots of different combinations to no success. The view is in the parent RelativeLayout. It's a touchable menu of icons, and is animated with an xml resource on a click to slide off screen leaving just a little tab showing, where it needs to stay until clicked again.
public void RetractTools (View v){
final ImageView finalImage1 = (ImageView) findViewById(R.id.paintsView);
Animation slideout = AnimationUtils.loadAnimation(this, R.anim.slideout_tools);
slideout.setFillAfter(true);
slideout.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationEnd(Animation animation) {
finalImage1.setEnabled(true);
optionMenu.showing = false;
optionMenu.inMotion = false;
finalImage1.layout(1258, 668, 1697, 752);
finalImage1.setRight(1280);
finalImage1.invalidate();
}
#Override
public void onAnimationRepeat(Animation arg0) {
}
#Override
public void onAnimationStart(Animation arg0) {
finalImage1.setEnabled(false);
}
});
optionMenu.inMotion = true;
v.startAnimation(slideout);
}// End RetractMenu
No matter what I try, I encounter problems. setFillAfter does nothing when set in the xml file. Set programmatically, it leaves the image in the right place but the touch controls remain where the menu was. I have tried setLeft and setRight which apparently only move the image, not the view position, and all sorts of different layout options, and fill and no fill and invalidating and not, but can't solve it. I clearly don't undersatnd the underlying mechanics needed to position and render a view! :D
Thanks.
EDIT : Solution
For anyone having similar issues, this is how I have found to work with relative layouts. You create a LayoutParams object with the specified size, and then you can assign it positions. eg.
final RelativeLayout.LayoutParams position = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
position.leftMargin = 440;
Then assign that to your view
myView.setLayoutParams(position);
So in summary, you use a LayoutParams object as an interface to your view's position, rather than accessing the view's coordinates directly as I assumed.
What you have is basically fine, with two flaws:
You are using setFillAfter(), which is not especially useful
You are calling layout() and setRight() and stuff, which is not especially effective
Instead, in onAnimationEnd(), you need to modify the LayoutParams of the View to reflect the new position you want the widget to be in. The size and position of a widget is dictated by the layout rules it negotiates with its container. Initially, those are set via your layout XML resource. By modifying the LayoutParams at runtime, you are changing what those rules are.
What those LayoutParams are (LinearLayout.LayoutParams, RelativeLayout.LayoutParams, etc.) and what values you should specify in them, we cannot tell you, because we don't know what you are doing.
Related
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 try to do a menu with a hide button. When I press the hide button, i do objectAnimator= ObjectAnimator.ofFloat(myLayout, "translationY"... to move the layout up.
My problem is when I move all the layout up, it didn't grow, it only move up with his fixed size. I don't know how to refresh the size of the layout to match_parent so my 2nd layout will take all the place.
Thanks
Kick off example:
ObjectAnimator anim = ObjectAnimator.ofFloat(myLayout, "translationY"...);
anim.addListener(new AnimatorListener() {
#Override
public void onAnimationEnd(Animator animation) {
((RelativeLayout.LayoutParams) view.getLayoutParams).width = ViewGroup.LayoutParams.MATCH_PARENT;
}
});
I think you can't achive this effect using only translation. Translating object still keep its bounds in layout (you can see it when you turn on Developer options -> Show layout bounds on your device). It's just drawing in a different place.
I don't know what you exactly want to do, so if that tip is not enough, try to give more details. For now I can only propose animating one of your views' height.
On my activity's creation I have set an AlphaAnimation in order to perform some visual effects. Now I have set a LayoutAnimationListener and overriden the onAnimationEnd() method. The Animation is executed correctly and the callback to onAnimationEnd() is working as well. Within the onAnimationEnd() method I want to perform another animation on two child objects of the Activity's layout. These are two EditTexts that are defined in XML with the property android:visibility="gone". For starters, what I want to do is to have them set as VISIBLE.
I've tried getting a reference from their parent, change their setVisibility(View.VISIBLE); and in the end invalidate the parent view.
This attempt does not seem to work. You will find the code attached below:
#Override
public void onAnimationEnd(Animation animation) {
Log.d(TAG, "onAnimationEnd()");
// TODO move icon up, make edittext's appear.
RelativeLayout layout = (RelativeLayout) View.inflate(
getApplicationContext(), R.layout.splash_dialog_layout,
null);
LinearLayout linear = (LinearLayout) layout.getChildAt(0);
Log.d(TAG, "Children" + linear.getChildCount());
email = (EditText) linear.findViewById(R.id.splash_Email);
email.setVisibility(View.VISIBLE);
pass = (EditText) linear.findViewById(R.id.splash_password);
pass.setVisibility(View.VISIBLE);
linear.invalidate();
layout.invalidate();
Splash_Activity.layout.invalidate();
}
};
I've tried to removeAllViews from a parent and add them one by one and invalidate again but that doesn't seem to work either.
There is probably a misunderstanding on my behalf as to how view inflation operates, shouldn't this work?
Thanks for your time.
Inflating a layout creates a new view hierarchy, which means the views you're obtaining are actually different from the ones displayed on screen even though they have the same IDs.
You can obtain on-screen views in several ways, but the easiest would probably be to query the containing Activity.
Activity.this.findViewById(R.id.splash_Email);
I've tried several ways of hiding a view and then removing it from the parent layout:
Call an alpha fade animation followed by a call to setVisibility(GONE);
Call an alpha fade animation followed by a call to setVisibility(GONE) inside of the AnimationListener
Call an alpha fade animation followed by removing the parent layout inside of the AnimationListener.
Each time, the resulting animation fails --- the view disappears twice from the screen. The alpha fade animation works fine but when you change the visibility or remove it from the parent view, it quickly reappears again before disappearing a second time. The result is an unexpected jittery animation.
Example code:
Animation animation = AnimationUtils.loadAnimation(AddTaskActivity.this,
R.anim.fade_out);
final LinearLayout parentView = (LinearLayout) findViewById(R.id.addtask_root);
animation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationEnd(Animation animation) {
parentView.post(new Runnable() {
public void run() {
parentView.removeView(test);
}
});
}
That animation fails---the test view disappears twice from view.
Any ideas?
I guess that if you transparent your view before remove it or change it's visibility,you can do what you want:
#Override
public void onAnimationEnd(Animation animation) {
parentView.post(new Runnable() {
public void run() {
// transparent your view
...
parentView.removeView(test);
}
});
Edit:
I saw android documentation in about animations and it says:
Another disadvantage of the view animation system is that it only
modified where the View was drawn, and not the actual View itself. For
instance, if you animated a button to move across the screen, the
button draws correctly, but the actual location where you can click
the button does not change, so you have to implement your own logic to
handle this.
With the property animation system, these constraints are completely
removed, and you can animate any property of any object (Views and
non-Views) and the object itself is actually modified. The property
animation system is also more robust in the way it carries out
animation. At a high level, you assign animators to the properties
that you want to animate, such as color, position, or size and can
define aspects of the animation such as interpolation and
synchronization of multiple animators.
So I guess that you have to use property animation.
I am trying to create a view that slides up from the bottom of the screen. I tried setting the initial position of the view (which should be offscreen) in xml, but instead of placing the imageview where I specified, it truncated it. My second thought was to set the position of the view programatically inside the onWindowFocusChanged method. Here's my code
#Override
public void onWindowFocusChanged(boolean hasFocus) {
if(hasFocus) {
slide_dock.layout(slide_dock.getLeft(), phone_height - 70, slide_dock.getRight(), phone_height + 230);
}
}
The problem is that this only works SOME of the time. I've been debugging it, and I believe the issue is that the layout values of slide_dock get altered after my onWindowFocusChanged function completes, I'm just not sure where. Can anyone here help me out? Or link me to somewhere that explains the layout process in-depth? I've been searching around to no avail.
Have you tried using the Animation framework? Use a RelativeLayout and align your child view to the bottom of the parent. Then use the following animation, maybe showing and hiding your view appropriately with View.setVisibility(int)
View myView = View(this);
TranslateAnimation slideUp = new TranslateAnimation(myView.getHeight(), 0, 0, 0);
slideUp.setDuration(250); // millis
slideUp.setFillAfter(true); // Required for animation to "stick" when done
myView.startAnimation(slideUp);
You might have to play with the TranslateAnimation constructor parameters to get it to work right (this is from the top of my head).
So I figured out the cause for the issue above, and I'm posting it here in case anyone ever runs into the same problem.
The reason why the ImageView was resizing was because in ImageView's onMeasure function, it resizes itself if it doesn't think that it will fit onto the screen. You can view the ImageView source here and see how it works: http://www.google.com/codesearch/p?hl=en#uX1GffpyOZk/core/java/android/widget/ImageView.java&d=3
To work around this, I created a custom view that extended ImageView and overrode the onMeasure method. In my new onMeasure method, I simply called setDimension to give my new view the dimensions that I wanted it to have, this effectively stopped the view from resizing as it was doing earlier
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec), 300);
}