基于ClipPathLayout轉(zhuǎn)場動畫布局的實現(xiàn)

基于ClipPathLayout轉(zhuǎn)場動畫布局的實現(xiàn)

在上篇Android中不規(guī)則形狀View的布局實現(xiàn)中講解了ClipPathLayout的使用及核心原理實現(xiàn),這篇將講解基于ClipPathLayout擴展出來的轉(zhuǎn)場動畫布局的實現(xiàn).

擴展的轉(zhuǎn)場動畫布局目前暫且有兩種,一種是針對View的切換的,一種是針對Fragment切換的.

依賴

轉(zhuǎn)場動畫的布局存在于ClipPathLayout中,所以添加如下依賴即可

implementation 'com.yxf:clippathlayout:1.0.+'

TransitionFrameLayout

這是一個用于View轉(zhuǎn)場切換的一個布局,其繼承關(guān)系如下

TransitionFrameLayout -> ClipPathFrameLayout -> FrameLayout

其中ClipPathFrameLayout具備完全的FrameLayout的功能,并且增加了對不規(guī)則圖形的布局支持.

TransitionFrameLayout,首先這個ViewGroup的設(shè)定就是用于做場景切換的,那么其實他只需要顯示一個子View,所以在TransitionFrameLayout中修改了顯示邏輯,添加的子View只有最后一個View會獲得顯示,其他View都是GONE隱藏狀態(tài).然后既然設(shè)定如此,那么addView和setVisibility將不建議使用,這兩個方法會破壞這個ViewGroup的場景設(shè)定.然后子View的大小也要求是和TransitionFrameLayout一致的,即使用match_parent的方式,不然可能會導(dǎo)致出現(xiàn)一些不和諧的切換效果.

使用

TransitionFrameLayout 的使用非常簡單,如果需要添加或者將隱藏的View顯示出來只需要調(diào)用TransitionFrameLayout.switchView即可,這個方法將會把switchView的View顯示出來,然后將原來顯示的View隱藏.

具體的使用以簡單的兩個TextView為例

<com.yxf.clippathlayout.transition.TransitionFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/blue_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#880000ff"
        android:gravity="center"
        android:text="藍色界面"
        android:textSize="30sp" />

    <TextView
        android:id="@+id/green_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#8800ff00"
        android:gravity="center"
        android:text="綠色界面"
        android:textSize="30sp" />

</com.yxf.clippathlayout.transition.TransitionFrameLayout>
mLayout = (TransitionFrameLayout) inflater.inflate(R.layout.fragment_view_transition, null);

現(xiàn)在綠色界面在上面顯示,藍色隱藏.

如果需要將藍色界面切換出來,可以調(diào)用如下代碼.

TransitionAdapter adapter = mLayout.switchView(mBlueView);

switchView有兩個方法

    @Override
    public TransitionAdapter switchView(View view) {
        return switchView(view, false);
    }

    /**
     * if you want add a view , just invoke switchView directly ,
     * do not invoke addView , it may cause some problem .
     *
     * @param view
     * @return
     */
    @Override
    public TransitionAdapter switchView(final View view, boolean reverse) {
        //.................
    }

reverse為false表示動畫擴張,為true表示收縮.

在switchView后獲得一個adapter對象,此時藍色界面還沒有展示出來.

可以通過adapter獲得一個ValueAnimator對象或者一個Controller對象.
可以直接調(diào)用

adapter.animate();

來啟動場景切換動畫效果.

也可以通過

adapter.getAnimator();

獲得一個屬性動畫,自己控制動畫過程.

還可以獲得一個Controller對象

mController = adapter.getController();

然后通過

mController.setProgress

來控制動畫的實現(xiàn)進度.當(dāng)?shù)竭_1時(進度范圍0~1),即動畫結(jié)束時,調(diào)用

adapter.finish();

來通知轉(zhuǎn)場結(jié)束了.

直接使用adapter.animate()的效果如下

image

也可以通過自己通過Controller控制進度,比如關(guān)聯(lián)滑動,可以獲得如下效果

image

具體實現(xiàn)請自行查閱源碼.

原理

現(xiàn)在是實現(xiàn)原理時間,這個實現(xiàn)是基于ClipPathLayout實現(xiàn)的,由于ClipPathLayout具備裁剪子View實現(xiàn)任意形狀View的能力.通過這一點,其實可以讓子View的Path信息發(fā)生變化,進而讓子View的繪制形狀發(fā)生變化.嗯,原理就是這么簡單.

當(dāng)前做的靜態(tài)的Path形態(tài)的變化效果,如果需要做動態(tài)的,對于Path生成器的實現(xiàn)會比較復(fù)雜,當(dāng)然動態(tài)的Path可以增加一些貝塞爾曲線之類的,實現(xiàn)更加炫酷的效果,有興趣的同學(xué)可以自己嘗試去實現(xiàn).

實現(xiàn)這個動畫需要解決幾個問題:

  • 動畫的擴散點(收縮點)定在哪里?

不同的擴散點,Path是不一樣的,但是也不能每次都重新寫一個Path吧,這樣太麻煩了,那把擴散點以參數(shù)的方式傳給Path生成器嗎?這樣做,增加了Path生成器的工作,并不合適.最終想到的方式是通過矩陣變幻的方式,通過Path.transform方法生成一個新的Path,這樣就可以實現(xiàn)Path的平移和縮放效果了.

  • 還有一個問題是動畫何時停止?

擴散肯定有個度,需要知道多大的Scale可以讓Path內(nèi)的區(qū)域覆蓋整個View.這是一個非常難的問題,如果是一個完全閉合而且里面完全填充的Path,還可以通過類似二分查找的方式找到合適的位置,但如果Path里面有鏤空的怎么辦?就像半個陰陽魚或者一個圓環(huán),在這種情況下沒有很好的辦法可以找到一個非常合適的Scale去讓擴大后的Path覆蓋掉整個View.這里默認將使用二分法的方式找出一個合適的區(qū)域,不過基于以上問題的存在,如果Path比較特殊可以實現(xiàn)TransitionPathGenerator接口,這個接口比普通的Path生成器多了一個方法(maxContainSimilarRange),用于確定限定范圍內(nèi)的Path可以包含的最大矩形區(qū)域,這個區(qū)域當(dāng)然最好是和外面?zhèn)鬟M來的區(qū)域相似的,然后獲得這個區(qū)域后就可以通過計算來獲得一個合適的Scale,讓經(jīng)過變幻后的Path可以剛好覆蓋整個View.

由于直接由Path生成器獲得的Path是不能直接使用的,需要轉(zhuǎn)換,所以也有了一個適配器(TransitionAdapter),這個適配器負責(zé)將原始的Path進行變幻,然后再交給ClipPathLayout處理.

嗯,原理大概是這樣吧!具體詳情參見源碼,還是寫的很簡單的.

TransitionFragmentContainer

這個是Fragment的容器,用于Fragment的場景切換,其繼承關(guān)系如下

TransitionFragmentContainer -> TransitionFrameLayout -> ClipPathFrameLayout -> FrameLayout

沒錯,這個是TransitionFrameLayout的子View.

使用

這個的使用就巨簡單了,將常用的Fragment容器FrameLayout在xml中替換成TransitionFragmentContainer即可

<com.yxf.clippathlayout.transition.TransitionFragmentContainer xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context=".MainActivity"
    tools:showIn="@layout/app_bar_main">

</com.yxf.clippathlayout.transition.TransitionFragmentContainer>

由于這個View是繼承于TransitionFrameLayout的,所以這個View支持替換掉默認的適配器,可以設(shè)置默認的動畫時間,插值器,擴散中心等信息.

    mContainer = findViewById(R.id.fragment_container);
    RandomTransitionPathGenerator generator =
            new RandomTransitionPathGenerator(new CircleTransitionPathGenerator());
    generator.add(new OvalTransitionPathGenerator());
    generator.add(new RhombusTransitionPathGenerator());
    mContainer.setAdapter(new TransitionAdapter(generator));

但是這個動畫是自動的,不支持主動控制,所以不應(yīng)該直接獲得其Animator或者Controller對象.

切換效果如下

image

原理

這個大部分的實現(xiàn)還是基于TransitionFrameLayout實現(xiàn)的,TransitionFragmentContainer需要做處理的是Fragment的添加和刪除過程.

Fragment的添加和刪除過程在容器中的表現(xiàn)就是可見性的控制和View的增加刪除.

所以TransitionFragmentContainer重寫了Fragment添加和刪除所會用到的addView和removeView和removeViewAt方法.

直接的添加過程還是很簡單的,直接在addView中調(diào)用switchView即可,但是Fragment的replace過程有點讓人頭疼.Fragment的replace會先調(diào)用刪除然后再添加,這樣的話就有個問題,如何判斷他是replace,而不是remove或者add呢?這里使用的方法是remove的時候使用一個屬性動畫,然后在動畫結(jié)束才會真正的把View刪掉,如果是替換的話,還會調(diào)用到addView方法,然后在addView中取消之前remove的動畫,并且繼承其需要remove的View,在新的addView動畫結(jié)束時將需要remove的View刪除,寫這部分邏輯的時候栽坑里好多次,都是時序問題導(dǎo)致的.........每次都要找很久才找到問題原因..............

代碼的具體實現(xiàn)請自行查閱源碼吧!

GitHub地址

ClipPathLayout

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市虱岂,隨后出現(xiàn)的幾起案子弛说,更是在濱河造成了極大的恐慌寒砖,老刑警劉巖夜只,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猜揪,死亡現(xiàn)場離奇詭異捂襟,居然都是意外死亡秽褒,警方通過查閱死者的電腦和手機坝冕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門徒探,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人喂窟,你說我怎么就攤上這事测暗⊙氪” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵碗啄,是天一觀的道長质和。 經(jīng)常有香客問我,道長挫掏,這世上最難降的妖魔是什么侦另? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮尉共,結(jié)果婚禮上褒傅,老公的妹妹穿的比我還像新娘。我一直安慰自己袄友,他們只是感情好殿托,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著剧蚣,像睡著了一般支竹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鸠按,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天礼搁,我揣著相機與錄音,去河邊找鬼目尖。 笑死馒吴,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的瑟曲。 我是一名探鬼主播饮戳,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼洞拨!你這毒婦竟也來了扯罐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤烦衣,失蹤者是張志新(化名)和其女友劉穎歹河,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體花吟,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡秸歧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了示辈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寥茫。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖矾麻,靈堂內(nèi)的尸體忽然破棺而出纱耻,到底是詐尸還是另有隱情芭梯,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布弄喘,位于F島的核電站玖喘,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蘑志。R本人自食惡果不足惜累奈,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望急但。 院中可真熱鬧澎媒,春花似錦、人聲如沸波桩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽镐躲。三九已至储玫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間萤皂,已是汗流浹背撒穷。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留裆熙,地道東北人端礼。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像弛车,于是被迫代替她去往敵國和親齐媒。 傳聞我的和親對象是個殘疾皇子蒲每,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容