冷門(mén)控件之ViewFlipper-實(shí)現(xiàn)類似拼多多詳情頁(yè)拼團(tuán)人數(shù)的上下翻滾效果

前世嗶嗶

對(duì)于ViewPager,相信大家都不陌生,但是對(duì)于ViewFlipper大家確聽(tīng)的少,甚至都沒(méi)聽(tīng)過(guò),首先來(lái)了解下這貨是干嘛的,能實(shí)現(xiàn)什么效果:

 * Simple {@link ViewAnimator} that will animate between two or more views
 * that have been added to it.  Only one child is shown at a time.  If
 * requested, can automatically flip between each child at a regular interval.

源碼中翻譯過(guò)來(lái)就是:
可以在兩個(gè)或多個(gè)視圖之間添加動(dòng)畫(huà),一次只能展示1個(gè)視圖,并可以自動(dòng)使用動(dòng)畫(huà)輪回播放翻轉(zhuǎn)。
所以,我們能用它實(shí)現(xiàn)的效果有:

  • 跑馬燈
  • 水平,垂直翻頁(yè)公告
  • 視圖動(dòng)畫(huà)切換(圖片切換,場(chǎng)景切換等),不過(guò)用ViewPager實(shí)現(xiàn)更好

正確的打開(kāi)方式

第一步,聲明控件,并往里面添加需要切換的視圖,就隨便往里面扔兩只TextView好了
 <ViewFlipper
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:autoStart="true"
        android:flipInterval="500"
        android:inAnimation="@anim/right_in"
        android:outAnimation="@anim/left_out"
        android:id="@+id/mViewFlipper"
        >

        <TextView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:gravity="center"
            android:text="第一個(gè)視圖" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="第二個(gè)視圖" />
    </ViewFlipper>

其中聲明的xml屬性如下:
android:autoStart: 為true則會(huì)自動(dòng)加載下一個(gè)View,默認(rèn)為false
android:flipInterval:設(shè)置View之間切換的動(dòng)畫(huà)時(shí)間間隔,默認(rèn)為3000
后面的屬性則是來(lái)自它的父類ViewAnimator的xml屬性(這個(gè)后面源碼會(huì)分析)
outAnimation,inAnimation:設(shè)置進(jìn)入和退出的動(dòng)畫(huà)
animateFirstView:加載第一個(gè)視圖是否使用動(dòng)畫(huà)加載,默認(rèn)為true

第二步,調(diào)用啟動(dòng)的代碼,當(dāng)然xml中的屬性使用java代碼也是可以設(shè)置的

調(diào)用startFlipping啟動(dòng),調(diào)用stopFlipping停止,調(diào)用showNext顯示下一個(gè)視圖,調(diào)用showPrevious顯示上一個(gè)視圖

用法其實(shí)很簡(jiǎn)單,實(shí)現(xiàn)的原理其實(shí)也非常的簡(jiǎn)單

妖魔鬼怪 一探究竟

一 繼承關(guān)系

此物繼承自ViewAnimator,而ViewAnimator又繼承自FrameLayout,所以,它內(nèi)部的View自然都有重疊屬性和視圖動(dòng)畫(huà)屬性

二 關(guān)鍵方法

關(guān)鍵的方法當(dāng)然是這兩個(gè)貨啦,這是源頭:

    public void startFlipping() {
        mStarted = true;
        updateRunning();
    }

    /**
     * No more flips
     */
    public void stopFlipping() {
        mStarted = false;
        updateRunning();
    }

check updateRunning=>

    private void updateRunning(boolean flipNow) {
        boolean running = mVisible && mStarted && mUserPresent;
        if (running != mRunning) {
            if (running) {
                //展示當(dāng)前的視圖
                showOnly(mWhichChild, flipNow);
                //延遲執(zhí)行一個(gè)任務(wù)
                postDelayed(mFlipRunnable, mFlipInterval);
            } else {
                removeCallbacks(mFlipRunnable);
            }
            mRunning = running;
        }
        if (LOGD) {
            Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
                    + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);
        }
    }

這里就做了兩件事,調(diào)用showOnly展示當(dāng)前的視圖,延遲執(zhí)行mFlipRunnable任務(wù),如果不是在運(yùn)行中,取消掉該任務(wù)
check mFlipRunnable =>

    private final Runnable mFlipRunnable = new Runnable() {
        @Override
        public void run() {
            if (mRunning) {
                showNext();
                postDelayed(mFlipRunnable, mFlipInterval);
            }
        }
    };

showNext就是切換到下一個(gè)視圖,所以這個(gè)任務(wù)的目的就是不斷延遲mFlipInterval毫秒切換到下一個(gè)視圖
check showNext=>

//和showPrevious一樣,最終都會(huì)調(diào)用這個(gè)方法,參數(shù)不同而已 
    @android.view.RemotableViewMethod
    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);
        }
    }

這個(gè)方法的作用是顯示指定的視圖
所以,把這些串起來(lái)就是:調(diào)用startFlipping,就會(huì)啟動(dòng)mFlipRunnable任務(wù)并以mFlipInterval毫秒的時(shí)間在輪詢調(diào)用showOnly方法來(lái)顯示視圖,這個(gè)方法需要傳入一個(gè)當(dāng)前View的下標(biāo),所以通過(guò)mWhichChild來(lái)保存當(dāng)前View的下標(biāo),而mWhichChild的維護(hù)是通過(guò)setDisplayedChild方法來(lái)實(shí)現(xiàn)的,通過(guò)對(duì)當(dāng)前View的個(gè)數(shù)做加減操作并保證其不大于當(dāng)前子視圖的個(gè)數(shù),如果大于,則重置為0,所以保證了視圖的循環(huán)顯示
但是,動(dòng)畫(huà)是在哪里執(zhí)行的呢,當(dāng)然是在我們還沒(méi)看的showOnly方法里拉:

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

這個(gè)方法需要傳入當(dāng)前需要執(zhí)行的動(dòng)畫(huà)的View下標(biāo),和一個(gè)是否需要執(zhí)行動(dòng)畫(huà)的標(biāo)志,當(dāng)然這個(gè)在ViewFlipper中是寫(xiě)死的為true,不然哪來(lái)的效果呢。
這個(gè)方法就做了兩件事,為當(dāng)前下標(biāo)匹配的View啟動(dòng)動(dòng)畫(huà)并將其顯示出來(lái),將其他的View隱藏掉并調(diào)用移出動(dòng)畫(huà)。
所以,ViewFlipper的原理其實(shí)就是顯示當(dāng)前的View,隱藏其他的View,并用動(dòng)畫(huà)來(lái)達(dá)到需要的效果,使用postDelayed循環(huán)調(diào)用來(lái)達(dá)到輪詢的目的,并沒(méi)有想象中的神秘,不是嗎?

今世嗶嗶

當(dāng)然在項(xiàng)目中使用還需要我們自己封裝一下,像那種標(biāo)題所說(shuō)的拼多多的那種布局,使用這個(gè)很容易實(shí)現(xiàn),但是需要注意數(shù)據(jù)源的賦值問(wèn)題,后端肯定是傳入一個(gè)集合過(guò)來(lái),那么如何計(jì)算下標(biāo)呢,其實(shí)我們從源碼中也能學(xué)習(xí)到,在輪詢到視圖的最后一個(gè)的時(shí)候,將下標(biāo)置為0就行了,然后執(zhí)行一次,下標(biāo)加1,這樣也就達(dá)到了數(shù)據(jù)源輪詢的目的

項(xiàng)目也有一個(gè)與拼多多類似的需求,一開(kāi)始是用的RecyclerView來(lái)做的,將adapter的itemCount置為無(wú)限大來(lái)達(dá)到輪詢的目的,雖然效果也能達(dá)到,但是每一個(gè)item里面還有個(gè)倒計(jì)時(shí),而每一次滾動(dòng),都會(huì)重新創(chuàng)建View,所以倒計(jì)時(shí)的狀態(tài)很難保存下來(lái),全部都亂了套,后來(lái)打算寫(xiě)個(gè)自定義,無(wú)意發(fā)現(xiàn)了這個(gè)控件,后來(lái)在gayHub上看到了基于此封裝的庫(kù),看了看源碼,寫(xiě)的挺好的,就非(bu)常(yao)愉(bi)快(lian)的拿過(guò)來(lái)用了,基本滿足我的需求,在此推薦給大家:傳送門(mén)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容