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

再次運行可以執行出場動畫了。

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