I wrote a little STT-functionality, with a floating button that is pulsating after being clicked on to notify that the app is listening. This works quite well so far with the one annoying behavior that my floating button does not return to its original size in some cases.
The animation increases and decreases the size of the button, and I guess it gets stuck in the increased state, hence the randomness of this behavior. I just can't figure out how to catch that and set the size to the original one.
Action Listener of my Button:
private View.OnTouchListener setVoiceButtonOnClick()
{
return new View.OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
if(!voiceButton.isInitialized())
voiceButton.initAnimationValues();
voiceButton.setPressed(true);
listen();
}
return true;
}
};
}
My Button extends FloatingActionButton, and does the following:
public class FloatingVoiceButton extends FloatingActionButton
{
public static final float DEFAULT_ANIMATION_FACTOR = 1.2f;
private boolean isInitialized = false;
private int originalHeight;
private int originalWidth;
private boolean isAnimationRunning;
private ObjectAnimator animator;
public FloatingVoiceButton(Context context)
{
super(context);
}
public void initAnimationValues()
{
isInitialized = true;
isAnimationRunning = false;
originalHeight = getMeasuredHeight();
originalWidth = getMeasuredWidth();
animator = ObjectAnimator.ofPropertyValuesHolder(
this,
PropertyValuesHolder.ofFloat("scaleX", DEFAULT_ANIMATION_FACTOR),
PropertyValuesHolder.ofFloat("scaleY", DEFAULT_ANIMATION_FACTOR));
animator.setDuration(200);
animator.setRepeatCount(ObjectAnimator.INFINITE);
animator.setRepeatMode(ObjectAnimator.REVERSE);
}
public boolean isInitialized()
{
return isInitialized;
}
public void resetButtonSize()
{
setMeasuredDimension(originalWidth, originalHeight);
}
public boolean isAnimationRunning()
{
return isAnimationRunning;
}
public void animate(boolean doAnimation)
{
isAnimationRunning = doAnimation;
if(doAnimation)
animator.start();
else
{
animator.end();
setPressed(false);
resetButtonSize();
//destroyDrawingCache(); tried these without success
//postInvalidate();
}
}
}
Finally I am controlling the button the start and end of the animation with my RecognitionListener:
public class InputVoiceRecognitionListener implements RecognitionListener
{
private EditText targetEditText;
private String originalContent;
private final String DELIMITER = "\n\n";
private FloatingVoiceButton button;
public InputVoiceRecognitionListener(EditText editText, FloatingVoiceButton button)
{
targetEditText = editText;
originalContent = editText.getText().toString();
this.button = button;
}
#Override
public void onReadyForSpeech(Bundle params)
{
button.animate(true);
}
#Override
public void onBeginningOfSpeech()
{
originalContent = targetEditText.getText().toString();
}
#Override
public void onRmsChanged(float rmsdB)
{}
#Override
public void onBufferReceived(byte[] buffer)
{}
#Override
public void onEndOfSpeech()
{
if(button.isAnimationRunning())
button.animate(false);
}
#Override
public void onError(int error)
{
if(button.isAnimationRunning())
button.animate(false);
}
#Override
public void onResults(Bundle results)
{
setRecognizedText(results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION));
}
#Override
public void onPartialResults(Bundle partialResults)
{
setRecognizedText(partialResults.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION));
}
#Override
public void onEvent(int eventType, Bundle params)
{
}
private void setRecognizedText(ArrayList<String> matches)
{
String result = "";
if(matches != null)
result = matches.get(0);
if((originalContent.trim()).length() > 0)
{
if(!originalContent.endsWith("\n\n"))
result = originalContent + DELIMITER + result;
else result = originalContent + result;
}
targetEditText.setText(result);
targetEditText.setSelection(result.length());
}
}
EDIT
This did it for me:
resettingAnimator = ObjectAnimator.ofPropertyValuesHolder(
this,
PropertyValuesHolder.ofFloat("scaleX", 1.0f),
PropertyValuesHolder.ofFloat("scaleY", 1.0f));
resettingAnimator.setDuration(0);
resettingAnimator.setRepeatCount(1);
and calling resettingAnimator.start(); when I finish my main animation.
Simple solution to this problem is that you define another animation after stopping your repeating one.
I just can't figure out how to catch that and set the size to the original one.
You, that is View, does know what is the "original" size, its the size of the scale factor 1f. So after stopping repeating animation just make another animations to set scale to 1f
PropertyValuesHolder.ofFloat("scaleX", 1f)
PropertyValuesHolder.ofFloat("scaleY", 1f))
This animation will run always, but will not be visible if your button is already at "normal" size.
With this in mind I would recommend that you use some other flag than isAnimationRunning(), either by some state (ex. selected) of your Fab, or some manually set arbitrary boolean.
I have 2 Activities , the Shared Element transition works fine.ChangeBounds is the only the transition applied.
I want to apply a fade transition while the shared element moves, so the ordering is ORDERING_TOGETHER.
public class TransitionUtils {
public static Transition makeSharedElementEnterTransition(final Context context, final long duration) {
TransitionSet set = new TransitionSet();
set.setOrdering(TransitionSet.ORDERING_TOGETHER);
set.setDuration(duration);
Transition changeBounds = new ChangeBounds();
changeBounds.addTarget(context.getString(R.string.transition_name_search_text));
set.addTransition(changeBounds);
Transition fade = new Fade(Fade.OUT);
fade.addTarget(context.getString(R.string.transition_name_search_text));
set.addTransition(fade);
return set;
}
}
The startActivity calls ActivityOptions.makeSceneTransitionAnimation
In the EndActivity , the enter shared element transition is set
public class EndActivity extends Activity{
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.blabla);
getWindow().setSharedElementEnterTransition(TransitionUtils.makeSharedElementEnterTransition(this,2000));
}
}
Notes : I noticed that
Fade() is often applied to getWindow().setEnterTransition()
setting a
duration to TransitionSet applies to all Transistions contained
except Fade.
How to apply a Fade Transition to a sharedElement ?
What am I doing wrong ?
android.transition.Fade uses TransitionAlpha , which cannot be resolved in my IDE. android.transition.Fade works for enter and exit transition between activities.
So I created my own Fade to use Alpha. An Android view's opacity is set by alpha. And shared element uses View.
You call it like this :
Transition fadeOut = new FadeTransition(1f, 0f, new LinearInterpolator());
fadeOut.addTarget(transitionName);
The full code is here
#TargetApi(21)
public class FadeTransition extends Transition {
private static final String PROPNAME_BACKGROUND = "android:faderay:background";
private static final String PROPNAME_TEXT_COLOR = "android:faderay:textColor";
private static final String PROPNAME_ALPHA = "android:faderay:alpha";
private float startAlpha;
private float endAlpha;
private TimeInterpolator timeInterpolator;
public FadeTransition(final float startAlpha, final float endAlpha, final TimeInterpolator timeInterpolator) {
this.startAlpha = startAlpha;
this.endAlpha = endAlpha;
this.timeInterpolator = timeInterpolator;
}
public FadeTransition(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
private void captureValues(final TransitionValues transitionValues) {
transitionValues.values.put(PROPNAME_BACKGROUND, transitionValues.view.getBackground());
transitionValues.values.put(PROPNAME_ALPHA, transitionValues.view.getAlpha());
if (transitionValues.view instanceof TextView) {
transitionValues.values.put(PROPNAME_TEXT_COLOR, ((TextView) transitionValues.view).getCurrentTextColor());
}
}
#Override
public void captureStartValues(final TransitionValues transitionValues) {
captureValues(transitionValues);
}
#Override
public void captureEndValues(final TransitionValues transitionValues) {
captureValues(transitionValues);
}
#SuppressLint("NewApi")
#Override
public Animator createAnimator(final ViewGroup sceneRoot, final TransitionValues startValues,
final TransitionValues endValues) {
TextView textView = (TextView) endValues.view;
if (startAlpha != endAlpha) {
textView.setAlpha(endAlpha);
}
ObjectAnimator fade = ObjectAnimator.ofFloat(textView, View.ALPHA, startAlpha, endAlpha);
fade.setInterpolator(timeInterpolator);
return fade;
}
}
So in my app, I have a scene transition on an image view that transitions it from one activity to another:
v.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent i = new Intent(v.getContext(),tracks.class);
i.putExtra("album", al);
ActivityOptions transitionActivityOptions = ActivityOptions.makeSceneTransitionAnimation((item_display) v.getContext(), cover, "pic");
v.getContext().startActivity(i, transitionActivityOptions.toBundle());
}
});
Now, the scale and positioning transition works fine. However, on the imageview in the 2nd activity, I have a tint attribute in the xml to make it darker (style choice). The tint does not animate, so the whole effect is slightly more jarring that I would like. Is there any way to get the tint to smoothly transition back and forth like the rest of the image?
Use a custom transition. Something like this.
public class TintTransition extends Transition {
private static final String PROPNAME_TINT = "com.example:TintTransition:tint";
public TintTransition(Context context, AttributeSet attrs) {
super(context, attrs);
}
private void captureValues(TransitionValues values) {
if (values.view instanceof AppCompatImageView) {
values.values.put(PROPNAME_TINT, ((AppCompatImageView) values.view).getImageTintList());
}
}
#Override
public void captureStartValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
#Override
public void captureEndValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
#Override
public Animator createAnimator(ViewGroup sceneRoot, final TransitionValues startValues, final TransitionValues endValues) {
if (endValues == null) {
return null;
}
if (!(endValues.view instanceof AppCompatImageView)) {
return null;
}
ColorStateList startColorStateList = (ColorStateList) startValues.values.get(PROPNAME_TINT);
ColorStateList endColorStateList = (ColorStateList) endValues.values.get(PROPNAME_TINT);
final int endColor = endColorStateList.getDefaultColor();
final int startColor = startColorStateList == null
? Color.TRANSPARENT
: startColorStateList.getDefaultColor();
ValueAnimator animator = ValueAnimator.ofObject(new ArgbEvaluator(), startColor, endColor);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
Integer color = (Integer) animation.getAnimatedValue();
if (color != null) {
((AppCompatImageView) endValues.view).setImageTintList(ColorStateList.valueOf(color));
}
}
});
return animator;
}
}
If you need to inflate from XML, this is the way to do it:
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<transition class="com.example.transitions.TintTransition" android:duration="400" android:interpolator="#android:interpolator/fast_out_slow_in"/>
</transitionSet>
I want there to be an icon, which when clicked on, will be replaced by a spinning progress bar. Once the appropriate task in the background has finished, the ProgressBar should be replaced by the icon again.
This is similar to the progress bar we all are used to in the action bar (e.g. as described here), but I want to achieve the same thing within a Fragment (dialog), so setActionView() is not available.
What would be the best way to tackle this?
So your next stop is ProgressButton by SundeepK (MIT License)
ProgressButton can be used to display a simple rotating Drawable to give the user
the effect of a loading button. The Drawable will be displayed once the user clicks the button and will have to be manually dismissed using the stopLoadingAnimation() method.
ProgressButton class:
public class ProgressButton extends ImageButton {
private boolean _shouldDisplayLoadingAnimation = false;
private Drawable _loadingAnimation;
private TextPaint _textPaint;
private Rect _textBounds;
private String _defaultText;
public ProgressButton(Context context_, AttributeSet attrs_, int defStyle_) {
super(context_, attrs_, defStyle_);
final TypedArray a = context_.obtainStyledAttributes(attrs_, R.styleable.ProgressButton,
R.attr.progressButtonStyle, R.style.ProgressButton_attrs);
this.setBackgroundColor(a.getInteger(R.styleable.ProgressButton_defaultBackgroundColor, Color.WHITE));
_loadingAnimation = getDrawable();
_loadingAnimation.setAlpha(0);
_defaultText = a.getString(R.styleable.ProgressButton_defaultText);
_textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
_textPaint.density = getResources().getDisplayMetrics().density;
_textPaint.setColor(a.getInteger(R.styleable.ProgressButton_defaultFontColor, Color.BLACK));
_textPaint.setTextAlign(Align.CENTER);
_textPaint.setTextSize(a.getInteger(R.styleable.ProgressButton_defaultFontSize, 40));
_textPaint.setFakeBoldText(true);
_textBounds = new Rect();
a.recycle();
}
public ProgressButton(Context context_, AttributeSet attrs_) {
this(context_, attrs_, 0);
}
public ProgressButton(Context context_) {
this(context_, null);
}
#Override
public boolean performClick() {
boolean isClicked = super.performClick();
if (isClicked) {
_shouldDisplayLoadingAnimation = true;
this.invalidate();
}
return isClicked;
};
public void startLoadingAnimation() {
_shouldDisplayLoadingAnimation = true;
this.invalidate();
}
public void stopLoadingAnimation() {
_shouldDisplayLoadingAnimation = false;
this.invalidate();
}
#Override
protected void onDraw(Canvas canvas_) {
if (_shouldDisplayLoadingAnimation) {
shouldShowAnimation(true);
} else {
_textPaint.getTextBounds(_defaultText, 0, _defaultText.length(), _textBounds);
canvas_.drawText( _defaultText , getWidth()/2, (getHeight()/2)+((_textBounds.bottom-_textBounds.top)/2) , _textPaint);
shouldShowAnimation(false);
_loadingAnimation.setVisible(false, false);
}
super.onDraw(canvas_);
}
private void shouldShowAnimation(boolean shouldShow_) {
if (_loadingAnimation instanceof Animatable) {
if (shouldShow_) {
_loadingAnimation.setAlpha(255);
((Animatable) _loadingAnimation).start();
} else {
_loadingAnimation.setAlpha(0);
((Animatable) _loadingAnimation).stop();
}
}
}
}
define ProgressButton in your layout:
<com.sun.progressbutton.ProgressButton
android:id="#+id/progressView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:padding="10dp"
android:src="#drawable/progress_view"
/>
Is there a way to animate a text color change (from anycolor to white)?
The only variant I came up with, is placing two textviews (with the same text) in one place, and fading the top one, so the bottom one (that has a white color) will become visible.
P.S. I scrapped the variant of the 2 TextViews since it looked weird (edges weren't smooth and, since I have a lot of such elements on the screen it was really lagging the scrolling). What I did, was a crazy hack that does the animation with the use of a Thread and setTextColor (that also forces redraw of a textview).
Since I needed only 2 color changes (from red to white, and from green to white) I hardcoded the values and all of the transition colors between them. So here's how it looks:
public class BlinkingTextView extends TextView {
public BlinkingTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void animateBlink(final boolean red) {
if (animator != null) {
animator.drop();
}
animator = new Animator(this, red);
animator.start();
}
public void clearBlinkAnimation() {
if (animator != null) {
animator.drop();
}
}
private Animator animator;
private final static class Animator extends Thread {
public Animator(final TextView textView, final boolean red) {
this.textView = textView;
if (red) {
SET_TO_USE = RED;
} else {
SET_TO_USE = GREEN;
}
}
private TextView textView;
private final int[] SET_TO_USE;
private final static int[] RED = {
-2142396,
-2008754,
-1874854,
-1740697,
-1540490,
-1405563,
-1205099,
-1004634,
-804170,
-669243,
-469036,
-334879,
-200979,
-67337,
-1
};
private final static int[] GREEN = {
-6959821,
-6565826,
-6106293,
-5646758,
-5055894,
-4530309,
-3939444,
-3283042,
-2692177,
-2166592,
-1575728,
-1116193,
-656660,
-262665,
-1
};
private boolean stop;
#Override
public void run() {
int i = 0;
while (i < 15) {
if (stop) break;
final int color = SET_TO_USE[i];
if (stop) break;
textView.post(new Runnable() {
#Override
public void run() {
if (!stop) {
textView.setTextColor(color);
}
}
});
if (stop) break;
i++;
if (stop) break;
try {
Thread.sleep(66);
} catch (InterruptedException e) {}
if (stop) break;
}
}
public void drop() {
stop = true;
}
}
}
You can use new Property Animation Api for color animation:
Integer colorFrom = getResources().getColor(R.color.red);
Integer colorTo = getResources().getColor(R.color.blue);
ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
colorAnimation.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animator) {
textView.setTextColor((Integer)animator.getAnimatedValue());
}
});
colorAnimation.start();
For backward compatability with Android 2.x use Nine Old Androids library from Jake Wharton.
The Easiest solution will be to use Object Animators :
ObjectAnimator colorAnim = ObjectAnimator.ofInt(yourTextView, "textColor",
Color.RED, Color.GREEN);
colorAnim.setEvaluator(new ArgbEvaluator());
colorAnim.start();
No need to keep handles to the two text views. First add the fadeIn/fadeOut animations:
textSwitcher.setInAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_in));
textSwitcher.setOutAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_out));
then:
TextView currentTextView = (TextView)(textSwitcher.getNextView().equals(
textSwitcher.getChildAt(0)) ?
textSwitcher.getChildAt(1) : textSwitcher.getChildAt(0)
);
// setCurrentText() first to be the same as newText if you need to
textSwitcher.setTextColor(fadeOutColor);
((TextView) textSwitcher.getNextView()).setTextColor(Color.WHITE);
textSwitcher.setText(newText);
Just implemented it like this so proven to work.
best way use ValueAnimator and ColorUtils.blendARGB
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
valueAnimator.setDuration(325);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float fractionAnim = (float) valueAnimator.getAnimatedValue();
textView.setTextColor(ColorUtils.blendARGB(Color.parseColor("#FFFFFF")
, Color.parseColor("#000000")
, fractionAnim));
}
});
valueAnimator.start();
Although I haven't found a totally distinct method, I have tried to use a TextSwitcher (with the fade animation) to create the colour-change effect. A TextSwitcher is a kind of ViewSwitcher which literally animates between two (internal) TextViews. Did you manually implement the same system unknowingly? ;) It manages a bit more of the process for you, so you may find it easier to work with (especially if you want to try more involved animations). I would create new subclass of TextSwitcher and some methods e.g. setColour() which can set the new colour and then trigger an animation. The animation code can then be moved outside of your main application.
make sure you keep a handle on the two TextViews that are put into the switcher
change the colour of the other TextView and call setText() to animate between them
If you are already using a ViewSwitcher then I don't think there is an easier way to implement this.
As others mention, using ObjectAnimator solves for this. However, in the existing posts - I wasn't seeing how to set duration. For me the color change would happen immediately.
The solution below shows:
setting the animation with some interval; thanks to post: https://plus.google.com/+CyrilMottier/posts/X4yoNHHszwq
a way to continuously cycle back and forth between the 2 colors
void animateTextViewColors(TextView textView, Integer colorTo) {
final Property<TextView, Integer> property = new Property<TextView, Integer>(int.class, "textColor") {
#Override
public Integer get(TextView object) {
return object.getCurrentTextColor();
}
#Override
public void set(TextView object, Integer value) {
object.setTextColor(value);
}
};
final ObjectAnimator animator = ObjectAnimator.ofInt(textView, property, colorTo);
animator.setDuration(8533L);
animator.setEvaluator(new ArgbEvaluator());
animator.setInterpolator(new DecelerateInterpolator(2));
animator.start();
}
void oscillateDemo(final TextView textView) {
final int whiteColor = ContextCompat.getColor(TheApp.getAppContext(), R.color.white);
final int yellowColor = ContextCompat.getColor(TheApp.getAppContext(), R.color.yellow);
final int counter = 100;
Thread oscillateThread = new Thread() {
#Override
public void run() {
for (int i = 0; i < counter; i++) {
final int fadeToColor = (i % 2 == 0)
? yellowColor
: whiteColor;
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
animateTextViewColors(textView, fadeToColor);
}
});
try {
Thread.sleep(2450);
}
catch (InterruptedException iEx) {}
}
}
};
oscillateThread.start();
}
I scrapped the variant of the 2 TextViews since it looked weird (edges weren't smooth and, since I have a lot of such elements on the screen it was really lagging the scrolling). What I did, was a crazy hack that does the animation with the use of a Thread and setTextColor (that also forces redraw of a textview).
Since I needed only 2 color changes (from red to white, and from green to white) I hardcoded the values and all of the transition colors between them. So here's how it looks:
public class BlinkingTextView extends TextView {
public BlinkingTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void animateBlink(final boolean red) {
if (animator != null) {
animator.drop();
}
animator = new Animator(this, red);
animator.start();
}
public void clearBlinkAnimation() {
if (animator != null) {
animator.drop();
}
}
private Animator animator;
private final static class Animator extends Thread {
public Animator(final TextView textView, final boolean red) {
this.textView = textView;
if (red) {
SET_TO_USE = RED;
} else {
SET_TO_USE = GREEN;
}
}
private TextView textView;
private final int[] SET_TO_USE;
private final static int[] RED = {
-2142396,
-2008754,
-1874854,
-1740697,
-1540490,
-1405563,
-1205099,
-1004634,
-804170,
-669243,
-469036,
-334879,
-200979,
-67337,
-1
};
private final static int[] GREEN = {
-6959821,
-6565826,
-6106293,
-5646758,
-5055894,
-4530309,
-3939444,
-3283042,
-2692177,
-2166592,
-1575728,
-1116193,
-656660,
-262665,
-1
};
private boolean stop;
#Override
public void run() {
int i = 0;
while (i < 15) {
if (stop) break;
final int color = SET_TO_USE[i];
if (stop) break;
textView.post(new Runnable() {
#Override
public void run() {
if (!stop) {
textView.setTextColor(color);
}
}
});
if (stop) break;
i++;
if (stop) break;
try {
Thread.sleep(66);
} catch (InterruptedException e) {}
if (stop) break;
}
}
public void drop() {
stop = true;
}
}
}
The issue I found with valueAnimator as well as ObjectAnimator is that the animator iterates through a number of random colors, and the transition doesn't look smooth. I wrote the following code which worked smoothly. Hope it helps someone else also.
public static void changeTextColor(final TextView textView, int startColor, int endColor,
final long animDuration, final long animUnit){
if (textView == null) return;
final int startRed = Color.red(startColor);
final int startBlue = Color.blue(startColor);
final int startGreen = Color.green(startColor);
final int endRed = Color.red(endColor);
final int endBlue = Color.blue(endColor);
final int endGreen = Color.green(endColor);
new CountDownTimer(animDuration, animUnit){
//animDuration is the time in ms over which to run the animation
//animUnit is the time unit in ms, update color after each animUnit
#Override
public void onTick(long l) {
int red = (int) (endRed + (l * (startRed - endRed) / animDuration));
int blue = (int) (endBlue + (l * (startBlue - endBlue) / animDuration));
int green = (int) (endGreen + (l * (startGreen - endGreen) / animDuration));
textView.setTextColor(Color.rgb(red, green, blue));
}
#Override
public void onFinish() {
textView.setTextColor(Color.rgb(endRed, endGreen, endBlue));
}
}.start();
}