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();
}
Related
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 implement a custom SpinNumberView: it is square shaped (say 40x40), it has a vertical LinearLayout as a subview, within this linear layout are a bunch of 40x40 cells stacked vertically. I want to animate the cells to scroll vertically by changing offsetY of the LinearLayout.
But there is one problem: only the cell initially in bounds (the first) is rendered, the cells outside of the bounds are not drawn, so when I animate the LinearLayout to scroll, the linear layout is spinning, but only the first cell is visible, others are blank spaces. Here is my entire code for the custom View:
public class SpinNumberView extends RelativeLayout {
private int startNumber;
private int endNumber;
private int number;
private int gridsize;
private int index;
public static final double stepDuration = 0.1;
private boolean inAnimation = true;
ArrayList<Integer> numbers;
public LinearLayout container;
public SpinNumberView(Context context) {
super(context);
}
public SpinNumberView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void dispatchDraw(Canvas canvas) {
// draw the background black solid circle
float radius = (float)(this.gridsize);
Paint p = new Paint();
p.setStyle(Paint.Style.FILL);
p.setARGB(192, 0, 0, 0);
canvas.drawCircle(radius/2, radius/2, radius/2, p);
// draw 1px white border
Paint pp = new Paint();
pp.setStyle(Paint.Style.STROKE);
pp.setStrokeWidth(2.0f);
pp.setARGB(192, 255, 255, 255);
canvas.drawCircle(radius/2, radius/2, radius/2-1, pp);
// clip to the circle
Path path = new Path();
RectF r = new RectF((float)0.0, (float)0.0, radius, radius);
path.addRoundRect(r, radius/2, radius/2, Path.Direction.CW);
canvas.clipPath(path);
super.dispatchDraw(canvas);
}
#Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
super.onLayout(b, i, i1, i2, i3);
}
class AniListener implements Animator.AnimatorListener {
#Override
public void onAnimationStart(Animator animator) {}
#Override
public void onAnimationEnd(Animator animator) {
SpinNumberView.this.animateStep();
}
#Override
public void onAnimationCancel(Animator animator) {}
#Override
public void onAnimationRepeat(Animator animator) {}
}
public void animateStep() {
this.container.setTranslationY(0);
float offset;
TimeInterpolator inter;
if(this.inAnimation) {
offset = (float)this.gridsize * this.numbers.size();
inter = new LinearInterpolator();
} else {
offset = (float)this.gridsize * this.index;
inter = new DecelerateInterpolator();
}
long duration = (long)(SpinNumberView.stepDuration * this.numbers.size() * 1000);
ViewPropertyAnimator ani = this.container.animate().translationYBy(-offset).setDuration(duration);
ani.setInterpolator(inter);
if(this.inAnimation) {
ani.setListener(new AniListener());
} else {
ani.setListener(null);
}
ani.start();
}
public void stopAnimation() {
this.inAnimation = false;
}
public void startAnimation() {
this.inAnimation = true;
float offset = (float)this.gridsize * this.numbers.size();
long duration = (long)(SpinNumberView.stepDuration * this.numbers.size() * 1000);
ViewPropertyAnimator ani = this.container.animate().translationYBy(-offset).setDuration(duration);
TimeInterpolator inter = new AccelerateInterpolator();
ani.setInterpolator(inter);
ani.setListener(new AniListener());
ani.start();
}
public void setup(int number, int start, int end, int gridsize) {
this.setBackgroundColor(Color.TRANSPARENT);
this.setAlpha((float) 0.5);
this.setClipChildren(false);
this.number = number;
this.startNumber = start;
this.endNumber = end;
this.gridsize = gridsize;
this.numbers = new ArrayList<>();
for(int i=start; i<=end;i++) {
this.numbers.add(i);
}
Collections.shuffle(this.numbers);
// Find index of target number within shuffled array
this.index = this.numbers.indexOf(this.number);
this.container = new LinearLayout(this.getContext());
this.container.setOrientation(LinearLayout.VERTICAL);
this.container.setGravity(Gravity.CENTER_HORIZONTAL);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(this.gridsize, this.gridsize * (this.numbers.size()+1));
this.container.setLayoutParams(params);
this.addView(this.container);
int offsety = 0;
// setup all the number views
for(int k=0;k<this.numbers.size()+1;k++) {
String txt;
if(k==this.numbers.size()) {
txt = Integer.toString(this.numbers.get(0));
} else {
txt = Integer.toString(this.numbers.get(k));
}
TextView tv = new TextView(this.getContext());
tv.setLayoutParams(new LayoutParams(this.gridsize, this.gridsize));
tv.setText(txt);
tv.setTextSize(24.0f);
tv.setTextColor(Color.WHITE);
tv.setTextAlignment(TextView.TEXT_ALIGNMENT_CENTER);
tv.setLines(1);
tv.setGravity(Gravity.CENTER_VERTICAL);
this.container.addView(tv);
offsety += this.gridsize;
}
this.invalidate();
}
}
Why is this happening?
BTW: I take a screenshot with getDrawingCache() of screen content, the cells are visible in the screenshot!
Yes! It happend when we get some view height or width of a view. Because didn't completely render the view when we call its height or width yet.
Solution:
Use this code to get Height and width
EditText edt = (EditText) findViewbyid(R.id.tv);
edt.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int height= edt.getHeight();
int width = edt.getHeight();
edt.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
To answer my own question:
When overriding onLayout() function, I need to layout the subviews myself like this:
#Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
super.onLayout(b, i, i1, i2, i3);
this.container.layout(0, 0, this.gridsize, this.gridsize * (this.endNumber-this.startNumber+2));
}
Glad you solved it by yourself, in iOS, we use something like Redraw method for these scenarios. Hopefully it will help you to further optimize your code.
This question already has an answer here:
Object animator not removing update listener android
(1 answer)
Closed 6 years ago.
Good day.Android object animator is keep firing like crazy nothing helps to stop it!NOTHING! please help me what i have done wrong?
AnimatedColorSpan span = new AnimatedColorSpan(context);
final SpannableString spannableString = new SpannableString(textToShow);
this.spannableString = spannableString;
String substring = textToShow.toLowerCase();
int start = textToShow.toLowerCase().indexOf(substring);
int end = start + substring.length();
spannableString.setSpan(span, start, end, 0);
objectAnimator = ObjectAnimator.ofFloat(
span, ANIMATED_COLOR_SPAN_FLOAT_PROPERTY, 0, 100);
objectAnimator.setEvaluator(new FloatEvaluator());
objectAnimator.addUpdateListener(updateListener);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.setDuration(DURATION);
objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
objectAnimator.start();
private static final Property<AnimatedColorSpan, Float> ANIMATED_COLOR_SPAN_FLOAT_PROPERTY
= new Property<AnimatedColorSpan, Float>(Float.class, "ANIMATED_COLOR_SPAN_FLOAT_PROPERTY") {
#Override
public void set(AnimatedColorSpan span, Float value) {
span.setTranslateXPercentage(value);
}
#Override
public Float get(AnimatedColorSpan span) {
return span.getTranslateXPercentage();
}
};
public static RainbowAnimation get() {
return rainbowAnimation;
}
public void stopRainbowAnimation() {
stopCalled = true;
if (objectAnimator != null) {
objectAnimator.removeUpdateListener(updateListener);
objectAnimator.cancel();
objectAnimator.removeAllListeners();
objectAnimator.removeAllUpdateListeners();
textViewToAttach.setText(textToShow);
}
System.gc();
}
private ValueAnimator.AnimatorUpdateListener updateListener = new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
if (!stopCalled) {
textViewToAttach.setText(spannableString);
} else {
if (objectAnimator != null) {
objectAnimator.setDuration(10);
objectAnimator.removeUpdateListener(this);
objectAnimator = null;
}
}
}
};
I have tried everything here and you can see i am removing everything which is possible to remove but it keeps just logging and it drives me crazy because heaps grows as hell!
What is wrong with my code guys what i have done wrong here?
If you want to stop animator call
objectAnimator.end();
If you want the animation not to repeat, please comment this line
objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
It won't repeat the animation again.
We want a Meter animation in TextView
To make it a little more interesting, I want each digit come from top to bottom or bottom to top ?
Right now I using listview for achieving this, I have also tried with TextSwitcher but its have a limitation of two child only.
I'm using getListView().smoothScrollToPosition(0...3...6...6...n);
Is there a simple way of doing this? because right now , we need to maintain 3 ListView and Adapter as well for maintaining this.
Please refer link to more understand this question
Display StopWatch Timer animated like the petrol pump meter using NSTimer
ListView's might be good enough solution, but I've implemented it with a custom View (FrameLayout), which contains inside 2 TextViews, which are animating based on the value changes:
The idea of code is very basic:
You pass to setValue desired value;
If it's bigger than current one - start animation from from bottom to top (and vice versa) to increment/decrement current value by 1. Here, we animating two TextViews to replace each other;
In AnimationEnd listener, check if we reached desired value - if not - do one more run (recursively);
public class DigitTextView extends FrameLayout {
private static int ANIMATION_DURATION = 250;
TextView currentTextView, nextTextView;
public DigitTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public DigitTextView(Context context) {
super(context);
init(context);
}
private void init(Context context) {
LayoutInflater.from(context).inflate(R.layout.digit_text_view, this);
currentTextView = (TextView) getRootView().findViewById(R.id.currentTextView);
nextTextView = (TextView) getRootView().findViewById(R.id.nextTextView);
nextTextView.setTranslationY(getHeight());
setValue(0);
}
public void setValue(final int desiredValue) {
if (currentTextView.getText() == null || currentTextView.getText().length() == 0) {
currentTextView.setText(String.format(Locale.getDefault(), "%d", desiredValue));
}
final int oldValue = Integer.parseInt(currentTextView.getText().toString());
if (oldValue > desiredValue) {
nextTextView.setText(String.format(Locale.getDefault(), "%d", oldValue-1));
currentTextView.animate().translationY(-getHeight()).setDuration(ANIMATION_DURATION).start();
nextTextView.setTranslationY(nextTextView.getHeight());
nextTextView.animate().translationY(0).setDuration(ANIMATION_DURATION).setListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {}
#Override
public void onAnimationEnd(Animator animation) {
currentTextView.setText(String.format(Locale.getDefault(), "%d", oldValue - 1));
currentTextView.setTranslationY(0);
if (oldValue - 1 != desiredValue) {
setValue(desiredValue);
}
}
#Override
public void onAnimationCancel(Animator animation) {}
#Override
public void onAnimationRepeat(Animator animation) {}
}).start();
} else if (oldValue < desiredValue) {
nextTextView.setText(String.format(Locale.getDefault(), "%d", oldValue+1));
currentTextView.animate().translationY(getHeight()).setDuration(ANIMATION_DURATION).start();
nextTextView.setTranslationY(-nextTextView.getHeight());
nextTextView.animate().translationY(0).setDuration(ANIMATION_DURATION).setListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {}
#Override
public void onAnimationEnd(Animator animation) {
currentTextView.setText(String.format(Locale.getDefault(), "%d", oldValue + 1));
currentTextView.setTranslationY(0);
if (oldValue + 1 != desiredValue) {
setValue(desiredValue);
}
}
#Override
public void onAnimationCancel(Animator animation) {}
#Override
public void onAnimationRepeat(Animator animation) {}
}).start();
}
}
}
And it's XML:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="48dp"
android:layout_height="56dp"
android:padding="8dp"
android:background="#drawable/rounded_blue_rect">
<TextView
android:id="#+id/currentTextView"
android:textColor="#FFFFFF"
android:textSize="18sp"
android:gravity="center"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/nextTextView"
android:textColor="#FFFFFF"
android:textSize="18sp"
android:layout_gravity="center"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</FrameLayout>
And it's very easy to use:
Add to layout:
<klogi.com.myapplication.DigitTextView
android:id="#+id/digitTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
And set Value in code:
DigitTextView digitTextView = (DigitTextView) findViewById(R.id.digitTextView);
digitTextView.setValue(5);
Upd:
Another option to use, from what I see, is to set up a bit customized NumberPicker
I hope, it helps!
Ever since Robinhood won the Material design awards they have open sourced there custom TextView just like you are describing.
Check out Robinhood's Ticker library
This code performs the same animation where number rolldown from top to bottom.
Rolling-TextView-Animation
You can also use a handler to get the desired effect. Using this, you won't have to make any custom views.
Create a function handleTextView which takes in initialValue, finalValue and targetTextview as arguments. The method is-
private void handleTextView(int initialValue, int finalValue, final TextView targetTextview) {
DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator(1f);
final int newInitialValue = Math.min(initialValue, finalValue);
final int newFinalValue = Math.max(initialValue, finalValue);
final int difference = Math.abs(finalValue - initialValue);
Handler handler = new Handler();
for (int count = newInitialValue; count <= newFinalValue; count++) {
//Time to display the current value to the user.
int time = Math.round(decelerateInterpolator.getInterpolation((((float) count) / difference)) * 100) * count;
final int finalCount = ((initialValue > finalValue) ? initialValue - count : count);
handler.postDelayed(new Runnable() {
#Override
public void run() {
targetTextview.setText(finalCount.toString());
}
}, time);
}
}
UPDATE:
Option 2- You can use a value animator as well-
private void handleTextView(int initialValue, int finalValue, final TextView textview) {
ValueAnimator valueAnimator = ValueAnimator.ofInt(initialValue, finalValue);
valueAnimator.setDuration(1500);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
textview.setText(valueAnimator.getAnimatedValue().toString());
}
});
valueAnimator.start();
}
By using this method we do not need to do any math.
I have been searching a lot on how to animate the strike-through affect on a TextView to no results. Only thing I am getting on forums and StackOverflow is:
some_text_view.setPaintFlags(some_text_view.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG)
What I want to do is, animate the strike-through affect like in todo apps on Play Store e.g. Any.do has it on an item left-to-right swipe.
You have a couple of options:
Extend TextView and make a custom view which checks if the STRIKE_THRU_TEXT_FLAG is set and fires off an animation that will draw a small line on the text incrementing it's width on each frame of the animation.
Use an empty view and place it on your TextView (using RelativeLayout, FrameLayout etc). Make sure the dimensions of this view match exactly with your TextView. Then animate this view following the same strategy as before: Draw a horizontal line at the center of the view whose width is incremented at each frame of the animation.
If you want to know how to the animation itself, then you can look up Animator, AnimatorSet etc and their related guides.
I used this approach to make strikethrough animation:
private void animateStrikeThrough1(final TextView tv) {
final int ANIM_DURATION = 1000; //duration of animation in millis
final int length = tv.getText().length();
new CountDownTimer(ANIM_DURATION, ANIM_DURATION/length) {
Spannable span = new SpannableString(tv.getText());
StrikethroughSpan strikethroughSpan = new StrikethroughSpan();
#Override
public void onTick(long millisUntilFinished) {
//calculate end position of strikethrough in textview
int endPosition = (int) (((millisUntilFinished-ANIM_DURATION)*-1)/(ANIM_DURAT [ION/length));
endPosition = endPosition > length ?
length : endPosition;
span.setSpan(strikethroughSpan, 0, endPosition,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(span);
}
#Override
public void onFinish() {
}
}.start();
}
private fun TextView.startStrikeThroughAnimation(): ValueAnimator {
val span = SpannableString(text)
val strikeSpan = StrikethroughSpan()
val animator = ValueAnimator.ofInt(text.length)
animator.addUpdateListener {
span.setSpan(strikeSpan, 0, it.animatedValue as Int, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
text = span
invalidate()
}
animator.start()
return animator
}
private fun TextView.reverseStrikeThroughAnimation(): ValueAnimator {
val span = SpannableString(text.toString())
val strikeSpan = StrikethroughSpan()
val animator = ValueAnimator.ofInt(text.length, 0)
animator.addUpdateListener {
span.setSpan(strikeSpan, 0, it.animatedValue as Int, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
text = span
invalidate()
}
animator.start()
return animator
}
// Created by kot32 on 2017/10/26.
public class AnimationText extends TextView {
private boolean isAnimationStarted;
private float targetLength;
private float totalLength;
private Paint strikePaint;
private float startY;
//should always show Strike-Through
private boolean isDeleted;
public AnimationText(Context context, AttributeSet attrs) {
super(context, attrs);
strikePaint = new Paint();
strikePaint.setColor(Color.BLACK);
strikePaint.setAntiAlias(true);
strikePaint.setStyle(Paint.Style.FILL_AND_STROKE);
strikePaint.setStrokeWidth(5);
}
public AnimationText(Context context) {
super(context);
strikePaint = new Paint();
strikePaint.setColor(Color.BLACK);
strikePaint.setAntiAlias(true);
strikePaint.setStyle(Paint.Style.FILL_AND_STROKE);
strikePaint.setStrokeWidth(5);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isAnimationStarted) {
//画线
canvas.drawLine(0, startY, targetLength, startY, strikePaint);
}
if (isDeleted && !isAnimationStarted) {
canvas.drawLine(0, startY, totalLength, startY, strikePaint);
}
}
public void startStrikeThroughAnimation() {
totalLength = getWidth();
startY = (float) getHeight() / 2;
isAnimationStarted = true;
//利用动画逐渐画出一条删除线
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this, "targetLength", 0, totalLength);
objectAnimator.setInterpolator(new AccelerateInterpolator());
objectAnimator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
isAnimationStarted = false;
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
objectAnimator.setDuration(300);
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
invalidate();
}
});
objectAnimator.start();
postInvalidate();
}
public void setDeleted(boolean deleted) {
isDeleted = deleted;
totalLength = getWidth();
}
public float getTargetLength() {
return targetLength;
}
public void setTargetLength(float targetLength) {
this.targetLength = targetLength;
}
}