FliCard and ListView A strange visibility bug in Android - android

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);
}
}
}

Related

How to swipe ViewPager images automatically using TimeTask

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) {
}
}

How to filter list of all users currently signed up for app to implement a sort of inbox

I have a messaging app for Android and am attempting to filter my user list that currently shows all signed up users. I am using Parse. I would like to query the Parse database to filter the list of all users to only show users that you have current conversations with. So, some sort of inbox scenario. I am fairly new to android development and would appreciate some help implementing this logic. I've been scouring the internet (including stack overflow) for the past 24 hours and finally caved in and tried my luck here. Any help is greatly appreciated!
UserList.java
public class UserList extends CustomActivity
{
/** The Chat list. */
private ArrayList<ParseUser> uList;
/** The user. */
public static ParseUser user;
/* (non-Javadoc)
* #see android.support.v4.app.FragmentActivity#onCreate(android.os.Bundle)
*/
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
user = ParseUser.getCurrentUser();
setContentView(R.layout.user_list);
updateUserStatus(true);
}
/* (non-Javadoc)
* #see android.support.v4.app.FragmentActivity#onDestroy()
*/
#Override
protected void onDestroy()
{
super.onDestroy();
updateUserStatus(false);
}
/* (non-Javadoc)
* #see android.support.v4.app.FragmentActivity#onResume()
*/
#Override
protected void onResume()
{
super.onResume();
loadUserList();
}
/**
* Update user status.
*
* #param online
* true if user is online
*/
private void updateUserStatus(boolean online)
{
user.put("online", online);
user.saveEventually();
}
/**
* Load list of users.
*/
private void loadUserList()
{
final ProgressDialog dia = ProgressDialog.show(this, null,
getString(R.string.alert_loading));
ParseUser.getQuery().whereNotEqualTo("username", user.getUsername())
.findInBackground(new FindCallback<ParseUser>() {
#Override
public void done(List<ParseUser> li, ParseException e) {
dia.dismiss();
if (li != null) {
if (li.size() == 0)
Toast.makeText(UserList.this,
R.string.msg_no_user_found,
Toast.LENGTH_SHORT).show();
uList = new ArrayList<ParseUser>(li);
ListView list = (ListView) findViewById(R.id.list);
list.setAdapter(new UserAdapter());
list.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0,
View arg1, int pos, long arg3) {
startActivity(new Intent(UserList.this,
Chat.class).putExtra(
Const.EXTRA_DATA, uList.get(pos)
.getUsername()));
}
});
} else {
Utils.showDialog(
UserList.this,
getString(R.string.err_users) + " "
+ e.getMessage());
e.printStackTrace();
}
}
});
}
/**
* The Class UserAdapter is the adapter class for User ListView. This
* adapter shows the user name and it's only online status for each item.
*/
private class UserAdapter extends BaseAdapter
{
/* (non-Javadoc)
* #see android.widget.Adapter#getCount()
*/
#Override
public int getCount()
{
return uList.size();
}
/* (non-Javadoc)
* #see android.widget.Adapter#getItem(int)
*/
#Override
public ParseUser getItem(int arg0)
{
return uList.get(arg0);
}
/* (non-Javadoc)
* #see android.widget.Adapter#getItemId(int)
*/
#Override
public long getItemId(int arg0)
{
return arg0;
}
/* (non-Javadoc)
* #see android.widget.Adapter#getView(int, android.view.View, android.view.ViewGroup)
*/
#Override
public View getView(int pos, View v, ViewGroup arg2)
{
if (v == null)
v = getLayoutInflater().inflate(R.layout.chat_item, null);
ParseUser c = getItem(pos);
TextView lbl = (TextView) v;
lbl.setText(c.getUsername());
lbl.setCompoundDrawablesWithIntrinsicBounds(
c.getBoolean("online") ? R.drawable.ic_online
: R.drawable.ic_offline, 0, R.drawable.arrow, 0);
return v;
}
}
}
Conversation.java
public class Conversation
{
/** The Constant STATUS_SENDING. */
public static final int STATUS_SENDING = 0;
/** The Constant STATUS_SENT. */
public static final int STATUS_SENT = 1;
/** The Constant STATUS_FAILED. */
public static final int STATUS_FAILED = 2;
/** The msg. */
private String msg;
/** The status. */
private int status = STATUS_SENT;
/** The date. */
private Date date;
/** The sender. */
private String sender;
/**
* Instantiates a new conversation.
*
* #param msg
* the msg
* #param date
* the date
* #param sender
* the sender
*/
public Conversation(String msg, Date date, String sender)
{
this.msg = msg;
this.date = date;
this.sender = sender;
}
/**
* Instantiates a new conversation.
*/
public Conversation()
{
}
/**
* Gets the msg.
*
* #return the msg
*/
public String getMsg()
{
return msg;
}
/**
* Sets the msg.
*
* #param msg
* the new msg
*/
public void setMsg(String msg)
{
this.msg = msg;
}
/**
* Checks if is sent.
*
* #return true, if is sent
*/
public boolean isSent()
{
return UserList.user.getUsername().equals(sender);
}
/**
* Gets the date.
*
* #return the date
*/
public Date getDate()
{
return date;
}
/**
* Sets the date.
*
* #param date
* the new date
*/
public void setDate(Date date)
{
this.date = date;
}
/**
* Gets the sender.
*
* #return the sender
*/
public String getSender()
{
return sender;
}
/**
* Sets the sender.
*
* #param sender
* the new sender
*/
public void setSender(String sender)
{
this.sender = sender;
}
/**
* Gets the status.
*
* #return the status
*/
public int getStatus()
{
return status;
}
/**
* Sets the status.
*
* #param status
* the new status
*/
public void setStatus(int status)
{
this.status = status;
}
}
Chat.java
public class Chat extends CustomActivity
{
/** The Conversation list. */
private ArrayList<Conversation> convList;
/** The chat adapter. */
private ChatAdapter adp;
/** The Editext to compose the message. */
private EditText txt;
/** The user name of buddy. */
private String buddy;
/** The date of last message in conversation. */
private Date lastMsgDate;
/** Flag to hold if the activity is running or not. */
private boolean isRunning;
/** The handler. */
private static Handler handler;
/* (non-Javadoc)
* #see android.support.v4.app.FragmentActivity#onCreate(android.os.Bundle)
*/
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.chat);
convList = new ArrayList<Conversation>();
ListView list = (ListView) findViewById(R.id.list);
adp = new ChatAdapter();
list.setAdapter(adp);
list.setTranscriptMode(AbsListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
list.setStackFromBottom(true);
txt = (EditText) findViewById(R.id.txt);
txt.setInputType(InputType.TYPE_CLASS_TEXT
| InputType.TYPE_TEXT_FLAG_MULTI_LINE);
setTouchNClick(R.id.btnSend);
buddy = getIntent().getStringExtra(Const.EXTRA_DATA);
getActionBar().setTitle(buddy);
handler = new Handler();
}
/* (non-Javadoc)
* #see android.support.v4.app.FragmentActivity#onResume()
*/
#Override
protected void onResume()
{
super.onResume();
isRunning = true;
loadConversationList();
}
/* (non-Javadoc)
* #see android.support.v4.app.FragmentActivity#onPause()
*/
#Override
protected void onPause()
{
super.onPause();
isRunning = false;
}
/* (non-Javadoc)
* #see com.socialshare.custom.CustomFragment#onClick(android.view.View)
*/
#Override
public void onClick(View v)
{
super.onClick(v);
if (v.getId() == R.id.btnSend)
{
sendMessage();
}
}
/**
* Call this method to Send message to other person. It does nothing if the text
* is empty otherwise it creates a Parse object for Chat message and send it
* to Parse server.
*/
private void sendMessage()
{
if (txt.length() == 0)
return;
String s = txt.getText().toString();
final Conversation c = new Conversation(s, new Date(),
UserList.user.getUsername());
c.setStatus(Conversation.STATUS_SENDING);
convList.add(c);
adp.notifyDataSetChanged();
txt.setText(null);
ParseObject po = new ParseObject("Chat");
po.put("sender", UserList.user.getUsername());
po.put("receiver", buddy);
// po.put("createdAt", "");
po.put("message", s);
po.saveEventually(new SaveCallback() {
#Override
public void done(ParseException e)
{
if (e == null)
c.setStatus(Conversation.STATUS_SENT);
else
c.setStatus(Conversation.STATUS_FAILED);
adp.notifyDataSetChanged();
}
});
}
/**
* Load the conversation list from Parse server and save the date of last
* message that will be used to load only recent new messages
*/
private void loadConversationList()
{
ParseQuery<ParseObject> q = ParseQuery.getQuery("Chat");
if (convList.size() == 0)
{
// load all messages...
ArrayList<String> al = new ArrayList<String>();
al.add(buddy);
al.add(UserList.user.getUsername());
q.whereContainedIn("sender", al);
q.whereContainedIn("receiver", al);
}
else
{
// load only newly received message..
if (lastMsgDate != null)
q.whereGreaterThan("createdAt", lastMsgDate);
q.whereEqualTo("sender", buddy);
q.whereEqualTo("receiver", UserList.user.getUsername());
}
q.orderByDescending("createdAt");
q.setLimit(30);
q.findInBackground(new FindCallback<ParseObject>() {
#Override
public void done(List<ParseObject> li, ParseException e)
{
if (li != null && li.size() > 0)
{
for (int i = li.size() - 1; i >= 0; i--)
{
ParseObject po = li.get(i);
Conversation c = new Conversation(po
.getString("message"), po.getCreatedAt(), po
.getString("sender"));
convList.add(c);
if (lastMsgDate == null
|| lastMsgDate.before(c.getDate()))
lastMsgDate = c.getDate();
adp.notifyDataSetChanged();
}
}
handler.postDelayed(new Runnable() {
#Override
public void run()
{
if (isRunning)
loadConversationList();
}
}, 1000);
}
});
}
/**
* The Class ChatAdapter is the adapter class for Chat ListView. This
* adapter shows the Sent or Receieved Chat message in each list item.
*/
private class ChatAdapter extends BaseAdapter
{
/* (non-Javadoc)
* #see android.widget.Adapter#getCount()
*/
#Override
public int getCount()
{
return convList.size();
}
/* (non-Javadoc)
* #see android.widget.Adapter#getItem(int)
*/
#Override
public Conversation getItem(int arg0)
{
return convList.get(arg0);
}
/* (non-Javadoc)
* #see android.widget.Adapter#getItemId(int)
*/
#Override
public long getItemId(int arg0)
{
return arg0;
}
/* (non-Javadoc)
* #see android.widget.Adapter#getView(int, android.view.View, android.view.ViewGroup)
*/
#Override
public View getView(int pos, View v, ViewGroup arg2)
{
Conversation c = getItem(pos);
if (c.isSent())
v = getLayoutInflater().inflate(R.layout.chat_item_sent, null);
else
v = getLayoutInflater().inflate(R.layout.chat_item_rcv, null);
TextView lbl = (TextView) v.findViewById(R.id.lbl1);
lbl.setText(DateUtils.getRelativeDateTimeString(Chat.this, c
.getDate().getTime(), DateUtils.SECOND_IN_MILLIS,
DateUtils.DAY_IN_MILLIS, 0));
lbl = (TextView) v.findViewById(R.id.lbl2);
lbl.setText(c.getMsg());
lbl = (TextView) v.findViewById(R.id.lbl3);
if (c.isSent())
{
if (c.getStatus() == Conversation.STATUS_SENT)
lbl.setText("Delivered");
else if (c.getStatus() == Conversation.STATUS_SENDING)
lbl.setText("Sending...");
else
lbl.setText("Failed");
}
else
lbl.setText("");
return v;
}
}
/* (non-Javadoc)
* #see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
*/
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
if (item.getItemId() == android.R.id.home)
{
finish();
}
return super.onOptionsItemSelected(item);
}
}

Screen becomes blank for long time while loading SherlockFragmentActivity with fragments

I am implementing jeremyfeinstein sliding menu library. for this I have an activity which extends SherlockFragmentActivity. for this activity I have two fragments one is for left menu and another is for Main screen. Everything is working fine and smooth but still I have a problem that is when my Sliding menu activity starts, it becomes blank for 8-10 seconds. after 8-10 seconds my main screen fragment becomes visible.
this is my base class:
public class SlidingFragmentActivity extends SherlockFragmentActivity implements SlidingActivityBase {
private SlidingActivityHelper mHelper;
/* (non-Javadoc)
* #see android.support.v4.app.FragmentActivity#onCreate(android.os.Bundle)
*/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHelper = new SlidingActivityHelper(this);
mHelper.onCreate(savedInstanceState);
}
/* (non-Javadoc)
* #see android.app.Activity#onPostCreate(android.os.Bundle)
*/
#Override
public void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
mHelper.onPostCreate(savedInstanceState);
}
/* (non-Javadoc)
* #see android.app.Activity#findViewById(int)
*/
#Override
public View findViewById(int id) {
View v = super.findViewById(id);
if (v != null)
return v;
return mHelper.findViewById(id);
}
/* (non-Javadoc)
* #see android.support.v4.app.FragmentActivity#onSaveInstanceState(android.os.Bundle)
*/
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mHelper.onSaveInstanceState(outState);
}
/* (non-Javadoc)
* #see android.app.Activity#setContentView(int)
*/
#Override
public void setContentView(int id) {
setContentView(getLayoutInflater().inflate(id, null));
}
/* (non-Javadoc)
* #see android.app.Activity#setContentView(android.view.View)
*/
#Override
public void setContentView(View v) {
setContentView(v, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
/* (non-Javadoc)
* #see android.app.Activity#setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
#Override
public void setContentView(View v, LayoutParams params) {
super.setContentView(v, params);
mHelper.registerAboveContentView(v, params);
}
/* (non-Javadoc)
* #see com.jeremyfeinstein.slidingmenu.lib.app.SlidingActivityBase#setBehindContentView(int)
*/
public void setBehindContentView(int id) {
setBehindContentView(getLayoutInflater().inflate(id, null));
}
/* (non-Javadoc)
* #see com.jeremyfeinstein.slidingmenu.lib.app.SlidingActivityBase#setBehindContentView(android.view.View)
*/
public void setBehindContentView(View v) {
setBehindContentView(v, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
/* (non-Javadoc)
* #see com.jeremyfeinstein.slidingmenu.lib.app.SlidingActivityBase#setBehindContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
public void setBehindContentView(View v, LayoutParams params) {
mHelper.setBehindContentView(v, params);
}
/* (non-Javadoc)
* #see com.jeremyfeinstein.slidingmenu.lib.app.SlidingActivityBase#getSlidingMenu()
*/
public SlidingMenu getSlidingMenu() {
return mHelper.getSlidingMenu();
}
/* (non-Javadoc)
* #see com.jeremyfeinstein.slidingmenu.lib.app.SlidingActivityBase#toggle()
*/
public void toggle() {
mHelper.toggle();
}
/* (non-Javadoc)
* #see com.jeremyfeinstein.slidingmenu.lib.app.SlidingActivityBase#showAbove()
*/
public void showContent() {
mHelper.showContent();
}
/* (non-Javadoc)
* #see com.jeremyfeinstein.slidingmenu.lib.app.SlidingActivityBase#showBehind()
*/
public void showMenu() {
mHelper.showMenu();
}
/* (non-Javadoc)
* #see com.jeremyfeinstein.slidingmenu.lib.app.SlidingActivityBase#showSecondaryMenu()
*/
public void showSecondaryMenu() {
mHelper.showSecondaryMenu();
}
/* (non-Javadoc)
* #see com.jeremyfeinstein.slidingmenu.lib.app.SlidingActivityBase#setSlidingActionBarEnabled(boolean)
*/
public void setSlidingActionBarEnabled(boolean b) {
mHelper.setSlidingActionBarEnabled(b);
}
/* (non-Javadoc)
* #see android.app.Activity#onKeyUp(int, android.view.KeyEvent)
*/
#Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
boolean b = mHelper.onKeyUp(keyCode, event);
if (b) return b;
return super.onKeyUp(keyCode, event);
}
}
Here is my Main activity which loads fragments
public class SliderMenuMainActivity extends SlidingFragmentActivity
{
private Fragment mContent;
ImageButton btnToggle,refresh;
String ns = Context.NOTIFICATION_SERVICE;
public static int msg_count = 0;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(obj==null)
obj=new Progress_Dialog(this);
obj.setCancelable(false);
obj.onPreExecute("MAIN Screen");
if (savedInstanceState != null)
mContent = getSupportFragmentManager().getFragment(savedInstanceState, "mContent");
prepareScreen();
}
private void prepareScreen()
{
setContentView(R.layout.activity_slider_menu_main);
ActionBar ab = getSherlock().getActionBar();
LayoutInflater li = LayoutInflater.from(this);
View customView = li.inflate(R.layout.custom_titlebar, null);
customView.setLayoutParams(new ActionBar.LayoutParams(ActionBar.LayoutParams.MATCH_PARENT ,ActionBar.LayoutParams.MATCH_PARENT));
ab.setCustomView(customView);
ab.setDisplayShowHomeEnabled(false);
ab.setDisplayShowCustomEnabled(true);
if (findViewById(R.id.menu_frame) == null)
{
setBehindContentView(R.layout.menu_frame);
getSlidingMenu().setSlidingEnabled(true);
getSlidingMenu().setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);
// show home as up so we can toggle
//getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
else
{
// add a dummy view
View v = new View(this);
setBehindContentView(v);
getSlidingMenu().setSlidingEnabled(false);
getSlidingMenu().setTouchModeAbove(SlidingMenu.TOUCHMODE_NONE);
}
if (mContent == null)
mContent = new MainScreenFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.content_frame, mContent).commit();
// set the Behind View Fragment
getSupportFragmentManager().beginTransaction().replace(R.id.menu_frame, new MenuFragment()).commit();
// customize the SlidingMenu
SlidingMenu sm = getSlidingMenu();
sm.setBehindOffsetRes(R.dimen.slidingmenu_offset);
sm.setShadowWidthRes(R.dimen.shadow_width);
sm.setShadowDrawable(R.drawable.shadow);
sm.setBehindScrollScale(0.25f);
sm.setFadeDegree(0.25f);
setSlidingActionBarEnabled(false);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
toggle();
}
return super.onOptionsItemSelected(item);
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getSupportFragmentManager().putFragment(outState, "mContent", mContent);
}
public void switchContent(final Fragment fragment) {
mContent = fragment;
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.content_frame, fragment)
.commit();
Handler h = new Handler();
h.postDelayed(new Runnable() {
public void run() {
getSlidingMenu().showContent();
}
}, 50);
}
#Override
public void onResume()
{
super.onResume();
if(obj!=null)
obj.onPostExecute();
}
}
So please help me with this and show me a way to overcome this problem. thanks in advance.
EDIT 1
I have placed all the code to the onResume() but it still have same problem. now it is taking about 2 to 3 second to become activity visible.why it is taking this amount of time when I don't have any code into onCreate(). I have removed all the libraries and now I am using only support library(4 and 7) with navigation drawer with same result.
Issue:
blank screen displayed because your content view is not set up until prepareScreen executes.
Solution:
you need to put setContentView(R.layout.activity_slider_menu_main); directly after super.onCreate(savedInstanceState); inside onCreate method.
You are doing lot of operations in onCreate(). You need to just set the primary layout and stuff like that. all the operations can be called in onResume().

Android Gallery issue, out of memory

The following code give java.lang.OutOfMemoryError : bitmap exceeds VM budget
i'm writing an app to swipe through a series of images , which are all full screen. I know i'm loading all images together thats whats causing the problem , but i dont know how to overcome this issue. Somebody please help .
package akash.gal;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Gallery;
import android.widget.ImageView;
public class GalActivity extends Activity {
/** Called when the activity is first created. */
Integer imageids[]={R.drawable.one,R.drawable.two,R.drawable.three};
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Gallery gallery=(Gallery) findViewById(R.id.gallery1);
gallery.setAdapter(new ImageAdapter(this));
}
public class ImageAdapter extends BaseAdapter{
private Context context;
private int itemBackground;
public ImageAdapter(Context c)
{
context=c;
TypedArray a=obtainStyledAttributes(R.styleable.Gallery1);
itemBackground=a.getResourceId(R.styleable.Gallery1_android_galleryItemBackground,0);
a.recycle();
}
public int getCount() {
// TODO Auto-generated method stub
return imageids.length;
}
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return arg0;
}
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return arg0;
}
public View getView(int arg0, View arg1, ViewGroup arg2) {
// TODO Auto-generated method stub
ImageView imageView=new ImageView(context);
imageView.setImageResource(imageids[arg0]);
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
DisplayMetrics dm=new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
imageView.setLayoutParams(new Gallery.LayoutParams(dm.widthPixels,dm.heightPixels));
imageView.setBackgroundResource(itemBackground);
return imageView;
}
}
}
In Main Activity :--
public class Main extends Activity {
String mImagesPath;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mImagesPath = this.getFilesDir().getParent() + "/images/";
createImagesDir(mImagesPath);
copyImagesToStorage();
loadGridView();
}
/**
* Method handles the logic for setting the adapter for the gridview
*/
private void loadGridView(){
GridView lLazyGrid = (GridView) this.findViewById(R.id.gridview);
try {
LazyImageAdapter lLazyAdapter = new LazyImageAdapter(this.getApplicationContext(),
null,
mImagesPath);
lLazyGrid.setAdapter(lLazyAdapter);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Copy images from assets to storage
*/
private void copyImagesToStorage(){
AssetManager lAssetManager = getAssets();
String[] lFiles = null;
String lTag = "copyImageFail";
try {
// get all of the files in the assets directory
lFiles = lAssetManager.list("");
} catch (IOException e) {
Log.e(lTag, e.getMessage());
}
for(int i=0; i<lFiles.length; i++) {
// We have a file to copy
try {
// copy the file
copyFile(lFiles[i], mImagesPath + lFiles[i]);
} catch(Exception e) {
Log.e(lTag, e.getMessage());
}
}
}
/**
* Method copies the contents of one stream to another
* #param aIn stream to copy from
* #param aOut stream to copy to
* #throws IOException
*/
private void copyFile(String aIn, String aOut) throws IOException {
byte[] lBuffer = new byte[1024];
int lRead;
final int lOffset = 0;
// create an in and out stream
InputStream lIn = getAssets().open(aIn);
OutputStream lOut = new FileOutputStream(aOut);
// Copy contents while there is data
while((lRead = lIn.read(lBuffer)) != -1){
lOut.write(lBuffer, lOffset, lRead);
}
// clean up after our streams
lIn.close();
lIn = null;
lOut.flush();
lOut.close();
lOut = null;
}
/**
* Create the directory specified at aPath if it does not exist
* #param aPath directory to check for and create
*/
private void createImagesDir(String aPath){
File lDir = new File(aPath);
if(!lDir.exists()){
lDir.mkdir();
}
}
}
=====================================
In ImageLoader Class :
public class ImageLoader extends Thread {
public interface ImageLoadListener {
void handleImageLoaded(ViewSwitcher aViewSwitcher, ImageView aImageView, Bitmap aBitmap);
}
private static final String TAG = ImageLoader.class.getSimpleName();
ImageLoadListener mListener = null;
private Handler handler;
/**
* Image loader takes an object that extends ImageLoadListener
* #param lListener
*/
ImageLoader(ImageLoadListener lListener){
mListener = lListener;
}
#Override
public void run() {
try {
// preparing a looper on current thread
// the current thread is being detected implicitly
Looper.prepare();
// Looper gets attached to the current thread by default
handler = new Handler();
Looper.loop();
// Thread will start
} catch (Throwable t) {
Log.e(TAG, "ImageLoader halted due to a error: ", t);
}
}
/**
* Method stops the looper and thus the thread
*/
public synchronized void stopThread() {
// Use the handler to schedule a quit on the looper
handler.post(new Runnable() {
public void run() {
// This runs on the ImageLoader thread
Log.i(TAG, "DownloadThread loop quitting by request");
Looper.myLooper().quit();
}
});
}
/**
* Method queues the image at path to load
* Note that the actual loading takes place in the UI thread
* the ImageView and ViewSwitcher are just references for the
* UI thread.
* #param aPath - Path where the bitmap is located to load
* #param aImageView - The ImageView the UI thread will load
* #param aViewSwitcher - The ViewSwitcher that needs to display the imageview
*/
public synchronized void queueImageLoad(
final String aPath,
final ImageView aImageView,
final ViewSwitcher aViewSwitcher) {
// Wrap DownloadTask into another Runnable to track the statistics
handler.post(new Runnable() {
public void run() {
try {
synchronized (aImageView){
// make sure this thread is the only one performing activities on
// this imageview
BitmapFactory.Options lOptions = new BitmapFactory.Options();
lOptions.inSampleSize = 1;
Bitmap lBitmap = BitmapFactory.decodeFile(aPath, lOptions);
//aImage.setImageBitmap(lBitmap);
// Load the image here
signalUI(aViewSwitcher, aImageView, lBitmap);
}
}
catch(Exception e){
e.printStackTrace();
}
}
});
}
/**
* Method is called when the bitmap is loaded. The UI thread adds the bitmap to the imageview.
* #param aViewSwitcher - The ViewSwitcher that needs to display the imageview
* #param aImageView - The ImageView the UI thread will load
* #param aImage - The Bitmap that gets loaded into the ImageView
*/
private void signalUI(
ViewSwitcher aViewSwitcher,
ImageView aImageView,
Bitmap aImage){
if(mListener != null){
// we have an object that implements ImageLoadListener
mListener.handleImageLoaded(aViewSwitcher, aImageView, aImage);
}
}
}
======================================
IN LazyImageAdapter :
public class LazyImageAdapter extends BaseAdapter implements ImageLoadListener {
private static final int PROGRESSBARINDEX = 0;
private static final int IMAGEVIEWINDEX = 1;
private Context mContext = null;
private OnClickListener mItemClickListener;
private Handler mHandler;
private ImageLoader mImageLoader = null;
private File mDirectory;
/**
* Lazy loading image adapter
* #param aContext
* #param lClickListener click listener to attach to each item
* #param lPath the path where the images are located
* #throws Exception when path can't be read from or is not a valid directory
*/
public LazyImageAdapter(
Context aContext,
OnClickListener lClickListener,
String lPath
) throws Exception {
mContext = aContext;
mItemClickListener = lClickListener;
mDirectory = new File(lPath);
// Do some error checking
if(!mDirectory.canRead()){
throw new Exception("Can't read this path");
}
else if(!mDirectory.isDirectory()){
throw new Exception("Path is a not a directory");
}
mImageLoader = new ImageLoader(this);
mImageLoader.start();
mHandler = new Handler();
}
#Override
protected void finalize() throws Throwable {
// TODO Auto-generated method stub
super.finalize();
// stop the thread we started
mImageLoader.stopThread();
}
public int getCount() {
return mDirectory.listFiles().length;
}
public Object getItem(int aPosition) {
String lPath = null;
File []lFiles = mDirectory.listFiles();
if(aPosition < lFiles.length){
lPath = mDirectory.listFiles()[aPosition].getAbsolutePath();
}
return lPath;
}
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return 0;
}
public View getView(final int aPosition, View aConvertView, ViewGroup parent) {
final ViewSwitcher lViewSwitcher;
String lPath = (String)getItem(aPosition);
// logic for conserving resources see google video on making your ui fast
// and responsive
if (null == aConvertView) {
lViewSwitcher = new ViewSwitcher(mContext);
lViewSwitcher.setPadding(8, 8, 8, 8);
ProgressBar lProgress = new ProgressBar(mContext);
lProgress.setLayoutParams(new ViewSwitcher.LayoutParams(80, 80));
lViewSwitcher.addView(lProgress);
ImageView lImage = new ImageView(mContext);
lImage.setLayoutParams(new ViewSwitcher.LayoutParams(100, 100));
lViewSwitcher.addView(lImage);
// attach the onclick listener
lViewSwitcher.setOnClickListener(mItemClickListener);
} else {
lViewSwitcher = (ViewSwitcher) aConvertView;
}
ViewTagInformation lTagHolder = (ViewTagInformation) lViewSwitcher
.getTag();
if (lTagHolder == null ||
!lTagHolder.aImagePath.equals(lPath)) {
// The Tagholder is null meaning this is a first time load
// or this view is being recycled with a different image
// Create a ViewTag to store information for later
ViewTagInformation lNewTag = new ViewTagInformation();
lNewTag.aImagePath = lPath;
lViewSwitcher.setTag(lNewTag);
// Grab the image view
// Have the progress bar display
// Then queue the image loading
ImageView lImageView = (ImageView) lViewSwitcher.getChildAt(1);
lViewSwitcher.setDisplayedChild(PROGRESSBARINDEX);
mImageLoader.queueImageLoad(lPath, lImageView, lViewSwitcher);
}
return lViewSwitcher;
}
public void handleImageLoaded(
final ViewSwitcher aViewSwitcher,
final ImageView aImageView,
final Bitmap aBitmap) {
// The enqueue the following in the UI thread
mHandler.post(new Runnable() {
public void run() {
// set the bitmap in the ImageView
aImageView.setImageBitmap(aBitmap);
// explicitly tell the view switcher to show the second view
aViewSwitcher.setDisplayedChild(IMAGEVIEWINDEX);
}
});
}
}
/**
* View holder pattern as described in google sample code
* we may want to add more attributes to this if the path was
* say being stored in a sqlite database
* #author bacaj
*/
class ViewTagInformation {
String aImagePath;
}

Android AnimationDrawable and knowing when animation ends

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.

Categories

Resources