ViewFlipper源碼分析

背景

最近產(chǎn)品有個(gè)需求是給用戶(hù)上下滾動(dòng)播放運(yùn)營(yíng)消息昵济,這個(gè)需求我之前搞過(guò)铲觉,實(shí)現(xiàn)方式很是復(fù)雜逼蒙,采用的自定義view的方式繪制出來(lái)的从绘,今天用一種簡(jiǎn)單的方式實(shí)現(xiàn)

ViewFlipper

這個(gè)就是我們今天的主角,通過(guò)ViewFlipper就是可以實(shí)現(xiàn)View之間的切換

效果展示

ok.gif

效果展示可能有點(diǎn)卡頓是牢,但是實(shí)際上是不卡頓的

代碼示例

1.xml 描述View

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".MainActivity">

    <ViewFlipper
        android:id="@+id/viewFlipper"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</android.support.constraint.ConstraintLayout>

2.動(dòng)畫(huà)文件

anim_push_in.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="500"
        android:fromYDelta="100%p"
        android:toYDelta="0"/>  
    <alpha
        android:duration="500"
        android:fromAlpha="0.0"
        android:toAlpha="1.0"/>
</set>

anim_push_out.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="500"
        android:fromYDelta="0"
        android:toYDelta="-100%p"/>
    <alpha
        android:duration="500"
        android:fromAlpha="1.0"
        android:toAlpha="0.0"/>
</set>


3.代碼編寫(xiě)

package demo.nate.com.viewflipper;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Gravity;
import android.widget.TextView;
import android.widget.ViewFlipper;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private ViewFlipper mViewFlipper;

    private String[] mContents = new String[]{"窗前明月光僵井。", "疑是地上霜","舉頭望明月","低頭思故鄉(xiāng)"};

    private List<TextView> mViews = new ArrayList<>(mContents.length);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mViewFlipper = findViewById(R.id.viewFlipper);
        createView();
        addView();
        mViewFlipper.setInAnimation(this, R.anim.anim_push_in);
        mViewFlipper.setOutAnimation(this, R.anim.anim_push_out);
        mViewFlipper.setFlipInterval(2000);
        mViewFlipper.startFlipping();
    }

    private void addView() {
        for (TextView view : mViews) {
            mViewFlipper.addView(view);
        }
    }

    private void createView() {
        for (int i = 0; i < mContents.length; i++) {
            TextView textView = new TextView(this);
            textView.setTextSize(24);
            textView.setGravity(Gravity.CENTER);
            textView.setTextColor(getResources().getColor(R.color.colorAccent));
            textView.setText(mContents[i]);
            mViews.add(textView);
        }
    }
}

就這么簡(jiǎn)單

ViewFlipper 源碼分析

如果只是講使用的化,根本沒(méi)有必要驳棱,代碼太簡(jiǎn)單了批什,我希望能從原理上了解ViewFlipper,所以我需要分析一下他的源代碼

1.先分析一下繼承關(guān)系

viewFlipper.png

其實(shí)ViewFlipper就是一個(gè)FrameLayout社搅,其核心代碼就是ViewAnimator上

2.ViewAnimtor 方法概率

ViewAnimator 方法上.png

上面這些方法比較關(guān)鍵驻债,我們后續(xù)詳細(xì)分析一下

ViewAnimator方法下.png

總結(jié)主要是提供了一些動(dòng)畫(huà)設(shè)置接口

3.整個(gè)流程分析

啟動(dòng)動(dòng)畫(huà)

        mViewFlipper.startFlipping();
 /**
     * Start a timer to cycle through child views
     */
    public void startFlipping() {
        mStarted = true;
        updateRunning();
    }
    
      private void updateRunning() {
        updateRunning(true);
    }
    

mStarted 變量默認(rèn)是false,updateRunning()主要作用是循環(huán)執(zhí)行動(dòng)畫(huà)

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

這個(gè)地方有幾個(gè)變量首先要識(shí)別一下形葬,mStarted 這個(gè)變量我們之前已經(jīng)看過(guò)了合呐,默認(rèn)值是false,啟動(dòng)動(dòng)畫(huà)的時(shí)候設(shè)置了true笙以,mVisible 這個(gè)變量我們先看一下這個(gè)值賦值是在哪里淌实?

 @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mVisible = false;

        getContext().unregisterReceiver(mReceiver);
        updateRunning();
    }

    @Override
    protected void onWindowVisibilityChanged(int visibility) {
        super.onWindowVisibilityChanged(visibility);
        mVisible = visibility == VISIBLE;
        updateRunning(false);
    }

其實(shí)就是在view顯示的時(shí)候,把這個(gè)值設(shè)置為true

現(xiàn)在只有mUserPresent的值我們還沒(méi)有分析了,通過(guò)分析代碼我們可以發(fā)現(xiàn)這個(gè)值在初始化的時(shí)候就是true

  public class ViewFlipper extends ViewAnimator {
    private static final String TAG = "ViewFlipper";
    private static final boolean LOGD = false;

    private static final int DEFAULT_INTERVAL = 3000;

    private int mFlipInterval = DEFAULT_INTERVAL;
    private boolean mAutoStart = false;

    private boolean mRunning = false;
    private boolean mStarted = false;
    private boolean mVisible = false;
    private boolean mUserPresent = true;

對(duì)他的修改也只是在接收屏幕關(guān)閉的廣播時(shí)候進(jìn)行修改

  public ViewFlipper(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.obtainStyledAttributes(attrs,
                com.android.internal.R.styleable.ViewFlipper);
        mFlipInterval = a.getInt(
                com.android.internal.R.styleable.ViewFlipper_flipInterval, DEFAULT_INTERVAL);
        mAutoStart = a.getBoolean(
                com.android.internal.R.styleable.ViewFlipper_autoStart, false);
        a.recycle();
    }

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            if (Intent.ACTION_SCREEN_OFF.equals(action)) {
                mUserPresent = false;
                updateRunning();
            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
                mUserPresent = true;
                updateRunning(false);
            }
        }
    };

還是回到我們主線(xiàn)代碼中

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

變量running的值是true拆祈,mRunning 的值是false恨闪,也就走到了showOnly這個(gè)方法中,然后postDelayed(mFlipRunnable, mFlipInterval)這個(gè)方法就是定時(shí)切換view然后執(zhí)行動(dòng)畫(huà)放坏,我們先看看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è)方法定義在ViewAnimator中凛剥,整體的邏輯也是比較簡(jiǎn)單的,就是根據(jù)childIndex的索引查找指定的view轻姿,執(zhí)行進(jìn)入動(dòng)畫(huà)犁珠,其他view執(zhí)行移出動(dòng)畫(huà),那么循環(huán)動(dòng)畫(huà)是如何執(zhí)行的呢互亮?

    private final Runnable mFlipRunnable = new Runnable() {
        @Override
        public void run() {
            if (mRunning) {
                showNext();
                postDelayed(mFlipRunnable, mFlipInterval);
            }
        }
    };
    
    public void showNext() {
        setDisplayedChild(mWhichChild + 1);
    }
    
    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);
        }
    }

總體上就是控制childIndex的大小犁享,不讓其查過(guò)最大值和最小值,剩下邏輯跟之前一樣豹休,就是執(zhí)行動(dòng)畫(huà)

總結(jié)

  1. 從使用角度來(lái)看炊昆,ViewFlipper可以實(shí)現(xiàn)兩個(gè)View之間的任意動(dòng)畫(huà),讓開(kāi)發(fā)正更加關(guān)注動(dòng)畫(huà)的編寫(xiě)而不需要關(guān)系動(dòng)畫(huà)的執(zhí)行邏輯
  2. 從代碼設(shè)計(jì)角度來(lái)看威根,這種用FrameLayout來(lái)父布局凤巨,同過(guò)兩個(gè)view來(lái)不斷執(zhí)行動(dòng)畫(huà)來(lái)達(dá)到設(shè)計(jì)上的動(dòng)畫(huà)效果,這種實(shí)現(xiàn)方式很是簡(jiǎn)單洛搀,在工作中很有借鑒性
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末敢茁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子留美,更是在濱河造成了極大的恐慌彰檬,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谎砾,死亡現(xiàn)場(chǎng)離奇詭異逢倍,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)景图,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)较雕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人挚币,你說(shuō)我怎么就攤上這事亮蒋。” “怎么了忘晤?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵宛蚓,是天一觀(guān)的道長(zhǎng)激捏。 經(jīng)常有香客問(wèn)我设塔,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任闰蛔,我火速辦了婚禮痕钢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘序六。我一直安慰自己任连,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布例诀。 她就那樣靜靜地躺著随抠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪繁涂。 梳的紋絲不亂的頭發(fā)上拱她,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音扔罪,去河邊找鬼秉沼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛矿酵,可吹牛的內(nèi)容都是我干的唬复。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼全肮,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼敞咧!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起辜腺,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤妄均,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后哪自,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體丰包,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年壤巷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了邑彪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡胧华,死狀恐怖寄症,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情矩动,我是刑警寧澤有巧,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站悲没,受9級(jí)特大地震影響篮迎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一甜橱、第九天 我趴在偏房一處隱蔽的房頂上張望逊笆。 院中可真熱鬧,春花似錦岂傲、人聲如沸难裆。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)乃戈。三九已至,卻和暖如春亩进,著一層夾襖步出監(jiān)牢的瞬間偏化,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工镐侯, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留侦讨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓苟翻,卻偏偏與公主長(zhǎng)得像韵卤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子崇猫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353