FliCard 和 ListView Android 中一个奇怪的可见性错误

Posted

技术标签:

【中文标题】FliCard 和 ListView Android 中一个奇怪的可见性错误【英文标题】:FliCard and ListView A strange visibility bug in Android 【发布时间】:2015-08-28 08:38:39 【问题描述】:

我正在尝试在 ListView 中为我的项目实现 FlipCard 行为,错误是我的 convertView 不会根据我在 getView 方法中设置的可见性更新其可见性状态。就像没有人关心我的能见度变化一样。 重现问题:单击项目图片(太阳、云...),它将翻转项目并呈现其背面。然后向上或向下滚动,直到翻转的convertView被未翻转的View重用。未翻转的视图将不再显示其内容。 第一项应该显示其内容,但它什么也不显示,因为使用的 convertView(由 getView 参数给出的)在上次使用时将其可见性设置为 GONE。

你可以在这里找到完整的项目:https://github.com/MathiasSeguy-android2EE/ForecastYahooRest 你必须查看分支“flipcard”

所以 ArrayAdapter 涉及到:

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

好的,我深入研究了那个错误,但我仍然不明白(我有很多日志)所以 我的问题就在这里,视图告诉我,它是可见的,

但它没有显示

重现问题的简单方法,进入横向模式,翻转前两项,滚动到列表末尾。

非常感谢那些试图回答的人。 马蒂亚斯

【问题讨论】:

【参考方案1】:

优皮,我找到了!!总结 好的,对我来说,这是一个错误或过度优化的行为。 因此,当我的视图翻转时,我隐藏前面以显示后面,而未翻转时,我隐藏后面以显示前面。 问题是如果我在与项目 n1 关联的视图 v1 中隐藏前面。我滚动。然后这个视图在getView 方法中作为convertView 被重用为n2 项。但是对于项目 n2,我们显示前面... 并且出现了错误:前视图已几乎被删除/垃圾收集或其他任何东西,但不再存在。它响应函数的调用,但它的内部状态是错误的。所以它告诉你,我是可见的,但它不是,它是一个幽灵。

我的理解: 我的想法(这是一个假设) 所以这里的重点是对 ListView 的过度优化: 当视图进入转换视图池时,系统会销毁处于“可见性消失”状态的资源。我的意思是具有 Visibility=Gone 的 ViewGroup 根的视图。

我的理解是错误的 所以 Romain Guy 告诉我,“ListView 不会破坏 GONE 视图。特别是因为它不查看回收视图的子视图。如果你可以在 View 上调用一个方法,它显然没有被 GC。它可能是UI Toolkit 绘图或适配器中的错误。" .... 好的,我将继续深入研究我的问题以了解这一点。

解决方案: 所以解决方案很明显,我需要两个转换视图池,一个默认为正面可见,另一个默认为背面可见。所以我创建了两个布局,一个前面可见,另一个后面可见,我使用 getViewTypeCount() 和 getItemViewType(int position) 方法,3分钟后它开始工作了。

结论 总而言之,当您在 ListView 中隐藏和显示项目中的元素时,您需要定义与 convertViews 池一样多的配置。 :( 我很难过理解这一点,如果没有错误来证明这一点,我永远不会相信。

项目 你可以在这里找到完整的项目: https://github.com/MathiasSeguy-Android2EE/ForecastYahooRest 并且您必须检查分支“翻转卡”是否已更新。

守则:

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

【讨论】:

以上是关于FliCard 和 ListView Android 中一个奇怪的可见性错误的主要内容,如果未能解决你的问题,请参考以下文章

ListView的简单使用

NestedScrollView嵌套ListView时只显示一行的解决方法

Listview 中项目之间的距离

ListView 重叠折叠工具栏

如何在ListView中显示HashMap中的项目和子项目

listview与viewpager结合使用