I have problem with my android animation. When I click the first time on my ImageView, my image have a rotation but when click the second time on my ImageView, rotation don't work. Why is it happened?
My rotation method in Java code:
private boolean tomIsInvisible = false;
public void eraseTom(View view) {
ImageView tom = findViewById(R.id.tom);
ImageView jerry = findViewById(R.id.jerry);
if (tomIsInvisible) {
tom.animate()
.rotation(3600)
.scaleX(1)
.scaleY(1)
.alpha(1)
.setDuration(3000);
jerry.animate()
.rotation(3600)
.scaleX(0)
.scaleY(0)
.alpha(0)
.setDuration(3000);
tomIsInvisible = false;
} else if(!tomIsInvisible) {
tom.animate()
.rotation(3600)
.scaleX(0)
.scaleY(0)
.alpha(0)
.setDuration(3000);
jerry.animate()
.rotation(3600)
.alpha(1)
.scaleX(1)
.scaleY(1)
.setDuration(3000);
tomIsInvisible = true;
}
}
Others methods on my ImageView work correctly.
When you call rotation(3600) first time it animates rotation from 0 to 3600. (default view rotation is 0). After first animation rotation is 3600 (ViewPropertyAnimator changes actual value of property while it animate things). So when you call rotation(3600) second time rotation of your view is already 3600 and you try to animate it to 3600. Thats why there is no visual changes. There is rotationBy method which apply specified value to view rotation, try to use it.
view.animate().rotationBy(3600)
Another solution is to animate from 0 to 360 and in the animation end reset view rotation to 0.
Related
So I am facing a weird bug I cannot explain - I cannot even reproduce it sometimes.
Basic context:
I have an application, which lists objects. Every object has a name and a point value. For every object, the addCustomSpinner function creates a "ticket" (a custom view, kind-of-spinner) and shows them in a scrollview so the user can select the one needed. There are four different 'containers' for four different kind of objects - so the layout can be populated with four kind of "ticket" package.
The data for the objects are collected from a database. The addCustomSpinner is called with a for cycle for every object in the database, and - Important - before the for method, the Layout it populates with the tickets is cleared (removeAllViews).
Inside addCustomSpinner, everything is created as "new" - like the button in question.
addCustomSpinner creates this button and adds a new onClickListener. Inside onClickListener, a new boolean is created - this is used to show a different animation when the button is clicked again. On first click (boolean = true), the arrow turns 180 degrees and faces upwards, on second click (boolean = false) the arrow turns 180 degrees and faces downwards. Works like a charm, until...
The bug I am facing:
Sometimes - as I already mentioned, not every time - if I click the button for one "ticket", then leave it 'opened' and click on an another one, and leave it 'opened' also, THEN I choose to populate the layout with a different kind of "ticket" package - The arrow faces upwards by default on every ticket in every package! Sometimes - again, just sometimes - with the same pattern I can turn it back, but it happens just "by accident".
I don't understand how the animation and state of the buttons can be connected, if every created ticket is new, every button is new, every onClickListener is new, and every boolean inside onClickListener is new. And if these are connected somehow, then why can that be that every behavior is "unique" for the buttons, nothing else shows any connection - even this is just a "sometimes" bug, a pretty rare one.
Can anybody help me why this happens?
What I tried:
Well, tried to trace the issue - but since it happens just by accident, I have no clue, I just searched if I can do anything else than the boolean to add different animation for the clicks. Sadly using ObjectAnimator is not a good solution for me - not the same result at least, since my animated arrow not only rotates, but it also changes its color. Shapeshifter seemed like a good idea to create animations easily, but now as I see it, maybe a simple rotation will be my ultimate solution.
Here's the code for the button:
customButton.setOnClickListener(new View.OnClickListener() {
boolean isCustomButtonClicked = true;
#Override
public void onClick(View v) {
if (isCustomButtonClicked) {
customButton.setImageResource(R.drawable.avd_anim_arrow_blue_back);
Drawable d = customButton.getDrawable();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (d instanceof AnimatedVectorDrawable) {
animArrowAnim = (AnimatedVectorDrawable) d;
animArrowAnim.start();
}
}
routeWhoClimbed.setVisibility(View.VISIBLE);
isCustomButtonClicked = false;
} else if (!isCustomButtonClicked) {
customButton.setImageResource(R.drawable.avd_anim_arrow_blue);
Drawable d = customButton.getDrawable();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (d instanceof AnimatedVectorDrawable) {
animArrowAnim = (AnimatedVectorDrawable) d;
animArrowAnim.start();
}
}
routeWhoClimbed.setVisibility(GONE);
isCustomButtonClicked = true;
}
}
});
EDIT:
The full addCustomSpinner():
private void addCustomSpinner(Routes mRouteItemToAdd, String placeName) {
//creating a new View for my custom layout created in xml
View customRoutesView = new View(this);
LinearLayout.LayoutParams customViewParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
customRoutesView.setLayoutParams(customViewParams);
customRoutesView = LayoutInflater.from(this).inflate(
R.layout.custom_view_layout, routeLayout, false
);
//Setting up the views inside the custom view
ImageView imageViewDiffImage = customRoutesView.findViewById(R.id.routeDiffImageView);
TextView textViewRouteName = customRoutesView.findViewById(R.id.routeNameTextView);
TextView textViewRouteDiff = customRoutesView.findViewById(R.id.routeDiffTextView);
ImageButton customButton = customRoutesView.findViewById(R.id.customButton);
RadioButton climberNameOne = customRoutesView.findViewById(R.id.climberNameOne);
RadioButton climberNameTwo = customRoutesView.findViewById(R.id.climberNameTwo);
Button climbedItButton = customRoutesView.findViewById(R.id.climbed_it_button);
RadioGroup climberNameRadioGroup = customRoutesView.findViewById(R.id.climberNameRadioGroup);
RadioGroup climbingStyleRadioGroup = customRoutesView.findViewById(R.id.styleNameRadioGroup);
RelativeLayout routeWhoClimbed = customRoutesView.findViewById(R.id.routeWhoClimbedRelativeLayout);
imageViewDiffImage.setImageResource(R.mipmap.muscle);
textViewRouteName.setText(mRouteItemToAdd.name);
textViewRouteDiff.setText("Difficulty: " + (int) mRouteItemToAdd.difficulty);
climberNameOne.setText(climberName1);
climberNameTwo.setText(climberName2);
routeWhoClimbed.setVisibility(GONE);
//Here comes the button with the animated image
customButton.setOnClickListener(new View.OnClickListener() {
boolean isCustomButtonClicked = true;
#Override
public void onClick(View v) {
if (isCustomButtonClicked) {
customButton.setImageResource(R.drawable.avd_anim_arrow_blue_back);
Drawable d = customButton.getDrawable();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (d instanceof AnimatedVectorDrawable) {
animArrowAnim = (AnimatedVectorDrawable) d;
animArrowAnim.start();
}
}
routeWhoClimbed.setVisibility(View.VISIBLE);
isCustomButtonClicked = false;
} else if (!isCustomButtonClicked) {
customButton.setImageResource(R.drawable.avd_anim_arrow_blue);
Drawable d = customButton.getDrawable();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (d instanceof AnimatedVectorDrawable) {
animArrowAnim = (AnimatedVectorDrawable) d;
animArrowAnim.start();
}
}
routeWhoClimbed.setVisibility(GONE);
isCustomButtonClicked = true;
}
}
});
//Button, works like an 'OK' or something, and I have no
//problem with this
climbedItButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int checkedNameButton = climberNameRadioGroup.getCheckedRadioButtonId();
int checkedStyleButton = climbingStyleRadioGroup.getCheckedRadioButtonId();
RadioButton checkedNameRadioButton = (RadioButton) findViewById(checkedNameButton);
RadioButton checkedStyleRadioButton = (RadioButton) findViewById(checkedStyleButton);
String checkedName = (String) checkedNameRadioButton.getText();
String checkedStyle = (String) checkedStyleRadioButton.getText();
addClimbToDatabase(user.getUid(), checkedName, mRouteItemToAdd, placeName, checkedStyle);
}
});
//And finally, I add this new "ticket" with the custom view to the layout i want to show it. Again, this also works like a charm, no problem here.
routeLayout.addView(customRoutesView);
}
Ultimately, I did not manage to understand the problem throughly, but I was able to eliminate it.
So during my fixing tries I narrowed down the problem to the animated drawable state - credit to #avalerio for his pro tip, but the answer wasn't addig an id to the button. I think somehow and sometime, the state of the first animation (turning the arrow 180 degrees) stuck in the end position - causing the other views using this animatedDrawable showing it in end position on start.
.reset() did not help, since it resets the animatedVectorDrawable object, not the animation xml drawable state. My solution is a kind of workaround, but it is working: when the custom-view 'ticket' is created with the animated-drawable-imagebutton, I set the imageResource of the button to a not-animated xml drawable - this drawable is basically the start position of my animated-drawable. This way, when the 'tickets' are generated, the imagebutton is 'hardcoded' in the start position.
Not elegant, but works. BUT(!) I would really appreciate if someone could explain to me how this weird behavior is possible - just sometimes, randomly, with no pattern I can reproduce intentionally.
I know how to do this for 2 images but what about 3 images.
public void fadeBardock(View view) {
ImageView bardock = (ImageView) findViewById(R.id.bardock);
ImageView goku = (ImageView) findViewById(R.id.goku);
ImageView gohan = (ImageView) findViewById(R.id.gohan);
bardock.animate().alpha(0 f).setDuration(3000);
goku.animate().alpha(0 f).setDuration(4000);
gohan.animate().alpha(1 f).setDuration(5000);
}
After clicking on button I'm directly getting the third image without getting the second image.
Try this code:
bardock.animate().alpha(0f).setDuration(3000);
goku.setAlpha(0f);
goku.animate().alpha(1f).setDuration(3000).setStartDelay(1500);
goku.animate().alpha(0f).setDuration(3000).setStartDelay(4500);
gohan.setAlpha(0f);
gohan.animate().alpha(1f).setDuration(3000).setStartDelay(6000);
You can change duration and start delays to achieve your optimal result.
On More than one imageviews,
1. Create an array of ImageView.
2. Initialize the views in it.
3. assign unique id's to each view in same loop.
[ Another thing is, you can initialize the views in one for loop in onCreate() and then on button click, apply another for loop for animation.]
Now, using an index of array, assign an animation to each view with delay from previous views.
Start animation after the loop and check.
Hope it helps.
I´m sorry that the following question is very specific but I´m looking for more than one hour for a solution of my problem: A sprite changes its color every 3 seconds. I can detect whether the user taps the right color but I cannot detect if the user is just waiting for the next color to score. I tried the following code:
private float timeSeconds = 0f;
private float period = 3f;
timeSeconds += Gdx.graphics.getRawDeltaTime();
for (int i=0;i<4;i++) {
if (executed == true && !(sprite[zahl[i]].getBoundingRectangle().contains(touchPoint.x,
touchPoint.y))&& timeSeconds==0 && zahl[4] != zahl[i]) {
timeSeconds = 0;
this.dispose();
game.setScreen(new GameOverScreen(game, Integer.parseInt(score)));
return;
}
}
This code works partly: If the user doesn´t touch the screen, then the GameOverScreen will be shown. But if the user tapps the right color (=sprite), then the GameOverScreen is also shown. However, I want the game to continue if the user tapps the right sprite, and I want to end the game, which means showing the GameOverScreen, if the user doesn´t tap anything on the screen within three seconds.
It's a bit hard to understand what you're asking but I think your aim is for the user to touch the screen at least once in 3 seconds. If the user fails to touch in this time then the game over screen is shown. If the user has touched the screen and the right colour sprite was touched add 3 seconds to touch time. Then if user has touched at least 1 right sprite and fails to touch in 3 seconds move to level complete screen.
Let's star with the GameOverScreen. This screen should take your game object, the score and a boolean to say if game ended or completed. Then you can call the game screen with:
GameOverScreen(game,score,true); // game completed or
GameOverScreen(game,score,false); // game over
Now we have the end screen completed we can add a timer in your game called lastTouched:
float lastTouched = 3f; // 3 seconds of touch time
Next we add a boolean value so we know if the user touched at least one of the right colour sprites:
boolean atLeastOneTouched = false;
Now we lower the timer each frame with:
lastTouched -= Gdx.graphics.getRawDeltaTime();
We check your sprite touch as usual without the timer stuff:
for (int i=0;i<4;i++) {
if (executed == true && !(sprite[zahl[i]].getBoundingRectangle().contains(touchPoint.x,
touchPoint.y)) && zahl[4] != zahl[i]) {
// touched right colour
atLeastOneTouched = true;
lastTouched = 3f; // reset timer
}
}
Lastly we check if the timer has run out and then get according screen based on the atLeastOneTouched boolean:
if(lastTouched <= 0){
if(atLeastOneTouched){
GameOverScreen(game,score,true); // game over complete
}else{
GameOverScreen(game,score,false); // game over failed
}
}
I am currently trying to do a pseudo-circular reveal that will work on Android API levels below Lollipop. So, I have a floating action button (FAB) that I want (when pressed) to move vertically into a RelativeLayout (before clicked it sits outside of the RelativeLayout) and then scale by a certain factor so that it looks like a reveal.
I thought that using .setClipBounds() on the FAB would limit the scale animation in the area of the RelativeLayout but unfortunately the scale animation expands to take over the whole Activity area. This is the code in the click listener of the FAB:
circleFab.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
int cardHeight = relativeCardBuild.getHeight();
int cardWidth = relativeCardBuild.getWidth();
Log.i("RELAT H", "" + cardHeight);
Log.i("RELAT W", "" + cardWidth);
circleFab
.animate()
.translationY(-(cardHeight / 2))
.setInterpolator(new AccelerateDecelerateInterpolator())
.setDuration(200).start();
tickFab.animate().alpha(0.0f).setDuration(200).start();
circleFab.setClipBounds(new Rect(relativeCardBuild.getLeft(),
relativeCardBuild.getTop(), relativeCardBuild
.getRight(), relativeCardBuild.getBottom()));
Log.i("left ", relativeCardBuild.getLeft()+"");
Log.i("top ", relativeCardBuild.getTop()+"");
Log.i("right ", relativeCardBuild.getRight()+"");
Log.i("bottom ", relativeCardBuild.getBottom()+"");
circleFab.animate().setStartDelay(100).scaleX(20.0f)
.scaleY(20.0f).setDuration(500).start();
}
});
The logged values of the relativeCardBuild's left, top, right and bottom points are correct so I am indeed setting the clip bounds of the circleFab layout (it's a FrameLayout that contains an oval drawable and a bitmap drawable) correctly. Still, the scale animation takes up the whole activity screen.
Am I completely misunderstanding what .setClipBounds() does in this instance?
I'm trying to implement an animation which crossfades between 2 layout, with a layover between them. The first layout is the main layout, the second is a simple linear layout with textView and red background (your classic "Wrong!" screen if you will...). My goal is to animate a quick transition between the mainLayout and wrongLayout, have the program wait for a short period of time while displaying the wrongLayout and then automatically return to mainLayout. This turns out to be extremelly hard, I've tried using monitors, Thread.sleep() etc. but what I get is that the program waits before starting the animation and THEN performs it without any layover.
My code is as follows:
In the main method-
LinearLayout wrongLayout = (LinearLayout) findViewById(R.id.wrong_layout);
RelativeLayout mainLayout = (RelativeLayout) findViewById(R.id.main_layout);
int animationDuration = getResources().getInteger(
android.R.integer.config_longAnimTime);
crossfade(wrongLayout, mainLayout, animationDuration);
/* This is where I want it to wait for 1 second */
crossfade(mainLayout, wrongLayout, animationDuration);
and the crossfade method-
private void crossfade(View fadeInLayout, final View fadeOutLayout,
int animationDuration) {
// Set the content view to 0% opacity but visible, so that it is visible
// (but fully transparent) during the animation.
fadeInLayout.setAlpha(0f);
fadeInLayout.setVisibility(View.VISIBLE);
// Animate the content view to 100% opacity, and clear any animation
// listener set on the view.
fadeInLayout.animate()
.alpha(1f)
.setDuration(animationDuration)
.setListener(null);
// Animate the loading view to 0% opacity. After the animation ends,
// set its visibility to GONE as an optimization step (it won't
// participate in layout passes, etc.)
fadeOutLayout.animate()
.alpha(0f)
.setDuration(animationDuration)
.setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
fadeOutLayout.setVisibility(View.GONE);
}
});
}
Thanks a lot...
Ok, so I've found this post which deals with just about the same problem-
Why the Sleep executes first and than the code above it in android?
I've changed the appropriate execution function and now it seems to work properly, though I'm still puzzled by the fact that I cannot cause the code to stop at a designated location for a specific amount of time. Oh well...