Hi I am new in android and in my app I have a lot of images in my ArrayList that 's
why I want to swipe those images automatically for every 3 seconds with help of Timetasker and this process is continuously need to repeating up to we close app. Can some body help me please
MainActivity:-
public class MainActivity extends AppCompatActivity {
ViewPager viewPager;
Integer[] imageId = {R.drawable.image1, R.drawable.image2, R.drawable.image3, R.drawable.image4};
String[] imagesName = {"image1","image2","image3","image4"};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
viewPager = (ViewPager) findViewById(R.id.viewPager);
PagerAdapter adapter = new CustomAdapter(MainActivity.this,imageId,imagesName);
viewPager.setAdapter(adapter);
}
}
CustomAdapter:-
public class CustomAdapter extends PagerAdapter{
private Activity activity;
private Integer[] imagesArray;
private String[] namesArray;
public CustomAdapter(Activity activity,Integer[] imagesArray,String[] namesArray){
this.activity = activity;
this.imagesArray = imagesArray;
this.namesArray = namesArray;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
LayoutInflater inflater = ((Activity)activity).getLayoutInflater();
View viewItem = inflater.inflate(R.layout.image_item, container, false);
ImageView imageView = (ImageView) viewItem.findViewById(R.id.imageView);
imageView.setImageResource(imagesArray[position]);
TextView textView1 = (TextView) viewItem.findViewById(R.id.textview);
textView1.setText(namesArray[position]);
((ViewPager)container).addView(viewItem);
return viewItem;
}
#Override
public int getCount() {
// TODO Auto-generated method stub
return imagesArray.length;
}
#Override
public boolean isViewFromObject(View view, Object object) {
// TODO Auto-generated method stub
return view == ((View)object);
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
// TODO Auto-generated method stub
((ViewPager) container).removeView((View) object);
}
}
Your question is already answered here
Add this in your MainActivity.java
//...
int currentPage = 0;
Timer timer;
final long DELAY_MS = 500;//delay in milliseconds before task is to be executed
final long PERIOD_MS = 3000; // time in milliseconds between successive task executions.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
viewPager = (ViewPager) findViewById(R.id.viewPager);
PagerAdapter adapter = new CustomAdapter(MainActivity.this,imageId,imagesName);
viewPager.setAdapter(adapter);
/*After setting the adapter use the timer */
final Handler handler = new Handler();
final Runnable Update = new Runnable() {
public void run() {
if (currentPage == NUM_PAGES-1) {
currentPage = 0;
}
viewPager.setCurrentItem(currentPage++, true);
}
};
timer = new Timer(); // This will create a new Thread
timer.schedule(new TimerTask() { // task to be scheduled
#Override
public void run() {
handler.post(Update);
}
}, DELAY_MS, PERIOD_MS);
}
Here is the code for the automatic scroll the viewpager item:
public class MainActivity extends AppCompatActivity {
AutoScrollViewPager viewPager;
Integer[] imageId = {R.drawable.commitementlogo, R.drawable.like, R.drawable.like_select, R.drawable.plus};
String[] imagesName = {"image1","image2","image3","image4"};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPager= (AutoScrollViewPager) findViewById(R.id.viewpager);
viewPager.startAutoScroll();
viewPager.setInterval(3000);
viewPager.setCycle(true);
viewPager.setStopScrollWhenTouch(true);
PagerAdapter adapter = new CustomAdapter(MainActivity.this,imageId,imagesName);
viewPager.setAdapter(adapter);
}
}
Here AutoscrollViewpager class:
public class AutoScrollViewPager extends ViewPager {
public static final int DEFAULT_INTERVAL = 1500;
public static final int LEFT = 0;
public static final int RIGHT = 1;
/** do nothing when sliding at the last or first item **/
public static final int SLIDE_BORDER_MODE_NONE = 0;
/** cycle when sliding at the last or first item **/
public static final int SLIDE_BORDER_MODE_CYCLE = 1;
/** deliver event to parent when sliding at the last or first item **/
public static final int SLIDE_BORDER_MODE_TO_PARENT = 2;
/** auto scroll time in milliseconds, default is {#link #DEFAULT_INTERVAL} **/
private long interval = DEFAULT_INTERVAL;
/** auto scroll direction, default is {#link #RIGHT} **/
private int direction = RIGHT;
/** whether automatic cycle when auto scroll reaching the last or first item, default is true **/
private boolean isCycle = true;
/** whether stop auto scroll when touching, default is true **/
private boolean stopScrollWhenTouch = true;
/** how to process when sliding at the last or first item, default is {#link #SLIDE_BORDER_MODE_NONE} **/
private int slideBorderMode = SLIDE_BORDER_MODE_NONE;
/** whether animating when auto scroll at the last or first item **/
private boolean isBorderAnimation = true;
/** scroll factor for auto scroll animation, default is 1.0 **/
private double autoScrollFactor = 1.0;
/** scroll factor for swipe scroll animation, default is 1.0 **/
private double swipeScrollFactor = 1.0;
private Handler handler;
private boolean isAutoScroll = false;
private boolean isStopByTouch = false;
private float touchX = 0f, downX = 0f;
private CustomDurationScroller scroller = null;
public static final int SCROLL_WHAT = 0;
public AutoScrollViewPager(Context paramContext) {
super(paramContext);
init();
}
public AutoScrollViewPager(Context paramContext, AttributeSet paramAttributeSet) {
super(paramContext, paramAttributeSet);
init();
}
private void init() {
handler = new MyHandler(this);
setViewPagerScroller();
}
/**
* start auto scroll, first scroll delay time is {#link #getInterval()}
*/
public void startAutoScroll() {
isAutoScroll = true;
sendScrollMessage((long)(interval + scroller.getDuration() / autoScrollFactor * swipeScrollFactor));
}
/**
* start auto scroll
*
* #param delayTimeInMills first scroll delay time
*/
public void startAutoScroll(int delayTimeInMills) {
isAutoScroll = true;
sendScrollMessage(delayTimeInMills);
}
/**
* stop auto scroll
*/
public void stopAutoScroll() {
isAutoScroll = false;
handler.removeMessages(SCROLL_WHAT);
}
/**
* set the factor by which the duration of sliding animation will change while swiping
*/
public void setSwipeScrollDurationFactor(double scrollFactor) {
swipeScrollFactor = scrollFactor;
}
/**
* set the factor by which the duration of sliding animation will change while auto scrolling
*/
public void setAutoScrollDurationFactor(double scrollFactor) {
autoScrollFactor = scrollFactor;
}
private void sendScrollMessage(long delayTimeInMills) {
/** remove messages before, keeps one message is running at most **/
handler.removeMessages(SCROLL_WHAT);
handler.sendEmptyMessageDelayed(SCROLL_WHAT, delayTimeInMills);
}
/**
* set ViewPager scroller to change animation duration when sliding
*/
private void setViewPagerScroller() {
try {
Field scrollerField = ViewPager.class.getDeclaredField("mScroller");
scrollerField.setAccessible(true);
Field interpolatorField = ViewPager.class.getDeclaredField("sInterpolator");
interpolatorField.setAccessible(true);
scroller = new CustomDurationScroller(getContext(), (Interpolator)interpolatorField.get(null));
scrollerField.set(this, scroller);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* scroll only once
*/
public void scrollOnce() {
PagerAdapter adapter = getAdapter();
int currentItem = getCurrentItem();
int totalCount;
if (adapter == null || (totalCount = adapter.getCount()) <= 1) {
return;
}
int nextItem = (direction == LEFT) ? --currentItem : ++currentItem;
if (nextItem < 0) {
if (isCycle) {
setCurrentItem(totalCount - 1, isBorderAnimation);
}
} else if (nextItem == totalCount) {
if (isCycle) {
setCurrentItem(0, isBorderAnimation);
}
} else {
setCurrentItem(nextItem, true);
}
}
/**
* <ul>
* if stopScrollWhenTouch is true
* <li>if event is down, stop auto scroll.</li>
* <li>if event is up, start auto scroll again.</li>
* </ul>
*/
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int action = ev.getActionMasked();
if (stopScrollWhenTouch) {
if ((action == MotionEvent.ACTION_DOWN) && isAutoScroll) {
isStopByTouch = true;
stopAutoScroll();
} else if (ev.getAction() == MotionEvent.ACTION_UP && isStopByTouch) {
startAutoScroll();
}
}
if (slideBorderMode == SLIDE_BORDER_MODE_TO_PARENT || slideBorderMode == SLIDE_BORDER_MODE_CYCLE) {
touchX = ev.getX();
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
downX = touchX;
}
int currentItem = getCurrentItem();
PagerAdapter adapter = getAdapter();
int pageCount = adapter == null ? 0 : adapter.getCount();
/**
* current index is first one and slide to right or current index is last one and slide to left.<br/>
* if slide border mode is to parent, then requestDisallowInterceptTouchEvent false.<br/>
* else scroll to last one when current item is first one, scroll to first one when current item is last
* one.
*/
if ((currentItem == 0 && downX <= touchX) || (currentItem == pageCount - 1 && downX >= touchX)) {
if (slideBorderMode == SLIDE_BORDER_MODE_TO_PARENT) {
getParent().requestDisallowInterceptTouchEvent(false);
} else {
if (pageCount > 1) {
setCurrentItem(pageCount - currentItem - 1, isBorderAnimation);
}
getParent().requestDisallowInterceptTouchEvent(true);
}
return super.dispatchTouchEvent(ev);
}
}
getParent().requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(ev);
}
private static class MyHandler extends Handler {
private final WeakReference<AutoScrollViewPager> autoScrollViewPager;
public MyHandler(AutoScrollViewPager autoScrollViewPager) {
this.autoScrollViewPager = new WeakReference<AutoScrollViewPager>(autoScrollViewPager);
}
#Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case SCROLL_WHAT:
AutoScrollViewPager pager = this.autoScrollViewPager.get();
if (pager != null) {
pager.scroller.setScrollDurationFactor(pager.autoScrollFactor);
pager.scrollOnce();
pager.scroller.setScrollDurationFactor(pager.swipeScrollFactor);
pager.sendScrollMessage(pager.interval + pager.scroller.getDuration());
}
default:
break;
}
}
}
/**
* get auto scroll time in milliseconds, default is {#link #DEFAULT_INTERVAL}
*
* #return the interval
*/
public long getInterval() {
return interval;
}
/**
* set auto scroll time in milliseconds, default is {#link #DEFAULT_INTERVAL}
*
* #param interval the interval to set
*/
public void setInterval(long interval) {
this.interval = interval;
}
/**
* get auto scroll direction
*
* #return {#link #LEFT} or {#link #RIGHT}, default is {#link #RIGHT}
*/
public int getDirection() {
return (direction == LEFT) ? LEFT : RIGHT;
}
/**
* set auto scroll direction
*
* #param direction {#link #LEFT} or {#link #RIGHT}, default is {#link #RIGHT}
*/
public void setDirection(int direction) {
this.direction = direction;
}
/**
* whether automatic cycle when auto scroll reaching the last or first item, default is true
*
* #return the isCycle
*/
public boolean isCycle() {
return isCycle;
}
/**
* set whether automatic cycle when auto scroll reaching the last or first item, default is true
*
* #param isCycle the isCycle to set
*/
public void setCycle(boolean isCycle) {
this.isCycle = isCycle;
}
/**
* whether stop auto scroll when touching, default is true
*
* #return the stopScrollWhenTouch
*/
public boolean isStopScrollWhenTouch() {
return stopScrollWhenTouch;
}
/**
* set whether stop auto scroll when touching, default is true
*
* #param stopScrollWhenTouch
*/
public void setStopScrollWhenTouch(boolean stopScrollWhenTouch) {
this.stopScrollWhenTouch = stopScrollWhenTouch;
}
/**
* get how to process when sliding at the last or first item
*
* #return the slideBorderMode {#link #SLIDE_BORDER_MODE_NONE}, {#link #SLIDE_BORDER_MODE_TO_PARENT},
* {#link #SLIDE_BORDER_MODE_CYCLE}, default is {#link #SLIDE_BORDER_MODE_NONE}
*/
public int getSlideBorderMode() {
return slideBorderMode;
}
/**
* set how to process when sliding at the last or first item
*
* #param slideBorderMode {#link #SLIDE_BORDER_MODE_NONE}, {#link #SLIDE_BORDER_MODE_TO_PARENT},
* {#link #SLIDE_BORDER_MODE_CYCLE}, default is {#link #SLIDE_BORDER_MODE_NONE}
*/
public void setSlideBorderMode(int slideBorderMode) {
this.slideBorderMode = slideBorderMode;
}
/**
* whether animating when auto scroll at the last or first item, default is true
*
* #return
*/
public boolean isBorderAnimation() {
return isBorderAnimation;
}
/**
* set whether animating when auto scroll at the last or first item, default is true
*
* #param isBorderAnimation
*/
public void setBorderAnimation(boolean isBorderAnimation) {
this.isBorderAnimation = isBorderAnimation;
}
}
here CustomDurationScroller class:
public class CustomDurationScroller extends Scroller {
private double scrollFactor = 1;
public CustomDurationScroller(Context context) {
super(context);
}
public CustomDurationScroller(Context context, Interpolator interpolator) {
super(context, interpolator);
}
public void setScrollDurationFactor(double scrollFactor) {
this.scrollFactor = scrollFactor;
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, (int)(duration * scrollFactor));
}
}
and set the adapter same as you previously set.
Here is the total code using TimerTask:
public class MainActivity extends AppCompatActivity {
ViewPager viewPager;
Integer[] imageId = {R.drawable.image1, R.drawable.image2, R.drawable.image3, R.drawable.image4};
String[] imagesName = {"image1","image2","image3","image4"};
Timer timer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
viewPager = (ViewPager) findViewById(R.id.viewPager);
PagerAdapter adapter = new CustomAdapter(MainActivity.this,imageId,imagesName);
viewPager.setAdapter(adapter);
TimerTask timerTask = new TimerTask() {
#Override
public void run() {
viewPager.post(new Runnable(){
#Override
public void run() {
viewPager.setCurrentItem((viewPager.getCurrentItem()+1)%imageId.length);
}
});
}
};
timer = new Timer();
timer.schedule(timerTask, 3000, 3000);
}
#Override
protected void onDestroy() {
timer.cancel();
super.onDestroy();
}
}
Create handler in activity, then schedule a task. I think Handler is enough for this small task. Don't go for timer.
Runnable timeCounter = new Runnable() {
#Override
public void run() {
if((currentIndex+1)>imageId.length() ){
currentIndex=0;
}else{
currentIndex++;
}
ViewPager.setCurrentItem(currentIndex);
handler.postDelayed(timeCounter, 3*1000);
}
};
handler.postDelayed(timeCounter, 3*1000);
then in onDestroy() or where ever you want to stop
handler.removeCallbacks(timeCounter);
Another version of the answer:-
private int currentPage = -1;
// start auto scroll of viewpager
final Handler handler = new Handler();
final Runnable Update = new Runnable() {
public void run() {
viewPager.setCurrentItem(++currentPage, true);
// go to initial page i.e. position 0
if (currentPage == NUM_PAGES -1) {
currentPage = -1;
// ++currentPage will make currentPage = 0
}
}
};
timer = new Timer(); // This will create a new Thread
timer.schedule(new TimerTask() { // task to be scheduled
#Override
public void run() {
handler.post(Update);
}
}, 500, 1500);
a simple edit to #L. Swifter's code snippet for the ones wondering about variables, I wrapped it all in a method which you can add to your activity after setting the adapter
private void automateViewPagerSwiping() {
final long DELAY_MS = 500;//delay in milliseconds before task is to be executed
final long PERIOD_MS = 3000; // time in milliseconds between successive task executions.
final Handler handler = new Handler();
final Runnable update = new Runnable() {
public void run() {
if (viewPager.getCurrentItem() == adapter.getCount() - 1) { //adapter is your custom ViewPager's adapter
viewPager.setCurrentItem(0);
}
else {
viewPager.setCurrentItem(viewPager.getCurrentItem() + 1, true);
}
}
};
timer = new Timer(); // This will create a new Thread
timer.schedule(new TimerTask() { // task to be scheduled
#Override
public void run() {
handler.post(update);
}
}, DELAY_MS, PERIOD_MS);
}
For a simple solution to show a series of images automatically try the ViewFlipper in your xml file. Not suitable for all purposes, but I found it to be a useful solution to something I was putting together.
<ViewFlipper
android:id="#+id/viewflipper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoStart="true"
android:flipInterval="2000" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/picture1" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/picture2" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/picture3" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/picture4" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/picture5" />
Auto swipe ViewPager in Kotlin, simple and easy code.(if you have only 2 page)
val handler = Handler()
val update = Runnable {
viewPager.setCurrentItem(currentPage % 2, true);
currentPage++
}
var timer = Timer()// This will create a new Thread
timer!!.schedule(object : TimerTask() {
override fun run() {
handler.post(update)
}
}, 500(DELAY_MS), 3000(PERIOD_MS))
try this code:
In MainActivity -
int currentIndex=0; //for tracking current item
Create and set your TimerTask as per your requirement then in run() of TimerTask:
public void run() {
if((currentIndex+1)>imageId.length() ){
currentIndex=0;
}else{
currentIndex++;
}
ViewPager.setCurrentItem(currentIndex);
}
int ci=0;
java.util.Timer timer;
timer=new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
viewPager.post(new Runnable() {
#Override
public void run() {
Log.d("viewPager",""+ci);
viewPager.setCurrentItem(ci%7);
ci++;
}
});
}
},1000,5000);
it updates every 5seconds(5000)
You can use android TabLayout for indicators, ViewPager for slide screen and TimerTask to slide automatic
please check this link for step by step guideline and demo
CLICK HERE
Try this in your onCreate() method
final Handler handler = new Handler();
Timer timer = new Timer();
final Runnable runnable = new Runnable() {
public void run() {
int currentPage=viewPager.getCurrentItem();
//return to first page, if current page is last page
if (currentPage == titleNames.length-1) {
currentPage = -1;
}
viewPager.setCurrentItem(++currentPage, true);
}
};
timer.schedule(new TimerTask() {
#Override
public void run() {
handler.post(runnable);
}
},DELAY,PERRIOD)
using Kotlin coroutine
val totalPages = 3
CoroutineScope(Dispatchers.Main).launch {
while (isActive) {
delay(1500)
if (viewPager.currentItem + 1 > totalPages-1) {
viewPager.currentItem = 0
} else {
viewPager.currentItem++
}
}
}
Try to use ViewFlipper instead of viewpager
Layout xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ViewFlipper
android:id="#+id/imageFrames"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#android:drawable/screen_background_dark" >
</ViewFlipper>
<Button
android:id="#+id/slideShowBtn"
android:layout_width="200dp"
android:layout_height="wrap_content"
Activity
public class SlideShowActivity extends Activity {
ViewFlipper imageFrame;
Button slideShowBtn;
Handler handler;
Runnable runnable;
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.photo_slideshow_main);
imageFrame = (ViewFlipper) findViewById(R.id.imageFrames);
//get sd card path for images
File parentFolder = new
File(Environment.getExternalStorageDirectory()
.getAbsolutePath()
+ "/images");
addFlipperImages(imageFrame, parentFolder);
handler = new Handler();
imageFrame.setOnClickListener(SlideShowActivity.this);
slideShowBtn = (Button) findViewById(R.id.slideShowBtn);
slideShowBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View arg0) {
runnable = new Runnable() {
#Override
public void run() {
handler.postDelayed(runnable, 3000);
imageFrame.showNext();
}
};
handler.postDelayed(runnable, 500);
}
});
}
private void addFlipperImages(ViewFlipper flipper, File parent) {
int imageCount = parent.listFiles().length;
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.FILL_PARENT,
RelativeLayout.LayoutParams.FILL_PARENT);
for (int count = 0; count < imageCount - 1; count++) {
ImageView imageView = new ImageView(this);
Bitmap imbm = BitmapFactory.decodeFile(parent.listFiles()[count]
.getAbsolutePath());
imageView.setImageBitmap(imbm);
imageView.setLayoutParams(params);
flipper.addView(imageView);
}
}
}
#Override
public void onClick(View view) {
}
}
Related
I have looked at the github resource and also here but I'm unable to get my graph to display live data. Here's my code.
public class Blink_HR extends Fragment {
TextView textView;
LinearLayout linearLayout;
DecoView mDecoView;
private int mBackIndex;
private int mSeries1Index;
private int mSeries2Index;
private int mSeries3Index;
private final float
mSeriesMax = 50f;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.blink_hr, container, false);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
textView = (TextView) getActivity().findViewById(R.id.meditation);
linearLayout = (LinearLayout) getView().findViewById(R.id.blinkHR);
mDecoView = (DecoView)getActivity().findViewById(R.id.dynamicArcView);
mDecoView.addEvent(new DecoEvent.Builder(mSeriesMax)
.setIndex(mBackIndex)
.setDuration(10)
.build());
SeriesItem seriesItem = new SeriesItem.Builder(Color.parseColor("#FFE2E2E2"))
.setRange(0, mSeriesMax, 0)
.setInitialVisibility(true)
.build();
mBackIndex = mDecoView.addSeries(seriesItem);
}
void update(int id, int value) {
String heart = String.valueOf(value);
Log.d("Blink Hai", heart);
if (value > 0 && mDecoView!=null && mSeries1Index!=0) {
SeriesItem seriesItem = new SeriesItem.Builder(Color.parseColor("#FFFF8800"))
.setRange(0, (float)value, 0)
.setInitialVisibility(false)
.build();
mSeries1Index = mDecoView.addSeries(seriesItem);
}
if (mDecoView != null) {
mDecoView.addEvent(new DecoEvent.Builder(42.4f)
.setIndex(mSeries1Index)
.setDelay(3250)
.build());
mDecoView.executeReset();
}
}
}
My update function is called every 1 second and I would expect the graph to update this data in real-time. However all I get is a blimp on the screen.
There is a couple of issues with the code
An event is added to the series mBackIndex before mBackIndex has been initialized
Update is triggered every 1 second but a 3.25 second delay is added to the event before it will be processed
The event on update always sets the DecoView position to 42.4
executeReset() is called every time the update is triggered, this
resets all series in the charts and cancels all pending animations
Here is some sample code that will update a DecoView every 1 second to a random position with animation
public class FauxFitActivity extends AppCompatActivity {
private DecoView mDecoView;
private int mSeries1Index;
private final float mSeriesMax = 50f;
private Handler mHandler = new Handler();
Runnable runnable = new Runnable() {
#Override
public void run() {
update();
mHandler.postDelayed(this, 1000);
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_faux_fit);
mDecoView = (DecoView) findViewById(R.id.dynamicArcView);
createDataSeries1();
// Start the timer
mHandler.post(runnable);
}
private void createDataSeries1() {
final SeriesItem seriesItem = new SeriesItem.Builder(Color.parseColor("#FFFF8800"))
.setRange(0, mSeriesMax, 0)
.setInitialVisibility(false)
.build();
mSeries1Index = mDecoView.addSeries(seriesItem);
}
private void update() {
final Random rand = new Random();
int newPosition = rand.nextInt((int)mSeriesMax);
mDecoView.addEvent(new DecoEvent.Builder(newPosition).setIndex(mSeries1Index).setDuration(1000).build());
}
}
I am trying to implement a FlipCard behavior in a ListView for my items and the bug is that my convertView don't update its visibility state according to the visibility I set in the getView method. It's like nobody cares of of my visibility changes.
To reproduce the problem: click on an item picture (sun, cloud...), it will flip the item and present its backside. Then scroll up or down until the convertView flipped is reused by a View that is not flipped. The not flipped view will not display its content anymore.
The first item should displays its content but it displays nothing because the convertView used (the one gave by the getView parameter) had its visibility set to GONE the last time it was used.
You can find the full project here:
https://github.com/MathiasSeguy-Android2EE/ForecastYahooRest and you have to check out the branch "flipcard"
So the ArrayAdapter involves:
package com.android2ee.formation.restservice.sax.forecastyahoo.view.forecast.arrayadpater;
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.AnimatorSet;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.text.format.DateFormat;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android2ee.formation.restservice.sax.forecastyahoo.R;
import com.android2ee.formation.restservice.sax.forecastyahoo.transverse.model.YahooForcast;
import java.util.List;
/**
* #author Mathias Seguy (Android2EE)
* #goals
* This class aims to display the forecast in the listView
*/
public class ForecastArrayAdapter extends ArrayAdapter<YahooForcast> {
/**
* Handler to launch the animation runnable
*/
Handler handlerForAnimation;
/**
* To know when the item is flipped or not
* When flipped it show us its back side else its front side
*/
SparseBooleanArray isFlipped;
/**
* To detect the first launch
*/
int notifyDataSetChangedCallsNumber = 0;
/**
* The layout inflater
*/
LayoutInflater inflater;
/**
* The Context
*/
Context ctx;
/**
* To know if the device is postJellyBean or not
*/
boolean postJB;
/**
* To know if the device is postHoneyComb or not
*/
boolean postHC;
/**
* Drawable used for the backside of the item
*/
Drawable[] drawableBackground;
/**
*
* #param context
* #param forecast
*/
public ForecastArrayAdapter(Context context, List<YahooForcast> forecast) {
super(context, R.layout.item_forecast, forecast);
inflater = LayoutInflater.from(context);
ctx = context;
postJB = context.getResources().getBoolean(R.bool.postJB);
postHC = context.getResources().getBoolean(R.bool.postHC);
//instantiate the handler
handlerForAnimation = new Handler();
isFlipped=new SparseBooleanArray();
drawableBackground=new Drawable[5];
drawableBackground[0]=context.getResources().getDrawable(R.drawable.back1);
drawableBackground[1]=context.getResources().getDrawable(R.drawable.back2);
drawableBackground[2]=context.getResources().getDrawable(R.drawable.back3);
drawableBackground[3]=context.getResources().getDrawable(R.drawable.back4);
drawableBackground[4]=context.getResources().getDrawable(R.drawable.back5);
}
/**
* Private static better than temp
*/
private static View rowView;
/**
* Private static better than temp
*/
private static YahooForcast forcast;
/**
* Private static better than temp
*/
private static ViewHolder viewHolder;
/*
* (non-Javadoc)
*
* #see android.widget.ArrayAdapter#getView(int, android.view.View, android.view.ViewGroup)
*/
#SuppressLint("NewApi")
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Log.e("ForecastArrayAdapter","getView "+position);
rowView = convertView;
forcast = getItem(position);
if (rowView == null) {
// always add the layout, the parent and false
rowView = inflater.inflate(R.layout.item_forecast, null, false);
ViewHolder vh = new ViewHolder(rowView,position);
rowView.setTag(vh);
}
viewHolder = (ViewHolder) rowView.getTag();
//used for animation
viewHolder.currentPosition=position;
if (postJB) {
viewHolder.getImvIcon().setBackground(forcast.getImage());
viewHolder.getImvBack().setBackground(drawableBackground[position%5]);
} else {
viewHolder.getImvIcon().setBackgroundDrawable(forcast.getImage());
viewHolder.getImvBack().setBackgroundDrawable(drawableBackground[position % 5]);
}
if (forcast.getDate() != null) {
viewHolder.getTxvDate().setText(DateFormat.format("E dd MMM", forcast.getDate()));
} else {
viewHolder.getTxvDate().setText("unknown");
}
viewHolder.getTxvTendance().setText(forcast.getTendance());
if (forcast.getTempMax() != -1000) {
viewHolder.getTxvMax().setVisibility(View.VISIBLE);
viewHolder.getTxvMin().setVisibility(View.VISIBLE);
viewHolder.getTxvMax().setText(ctx.getString(R.string.max, forcast.getTempMax()));
viewHolder.getTxvMin().setText(ctx.getString(R.string.min, forcast.getTempMin()));
} else {
viewHolder.getTxvMax().setVisibility(View.GONE);
viewHolder.getTxvMin().setVisibility(View.GONE);
}
if (forcast.getTemp() != -1000) {
viewHolder.getTxvCurrent().setVisibility(View.VISIBLE);
viewHolder.getTxvCurrent().setText(ctx.getString(R.string.temp, forcast.getTemp()));
} else {
viewHolder.getTxvCurrent().setVisibility(View.GONE);
}
// launch animations to show the update to the user (not the first time but only when refreshing)
//because the first time is not an update, it's just loading data from db
if (notifyDataSetChangedCallsNumber >=2) {
viewHolder.launchUpdateAnimation(notifyDataSetChangedCallsNumber);
}
//and finally manage the visibility of the side : front or back side is visible
manageSideVisibility(position);
return rowView;
}
/* (non-Javadoc)
* #see android.widget.ArrayAdapter#notifyDataSetChanged()
*/
#Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
notifyDataSetChangedCallsNumber++;
}
/**************************************************
* Flipping Animation tricks
* **************************************************
*/
/**
* If the element has been flipped, flip it else set it has not flipped
* #param position
*/
private void manageSideVisibility(int position){
if(isFlipped.get(position)){
//the backside is visible
viewHolder.getImvBack().setVisibility(View.VISIBLE);
viewHolder.getLinRoot().setVisibility(View.GONE);
}else{
//the ffront is visible
viewHolder.getImvBack().setVisibility(View.GONE);
viewHolder.getLinRoot().setVisibility(View.VISIBLE);
}
}
/******************************************************************************************/
/** Runnable for animation **************************************************************************/
/******************************************************************************************/
public class MyRunnable implements Runnable {
/**
* The viewHolder that contains the view to animate
*/
private ViewHolder vh;
public MyRunnable(ViewHolder vh) {
this.vh=vh;
}
public void run() {
vh.animateUpdate();
}
}
/******************************************************************************************/
/** The ViewHolder pattern **************************************************************************/
/******************************************************************************************/
private class ViewHolder {
View view;
LinearLayout linRoot;
TextView txvDate;
TextView txvTendance;
ImageView imvIcon;
TextView txvCurrent;
TextView txvMin;
TextView txvMax;
TextView txvUpdating;
//For Update animation
Animation updateAnimation;
MyRunnable animationRunnable;
int dataTimeStamp=0;
//For animatibbbbbbon
ImageView imvBack;
int currentPosition;
//PostHoneyComb
Animator flipAnimatorIn;
Animator flipAnimatorOut;
Animator reverseFlipAnimatorIn;
Animator reverseFlipAnimatorOut;
AnimatorSet setFlip;
AnimatorSet setReverse;
//PreHoneyComb
Animation animInLegacy;
Animation animOutLegacy;
int id;
/**
* #param rowview
*/
private ViewHolder(View rowview,int position) {
super();
this.view = rowview;
animationRunnable=new MyRunnable(this);
id=position;
}
/**
* #return the txvDate
*/
public final TextView getTxvDate() {
if (null == txvDate) {
txvDate = (TextView) view.findViewById(R.id.date);
}
return txvDate;
}
/**
* #return the txvTendance
*/
public final TextView getTxvTendance() {
if (null == txvTendance) {
txvTendance = (TextView) view.findViewById(R.id.txv_tendance);
}
return txvTendance;
}
/**
* #return the imvIcon
*/
public final ImageView getImvIcon() {
if (null == imvIcon) {
imvIcon = (ImageView) view.findViewById(R.id.icon);
imvIcon.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(postHC){
animateItem();
}else{
flipItemLegacy();
}
}
});
}
return imvIcon;
}
/**
* #return the imvBack
*/
public final ImageView getImvBack() {
if (null == imvBack) {
imvBack = (ImageView) view.findViewById(R.id.imvBack);
imvBack.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(postHC){
reverseAnimateItem();
}else{
reverseItemLegacy();
}
}
});
}
return imvBack;
}
/**
* #return the txvTendance
*/
public final TextView getTxvUpdating() {
if (null == txvUpdating) {
txvUpdating = (TextView) view.findViewById(R.id.txv_updating);
}
return txvUpdating;
}
/**
* #return the txvCurrent
*/
public final TextView getTxvCurrent() {
if (null == txvCurrent) {
txvCurrent = (TextView) view.findViewById(R.id.txv_current);
txvCurrent.setText("Toto");
}
return txvCurrent;
}
/**
* #return the txvMin
*/
public final TextView getTxvMin() {
if (null == txvMin) {
txvMin = (TextView) view.findViewById(R.id.txv_min);
}
return txvMin;
}
/**
* #return the txvMax
*/
public final TextView getTxvMax() {
if (null == txvMax) {
txvMax = (TextView) view.findViewById(R.id.txv_max);
}
return txvMax;
}
/**
* #return the linRoot
*/
public final LinearLayout getLinRoot() {
if (null == linRoot) {
linRoot = (LinearLayout) view.findViewById(R.id.lay_item);
}
return linRoot;
}
/**************************************************
* Animation tricks
* All Version
* The UpdateAnimation
* **************************************************
*/
/**
* Launch the Update Animation
*/
public void animateUpdate() {
if (updateAnimation==null) {
updateAnimation=AnimationUtils.loadAnimation(getContext(), R.anim.anim_item_updated);
updateAnimation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
getTxvUpdating().setVisibility(View.VISIBLE);}
#Override
public void onAnimationEnd(Animation animation) {
getTxvUpdating().setVisibility(View.GONE);
}
#Override
public void onAnimationRepeat(Animation animation) {}
});
}
if (isFlipped.get(currentPosition)) {
getImvBack().startAnimation(updateAnimation);
} else {
//run it
getLinRoot().startAnimation(updateAnimation);
}
}
/**
* Launch the Update Animation
*/
public void launchUpdateAnimation(int ndscCallsNumber){
if(dataTimeStamp!=ndscCallsNumber) {
//it means an already runnable is associated with this item
//we need to remove it (else it gonna run the animation twice
//and it's strange for the user)
handlerForAnimation.removeCallbacks(animationRunnable);
//then launched it in few seconds
handlerForAnimation.postDelayed(animationRunnable, 300 * currentPosition);
Log.e("tag", "launchUpdateAnimation in " + 300 * currentPosition + " for item " + currentPosition);
dataTimeStamp=ndscCallsNumber;
}
}
/**************************************************
* Animation tricks
* preHoneyComb : 4 Gingerbread in fact
* **************************************************
*/
private void flipItemLegacy(){
if(animInLegacy==null){
animInLegacy= AnimationUtils.loadAnimation(getContext(),R.anim.forecast_item_in);
}
if(animOutLegacy==null){
animOutLegacy= AnimationUtils.loadAnimation(getContext(),R.anim.forecast_item_out);
}
animOutLegacy.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {}
public void onAnimationEnd(Animation animation) {
Log.e("ForecastArrayAdapter","flipItemLegacy onAnimationEnd called ");
getImvBack().setVisibility(View.VISIBLE);
getImvBack().startAnimation(animInLegacy);
getLinRoot().setVisibility(View.GONE);
}
public void onAnimationRepeat(Animation animation) {}
});
getLinRoot().startAnimation(animOutLegacy);
isFlipped.put(currentPosition,true);
}
private void reverseItemLegacy(){
if(animInLegacy==null){
animInLegacy= AnimationUtils.loadAnimation(getContext(),R.anim.forecast_item_in);
}
if(animOutLegacy==null){
animOutLegacy= AnimationUtils.loadAnimation(getContext(),R.anim.forecast_item_out);
}
animInLegacy.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {}
public void onAnimationEnd(Animation animation) {
getLinRoot().setVisibility(View.VISIBLE);
getLinRoot().startAnimation(animInLegacy);
getImvBack().setVisibility(View.GONE);
}
public void onAnimationRepeat(Animation animation) {}
});
getImvBack().startAnimation(animOutLegacy);
isFlipped.put(currentPosition,false);
}
/**************************************************
* Animation tricks
* postHoneyComb
* **************************************************
*/
#SuppressLint("NewApi")
private void animateItem(){
initialiseFlipAnimator();
setFlip.start();
isFlipped.put(currentPosition,true);
}
#SuppressLint("NewApi")
private void reverseAnimateItem(){
initialiseReverseFlipAnimator();
setReverse.start();
isFlipped.put(currentPosition,false);
}
#SuppressLint("NewApi")
private void initialiseReverseFlipAnimator() {
if(reverseFlipAnimatorIn==null){
reverseFlipAnimatorIn= AnimatorInflater.loadAnimator(getContext(), R.animator.flip_in);
reverseFlipAnimatorIn.addListener(new SimpleAnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
getLinRoot().setVisibility(View.VISIBLE);
}
#Override
public void onAnimationEnd(Animator animation) {
getImvBack().setVisibility(View.GONE);
}
});
reverseFlipAnimatorIn.setTarget(getLinRoot());
reverseFlipAnimatorOut= AnimatorInflater.loadAnimator(getContext(),R.animator.flip_out);
reverseFlipAnimatorOut.setTarget(imvBack);
setReverse=new AnimatorSet();
setReverse.playTogether(reverseFlipAnimatorIn,reverseFlipAnimatorOut);
}
}
#SuppressLint("NewApi")
private void initialiseFlipAnimator(){
Log.e("ForecastArrayAdapter","initialiseFlipAnimator");
if(flipAnimatorIn==null){
flipAnimatorIn= AnimatorInflater.loadAnimator(getContext(),R.animator.flip_in);
flipAnimatorIn.setTarget(getImvBack());
flipAnimatorOut= AnimatorInflater.loadAnimator(getContext(),R.animator.flip_out);
flipAnimatorOut.setTarget(getLinRoot());
flipAnimatorIn.addListener(new SimpleAnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
Log.e("tag","anim onAnimationStart");
getImvBack().setVisibility(View.VISIBLE);
}
#Override
public void onAnimationEnd(Animator animation) {
Log.e("tag","anim onAnimationEnd");
getLinRoot().setVisibility(View.GONE);
}
});
setFlip=new AnimatorSet();
setFlip.playTogether(flipAnimatorIn, flipAnimatorOut);
}
}
}
#SuppressLint("NewApi")
public abstract class SimpleAnimatorListener implements Animator.AnimatorListener {
/**
* <p>Notifies the start of the animation.</p>
*
* #param animation The started animation.
*/
public abstract void onAnimationStart(Animator animation);
/**
* <p>Notifies the end of the animation. This callback is not invoked
* for animations with repeat count set to INFINITE.</p>
*
* #param animation The animation which reached its end.
*/
public abstract void onAnimationEnd(Animator animation) ;
/**
* <p>Notifies the cancellation of the animation. This callback is not invoked
* for animations with repeat count set to INFINITE.</p>
*
* #param animation The animation which was canceled.
*/
#Override
public void onAnimationCancel(Animator animation) {
onAnimationEnd(animation);
}
/**
* <p>Notifies the repetition of the animation.</p>
*
* #param animation The animation which was repeated.
*/
#Override
public void onAnimationRepeat(Animator animation) {
onAnimationStart(animation);
}
}
}
Ok, I dive into that bug and I still don't understand (I had a lot of logs) so
my problem is here, the view tells me, it's Visible,
but it's not displayed
A simple way to reproduce the problem, go in landscape mode, flip the first two items, scroll to the end of the list.
Thanks a billion to those who will try to answer.
Mathias
Youpi, I found !!
Summary
Ok, for me, it's a bug or an over optimised behavior.
So when my view is flipped I hide the front to show the back, and unflipped, I hide the back to show the front.
The problem is if I hide the front in the view v1 associated with the item n1. I scroll. Then this view is reused as a convertView in the getView method with the items n2. But for the item n2, we display the front...
And the bug occurs: The front view has been almost deleted/garbage collected or what ever but is not here anymore. It answers to functions' calls but its inner state is wrong. So It tells you, I am visible but it's not, it's a ghost.
My comprehension:
What I think (it's an hypothesis)
So the point here is an over optimisation of the ListView:
When a view went in the pool of convert views, the system destroys the ressources that are in the "Visibility Gone" state. I mean the Views of the ViewGroup root that have Visibility=Gone.
My comprehension is Wrong
So Romain Guy told me, "ListView does not destroy GONE views. Esp. since it doesn't look at children of recycled views. And if you can call a method on a View it has clearly not been GC'd. It could be a bug in the UI Toolkit drawing or in the adapter."
.... Ok, I will continue do dive in my problem to understand so.
The solution:
So the solution is obvious, I need two convert views pools, one with front visible by default, the other with back visible by default. So I create two layouts, one with the front visible, the other with the back visible, i use the getViewTypeCount() and the getItemViewType(int position) methods, and 3 minutes latter it was working.
The conclusion
As conclusion, when, in ListView, you hide and show elements in your items, you need to define as much as configurations as convertViews pools.
:( I am sad to understand that and if the bug was not there to prove it, I will never believe that.
The project
You can find the full project here:
https://github.com/MathiasSeguy-Android2EE/ForecastYahooRest and you have to check out the branch "flipcard" is updated.
The Code:
package com.android2ee.formation.restservice.sax.forecastyahoo.view.forecast.arrayadpater;
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.AnimatorSet;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.text.format.DateFormat;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android2ee.formation.restservice.sax.forecastyahoo.R;
import com.android2ee.formation.restservice.sax.forecastyahoo.transverse.model.YahooForcast;
import java.util.List;
/**
* #author Mathias Seguy (Android2EE)
* #goals This class aims to display the forecast in the listView
*/
public class ForecastArrayAdapter extends ArrayAdapter<YahooForcast> {
/**
* Handler to launch the animation runnable
*/
Handler handlerForAnimation;
/**
* To know when the item is flipped or not
* When flipped it show us its back side else its front side
*/
SparseBooleanArray isFlipped;
/**
* To detect the first launch
*/
int notifyDataSetChangedCallsNumber = 0;
/**
* The layout inflater
*/
LayoutInflater inflater;
/**
* The Context
*/
Context ctx;
/**
* To know if the device is postJellyBean or not
*/
boolean postJB;
/**
* To know if the device is postHoneyComb or not
*/
boolean postHC;
/**
* Drawable used for the backside of the item
*/
Drawable[] drawableBackground;
int[] drawableRes;
/**
* #param context
* #param forecast
*/
public ForecastArrayAdapter(Context context, List<YahooForcast> forecast) {
super(context, R.layout.item_forecast, forecast);
inflater = LayoutInflater.from(context);
ctx = context;
postJB = context.getResources().getBoolean(R.bool.postJB);
postHC = context.getResources().getBoolean(R.bool.postHC);
//instantiate the handler
handlerForAnimation = new Handler();
isFlipped = new SparseBooleanArray(5);
drawableRes = new int[5];
drawableRes[0] = R.drawable.back1;
drawableRes[1] = R.drawable.back2;
drawableRes[2] = R.drawable.back3;
drawableRes[3] = R.drawable.back4;
drawableRes[4] = R.drawable.back5;
drawableBackground = new Drawable[5];
drawableBackground[0] = context.getResources().getDrawable(R.drawable.back1);
drawableBackground[1] = context.getResources().getDrawable(R.drawable.back2);
drawableBackground[2] = context.getResources().getDrawable(R.drawable.back3);
drawableBackground[3] = context.getResources().getDrawable(R.drawable.back4);
drawableBackground[4] = context.getResources().getDrawable(R.drawable.back5);
}
/**
* Private static better than temp
*/
private static View rowView;
/**
* Private static better than temp
*/
private static YahooForcast forcast;
/**
* Private static better than temp
*/
private static ViewHolder viewHolder;
/*
* (non-Javadoc)
*
* #see android.widget.ArrayAdapter#getView(int, android.view.View, android.view.ViewGroup)
*/
#SuppressLint("NewApi")
#Override
public View getView(int position, View convertView, ViewGroup parent) {
rowView = convertView;
forcast = getItem(position);
if (rowView == null) {
if(getItemViewType(position)==0){
//then used the layout of flipped view
// always add the layout, the parent and false
rowView = inflater.inflate(R.layout.item_forecast, null, false);
}else{
//then used the layout for not flipped view
// always add the layout, the parent and false
rowView = inflater.inflate(R.layout.item_forecast, null, false);
}
ViewHolder vh = new ViewHolder(rowView, position);
rowView.setTag(vh);
}
viewHolder = (ViewHolder) rowView.getTag();
//used for animation
viewHolder.setCurrentPosition(position);
if (postJB) {
viewHolder.getImvIcon().setBackground(forcast.getImage());
viewHolder.getImvBack().setBackground(drawableBackground[position % 5]);
} else {
viewHolder.getImvIcon().setBackgroundDrawable(forcast.getImage());
//if you don't use setBackgroundResource nothing happens on Gingerbread (deep sadness sometimes)
viewHolder.getImvBack().setBackgroundResource(drawableRes[position % 5]);
}
if (forcast.getDate() != null) {
viewHolder.getTxvDate().setText(DateFormat.format("E dd MMM", forcast.getDate()));
} else {
viewHolder.getTxvDate().setText("unknown");
}
viewHolder.getTxvTendance().setText(forcast.getTendance());
if (forcast.getTempMax() != -1000) {
viewHolder.getTxvMax().setVisibility(View.VISIBLE);
viewHolder.getTxvMin().setVisibility(View.VISIBLE);
viewHolder.getTxvMax().setText(ctx.getString(R.string.max, forcast.getTempMax()));
viewHolder.getTxvMin().setText(ctx.getString(R.string.min, forcast.getTempMin()));
} else {
viewHolder.getTxvMax().setVisibility(View.GONE);
viewHolder.getTxvMin().setVisibility(View.GONE);
}
if (forcast.getTemp() != -1000) {
viewHolder.getTxvCurrent().setVisibility(View.VISIBLE);
viewHolder.getTxvCurrent().setText(ctx.getString(R.string.temp, forcast.getTemp()));
} else {
viewHolder.getTxvCurrent().setVisibility(View.GONE);
}
// launch animations to show the update to the user (not the first time but only when refreshing)
//because the first time is not an update, it's just loading data from db
if (notifyDataSetChangedCallsNumber >= 2) {
viewHolder.launchUpdateAnimation(notifyDataSetChangedCallsNumber);
}
//and finally manage the visibility of the side : front or back side is visible
return rowView;
}
/* (non-Javadoc)
* #see android.widget.ArrayAdapter#notifyDataSetChanged()
*/
#Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
notifyDataSetChangedCallsNumber++;
}
/***********************************************************
* Trying to fix the bug of the visible view not displayed
* by managing 2 pools of views
**********************************************************/
#Override
public int getViewTypeCount() {
//Two pools: the one for flipped views, the other not flipped views
return 2;
}
#Override
public int getItemViewType(int position) {
//If the View is flipped then pick in the pool 0
//else pick in the pool 1
return isFlipped.get(position)?0:1;
}
/**************************************************
* Flipping Animation tricks
* **************************************************
*/
/**
* If the element has been flipped, flip it else set it has not flipped
*
* #param position
*/
private void manageSideVisibility(int position) {
if (isFlipped.get(position)) {
Log.e("ForecastArrayAdapter","ImvBack Visible"+position);
//the backside is visible
viewHolder.getImvBack().setVisibility(View.VISIBLE);
viewHolder.getLinRoot().setVisibility(View.GONE);
viewHolder.getImvBack().invalidate();
} else {
Log.e("ForecastArrayAdapter","ImvBack GONE"+position);
//the ffront is visible
viewHolder.getImvBack().setVisibility(View.GONE);
viewHolder.getLinRoot().setVisibility(View.VISIBLE);
viewHolder.getLinRoot().invalidate();
}
printView("ImvBack",viewHolder.getImvBack(),position);
printView("LinRoot",viewHolder.getLinRoot(),position);
}
public void printView(String viewName,View view,int position){
Log.e("ForecastArrayAdapter","("+viewName+","+position+") getWidth()="+view.getWidth());
Log.e("ForecastArrayAdapter","("+viewName+","+position+") getHeight()="+view.getHeight());
Log.e("ForecastArrayAdapter", "(" + viewName + "," + position + ") getHeight()=" + view.getBackground());
Log.e("ForecastArrayAdapter", "(" + viewName + "," + position + ") getVisibility()=" + getVisibility(view));
}
public String getVisibility(View view){
switch (view.getVisibility()){
case View.GONE:
return "GONE";
case View.VISIBLE:
return "VISIBLE";
case View.INVISIBLE:
return "INVISIBLE";
}
return "Unknow";
}
/******************************************************************************************/
/** Runnable for animation **************************************************************************/
/**
* **************************************************************************************
*/
public class MyRunnable implements Runnable {
/**
* The viewHolder that contains the view to animate
*/
private ViewHolder vh;
public MyRunnable(ViewHolder vh) {
this.vh = vh;
}
public void run() {
vh.animateUpdate();
}
}
public void printisFlipp(String methodName) {
for (int i = 0;i < 5; i++){
Log.e("ForecastArrayAdapter", "in("+methodName+") isFlipped[" + i + "]=" + isFlipped.get(i));
}
}
/******************************************************************************************/
/** The ViewHolder pattern **************************************************************************/
/******************************************************************************************/
private class ViewHolder {
View view;
LinearLayout linRoot;
TextView txvDate;
TextView txvTendance;
ImageView imvIcon;
TextView txvCurrent;
TextView txvMin;
TextView txvMax;
TextView txvUpdating;
//For Update animation
Animation updateAnimation;
MyRunnable animationRunnable;
int dataTimeStamp=0;
//For animation
ImageView imvBack;
int currentPosition;
public int getCurrentPosition() {
return currentPosition;
}
public void setCurrentPosition(int currentPosition) {
this.currentPosition = currentPosition;
}
//PostHoneyComb
Animator flipAnimatorIn;
Animator flipAnimatorOut;
Animator reverseFlipAnimatorIn;
Animator reverseFlipAnimatorOut;
AnimatorSet setFlip;
AnimatorSet setReverse;
//PreHoneyComb
Animation animInLegacy;
Animation animOutLegacy;
int id;
/**
* #param rowview
*/
private ViewHolder(View rowview,int position) {
super();
this.view = rowview;
animationRunnable=new MyRunnable(this);
id=position;
}
/**
* #return the txvDate
*/
public final TextView getTxvDate() {
if (null == txvDate) {
txvDate = (TextView) view.findViewById(R.id.date);
}
return txvDate;
}
/**
* #return the txvTendance
*/
public final TextView getTxvTendance() {
if (null == txvTendance) {
txvTendance = (TextView) view.findViewById(R.id.txv_tendance);
}
return txvTendance;
}
/**
* #return the imvIcon
*/
public final ImageView getImvIcon() {
if (null == imvIcon) {
imvIcon = (ImageView) view.findViewById(R.id.icon);
imvIcon.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(postHC){
animateItem();
}else{
flipItemLegacy();
}
}
});
}
return imvIcon;
}
/**
* #return the imvBack
*/
public final ImageView getImvBack() {
if (null == imvBack) {
imvBack = (ImageView) view.findViewById(R.id.imvBack);
imvBack.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(postHC){
reverseAnimateItem();
}else{
reverseItemLegacy();
}
}
});
}
return imvBack;
}
/**
* #return the txvTendance
*/
public final TextView getTxvUpdating() {
if (null == txvUpdating) {
txvUpdating = (TextView) view.findViewById(R.id.txv_updating);
}
return txvUpdating;
}
/**
* #return the txvCurrent
*/
public final TextView getTxvCurrent() {
if (null == txvCurrent) {
txvCurrent = (TextView) view.findViewById(R.id.txv_current);
txvCurrent.setText("Toto");
}
return txvCurrent;
}
/**
* #return the txvMin
*/
public final TextView getTxvMin() {
if (null == txvMin) {
txvMin = (TextView) view.findViewById(R.id.txv_min);
}
return txvMin;
}
/**
* #return the txvMax
*/
public final TextView getTxvMax() {
if (null == txvMax) {
txvMax = (TextView) view.findViewById(R.id.txv_max);
}
return txvMax;
}
/**
* #return the linRoot
*/
public final LinearLayout getLinRoot() {
if (null == linRoot) {
linRoot = (LinearLayout) view.findViewById(R.id.lay_item);
}
return linRoot;
}
/**************************************************
* Animation tricks
* All Version
* The UpdateAnimation
* **************************************************
*/
/**
* Launch the Update Animation
*/
public void animateUpdate() {
if (updateAnimation==null) {
updateAnimation=AnimationUtils.loadAnimation(getContext(), R.anim.anim_item_updated);
updateAnimation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
getTxvUpdating().setVisibility(View.VISIBLE);}
#Override
public void onAnimationEnd(Animation animation) {
getTxvUpdating().setVisibility(View.GONE);
}
#Override
public void onAnimationRepeat(Animation animation) {}
});
}
if (isFlipped.get(currentPosition)) {
getImvBack().startAnimation(updateAnimation);
} else {
//run it
getLinRoot().startAnimation(updateAnimation);
}
}
/**
* Launch the Update Animation
*/
public void launchUpdateAnimation(int ndscCallsNumber){
if(dataTimeStamp!=ndscCallsNumber) {
//it means an already runnable is associated with this item
//we need to remove it (else it gonna run the animation twice
//and it's strange for the user)
handlerForAnimation.removeCallbacks(animationRunnable);
//then launched it in few seconds
handlerForAnimation.postDelayed(animationRunnable, 300 * currentPosition);
dataTimeStamp=ndscCallsNumber;
}
}
/**************************************************
* Animation tricks
* preHoneyComb : 4 Gingerbread in fact
* **************************************************
*/
private void flipItemLegacy(){
initLegacyAnimation();
getLinRoot().startAnimation(animOutLegacy);
isFlipped.put(getCurrentPosition(), true);
}
private void reverseItemLegacy(){
initLegacyAnimation();
getImvBack().startAnimation(animInLegacy);
isFlipped.put(getCurrentPosition(),false);
}
private void initLegacyAnimation() {
if(animInLegacy==null){
animInLegacy= AnimationUtils.loadAnimation(getContext(), R.anim.forecast_item_in);
animInLegacy.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {}
public void onAnimationEnd(Animation animation) {
getLinRoot().setVisibility(View.VISIBLE);
getImvBack().setVisibility(View.GONE);
}
public void onAnimationRepeat(Animation animation) {}
});
}
if(animOutLegacy==null){
animOutLegacy= AnimationUtils.loadAnimation(getContext(),R.anim.forecast_item_out);
animOutLegacy.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {
}
public void onAnimationEnd(Animation animation) {
getImvBack().setVisibility(View.VISIBLE);
getLinRoot().setVisibility(View.GONE);
}
public void onAnimationRepeat(Animation animation) {
}
});
}
}
/**************************************************
* Animation tricks
* postHoneyComb
* **************************************************
*/
#SuppressLint("NewApi")
private void animateItem(){
initialiseFlipAnimator();
setFlip.start();
isFlipped.put(getCurrentPosition(), true);
printisFlipp("animateItem");
}
#SuppressLint("NewApi")
private void reverseAnimateItem(){
initialiseReverseFlipAnimator();
setReverse.start();
isFlipped.put(getCurrentPosition(), false);
printisFlipp("animateItem");
}
#SuppressLint("NewApi")
private void initialiseReverseFlipAnimator() {
if(reverseFlipAnimatorIn==null){
reverseFlipAnimatorIn= AnimatorInflater.loadAnimator(getContext(), R.animator.flip_in);
reverseFlipAnimatorIn.addListener(new SimpleAnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
getLinRoot().setVisibility(View.VISIBLE);
}
#Override
public void onAnimationEnd(Animator animation) {
getImvBack().setVisibility(View.GONE);
}
});
reverseFlipAnimatorIn.setTarget(getLinRoot());
reverseFlipAnimatorOut= AnimatorInflater.loadAnimator(getContext(),R.animator.flip_out);
reverseFlipAnimatorOut.setTarget(imvBack);
setReverse=new AnimatorSet();
setReverse.playTogether(reverseFlipAnimatorIn,reverseFlipAnimatorOut);
}
}
#SuppressLint("NewApi")
private void initialiseFlipAnimator(){
if(flipAnimatorIn==null){
flipAnimatorIn= AnimatorInflater.loadAnimator(getContext(),R.animator.flip_in);
flipAnimatorIn.setTarget(getImvBack());
flipAnimatorOut= AnimatorInflater.loadAnimator(getContext(),R.animator.flip_out);
flipAnimatorOut.setTarget(getLinRoot());
flipAnimatorIn.addListener(new SimpleAnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
getImvBack().setVisibility(View.VISIBLE);
}
#Override
public void onAnimationEnd(Animator animation) {
getLinRoot().setVisibility(View.GONE);
}
});
setFlip=new AnimatorSet();
setFlip.playTogether(flipAnimatorIn, flipAnimatorOut);
}
}
}
#SuppressLint("NewApi")
public abstract class SimpleAnimatorListener implements Animator.AnimatorListener {
/**
* <p>Notifies the start of the animation.</p>
*
* #param animation The started animation.
*/
public abstract void onAnimationStart(Animator animation);
/**
* <p>Notifies the end of the animation. This callback is not invoked
* for animations with repeat count set to INFINITE.</p>
*
* #param animation The animation which reached its end.
*/
public abstract void onAnimationEnd(Animator animation) ;
/**
* <p>Notifies the cancellation of the animation. This callback is not invoked
* for animations with repeat count set to INFINITE.</p>
*
* #param animation The animation which was canceled.
*/
#Override
public void onAnimationCancel(Animator animation) {
onAnimationEnd(animation);
}
/**
* <p>Notifies the repetition of the animation.</p>
*
* #param animation The animation which was repeated.
*/
#Override
public void onAnimationRepeat(Animator animation) {
onAnimationStart(animation);
}
}
}
I am making a slot machine app and using kankan's wheel for the same. I want to modify the library such that when the rotation stops the item it will point shoud be the one that I set . I have done this but there is a glitch that shows that we have changed the actual image to the one that we want . How to achieve this?
Update:
I have researched a lot on this and if I am right , android scroll is based on duration and distance not items . From kankan's wheel library I can get current item .Now , I am trying to stop the animation as well as scroll , as soon as a certain duration has been reached and the item is the one that I want (through index) . But this is not working .Please help!!
GameActivity
public class GameActivity extends Activity {
float mDeviceDensity;
String mUuid, mTitle, mContent, mReward;
ImageButton play;
SlotMachineAdapter slotAdapter;
private List<HashMap<String, Object>> slotImages = new ArrayList<HashMap<String, Object>>();
ArrayList<String> imagesWinId = new ArrayList<String>();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_filler_up_game);
DisplayMetrics display = getResources().getDisplayMetrics();
mDeviceDensity = display.density;
slotAdapter = new SlotMachineAdapter(this);
getPassedData();
setSoundPlayer(R.raw.clicks,true);
initWheel(R.id.slot_1, false, 0);
initWheel(R.id.slot_2, false, 1);
initWheel(R.id.slot_3, true, 2);
play = (ImageButton) findViewById(R.id.btn_mix);
play.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
shuffle(R.id.slot_1, 5000);
shuffle(R.id.slot_2, 7000);
shuffle(R.id.slot_3, 9000);
}
});
}
protected ImageLoader imageLoader;
ArrayList<SlotItem> arrListSlotItems;
private void getPassedData() {
try {
mUuid = getIntent().getStringExtra(getString(R.string.FILLER_UP_UUID));
imageLoader = ImageLoader.getInstance();
Uuid slotImagesExtra = (Uuid) (getIntent()
.getSerializableExtra(getString(R.string.FILLER_UP_IMAGES)));
arrListSlotItems = slotImagesExtra.getArrSlotItemArray();
for (int i = 0; i < arrListSlotItems.size(); i++)
downloadSlotImages(arrListSlotItems.get(i).getSlotId(), arrListSlotItems.get(i).getImageUrl());
} catch (Exception e) {
e.printStackTrace();
}
}
// Wheel scrolled flag
private boolean wheelScrolled = false;
// Wheel scrolled listener
OnWheelScrollListener scrolledListener = new OnWheelScrollListener() {
public void onScrollingStarted(WheelView wheel) {
wheelScrolled = true;
}
public void onScrollingFinished(WheelView wheel) {
wheelScrolled = false;
setStatus(wheel.getId(), getWheel(wheel.getId()).getWinningIndex());
}
};
// Wheel changed listener
private OnWheelChangedListener changedListener = new OnWheelChangedListener() {
public void onChanged(WheelView wheel, int oldValue, int newValue) {
if (!wheelScrolled) {
}
}
};
/**
* Updates status
*/
private void updateStatus() {
myThread();
}
public void myThread(){
Thread th=new Thread(){
#Override
public void run(){
try
{
Thread.sleep(2000);
GameActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
showAlertDialogWithSingleButton(GameActivity.this, mTitle, mContent, success);
}
});
}catch (InterruptedException e) {
// TODO: handle exception
}
}
};
th.start();
}
android.content.DialogInterface.OnClickListener success = new android.content.DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
if (mContent != null && mContent.contains("again"))
startHomeActivity();
else
startNewsActivity();
}
};
private void startHomeActivity() {
}
private void startNewsActivity() {
}
android.content.DialogInterface.OnClickListener fail = new android.content.DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
//
}
};
public void showAlertDialogWithSingleButton(final Activity ctx, final String title, final String message,
DialogInterface.OnClickListener onClickListener) {
// show dialog
}
private void initWheel(int id, boolean monitorScroll, int itemIndex) {
Random randomGenerator = new Random();
int index = randomGenerator.nextInt(arrListSlotItems.size());
WheelView wheel = getWheel(id);
wheel.setViewAdapter(slotAdapter);
wheel.setCurrentItem((index ));
wheel.setVisibleItems(1);
wheel.setWinningIndex(itemIndex);
wheel.addChangingListener(changedListener);
wheel.addScrollingListener(scrolledListener);
wheel.setCyclic(true);
wheel.setEnabled(false);
}
private WheelView getWheel(int id) {
return (WheelView) findViewById(id);
}
private void setStatus(int id, int item) {
int index = 0;
for (int i = 0; i < arrListSlotItems.size(); i++) {
SlotItem d = arrListSlotItems.get(i);
if (d.getSlotId() != 0 && d.getSlotId() == Integer.parseInt(imagesWinId.get(item)))
index = arrListSlotItems.indexOf(d);
}
getWheel(id).setCurrentItem(index, true);
if (id == R.id.slot_3) {
if(player.isPlaying())
{
stopBackgroundAudio();
}
updateStatus();
}
}
private void shuffle(int id, int duration) {
WheelView wheel = getWheel(id);
wheel.scroll(450 + (int) (Math.random() * 50), duration);
}
private class SlotMachineAdapter extends AbstractWheelAdapter {
final int IMAGE_WIDTH = getImageWidth(mDeviceDensity);
final int IMAGE_HEIGHT = getImageHeight(mDeviceDensity);
private Context context;
/**
* Constructor
*/
public SlotMachineAdapter(Context context) {
this.context = context;
}
/**
* Loads image from resources
*/
private Bitmap loadImage(Bitmap bitmap) {
Bitmap scaled = Bitmap.createScaledBitmap(bitmap, IMAGE_WIDTH, IMAGE_HEIGHT, true);
return scaled;
}
#Override
public int getItemsCount() {
return slotImages.size();
}
// Layout params for image view
final LayoutParams params = new LayoutParams(IMAGE_WIDTH, IMAGE_HEIGHT);
#Override
public View getItem(int index, View cachedView, ViewGroup parent) {
ImageView img;
if (cachedView != null) {
img = (ImageView) cachedView;
} else {
img = new ImageView(context);
}
img.setPadding(0, 5, 0, 5);
img.setLayoutParams(params);
#SuppressWarnings("unchecked")
SoftReference<Bitmap> bitmapRef = (SoftReference<Bitmap>) slotImages.get(index).get("image");
Bitmap bitmap = bitmapRef.get();
if (bitmap == null) {
bitmap = loadImage(bitmap);
}
img.setImageBitmap(bitmap);
return img;
}
}
private int getImageWidth(float density) {
}
private int getImageHeight(float density) {
}
private void downloadSlotImages(final int id, String slotObj) {
//downloading slot images from server
}
}
This is the code. Through this code, when slot stops I want it to scroll some more untill it reaches the image position that I receaved from server. I can do this .But this is providing a lil glitch . Is there any way to stop scrolling when the image is reached as soon as certain duration is reached.
P.S. If you need anymore detail I can provide you.
P.P.S. Screenshots wont give you any detailed insight about the issue.
After days of searching I finally did it.All I had to do was set interpolater as LinearInterpolater and While setting setCurrentItem set animation as true.
I created an AlertDialog. And I need to put a timer there somehow.
Timer must show time from 90 seconds to 0 seconds.
Does someone know how to make that inscription in textView("90 sseconds to acceptance...") change every second with different text? ("90 sseconds to acceptance..." -> "89 sseconds to acceptance..." -> etc..)
Does this work for you?
/*
new CountDownUpdate((TextView)findViewById(R.id.accept_text), 90,
new CountDownUpdate.Callback(){
#Override
public void onCountDownComplete(TextView textView)
{
Toast.makeText(getApplicationContext(), "BOOM!", Toast.LENGTH_LONG).show();
}
});
*/
private static class CountDownUpdate implements Runnable
{
private Callback mCallback;
private int mFrom;
private TextView mView;
public CountDownUpdate(TextView view, int from, Callback callback)
{
mCallback = callback;
mFrom = from;
mView = view;
mView.post(this);
}
#Override
public void run()
{
mView.setText(mFrom + " seconds to acceptance...");
if(mFrom-- == 0){
if(mCallback != null){
mCallback.onCountDownComplete(mView);
}
}
else{
mView.postDelayed(this, 1000);
}
}
public static interface Callback
{
public void onCountDownComplete(TextView textView);
}
}
I want to do an animation with several image-files, and for this the AnimationDrawable works very well. However, I need to know when the animation starts and when it ends (i.e add a listener like the Animation.AnimationListener). After having searched for answers, I'm having a bad feeling the AnimationDrawable does not support listeners..
Does anyone know how to create a frame-by-frame image animation with a listener on Android?
After doing some reading, I came up with this solution. I'm still surprised there isn't a listener as part of the AnimationDrawable object, but I didn't want to pass callbacks back and forward so instead I created an abstract class which raises an onAnimationFinish() method. I hope this helps someone.
The custom animation drawable class:
public abstract class CustomAnimationDrawableNew extends AnimationDrawable {
/** Handles the animation callback. */
Handler mAnimationHandler;
public CustomAnimationDrawableNew(AnimationDrawable aniDrawable) {
/* Add each frame to our animation drawable */
for (int i = 0; i < aniDrawable.getNumberOfFrames(); i++) {
this.addFrame(aniDrawable.getFrame(i), aniDrawable.getDuration(i));
}
}
#Override
public void start() {
super.start();
/*
* Call super.start() to call the base class start animation method.
* Then add a handler to call onAnimationFinish() when the total
* duration for the animation has passed
*/
mAnimationHandler = new Handler();
mAnimationHandler.post(new Runnable() {
#Override
public void run() {
onAnimationStart();
}
};
mAnimationHandler.postDelayed(new Runnable() {
#Override
public void run() {
onAnimationFinish();
}
}, getTotalDuration());
}
/**
* Gets the total duration of all frames.
*
* #return The total duration.
*/
public int getTotalDuration() {
int iDuration = 0;
for (int i = 0; i < this.getNumberOfFrames(); i++) {
iDuration += this.getDuration(i);
}
return iDuration;
}
/**
* Called when the animation finishes.
*/
public abstract void onAnimationFinish();
/**
* Called when the animation starts.
*/
public abstract void onAnimationStart();
}
To use this class:
ImageView iv = (ImageView) findViewById(R.id.iv_testing_testani);
iv.setOnClickListener(new OnClickListener() {
public void onClick(final View v) {
// Pass our animation drawable to our custom drawable class
CustomAnimationDrawableNew cad = new CustomAnimationDrawableNew(
(AnimationDrawable) getResources().getDrawable(
R.drawable.anim_test)) {
#Override
void onAnimationStart() {
// Animation has started...
}
#Override
void onAnimationFinish() {
// Animation has finished...
}
};
// Set the views drawable to our custom drawable
v.setBackgroundDrawable(cad);
// Start the animation
cad.start();
}
});
I needed to know when my one-shot AnimationDrawable completes, without having to subclass AnimationDrawable since I must set the animation-list in XML. I wrote this class and tested it on Gingerbread and ICS. It can easily be extended to give a callback on each frame.
/**
* Provides a callback when a non-looping {#link AnimationDrawable} completes its animation sequence. More precisely,
* {#link #onAnimationComplete()} is triggered when {#link View#invalidateDrawable(Drawable)} has been called on the
* last frame.
*
* #author Benedict Lau
*/
public abstract class AnimationDrawableCallback implements Callback {
/**
* The last frame of {#link Drawable} in the {#link AnimationDrawable}.
*/
private Drawable mLastFrame;
/**
* The client's {#link Callback} implementation. All calls are proxied to this wrapped {#link Callback}
* implementation after intercepting the events we need.
*/
private Callback mWrappedCallback;
/**
* Flag to ensure that {#link #onAnimationComplete()} is called only once, since
* {#link #invalidateDrawable(Drawable)} may be called multiple times.
*/
private boolean mIsCallbackTriggered = false;
/**
*
* #param animationDrawable
* the {#link AnimationDrawable}.
* #param callback
* the client's {#link Callback} implementation. This is usually the {#link View} the has the
* {#link AnimationDrawable} as background.
*/
public AnimationDrawableCallback(AnimationDrawable animationDrawable, Callback callback) {
mLastFrame = animationDrawable.getFrame(animationDrawable.getNumberOfFrames() - 1);
mWrappedCallback = callback;
}
#Override
public void invalidateDrawable(Drawable who) {
if (mWrappedCallback != null) {
mWrappedCallback.invalidateDrawable(who);
}
if (!mIsCallbackTriggered && mLastFrame != null && mLastFrame.equals(who.getCurrent())) {
mIsCallbackTriggered = true;
onAnimationComplete();
}
}
#Override
public void scheduleDrawable(Drawable who, Runnable what, long when) {
if (mWrappedCallback != null) {
mWrappedCallback.scheduleDrawable(who, what, when);
}
}
#Override
public void unscheduleDrawable(Drawable who, Runnable what) {
if (mWrappedCallback != null) {
mWrappedCallback.unscheduleDrawable(who, what);
}
}
//
// Public methods.
//
/**
* Callback triggered when {#link View#invalidateDrawable(Drawable)} has been called on the last frame, which marks
* the end of a non-looping animation sequence.
*/
public abstract void onAnimationComplete();
}
Here is how to use it.
AnimationDrawable countdownAnimation = (AnimationDrawable) mStartButton.getBackground();
countdownAnimation.setCallback(new AnimationDrawableCallback(countdownAnimation, mStartButton) {
#Override
public void onAnimationComplete() {
// TODO Do something.
}
});
countdownAnimation.start();
Animation end can be easily tracked by overriding selectDrawable method in AnimationDrawable class. Complete code is the following:
public class AnimationDrawable2 extends AnimationDrawable
{
public interface IAnimationFinishListener
{
void onAnimationFinished();
}
private boolean finished = false;
private IAnimationFinishListener animationFinishListener;
public IAnimationFinishListener getAnimationFinishListener()
{
return animationFinishListener;
}
public void setAnimationFinishListener(IAnimationFinishListener animationFinishListener)
{
this.animationFinishListener = animationFinishListener;
}
#Override
public boolean selectDrawable(int idx)
{
boolean ret = super.selectDrawable(idx);
if ((idx != 0) && (idx == getNumberOfFrames() - 1))
{
if (!finished)
{
finished = true;
if (animationFinishListener != null) animationFinishListener.onAnimationFinished();
}
}
return ret;
}
}
I used a recursive function that checks to see if the current frame is the last frame every timeBetweenChecks milliseconds.
private void checkIfAnimationDone(AnimationDrawable anim){
final AnimationDrawable a = anim;
int timeBetweenChecks = 300;
Handler h = new Handler();
h.postDelayed(new Runnable(){
public void run(){
if (a.getCurrent() != a.getFrame(a.getNumberOfFrames() - 1)){
checkIfAnimationDone(a);
} else{
Toast.makeText(getApplicationContext(), "ANIMATION DONE!", Toast.LENGTH_SHORT).show();
}
}
}, timeBetweenChecks);
}
A timer is a bad choice for this because you will get stuck trying to execute in a non UI thread like HowsItStack said. For simple tasks you can just use a handler to call a method at a certain interval. Like this:
handler.postDelayed(runnable, duration of your animation); //Put this where you start your animation
private Handler handler = new Handler();
private Runnable runnable = new Runnable() {
public void run() {
handler.removeCallbacks(runnable)
DoSomethingWhenAnimationEnds();
}
};
removeCallbacks assures this only executes once.
This is so simple when it come to using Kotlin, AnimationDrawable has two functions we could use to calculate the animation duration, then we could add a runnable with delay to create an Animation listener. here is a simple Kotlin extension.
fun AnimationDrawable.onAnimationFinished(block: () -> Unit) {
var duration: Long = 0
for (i in 0..numberOfFrames) {
duration += getDuration(i)
}
Handler().postDelayed({
block()
}, duration + 200)
}
if you want to impliment your animation in adapter - should use next
public class CustomAnimationDrawable extends AnimationDrawable {
/**
* Handles the animation callback.
*/
Handler mAnimationHandler;
private OnAnimationFinish onAnimationFinish;
public void setAnimationDrawable(AnimationDrawable aniDrawable) {
for (int i = 0; i < aniDrawable.getNumberOfFrames(); i++) {
this.addFrame(aniDrawable.getFrame(i), aniDrawable.getDuration(i));
}
}
public void setOnFinishListener(OnAnimationFinish onAnimationFinishListener) {
onAnimationFinish = onAnimationFinishListener;
}
#Override
public void stop() {
super.stop();
}
#Override
public void start() {
super.start();
mAnimationHandler = new Handler();
mAnimationHandler.postDelayed(new Runnable() {
public void run() {
if (onAnimationFinish != null)
onAnimationFinish.onFinish();
}
}, getTotalDuration());
}
/**
* Gets the total duration of all frames.
*
* #return The total duration.
*/
public int getTotalDuration() {
int iDuration = 0;
for (int i = 0; i < this.getNumberOfFrames(); i++) {
iDuration += this.getDuration(i);
}
return iDuration;
}
/**
* Called when the animation finishes.
*/
public interface OnAnimationFinish {
void onFinish();
}
}
and implementation in RecycleView Adapter
#Override
public void onBindViewHolder(PlayGridAdapter.ViewHolder holder, int position) {
final Button mButton = holder.button;
mButton.setBackgroundResource(R.drawable.animation_overturn);
final CustomAnimationDrawable mOverturnAnimation = new CustomAnimationDrawable();
mOverturnAnimation.setAnimationDrawable((AnimationDrawable) mContext.getResources().getDrawable(R.drawable.animation_overturn));
mOverturnAnimation.setOnFinishListener(new CustomAnimationDrawable.OnAnimationFinish() {
#Override
public void onFinish() {
// your perform
}
});
mButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
mOverturnAnimation.start();
}
});
}
I also like Ruslan's answer, but I had to make a couple of changes in order to get it to do what I required.
In my code, I have got rid of Ruslan's finished flag, and I have also utilised the boolean returned by super.selectDrawable().
Here's my code:
class AnimationDrawableWithCallback extends AnimationDrawable {
interface IAnimationFinishListener {
void onAnimationChanged(int index, boolean finished);
}
private IAnimationFinishListener animationFinishListener;
public IAnimationFinishListener getAnimationFinishListener() {
return animationFinishListener;
}
void setAnimationFinishListener(IAnimationFinishListener animationFinishListener) {
this.animationFinishListener = animationFinishListener;
}
#Override
public boolean selectDrawable(int index) {
boolean drawableChanged = super.selectDrawable(index);
if (drawableChanged && animationFinishListener != null) {
boolean animationFinished = (index == getNumberOfFrames() - 1);
animationFinishListener.onAnimationChanged(index, animationFinished);
}
return drawableChanged;
}
}
And here is an example of how to implement it...
public class MyFragment extends Fragment implements AnimationDrawableWithCallback.IAnimationFinishListener {
#Override
public void onAnimationChanged(int index, boolean finished) {
// Do whatever you need here
}
}
If you only want to know when the first cycle of animation has completed, then you can set a boolean flag in your fragment/activity.
I guess your Code does not work, because you try to modify a View from a non-UI-Thread. Try to call runOnUiThread(Runnable) from your Activity. I used it to fade out a menu after an animation for this menu finishes. This code works for me:
Animation ani = AnimationUtils.loadAnimation(YourActivityNameHere.this, R.anim.fadeout_animation);
menuView.startAnimation(ani);
// Use Timer to set visibility to GONE after the animation finishes.
TimerTask timerTask = new TimerTask(){
#Override
public void run() {
YourActivityNameHere.this.runOnUiThread(new Runnable(){
#Override
public void run() {
menuView.setVisibility(View.GONE);
}
});}};
timer.schedule(timerTask, ani.getDuration());
You can use registerAnimationCallback to check your animation start and end.
Here's the snippet code:
// ImageExt.kt
fun ImageView.startAnim(block: () -> Unit) {
(drawable as Animatable).apply {
registerAnimationCallback(
drawable,
object : Animatable2Compat.AnimationCallback() {
override fun onAnimationStart(drawable: Drawable?) {
block.invoke()
isClickable = false
isEnabled = false
}
override fun onAnimationEnd(drawable: Drawable?) {
isClickable = true
isEnabled = true
}
})
}.run { start() }
}
// Fragment.kt
imageView.startAnim {
// do something after the animation ends here
}
The purpose of the ImageExt was to disable after animation start (on progress) to prevent user spamming the animation and resulting in the broken / wrong vector shown.
With frame-by-frame, you might want to trigger another ImageView like this.
// Animation.kt
iv1.startAnim {
iv2.startAnim {
// iv3, etc
}
}
But the above solutions looks ugly. If anyone has a better approach, please comment below, or edit this answer directly.
I had the same problem when I had to implement a button click after animation stopped. I checked the current frame and the lastframe of animation drawable to know when an animation is stopped. Note that it is not a listener but just a way to know it animation has stopped.
if (spinAnimation.getCurrent().equals(
spinAnimation.getFrame(spinAnimation
.getNumberOfFrames() - 1))) {
Toast.makeText(MainActivity.this, "finished",
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "Not finished",
Toast.LENGTH_SHORT).show();
}
I don't know about all these other solutions, but this is the one that comes closest to simply adding a listener to the AnimationDrawable class.
class AnimationDrawableListenable extends AnimationDrawable{
static interface AnimationDrawableListener {
void selectIndex(int idx, boolean b);
}
public AnimationDrawableListener animationDrawableListener;
public boolean selectDrawable(int idx) {
boolean selectDrawable = super.selectDrawable(idx);
animationDrawableListener.selectIndex(idx,selectDrawable);
return selectDrawable;
}
public void setAnimationDrawableListener(AnimationDrawableListener animationDrawableListener) {
this.animationDrawableListener = animationDrawableListener;
}
}
I prefer not to go for timing solution, as it seems to me isn't reliable enough.
I love Ruslan Yanchyshyn's solution : https://stackoverflow.com/a/12314579/72437
However, if you notice the code carefully, we will receive animation end callback, during the animation start of last frame, not the animation end.
I propose another solution, by using a dummy drawable in animation drawable.
animation_list.xml
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true">
<item android:drawable="#drawable/card_selected_material_light" android:duration="#android:integer/config_mediumAnimTime" />
<item android:drawable="#drawable/card_material_light" android:duration="#android:integer/config_mediumAnimTime" />
<item android:drawable="#drawable/dummy" android:duration="#android:integer/config_mediumAnimTime" />
</animation-list>
AnimationDrawableWithCallback.java
import android.graphics.drawable.AnimationDrawable;
/**
* Created by yccheok on 24/1/2016.
*/
public class AnimationDrawableWithCallback extends AnimationDrawable {
public AnimationDrawableWithCallback(AnimationDrawable aniDrawable) {
/* Add each frame to our animation drawable */
for (int i = 0; i < aniDrawable.getNumberOfFrames(); i++) {
this.addFrame(aniDrawable.getFrame(i), aniDrawable.getDuration(i));
}
}
public interface IAnimationFinishListener
{
void onAnimationFinished();
}
private boolean finished = false;
private IAnimationFinishListener animationFinishListener;
public void setAnimationFinishListener(IAnimationFinishListener animationFinishListener)
{
this.animationFinishListener = animationFinishListener;
}
#Override
public boolean selectDrawable(int idx)
{
if (idx >= (this.getNumberOfFrames()-1)) {
if (!finished)
{
finished = true;
if (animationFinishListener != null) animationFinishListener.onAnimationFinished();
}
return false;
}
boolean ret = super.selectDrawable(idx);
return ret;
}
}
This is how we can make use of the above class.
AnimationDrawableWithCallback animationDrawable2 = new AnimationDrawableWithCallback(rowLayoutAnimatorList);
animationDrawable2.setAnimationFinishListener(new AnimationDrawableWithCallback.IAnimationFinishListener() {
#Override
public void onAnimationFinished() {
...
}
});
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
view.setBackground(animationDrawable2);
} else {
view.setBackgroundDrawable(animationDrawable2);
}
// https://stackoverflow.com/questions/14297003/animating-all-items-in-animation-list
animationDrawable2.setEnterFadeDuration(this.configMediumAnimTime);
animationDrawable2.setExitFadeDuration(this.configMediumAnimTime);
animationDrawable2.start();
i had used following method and it is really works.
Animation anim1 = AnimationUtils.loadAnimation( this, R.anim.hori);
Animation anim2 = AnimationUtils.loadAnimation( this, R.anim.hori2);
ImageSwitcher isw=new ImageSwitcher(this);
isw.setInAnimation(anim1);
isw.setOutAnimation(anim2);
i hope this will solve your problem.