probably another rhetorical question.
In iOS when we set a view's (any any UIView subclass such as UIButton) alpha to 0, iOS by default disables all user interaction on that view.
I got an Android app where I animate fade out the view by:
ObjectAnimator fadeOut = ObjectAnimator.ofFloat(buttonSelectionContainer, "alpha", 1, 0);
fadeOut.setDuration(500);
fadeOut.start();
However, I am noticing that when I tap the screen, the animation starts again, leading me to believe, in Android, even when a button alpha is set to 0, it is still tappable, is this true?
Is there a way to globally tell Android to disable user interaction for a view (and all its subviews) when its alpha is set to 0, either explicitly through using:
view.setAlpha(0.0f);
or through the ObjectAnimator like the above code block I used ?
A temporary work around for my problem would probably be to schedule this code to run after 500 ms:
// psuedocode: after 500ms
dispatch_doSomethingAfter(500)
{
myButton.setEnabled(false);
}
Not the ideal solution but might be my only solution, unless some bright Android developers out there has a better solution ?
Use addListener on your ObjectAnimator to control what happens after the animation has finished.
fadeOut.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
button.setEnabled(false);
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
You can create an Animator.AnimatorListener that automatically disables the target View when the animation ends.
Declare your custom DisableViewOnEndAnimatorListener class:
public class DisableViewOnEndAnimatorListener extends AnimatorListenerAdapter {
#Override
public void onAnimationEnd(Animator animation) {
if (animation instanceof ObjectAnimator) {
final Object target = ((ObjectAnimator) animation).getTarget();
if (target instanceof View) {
((View) target).setEnabled(false);
}
}
}
}
Then, in your code:
DisableViewOnEndAnimatorListener endAnimatorListener = new DisableViewOnEndAnimatorListener();
ObjectAnimator button1FadeOut = ObjectAnimator.ofFloat(button1, "alpha", 1, 0);
button1FadeOut.setDuration(500);
button1FadeOut.addListener(endAnimatorListener);
button1FadeOut.start();
ObjectAnimator button2FadeOut = ObjectAnimator.ofFloat(button2, "alpha", 1, 0);
button2FadeOut.setDuration(500);
button2FadeOut.addListener(endAnimatorListener);
button2FadeOut.start();
Related
My app plays a few seconds wav song in a loop through MediaPlayer.setLooping(true), and I want an indicator (a simple 3dp width view) that moves on the screen showing the progress, starting over at the left when the song restarts in the loop.
One way of doing this is through a HandlerTask like this:
mHandlerTask = new Runnable() {
#Override
public void run() {
if (mms != null && mms.isPlaying()) {
playprogress.setTranslationX(playprogresswidth * mms.getCurrentPosition() / duration);
}
mHandler.postDelayed(this, 500);
}
This works, but the movement is not continuous, with jumps every 500ms. Of course I can choose 50, or 5, or 1, instead 500, but then that should be quite heavy and a battery drainer.
So I implemented a ValueAnimator, like this:
ValueAnimator animation = ObjectAnimator.ofFloat(playprogress, "translationX", playprogresswidth);
animation.setRepeatCount(ValueAnimator.INFINITE);
animation.setRepeatMode(ValueAnimator.RESTART);
animation.setDuration(duration);
animation.setInterpolator(new LinearInterpolator());
animation.start();
This works, except for the fact that the MediaPlayer and the animation are not perfectly synced, and go out of sync more and more as the loop restarts. And this actually makes sense: the MediaPlayer needs some extra time for the restart.
So how I can sync both? I could not find any listener in MediaPlayer to trigger a single animation for each loop. I tried with an animation.addListener(new Animator.AnimatorListener() {...}) to try to fiddle in the duration without much luck. This is the best I got so far, with a non-repeat animation (mms is the MediaPlayer):
ValueAnimator animation = ObjectAnimator.ofFloat(playprogress, "translationX", playprogresswidth);
animation.setDuration(duration+150);
animation.setInterpolator(new LinearInterpolator());
animation.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {}
#Override
public void onAnimationEnd(Animator animation) {
if (mms != null && mms.isPlaying()) {
animation.setDuration(duration - mms.getCurrentPosition() + 150);
animation.start();
}
}
#Override
public void onAnimationCancel(Animator animation) {}
#Override
public void onAnimationRepeat(Animator animation) {}
});
animation.start();
The problem with this approach is that 150: it should be device independent.
EDIT
I found a much better solution, although probably not the best one yet:
playprogress.setTranslationX(0);
ValueAnimator animation = ObjectAnimator.ofFloat(playprogress, "translationX", playprogresswidth);
animation.setInterpolator(new LinearInterpolator());
animation.setDuration(duration);
animation.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
if (mms != null && mms.isPlaying()) {
animation.setStartDelay(duration-mms.getCurrentPosition());
animation.start();
}
}
});
animation.start();
I created two short methods to help me to show or hide a view when a certain checkbox is checked.
I have a Init method, in which I initialize a checkbox and a view, and a toggle method that is called from inside the checkbox status change listener and toggle the view status with an animation.
void toggleViewVisibility(final boolean b, final View v) {
v.setAlpha(b ? 0.0f : 1.0f);
v.setTranslationY(b ? v.getHeight() : 0);
if (b) {
v.setVisibility(View.VISIBLE);
v.animate().alpha(1.0f).translationY(0).setDuration(400);
} else {
v.animate().alpha(0.0f).translationY(v.getHeight()).setDuration(400).setListener(new Animator.AnimatorListener() {
#Override public void onAnimationStart(Animator animator) {}
#Override public void onAnimationEnd(Animator animator) { v.setVisibility(View.GONE); }
#Override public void onAnimationCancel(Animator animator) {}
#Override public void onAnimationRepeat(Animator animator) {}
});
}
}
void toggleViewVisibilityInit(final boolean b, final View v, final AnimateCheckBox c) {
v.setAlpha(b ? 1.0f : 0.0f);
v.setTranslationY(b ? 0 : v.getHeight());
v.setVisibility(b ? View.VISIBLE : View.GONE);
c.setChecked(b);
}
It works fine with the alpha animation, but has a small problem with the translation animation, although they are treated and initialized in the exact same way. Why?
In particular, the translation works fine whenever the checkbox status changes, but when the checkbox starts off, so the view is invisible, only at the VERY FIRST status change the view appears with alpha animation but does not perform the translation animation.
It looks like that when the view has been just created, it's translation status is not initialized, while alpha status is, although it is done in the toggleViewVisibilityInit() method.
Does anybody know why this should happen? It looks like as soon as the view is created the translation is not taken into consideration.
The view is not yet drawn so it's height is unknown (thus equals 0).
If you know this, it is easy to look for solutions, eg. getWidth() and getHeight() of View returns 0
Hope this helps!
I have a ViewPager which has 4 pages and I want it scroll automatically.
What I am doing is viewPager.setCurrentItem(index); bound with a TimerTask where index is a precomputed integer.
The logic works fine but when it scroll (triggered by setCurrentImte) the animation is too fast.
How can I control the scroll speed of it? Please note I am talking about the scrolling animation speed, instead of the interval of 2 successive scroll.
Why not use a ViewFlipper instead? A ViewFlipper has built in functionality to handle auto-scrolling between views, which means you can dump the timer. Also, it implements ViewAnimator which gives you full control over the animations. It is relatively simple to create custom animations, and you have control over the time it takes to animate. A simple tutorial for a slide animation can be found here. Take note of the duration attribute of the animation xml files.
Looks like ViewPager doesn't provide such an API. I would suggest to do any of the following:
Take VieWPager code (it's under Your android sdk extras folder) and twek it the way you need (e.g. add necessary API). Now it seems to use internal constant (MAX_SETTLE_DURATION);
Intercept touches by yourself and call fakeDragBy() (not sure if its what You're looking for);
Why not try something like this? Use a value animator to animate the scroll values, then update the positioning so the fragment loading is correctly handled. Works fine for me :).
if(pager != null){
ValueAnimator scrollAnimator = ValueAnimator.ofInt(pager.getScrollX(), pager.getScrollX()+2560);
final int virtualPage = pager.getCurrentItem();
scrollAnimator.setDuration(1000);
scrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
pager.setScrollX((Integer)animation.getAnimatedValue());
}
});
scrollAnimator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
pager.setCurrentVirtualItem(virtualPage, false);
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
scrollAnimator.start();
}
I have the same requirement on an app i am developing and my solution was this.
Observable.range(1, vpAdapter.getCount() - 1)
.concatMap(i-> Observable.just(i).delay(5000, TimeUnit.MILLISECONDS))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(integer -> {
currentAutoPage = integer;
binding.vpTour.beginFakeDrag();
lastFakeDrag = 0;
ValueAnimator va = ValueAnimator.ofInt(0, binding.vpTour.getWidth());
va.setDuration(1000);
va.addUpdateListener(animation -> {
if (binding.vpTour.isFakeDragging()) {
int animProgress = (Integer) animation.getAnimatedValue();
binding.vpTour.fakeDragBy(lastFakeDrag - animProgress);
lastFakeDrag = animProgress;
}
});
va.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (binding.vpTour.isFakeDragging()) {
binding.vpTour.endFakeDrag();
}
}
});
va.start();
}, Throwable::printStackTrace, () -> {
if (autoPageChangeDisposable != null && !autoPageChangeDisposable.isDisposed()) {
autoPageChangeDisposable.dispose();
}
});
In case you also want to stop the automatic swiping when user manually changes the page than just get the disposable from this code that i wrote, like this : "Disposable autoPageChangeDisposable = Observable.range(1, vpAdapter...." and than register and OnPageChangedListener and do this :
vpTour.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
if (position != currentAutoPage) {
if (autoPageChangeDisposable != null && !autoPageChangeDisposable.isDisposed()) {
autoPageChangeDisposable.dispose();
}
}
}
});
Am using an ObjectAnimator to translate the text in a text view similar to a stock ticker. I want to pause the ticker on press of a button. However, inspite of calling cancel()/end() on the animator object, the ticker continues to move till the end of the animation.
Have also tried explicitly clearing all animations on the underlying textView in the AnimatorListerner#onAnimCancel(). However the underlying textView doesn't seem to be associated with any animations.
Would be great if someone could point out as to what would be going wrong.
Relevant sections of code as below.
anim = ObjectAnimator.ofFloat(mTextViewTicker, "TranslationX", 0, -mSomeValue);
anim.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationCancel(Animator animation) {
Log.d(TAG,"Animation Pause invoked");
super.onAnimationCancel(animation);
}
public void onAnimationEnd(Animator animation) {
Log.d(TAG,"Animation Ended");
((View)((ObjectAnimator)animation).getTarget()).setTranslationX(anim.getAnimatedFraction());
}
});
anim.start();
public void stopScroll() {
anim.cancel();
}
stopScroll() is invoked from a button click in the activity.
I applied an infinite animation on an ImageView to indicate a running background thread in my app. When the thread finishes, I'm able to stop the animation by using clearAnimation(), but it snaps the ImageView back to its starting position, and I'd like the current animation cycle to complete (which is designed to gracefully end in its starting position). Is there a way to do this?
Register an AnimationListener, and wait until onAnimationRepeat() to clear it. I haven't tried this, but I think it will work.
Just call setRepeatCount(0) and then listen for onAnimationEnd. See here for more details.
You can set the desirable position of your animation in the onAnimationEnd() method of your animation listener. Then, instead of going to the initial position, the View will be displayed at the coordinates you set up there.
animation = new TranslateAnimation(0, -length, 0, 0); //setup here your translation parameters
animation.setInterpolator(new LinearInterpolator());
animation.setDuration(10);
animation.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {
animationRunning = true;
}
public void onAnimationRepeat(Animation animation) {
}
public void onAnimationEnd(Animation animation) {
animationRunning = false;
// setAnimationPositionAfterPositionChange();
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) runningText.getLayoutParams();
params.leftMargin = currentPosition; //Calculate you current position here
runningText.setLayoutParams(params);
}
});
runningText.setAnimation(animation);
If you are using animate()
you use setListener(null) to stop it the animation,
works for me.
image.animate()
.translationXBy(-width* 0.5f)
.setDuration(300)
.setStartDelay(6000)
.setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
image.animate().rotationBy(90).setDuration(1000).setStartDelay(1).setListener(null);
}
});