利用ViewFlipper实现跑马灯效果,OutAnimation不起作用的根源

ViewFlipper可以达到视图的切换功能,因此利用它也能实现跑马灯的效果,但是本人用的时候遇到一个问题就是,进场动画执行结束后不会执行出场动画,再翻看ViewFlipper源码之后有了头绪

ViewFlipper重点的源码如下

    //ViewFlipper内部的方法,根据mVisible 和 mRunning的状态来决定开始或者停止切换
    private void updateRunning(boolean flipNow) {
        boolean running = mVisible && mStarted && mUserPresent;
        if (running != mRunning) {
            if (running) {
                showOnly(mWhichChild, flipNow);
                postDelayed(mFlipRunnable, mFlipInterval);
            } else {
                removeCallbacks(mFlipRunnable);
            }
            mRunning = running;
        }
        if (LOGD) {
            Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
                    + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);
        }
    }

    //显示指定的child。其他显示在屏幕的view将要退出,移除的动画由getOutAnimation自定义实现,被指定的child进入屏      //幕会由getInAnimation自定义实现
    void showOnly(int childIndex, boolean animate) {
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (i == childIndex) {
                if (animate && mInAnimation != null) {
                    child.startAnimation(mInAnimation);
                }
                child.setVisibility(View.VISIBLE);
                mFirstTime = false;
            } else {
                if (animate && mOutAnimation != null && child.getVisibility() == View.VISIBLE) {
                    child.startAnimation(mOutAnimation);
                } else if (child.getAnimation() == mInAnimation)
                    child.clearAnimation();
                child.setVisibility(View.GONE);
            }
        }
    }

     //重写了addView方法,如果子child数量等于1 那么就显示,如果是多个,就都隐藏
    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        super.addView(child, index, params);
        if (getChildCount() == 1) {
            child.setVisibility(View.VISIBLE);
        } else {
            child.setVisibility(View.GONE);
        }
        if (index >= 0 && mWhichChild >= index) {
            // Added item above current one, increment the index of the displayed child
            setDisplayedChild(mWhichChild + 1);
        }
    }

     //设置哪个child将要显示
    public void setDisplayedChild(int whichChild) {
        mWhichChild = whichChild;
        if (whichChild >= getChildCount()) {
            mWhichChild = 0;
        } else if (whichChild < 0) {
            mWhichChild = getChildCount() - 1;
        }
        boolean hasFocus = getFocusedChild() != null;
        // This will clear old focus if we had it
        showOnly(mWhichChild);
        if (hasFocus) {
            // Try to retake focus if we had it
            requestFocus(FOCUS_FORWARD);
        }
    }

     //循环显示view
    private final Runnable mFlipRunnable = new Runnable() {
        @Override
        public void run() {
            if (mRunning) {
                showNext();
                postDelayed(mFlipRunnable, mFlipInterval);
            }
        }
    };
    
    //手动显示下一个view
    public void showNext() {
        setDisplayedChild(mWhichChild + 1);
    }

ViewFlipper的源码读起来不是很复杂,这时候就要改动它,使之有跑马灯的效果,

简单需求:实现一行文字从下进入屏幕,中间停留4秒,从上面滑出屏幕,然后再从下面进入,一直循环。

思路:新建类MyMarqueeView 继承 ViewFlipper,然后根据要显示的内容去创建两个TextView,一个TextView滑出屏幕时,另一个TextView进入屏幕。

代码很简单就不附上了,最终显示效果的时候,进场动画是有的,但是在中间停留一段时间后,出场动画没有执行,而是直接消失从底部又开始进场动画,停留,如此循环,看了代码之后发现outAnimation设置了,并且inAnimation和outAnimaton的AnimationListener都打印出数据了证明都是执行了,此时表示很奇怪,由于MyMarqueeView是根据用户自定义显示在界面某个地方的,且根据读取的配置数据来显示多大区域,因此MyMarqueeView的父布局是重写了onMeasure和onLayout方法,MyMarquee也得重写onMeasure方法,于是本人就在MyMarqueeView的onMeasure方法中寻找线索,onMeasure方法中调用了measureChildren去设置子View的大小,并且利用setMeasuredDimension改变了自己显示的大小,于是就打印MyMarqueeView子View的宽高,即那两个TextView的宽高,发现一个宽高是正常要显示的大小,一个是整个手机屏幕的大小,正常来说不都应该显示成自定义的大小吗?!带着疑问,看ViewFlipper的源码,尤其是着重看界面切换的代码,此时发现showOnly方法,要显示的View当然是进行进场动画,而其他View按本意应该走下面的代码,

并且执行startAnimation(mOutAnimation)

                if (animate && mOutAnimation != null && child.getVisibility() == View.VISIBLE) {
                    child.startAnimation(mOutAnimation);
                } else if (child.getAnimation() == mInAnimation)
                    child.clearAnimation();
                child.setVisibility(View.GONE);

结果if和else if都没有执行,这说明将要退场的View的visiability值应该是GONE,那这个View的visiability在哪设置成GONE了呢,

开始MyMarqueeView是没有child的,是代码中添加了两个TextView,因此从addView为出发点开始继续寻找线索,最终在ViewAnimator的addView方法中找到了缘由,当我们调用了MyMarqueeView的addView(new TextView)时,必然会调用父类ViewAnimator的addView方法,而它重写的addView方法(见上面)明确定义了,当childCount>1时,新添加的child的visiability值就是GONE,回到上面measureChildren过后打印两个TextView的大小不一样,是因为visiability一个是VISIABLE一个是GONE的原因?进入ViewGroup的measureChildren的方法,源码如下:

    /**
     * Ask all of the children of this view to measure themselves, taking into
     * account both the MeasureSpec requirements for this view and its padding.
     * We skip children that are in the GONE state The heavy lifting is done in
     * getChildMeasureSpec.
     *
     * @param widthMeasureSpec The width requirements for this view
     * @param heightMeasureSpec The height requirements for this view
     */
    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

发现隐藏的View确实没有按父类提供的大小去measure,因此出现了一个TextView的大小不是自定义的大小,超出了父类MyMarqueeView大小的child出场动画当然是看不到。

解决:

重写MyMarqueeView的measureChildren方法,让隐藏的TextView也要计算出它的大小

    @Override
    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = getChildCount();
        for (int i = 0; i < size; ++i) {
            final View child = getChildAt(i);
            //显示的child和未显示的child都要去计算大小
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }

再次运行可以执行出场动画了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章