I'm trying to get an image to move across the screen using the fling animation (https://developer.android.com/guide/topics/graphics/fling-animation). The activity is using a constraint layout and only two views are on it - the button and the imageview. When the ball starts to move up the y-axis, it eventually disappears. Here is a GIF that shows the problem https://gyazo.com/cc822247117485beb8d0aa8316072b00
Code for moving ball:
//Button code
public void start(View v) {
FlingAnimation fling = new FlingAnimation(ball, DynamicAnimation.SCROLL_Y);
fling.setMinValue(0)
.setFriction(1.1f)
.setStartVelocity(1000);
fling.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {
#Override
public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) {
ball.setX(ball.getX() + 4);
}
});
fling.addEndListener(new DynamicAnimation.OnAnimationEndListener() {
#Override
public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, float velocity) {
Toast t = Toast.makeText(getApplication(), "ended", Toast.LENGTH_SHORT);
t.show();
}
});
fling.start();
}
This is a bit confusing indeed, but the reason is that DynamicAnimation.SCROLL_Y is just scrolling your view in the bounds of it position, so it just disappearing in the end. You can see this if you will write something like:
fling.addEndListener(new DynamicAnimation.OnAnimationEndListener() {
#Override
public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, float velocity) {
Toast t = Toast.makeText(getApplication(), "ended", Toast.LENGTH_SHORT);
t.show();
v.setScrollY(0);
}
});
You will see the view pop back to it original position.
The solution is quite simple: Don't use DynamicAnimation.SCROLL_Y, instead use: DynamicAnimation.Y.
Related
I have an "arrow" image on the right edge of the screen, pointing right.
It is a part of a bigger image hidden right, out of the screen.
By cliking on this arrow, i'd like it to point and slide to the left dragging within the screen the rest of the image that was hidden.
If I can, I'd also like to set the sliding speed of the sliding image.
Something like this:
Is it possible?
To answer your question, Yes, it is possible.
Hopefully you are using a fixed width for your item in which case you can just get it in onCreate() and initiate your ValueAnimator. If not you will need to wait for the layout to be drawn and get your width onGlobalLayout callback.
public void onCreate(){
super.onCreate(..)
...
init();
...
}
like so:
private void init(){
mButtonView = findViewById(R.id.button_view);
ImageView arrowButton = mButtonView.findViewById(R.id.arrow_image_container);
arrowButton.setOnClickListener(this);
int visibleLength = arrowButton.getWidth();
int totalLength = mButtonView.getWidth();
int translationDistance = totalLength - visibleLength;
mButtonTranslator = ValueAnimator.ofInt(0,translationDistance);
mButtonTranslator.addUpdateListener((animatedValue)->{
float distanceTraveled = (float)animatedValue.getAnimatedValue();
mButtonView.setTranslationX(distanceTraveled);
});
}
Implement View.OnClickListener and add a toggle method:
#Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.R.id.arrow_image_container:
toggleButton();
break;
}
}
Have a boolean member (mIsButtonVisible) to hold visibility state:
private void toggleButton(){
if (mIsButtonVisible){
hideButton(true);
} else {
showButton(true);
}
}
show:
private void showButton(boolean animate){
if (!mIsButtonVisible){
mButtonTranslator.setDuration(animate ? ANIMATION_DURATION_IN_MILLIS : 0);
mButtonTranslator.start();
mIsButtonVisible = true;
}
}
and hide:
private void hideButton(boolean animate){
if (mIsButtonVisible){
mButtonTranslator.setDuration(animate ? ANIMATION_DURATION_IN_MILLIS : 0);
mButtonTranslator.reverse();
mIsButtonVisible = false;
}
}
Once initialisation is done and you have your animator initialised you can hide the button off the screen in onViewCreated():
hideButton(false);
I have a layout that has several cardView, that have recyclerViews in them
in the beginning the first recyclerview is empty, so the card doesn't show.
later on, the recyclerview gets populated, so I want it to have a nice animation to it.
I'm trying to move the other cards down, and then fade in the card.
to do that I set the database of the recyclerView, and then I attach a OnPreDrawListener to the cardview around it, so I can get the height of the view, then I set the view to GONE and run a transationY animation on the card below it.
Thing is that when I call getMeasuredHeight I get 0.
It's almost as the notifyDatasetChanged() only happens in the next frame, so the view didn't get it's new height yet.
here is my code:
private void runSearchAnimation(List<Route> searchResult) {
if(rvSearchResults.getMeasuredHeight() == 0) {
Log.i(TAG, "height is 0");
resultsAdapter.setDatabase(searchResult);
cvSearchResults.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
cvSearchResults.getViewTreeObserver().removeOnPreDrawListener(this);
Log.i(TAG, "cvSearchResults.getMeasuredHeight() = " + cvSearchResults.getMeasuredHeight());
cvSearchResults.setVisibility(View.GONE);
ObjectAnimator contentAnim = ObjectAnimator.ofFloat(cvRecentSearches,
"translationY", cvRecentSearches.getTranslationY(), cvSearchResults.getMeasuredHeight());
contentAnim.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(Animator animation) {
Log.i(TAG, "animation started");
}
#Override
public void onAnimationEnd(Animator animation) {
Log.i(TAG, "animation ended");
cvSearchResults.setVisibility(View.VISIBLE);
}
});
contentAnim.setDuration(300);
contentAnim.start();
return true;
}
});
}
}
Does anyone have an idea on how to solve this?
I believe that RecyclerView supports such animations via RecyclerView.ItemAnimator.
See the docs.
so after some thinking I came up with this easy solution:
cvSearchResults.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
if(cvSearchResults.getMeasuredHeight() != 0) {
cvSearchResults.getViewTreeObserver().removeOnPreDrawListener(this);
Since I will keep getting my onPreDraw method called until I will remove the listener, I remove it only when I know the view has been measured with the recyclerview data I gave it
I'm building an app and i wanted to animate some imageView element. The problem is that at the end of the animation, the element gets an unexpected boost in the y axis, instead the defined one.
I know that at the end of the animation process the animated element returns to its original x,y. I manually added the animated offset at the onAnimationEnd method. For some odd reason the animated element jumps about 40 pixels up, and I know I didnt add it anywhere by mistake. Whats wrong here?
public class MyActivity extends Activity {
private ImageView logo,moto;
private int[] imageView_XY;
public float logoXpos,logoYpos;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.verification_activity);
logo = (ImageView) findViewById(R.id.imageView1);
//animates the logo
animate(logo,30);
}
public void animate(final ImageView imageView, int amount){
//gets the current x,y of the widget
imageView_XY = new int[2];
imageViewElement.getLocationInWindow(imageView_XY);
logoXpos = (int) imageViewElement.getX();
logoYpos = imageView_XY[1];
Log.d("Animation XY: "," current location "+"("+logoXpos+","+logoYpos+")"); //prints out 0,0
//animates the widget 100 px up
TranslateAnimation anim = new TranslateAnimation(0,0,0,-amount);
logoAnim.setDuration(1000);
logoAnim.setFillAfter(false);
logoAnim.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
Log.d("Animation: ","animation ended");
logoXpos = imageView_XY[0];
logoYpos = imageView_XY[1];
Log.d("Animation ended XY: "," current location "+"("+logoXpos+","+logoYpos+")"); //prints out 0,0 again
imageViewElement.setY(logoYpos+30);
}
#Override
public void onAnimationRepeat(Animation animation) {
Log.d("Animation: ","animation repeat");
}
});
imageViewElement.startAnimation(anim);
}
OK I got this working. For future googlers:
I ended up setting the logoAnim.setFillAfter(false); to true and got rid of the manual shift at the end of the animation imageViewElement.setY(logoYpos+30);.
Im trying to make an Imageview (a ball) move around the layout and bounce a number of times before stoping when a buttom is pressed. The probles is that although the logcat says its happening, i dont see it moving.
Here it is
public class BallPhisics {
int x =400;
int y = 0;
boolean bounceX = false;
boolean bounceY= false;
int counter =0;
ImageView object;
public BallPhisics(ImageView i){
object=i;
}
public void applyMovement() {
while (true) {
object.setLeft((int) object.getX()+x); //i know i shouldnt use pixels
Log.d("EVENT", "X moved"); Log.d("Ended",Integer.toString(object.getLeft()));
object.setBottom((int)(object.getY() + y));
Log.d("EVENT", "Y moved");
try {
Thread.sleep(1000);
Log.d("EVENT", "Time 1 used");
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
if (object.getX()<=50||(object.getRight()<=50)){
bounceX =true;
break;
}
if (object.getY()<=50||object.getTop()<=50){
bounceY=true;
break;
}
}
this.bouncing();
}
public void bouncing(){
Log.d("EVENT", "Bouncing!!");
if (bounceX&&bounceY){
x=-x;
y=-y;
}
else if (bounceX){
x=-x;
y=(int)(Math.random()*100- 50 +y);
}
else if (bounceY) {
x = (int) (Math.random() * 100 - 50 + x);
y = -y;
}
counter++;
if(counter==5){return;}
this.applyMovement();
}
And on mainActivity the onclick event.
public void StartBall (View view){
ImageView imageview=(ImageView) findViewById(R.id.imageView);
BallPhisics phisics = new BallPhisics(imageview);
Log.d("EVENT", Integer.toString(imageview.getLeft() )+" before");
phisics.applyMovement();
Log.d("EVENT",Integer.toString(imageview.getLeft())+" after" );
}
Sorry if it is a lot of reading. By the way does anyone knows the proper way of moving a view?
Thanks in advance
First of all to move a View it's probably not a good idea to use setLeft (int left) or setBottom (int bottom) because this method is meant to be called by the layout system and should not generally be called otherwise, according to the documentation.
To move a View dynamically you should take a look at LayoutParams or setX (float x) and setY (float Y), if you can limit your support to Honeycomb (API Level 11).
Regardless which one you use, you may find it difficult to achieve a smooth movement. Therefore I recommend you to use the view animation system to perform tweened animation of your ImageView.
Below you will find an example of a chain animation that moves an ImageView starting from left to right with an arbitrary y-coordinate. After each translation from one x-border to the next, the onAnimationEnd will be called and start an animation in the other direction.
public class MainActivity extends Activity {
//the ImageView you want to animate
private ImageView imageview;
private Animation mAnimation;
//The coordinates the view moved to in the last animation
private float lastX=0;
private float lastY=0;
private float secondlastY;
//Listener that implements a translate animation chain
AnimationListener animationListener=new AnimationListener() {
#Override
public void onAnimationEnd(Animation animation) {
float newY=(float)(Math.random()*0.75);
//this prevents that we move back to the position we came from
// to get a more natural bounce animation
while(newY<=secondlastY+0.15 && newY>=secondlastY-0.15){
newY=(float)(Math.random()*0.75);
}
if(lastX==0.75f){
//test if we are on the right border of the parent
mAnimation=newAnimation(lastX,lastY,0f,newY);
mAnimation.setAnimationListener(animationListener);
lastX=0f;
}else if(lastX==0.0f){
//test if we are on the left border of the parent
mAnimation=newAnimation(lastX,lastY,0.75f,newY);
mAnimation.setAnimationListener(animationListener);
lastX=0.75f;
}
secondlastY=lastY;
lastY=newY;
imageview.startAnimation(mAnimation);
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageview=(ImageView) findViewById(R.id.imageView);
//the coordinates the first animation should move to
lastY=(float)(Math.random()*0.75);
lastX=0.75f;
mAnimation=newAnimation(0f,0f,lastX,lastY);
mAnimation.setAnimationListener(animationListener);
imageview.startAnimation(mAnimation);
}
//Method that returns a new animation with given start and end coordinates
private Animation newAnimation(float startX, float startY, float endX, float endY){
Animation mAnimation = new TranslateAnimation(
TranslateAnimation.RELATIVE_TO_PARENT, startX,
TranslateAnimation.RELATIVE_TO_PARENT, endX,
TranslateAnimation.RELATIVE_TO_PARENT, startY,
TranslateAnimation.RELATIVE_TO_PARENT, endY );
mAnimation.setDuration(2500);
mAnimation.setRepeatCount(0);
mAnimation.setRepeatMode(Animation.REVERSE);
mAnimation.setFillAfter(true);
return mAnimation;
}
}
Note:
The translation animation is relative to the parents width and height. If you move the View all the way to x=1.0 or y=1.0 you will move parts of the view out of the parent layout. Because it's sufficient for this example I chose to set 0.75 to the max position in either direction. But you should probably set this dynamically in regards to the width and height of your ImageView and ParentLayout.
I would like to apply successive animations (say ScaleAnimation) to an ImageView showing a resource image. The animation is triggered by a button. For example, I would like to incrementally enlarge an image upon each button click.
I've set fillAfter="true" on the animation. However, all the animations start from the original state of the ImageView. It seems as if the ImageView resets its state and the animation is always the same, instead of starting from the final state of the previous animation.
What am I doing wrong?
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button button = (Button)findViewById(R.id.Button01);
button.setOnClickListener(new OnClickListener() {
public void onClick(View arg0) {
animate();
}});
}
private void animate() {
ImageView imageView = (ImageView) findViewById(R.id.ImageView01);
ScaleAnimation scale = new ScaleAnimation((float)1.0, (float)1.5, (float)1.0, (float)1.5);
scale.setFillAfter(true);
scale.setDuration(500);
imageView.startAnimation(scale);
}
It seems as if the ImageView resets
its state and the animation is always
the same, instead of starting from the
final state of the previous animation.
Precisely! I'm sure there's a use for fillAfter="true", but I haven't figured out the point for it yet.
What you need to do is set up an AnimationListener on each Animation of relevance, and do something in the listener's onAnimationEnd() to actually persist the end state of your animation. I haven't played with ScaleAnimation so I'm not quite sure what the way to "persist the end state" would be. If this were an AlphaAnimation, going from 1.0 to 0.0, you would make the widget INVISIBLE or GONE in onAnimationEnd(), for example.
I've had the same problem and created the following code to easily use different animations. It only supports translation and alpha levels for now as I haven't used scaling, but could easily be extended to support more features.
I reset the scroll and the visibility before starting the animation, but that's just because I needed on/off animations.
And the "doEnd" boolean is there to avoid a stack overflow on the recursion (scrollTo calls onAnimationEnd for some obscure reason...)
private void setViewPos(View view, Animation anim, long time){
// Get the transformation
Transformation trans = new Transformation();
anim.getTransformation(time, trans);
// Get the matrix values
float[] values = new float[9];
Matrix m = trans.getMatrix();
m.getValues(values);
// Get the position and apply the scroll
final float x = values[Matrix.MTRANS_X];
final float y = values[Matrix.MTRANS_Y];
view.scrollTo(-(int)x, -(int)y);
// Show/hide depending on final alpha level
if (trans.getAlpha() > 0.5){
view.setVisibility(VISIBLE);
} else {
view.setVisibility(INVISIBLE);
}
}
private void applyAnimation(final View view, final Animation anim){
view.scrollTo(0, 0);
view.setVisibility(VISIBLE);
anim.setAnimationListener(new AnimationListener(){
private boolean doEnd = true;
#Override
public void onAnimationEnd(Animation animation) {
if (doEnd){
doEnd = false;
setViewPos(view, animation, anim.getStartTime() + anim.getDuration());
}
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override
public void onAnimationStart(Animation animation) {
}
});
view.startAnimation(anim);
}