Related
I'm changing slide with the following code:
viewPager.setCurrentItem(index++, true);
But it changes too fast. Is there a way to set manually the animation speed?
I've wanted to do myself and have achieved a solution (using reflection, however). I haven't tested it yet but it should work or need minimal modification. Tested on Galaxy Nexus JB 4.2.1. You need to use a ViewPagerCustomDuration in your XML instead of ViewPager, and then you can do this:
ViewPagerCustomDuration vp = (ViewPagerCustomDuration) findViewById(R.id.myPager);
vp.setScrollDurationFactor(2); // make the animation twice as slow
ViewPagerCustomDuration.java:
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.animation.Interpolator;
import java.lang.reflect.Field;
public class ViewPagerCustomDuration extends ViewPager {
public ViewPagerCustomDuration(Context context) {
super(context);
postInitViewPager();
}
public ViewPagerCustomDuration(Context context, AttributeSet attrs) {
super(context, attrs);
postInitViewPager();
}
private ScrollerCustomDuration mScroller = null;
/**
* Override the Scroller instance with our own class so we can change the
* duration
*/
private void postInitViewPager() {
try {
Field scroller = ViewPager.class.getDeclaredField("mScroller");
scroller.setAccessible(true);
Field interpolator = ViewPager.class.getDeclaredField("sInterpolator");
interpolator.setAccessible(true);
mScroller = new ScrollerCustomDuration(getContext(),
(Interpolator) interpolator.get(null));
scroller.set(this, mScroller);
} catch (Exception e) {
}
}
/**
* Set the factor by which the duration will change
*/
public void setScrollDurationFactor(double scrollFactor) {
mScroller.setScrollDurationFactor(scrollFactor);
}
}
ScrollerCustomDuration.java:
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.animation.Interpolator;
import android.widget.Scroller;
public class ScrollerCustomDuration extends Scroller {
private double mScrollFactor = 1;
public ScrollerCustomDuration(Context context) {
super(context);
}
public ScrollerCustomDuration(Context context, Interpolator interpolator) {
super(context, interpolator);
}
#SuppressLint("NewApi")
public ScrollerCustomDuration(Context context, Interpolator interpolator, boolean flywheel) {
super(context, interpolator, flywheel);
}
/**
* Set the factor by which the duration will change
*/
public void setScrollDurationFactor(double scrollFactor) {
mScrollFactor = scrollFactor;
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, (int) (duration * mScrollFactor));
}
}
I have found better solution, based on #df778899's answer and the
Android ValueAnimator API. It works fine without reflection and is very flexible.
Also there is no need for making custom ViewPager and putting it into android.support.v4.view package.
Here is an example:
private void animatePagerTransition(final boolean forward) {
ValueAnimator animator = ValueAnimator.ofInt(0, viewPager.getWidth());
animator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
viewPager.endFakeDrag();
}
#Override
public void onAnimationCancel(Animator animation) {
viewPager.endFakeDrag();
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.setInterpolator(new AccelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
private int oldDragPosition = 0;
#Override
public void onAnimationUpdate(ValueAnimator animation) {
int dragPosition = (Integer) animation.getAnimatedValue();
int dragOffset = dragPosition - oldDragPosition;
oldDragPosition = dragPosition;
viewPager.fakeDragBy(dragOffset * (forward ? -1 : 1));
}
});
animator.setDuration(AppConstants.PAGER_TRANSITION_DURATION_MS);
if (viewPager.beginFakeDrag()) {
animator.start();
}
}
UPDATE:
Just checked if this solution can be used to swipe several pages at once (for example if first page should be showed after the last one). This is slightly modified code to handle specified page count:
private int oldDragPosition = 0;
private void animatePagerTransition(final boolean forward, int pageCount) {
// if previous animation have not finished we can get exception
if (pagerAnimation != null) {
pagerAnimation.cancel();
}
pagerAnimation = getPagerTransitionAnimation(forward, pageCount);
if (viewPager.beginFakeDrag()) { // checking that started drag correctly
pagerAnimation.start();
}
}
private Animator getPagerTransitionAnimation(final boolean forward, int pageCount) {
ValueAnimator animator = ValueAnimator.ofInt(0, viewPager.getWidth() - 1);
animator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
viewPager.endFakeDrag();
}
#Override
public void onAnimationCancel(Animator animation) {
viewPager.endFakeDrag();
}
#Override
public void onAnimationRepeat(Animator animation) {
viewPager.endFakeDrag();
oldDragPosition = 0;
viewPager.beginFakeDrag();
}
});
animator.setInterpolator(new AccelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
int dragPosition = (Integer) animation.getAnimatedValue();
int dragOffset = dragPosition - oldDragPosition;
oldDragPosition = dragPosition;
viewPager.fakeDragBy(dragOffset * (forward ? -1 : 1));
}
});
animator.setDuration(AppConstants.PAGER_TRANSITION_DURATION_MS / pageCount); // remove divider if you want to make each transition have the same speed as single page transition
animator.setRepeatCount(pageCount);
return animator;
}
public class PresentationViewPager extends ViewPager {
public static final int DEFAULT_SCROLL_DURATION = 250;
public static final int PRESENTATION_MODE_SCROLL_DURATION = 1000;
public PresentationViewPager (Context context) {
super(context);
}
public PresentationViewPager (Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setDurationScroll(int millis) {
try {
Class<?> viewpager = ViewPager.class;
Field scroller = viewpager.getDeclaredField("mScroller");
scroller.setAccessible(true);
scroller.set(this, new OwnScroller(getContext(), millis));
} catch (Exception e) {
e.printStackTrace();
}
}
public class OwnScroller extends Scroller {
private int durationScrollMillis = 1;
public OwnScroller(Context context, int durationScroll) {
super(context, new DecelerateInterpolator());
this.durationScrollMillis = durationScroll;
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, durationScrollMillis);
}
}
}
Better solution is to simply access the private fields by creating the class in the support package. EDIT This is bound to the MAX_SETTLE_DURATION of 600ms, set by the ViewPagerclass.
package android.support.v4.view;
import android.content.Context;
import android.util.AttributeSet;
public class SlowViewPager extends ViewPager {
// The speed of the scroll used by setCurrentItem()
private static final int VELOCITY = 200;
public SlowViewPager(Context context) {
super(context);
}
public SlowViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
setCurrentItemInternal(item, smoothScroll, always, VELOCITY);
}
}
You can, of course, then add a custom attribute so this can be set via XML.
Here is my code used in Librera Reader
public class MyViewPager extends ViewPager {
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
initMyScroller();
}
private void initMyScroller() {
try {
Class<?> viewpager = ViewPager.class;
Field scroller = viewpager.getDeclaredField("mScroller");
scroller.setAccessible(true);
scroller.set(this, new MyScroller(getContext())); // my liner scroller
Field mFlingDistance = viewpager.getDeclaredField("mFlingDistance");
mFlingDistance.setAccessible(true);
mFlingDistance.set(this, Dips.DP_10);//10 dip
Field mMinimumVelocity = viewpager.getDeclaredField("mMinimumVelocity");
mMinimumVelocity.setAccessible(true);
mMinimumVelocity.set(this, 0); //0 velocity
} catch (Exception e) {
LOG.e(e);
}
}
public class MyScroller extends Scroller {
public MyScroller(Context context) {
super(context, new LinearInterpolator()); // my LinearInterpolator
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, 175);//175 duration
}
}
}
I used Cicero Moura's version to make a Kotlin class that still works perfectly as of Android 10.
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.animation.DecelerateInterpolator
import android.widget.Scroller
import androidx.viewpager.widget.ViewPager
class CustomViewPager(context: Context, attrs: AttributeSet) :
ViewPager(context, attrs) {
private companion object {
const val DEFAULT_SPEED = 1000
}
init {
setScrollerSpeed(DEFAULT_SPEED)
}
var scrollDuration = DEFAULT_SPEED
set(millis) {
setScrollerSpeed(millis)
}
private fun setScrollerSpeed(millis: Int) {
try {
ViewPager::class.java.getDeclaredField("mScroller")
.apply {
isAccessible = true
set(this#CustomViewPager, OwnScroller(millis))
}
} catch (e: Exception) {
e.printStackTrace()
}
}
inner class OwnScroller(private val durationScrollMillis: Int) : Scroller(context, AccelerateDecelerateInterpolator()) {
override fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int, duration: Int) {
super.startScroll(startX, startY, dx, dy, durationScrollMillis)
}
}
}
Initializing from the activity class:
viewPager.apply {
scrollDuration = 2000
adapter = pagerAdapter
}
After wasting my whole day I found a solution set offscreenPageLimit to total no. of the page.
In order to keep a constant length ViewPager scrolls smooth, setOffScreenLimit(page.length) will keep all the views in memory. However, this poses a problem for any animations that involves calling View.requestLayout function (e.g. any animation that involves making changes to the margin or bounds). It makes them really slow (as per Romain Guy) because the all of the views that's in memory will be invalidated as well. So I tried a few different ways to make things smooth but overriding requestLayout and other invalidate methods will cause many other problems.
A good compromise is to dynamically modify the off screen limit so that most of the scrolls between pages will be very smooth while making sure that all of the in page animations smooth by removing the views when the user. This works really well when you only have 1 or 2 views that will have to make other views off memory.
***Use this when no any solution works because by setting offeset limit u will load all the fragments at the same time
Is the SwitchPreference introduced in ICS compatible in the android-support-v4 library? I'm trying to update some old projects and would like to use SwitchPreferences if possible.
I know I can make a separate resource file to distinguish the API version, but I'd like to avoid that if at all possible.
Is the SwitchPreference introduced in ICS compatible in the android-support-v4 library?
No, sorry.
However, it shouldn't be too tricky to backport it, if someone hasn't already.
Actually, it may be a bit of a pain, since it also requires a backport of Switch, and backporting widgets is sometimes troublesome because they frequently use package-private methods that backports cannot access.
I know I can make a separate resource file to distinguish the API version, but I'd like to avoid that if at all possible.
Well, that would certainly be way simpler than the alternatives:
the aforementioned backport
creating some sort of alias Preference mechanism that allows you to use SwitchPreference on newer devices and CheckBoxPreference on older devices with only one resource file
android-switch-backport has a SwitchPreference which works on Android 2.1+.
https://github.com/BoD/android-switch-backport
I've tried every solution that i found but non of them were fit my needs, so i created my own widget wich is used ObjectAnimator from nineOld compatibility lib and works pretty fine on any android API.
import android.widget.RelativeLayout;
import com.myapp.utilities.AppUtils;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorListenerAdapter;
import com.nineoldandroids.animation.ObjectAnimator;
public class SwitchButton extends RelativeLayout {
public static final int TEXT_SIZE = 11;
public float HANDLE_SHIFT = -40f;
public float TEXT_RIGHT_SHIFT = 40f;
public static int BUTTON_ID = 0x00009999;
public static int TEXT_ID = 0x00008888;
private Button handleButton;
private RoboTextView textView;
private boolean switchEnabled;
private String yesStr;
private String noStr;
private int TEXT_LEFT_PADDING = 13;
private ObjectAnimator animateHandleLeftShift;
private ObjectAnimator animateHandleRightShift;
private int HANDLE_BUTTON_HEIGHT = 22;
private int HANDLE_BUTTON_WIDTH = 42;
private ObjectAnimator animateTextLeftShift;
private ObjectAnimator animateTextRightShift;
public SwitchButton(Context context) {
super(context);
onCreate(context);
}
private void onCreate(Context context) {
float density = context.getResources().getDisplayMetrics().density;
TEXT_LEFT_PADDING *= density;
HANDLE_BUTTON_HEIGHT *= density;
HANDLE_BUTTON_WIDTH *= density;
HANDLE_SHIFT *= density;
TEXT_RIGHT_SHIFT *= density;
yesStr = getContext().getString(R.string.yes).toUpperCase();
noStr = getContext().getString(R.string.no).toUpperCase();
{// Button
handleButton = new Button(getContext());
RelativeLayout.LayoutParams buttonParams = new LayoutParams(HANDLE_BUTTON_WIDTH, HANDLE_BUTTON_HEIGHT);
buttonParams.addRule(RelativeLayout.CENTER_VERTICAL);
buttonParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
handleButton.setBackgroundResource(R.drawable.button_switch_handle_selector);
handleButton.setId(BUTTON_ID);
addView(handleButton, buttonParams);
}
{// Text
textView = new RoboTextView(getContext());
LayoutParams textParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
textParams.addRule(RelativeLayout.CENTER_VERTICAL);
textView.setText(yesStr);
textView.setTextColor(getContext().getResources().getColor(R.color.new_normal_gray));
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, TEXT_SIZE);
textView.setPadding(TEXT_LEFT_PADDING, 0, 0, 0);
textView.setFont(RoboTextView.ROBOTO_BOLD_FONT);
textView.setId(TEXT_ID);
float shadowRadius = 0.5f ;
float shadowDx = 0;
float shadowDy = 1;
textView.setShadowLayer(shadowRadius, shadowDx, shadowDy, Color.BLACK);
addView(textView, textParams);
}
initFlipAnimation();
}
#Override
public void setOnClickListener(OnClickListener l) {
handleButton.setOnClickListener(l);
textView.setOnClickListener(l);
}
public void toggle(View view){
if (AppUtils.HONEYCOMB_PLUS_API && view.getId() == TEXT_ID) { // ignore text clicks
return;
}
switchEnabled = !switchEnabled;
if (switchEnabled) {
// animate handle to the left
animateHandleLeftShift.start();
animateTextLeftShift.start();
textView.setText(noStr);
} else {
animateHandleRightShift.start();
animateTextRightShift.start();
textView.setText(yesStr);
}
}
private android.view.animation.Interpolator accelerator = new LinearInterpolator();
private static final int DURATION = 70;
private void initFlipAnimation() {
animateHandleLeftShift = ObjectAnimator.ofFloat(handleButton, "translationX", 0f, HANDLE_SHIFT);
animateHandleLeftShift.setDuration(DURATION);
animateHandleLeftShift.setInterpolator(accelerator);
animateHandleRightShift = ObjectAnimator.ofFloat(handleButton, "translationX", HANDLE_SHIFT, 0f);
animateHandleRightShift.setDuration(DURATION);
animateHandleRightShift.setInterpolator(accelerator);
animateHandleLeftShift.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator anim) {
// TODO
}
});
animateTextLeftShift = ObjectAnimator.ofFloat(textView, "translationX", 0f, TEXT_RIGHT_SHIFT);
animateTextLeftShift.setDuration(DURATION);
animateTextLeftShift.setInterpolator(accelerator);
animateTextRightShift = ObjectAnimator.ofFloat(textView, "translationX", TEXT_RIGHT_SHIFT, 0f);
animateTextRightShift.setDuration(DURATION);
animateTextRightShift.setInterpolator(accelerator);
}
}
In XML
<com.chess.SwitchButton
android:id="#+id/ratedGameSwitch"
android:layout_width="#dimen/button_switch_width"
android:layout_height="#dimen/button_switch_height"
android:background="#drawable/button_switch_back"
/>
In the Activity/Fragment you only have to findViewById and set clickListener to it, and in onClick callback handle it:
switchButton = (SwitchButton) optionsView.findViewById(R.id.ratedGameSwitch);
switchButton.setOnClickListener(this);
#Override
public void onClick(View view) {
if (view.getId() == SwitchButton.BUTTON_ID || view.getId() == SwitchButton.TEXT_ID){
switchButton.toggle(view);
}
}
Try this solution, if you want to create settings activity programmatically.
public class SettingsActivity extends PreferenceActivity {
#TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
PreferenceScreen rootScreen = getPreferenceManager()
.createPreferenceScreen(this);
setPreferenceScreen(rootScreen);
Preference NotifCheck=null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
NotifCheck = new SwitchPreference(this);
} else {
NotifCheck = new CheckBoxPreference(this);
}
NotifCheck.setKey("yourKey");
NotifCheck.setTitle(R.string.ShowNotification);
NotifCheck.setEnabled(true);
rootScreen.addPreference(NotifCheck);
}
}
I'm using RotateAnimation and TranslateAnimation in my game project. After completing rotate i'm saving image next using TranslateAnimation. It works fine, but completing rotateanimation the image blinks once.
public void Trainplace(int x,int y,Bitmap b){
param.setMargins(x, y, 0, 0);
Train.setLayoutParams(param);
Train.setImageBitmap(b);
Train.setVisibility(0);
}
public void Trainmove(int x){
TAnimation=new TranslateAnimation(0, 0, 0,x);
TAnimation.setInterpolator(new LinearInterpolator());
TAnimation.setDuration(2000);
TAnimation.setFillAfter(true);
//TAnimation.setFillBefore(true);
Train.startAnimation(TAnimation);
}
public void Trainrotate(int a,int b,int c,int d){
RAnimation=new RotateAnimation(a,b,c,d);
RAnimation.setInterpolator(new LinearInterpolator());
RAnimation.setDuration(2000);
RAnimation.setFillAfter(true);
Train.startAnimation(RAnimation);
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.b:
Trainplace(20, 255,bmpy1);
Trainrotate(0,90,50,25);
//Stop button clicks
//B.setEnabled(false);
break;
case R.id.b2:
Trainplace(35, 230,bmpx1);
Trainrotate(0,-90,20,-30);
RAnimation.setAnimationListener(new AnimationListener() {
public void onAnimationStart(Animation arg0) {
}
public void onAnimationRepeat(Animation arg0) {
}
public void onAnimationEnd(Animation arg0) {
int p=0;
while(p<=1){
if(RAnimation.hasEnded()){
Toast.makeText(getApplicationContext(), "End", Toast.LENGTH_SHORT).show();
p=2;
}
}
Trainplace(85, 170,bmpy1);
Trainmove(-100);
}
});
break;
My problem is with completing the rotateanimation - the current image is replaced by another image (ex. my train image rotated 90 degrees then i m placed 90 degrees rotated image in that place )and then move forward by using Translateanimation this is working.
When placing the image inbetween, after completing Rotateanimation and before starting Translateanimation the train image is blinking once.
The same image will place with noproblem (means Translateanimation to Rotateanimation).
When I get blinks in animation, its most probably because the animation listeners 'onAnimationEnd' is firing before the animation is actually done.
You can create av custom view or layout and overide its 'animationEnd' method and add a interface so you can set up a listener.
Here's an example
CustomLayout class
public class CustomLayout extends LinearLayout {
private LayoutAnimationListener mListner;
public CustomLayout(Context context) {
super(context);
}
public CustomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onAnimationEnd() {
super.onAnimationEnd();
if(mListner != null){
mListner.onAnimationEnd(CustomLayout.this);
}
}
#Override
protected void onAnimationStart() {
super.onAnimationStart();
if(mListner != null){
mListner.onAnimationStart(CustomLayout.this);
}
}
public static interface LayoutAnimationListener {
//Notifies the start of the animation.
void onAnimationStart(CustomLayout cLayout);
//Notifies the end of the animation.
void onAnimationEnd(CustomLayout cLayout);
}
public void setLayoutAnimationListener(LayoutAnimationListener listener){
mListner = listener;
}
}
So in your activity you can use it as a normal linearlayout and instead of adding a animation listener to the animation, you can add it to the layout like
com.blessan.CustomLayout layout =
(com.blessan.CustomLayout)findViewById(R.id.counts_layout);
LayoutAnimationListener layoutAnimListener = new LayoutAnimationListener() {
#Override
public void onAnimationStart(CustomLayout cLayout) {
}
#Override
public void onAnimationEnd(CustomLayout cLayout) {
}
};
layout.setLayoutAnimationListener(layoutAnimListener);
This might work for you. Give it a try.
Just an interesting query here, is there a way to capture when a zoom animation sequence has ended when calling either:
MapController.zoomIn() or MapController.zoomOut();
I know that it does kick off an animation sequence to zoom in/out to the next level, however there is no known way I can find/google search, etc to find out when it finishes that sequence. I need to be able to run an update command when that is stopped so my map updates correctly.
I've found that by running the update command after calling the above function the Projection isn't from the zoom out level but somewhere inbetween (so I can't show all the data I need).
I have to admit I punted here, it's a hack but it works great. I started off with a need to know when a zoom occured, and once I hooked into that (and after some interesting debugging) I found some values were "between zoom" values, so I needed to wait till after the zoom was done.
As suggested elsewhere on Stack Overflow my zoom listener is an overridden MapView.dispatchDraw that checks to see if the zoom level has changed since last time.
Beyond that I added an isResizing method that checks if the timestamp is more than 100ms since the getLongitudeSpan value stopped changing. Works great. Here is the code:
My very first Stack Overflow post! Whoo Hoo!
public class MapViewWithZoomListener extends MapView {
private int oldZoomLevel = -1;
private List<OnClickListener> listeners = new ArrayList<OnClickListener>();
private long resizingLongitudeSpan = getLongitudeSpan();
private long resizingTime = new Date().getTime();
public MapViewWithZoomListener(Context context, String s) {
super(context, s);
}
public MapViewWithZoomListener(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
}
public MapViewWithZoomListener(Context context, AttributeSet attributeSet, int i) {
super(context, attributeSet, i);
}
public boolean isResizing() {
// done resizing if 100ms has elapsed without a change in getLongitudeSpan
return (new Date().getTime() - resizingTime < 100);
}
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (getZoomLevel() != oldZoomLevel) {
new AsyncTask() {
#Override
protected Object doInBackground(Object... objects) {
try {
if (getLongitudeSpan() != resizingLongitudeSpan) {
resizingLongitudeSpan = getLongitudeSpan();
resizingTime = new Date().getTime();
}
Thread.sleep(125); //slightly larger than isMoving threshold
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
if (!isResizing() && oldZoomLevel != getZoomLevel()) {
oldZoomLevel = getZoomLevel();
invalidate();
for (OnClickListener listener : listeners) {
listener.onClick(null);
}
}
}
}.execute();
}
}
public void addZoomListener(OnClickListener listener) {
listeners.add(listener);
}
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();
}