基于ClipPathLayout轉(zhuǎn)場動(dòng)畫布局的實(shí)現(xiàn)

基于ClipPathLayout轉(zhuǎn)場動(dòng)畫布局的實(shí)現(xiàn)

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

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

依賴

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

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

TransitionFrameLayout

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

TransitionFrameLayout -> ClipPathFrameLayout -> FrameLayout

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

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

使用

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

具體的使用以簡單的兩個(gè)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="藍(lán)色界面"
        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)在綠色界面在上面顯示,藍(lán)色隱藏.

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

TransitionAdapter adapter = mLayout.switchView(mBlueView);

switchView有兩個(gè)方法

    @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表示動(dòng)畫擴(kuò)張,為true表示收縮.

在switchView后獲得一個(gè)adapter對象,此時(shí)藍(lán)色界面還沒有展示出來.

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

adapter.animate();

來啟動(dòng)場景切換動(dòng)畫效果.

也可以通過

adapter.getAnimator();

獲得一個(gè)屬性動(dòng)畫,自己控制動(dòng)畫過程.

還可以獲得一個(gè)Controller對象

mController = adapter.getController();

然后通過

mController.setProgress

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

adapter.finish();

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

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

image

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

image

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

原理

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

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

實(shí)現(xiàn)這個(gè)動(dòng)畫需要解決幾個(gè)問題:

  • 動(dòng)畫的擴(kuò)散點(diǎn)(收縮點(diǎn))定在哪里?

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

  • 還有一個(gè)問題是動(dòng)畫何時(shí)停止?

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

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

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

TransitionFragmentContainer

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

TransitionFragmentContainer -> TransitionFrameLayout -> ClipPathFrameLayout -> FrameLayout

沒錯(cuò),這個(gè)是TransitionFrameLayout的子View.

使用

這個(gè)的使用就巨簡單了,將常用的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>

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

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

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

切換效果如下

image

原理

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

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

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

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

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

GitHub地址

ClipPathLayout

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

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

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