專案需求討論 — 用Transition做一個漂亮的登入介面

青蛙要fly發表於2018-01-10

前言

一次在逛Github的時候,看到一個漂亮的登入介面,用的是Transition做的。我就直接貼上地址:

MaterialLogin

當然,如果單純的直接拿過來用,沒有任何意義。主要還是來看具體如何實現的。我就來寫下具體如何一步步的來實現這個效果。

我也按照相應的原理寫了個Demo。最後的效果如下圖所示(其中layout佈局我就直接從github上面拷貝過來了):

專案需求討論 — 用Transition做一個漂亮的登入介面


基礎

首先我們來看下什麼是Transition。大家看仔細是Transition,而不是Translate。我們直接看翻譯:

專案需求討論 — 用Transition做一個漂亮的登入介面

而Translate通常我們指的是平移的動畫操作。

Transition:

所以我們知道了用的是過渡的方式來做,那什麼是過渡呢?

Android 4.4:

Android對於開發者提供了越來越多的動畫API支援。從API 1就存在的Drawable Animation和View Animation,以及API 11(Android 3.0)以後加入的Property Animation。而過渡動畫Transition是在API 19(Android 4.4.2)中加入的。

基礎知識我就不說了,直接看其他文章傳送門:

Android 過渡(Transition)動畫解析之基礎篇

所以初步我們可以理解為(可能這麼說明有不對,可以提出):

專案需求討論 — 用Transition做一個漂亮的登入介面

場景(scenes)和變換(transitions)。場景(scenes)定義了當前的UI狀態,變換(transitions)則定義了在不同場景之間動畫變化的過程。

當一個場景改變的時候,transition主要負責:

(1)捕捉每個View在開始場景和結束場景時的狀態。

(2)根據兩個場景(開始和結束)之間的區別建立一個Animator。

Android 5.0

Android 5.0中Transition可以被用來實現Activity或者Fragment切換時的異常複雜的動畫效果。

雖然在以前的版本中,已經可以使用Activity的overridePendingTransition() 和 FragmentTransaction的setCustomAnimation()來實現Activity或者Fragment的動畫切換,但是他們僅僅侷限與將整個檢視一起動畫變換。新的Lollipop api更進了一步,讓單獨的view也可以在進入或者退出其佈局容器中時發生動畫效果,甚至還可以在不同的activity/Fragment中共享一個view。

還是上面那個圖,只是變成了二個Activity介面:

專案需求討論 — 用Transition做一個漂亮的登入介面

我們在跳轉到第二個Activity的時候,我們會有個過場動畫。會第一個Activity的按鈕移動到第二個Activity的按鈕。效果如下所示:

專案需求討論 — 用Transition做一個漂亮的登入介面

所以我們再回頭看下面這種效果,是不是就知道怎麼實現了,用的是Activity的過渡動畫了。

專案需求討論 — 用Transition做一個漂亮的登入介面

大家也可以看看下面的相關文章連結:

Activity和Fragment Transition介紹

深入理解Content Transition

深入理解共享元素變換(Shared Element Transition)-上


正文

我們先準備第一個Activity,介面如下:

專案需求討論 — 用Transition做一個漂亮的登入介面
Activity 1

第一步:fab按鈕的移動:

我們讓那個按鈕"+"能移動到頂部:

專案需求討論 — 用Transition做一個漂亮的登入介面

我們由前面的demo說明已經知道了,啟動第二個Activity,我們我們先讓第二個Activity的介面如下所示:

專案需求討論 — 用Transition做一個漂亮的登入介面

我們設定第二個Activity的主題為:

<style name="Translucent" parent="Theme.AppCompat.Light.NoActionBar">
    //第一個activity的狀態列顏色為#0288D1
    <item name="colorPrimaryDark">#0288D1</item>
    //第二個activity的背景為透明,
    //這樣可以看得到第一個Activity的介面
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowIsTranslucent">true</item>
</style>
複製程式碼

沒錯,我們在第二個介面先寫上一個按鈕"X",這樣我們啟動第二個Activity的時候就蓋在了第一個Activity的上面,同時這個fab按鈕也有了動畫的效果:

專案需求討論 — 用Transition做一個漂亮的登入介面

程式碼很簡單,只要讓第一個Activity的按鈕的android:transitionName與第二個Activity的按鈕的android:transitionName一樣就可以。我們稱這個為共享元素

FloatingActionButton btn = findViewById(R.id.fab);;

ActivityOptionsCompat optionsCompat
    = ActivityOptionsCompat.makeSceneTransitionAnimation(LoginMainActivity.this,btn,btn.getTransitionName());
startActivity(new Intent(LoginMainActivity.this,RegisterMainActivity.class),optionsCompat.toBundle());
複製程式碼

然後通過ActivityOptionsCompat來記錄當前這個Activity的這個fab按鈕的狀態。然後在startActivity的時候,通過optionsCompat.toBundle()把內容帶到了第二個Activity中。第二個Activity就會讓現在的相同trasitionName的fab按鈕,以傳過來的第一個Activity的按鈕相同位置的為起始點,然後通過動畫到了最終的地方。(所以動畫是在第二個Activity中完成的,只是按鈕的起始狀態是以第一個Activity傳過來的按鈕的狀態資訊相同,然後到終端使用者設定的位置。)

我們可以看到,共享元素變換並不是真正實現了兩個activity或者Fragment之間元素的共享,實際上我們看到的幾乎所有變換效果中(不管是B進入還是B返回A),共享元素都是在B中繪製出來的。Framework沒有真正試圖將A中的某個元素傳遞給B,而是採用了不同的方法來達到相同的視覺效果。A傳遞給B的是共享元素的狀態資訊。B利用這些資訊來初始化共享View元素,讓它們的位置、大小、外觀與在A中的時候完全一致。當變換開始的時候,B中除了共享元素之外,所有的其他元素都是不可見的。隨著動畫的進行,framework 逐漸將B的activity視窗顯示出來,當動畫完成,B的視窗才完全可見。

並且其實動畫是繪製在ViewOverlay上面,可以看看這篇文章:ViewOverlay與animation介紹

第二步讓fab按鈕通過曲線路徑變化:

我們直接不做任何處理,預設是fab按鈕的位置變化是直線。 我們更希望是:

專案需求討論 — 用Transition做一個漂亮的登入介面

我們可以設定共享元素的進入動畫:

<?xml version="1.0" encoding="utf-8"?>
<transitionSet
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:interpolator/linear_out_slow_in"
    android:duration="3000">

    <changeBounds>
        <arcMotion
            android:maximumAngle="0"
            android:minimumHorizontalAngle="60"
            android:minimumVerticalAngle="90" />
    </changeBounds>
</transitionSet>
複製程式碼
//獲取過渡動畫
Transition transition = TransitionInflater.from(this).inflateTransition(R.transition.fabtransition);
設定共享元素的進入動畫
getWindow().setSharedElementEnterTransition(transition);
複製程式碼

這裡使用的是arcMotion來做的曲線路徑.

ArcMotion文件

裡面的介紹我用的谷歌翻譯翻譯的,大致應該是這個意思: PathMotion在包含兩個點的假想圓上沿圓弧生成曲線路徑。 如果點之間的水平距離小於垂直距離,則圓的中心點將與終點水平對齊。 如果垂直距離小於水平距離,則圓的中心點將與終點垂直對齊。 當兩點接近水平或垂直時,運動的曲線將會變小,因為圓的中心距兩點都很遠。 要強制路徑的曲率,可以使用setMinimumHorizontalAngle(float)和setMinimumVerticalAngle(float)來設定兩點之間的弧的最小角度。

其他參考文章:

曲線運動-1

曲線運動 - 2

第三步fab按鈕動畫結束後出現註冊介面:

我們上一步對fab按鈕設定了過渡的動畫。我們可以對這個過渡動畫設定結束的監聽,然後其他我們的註冊介面的出現:

Transition transition = TransitionInflater.from(this).inflateTransition(R.transition.fabtransition);
getWindow().setSharedElementEnterTransition(transition);
transition.addListener(new Transition.TransitionListener() {
            
    .....
    .....
            
    @Override
    public void onTransitionEnd(Transition transition) {
        transition.removeListener(this);
        
        
        /*我們可以在動畫結束後,
            可以在這裡寫上程式碼,
            讓註冊介面出現
        */
        .....
        .....
       
    }
});
複製程式碼

這是我們的第二個Activity的佈局變成了這樣:

專案需求討論 — 用Transition做一個漂亮的登入介面

只不過預設這個註冊介面是不可見的,等到我們的fab按鈕動畫結束後,我們再讓註冊介面可見就可以了。

這裡我們可以直接在上面fab按鈕動畫結束的時候,直接讓註冊介面出現(因為這個註冊介面是用CardView寫的,所以這裡直接用cardView來指這個例項),我們可以在上面的結束監聽裡面直接設定:

@Override
public void onTransitionEnd(Transition transition) {
    transition.removeListener(this);
    //設定可見
    cardView.setVisibility(View.VISIBLE);
}
複製程式碼

效果如下:

專案需求討論 — 用Transition做一個漂亮的登入介面

我們發現,直接突然出現,雖然功能實現了,但我們還是希望有更好看的效果,就像文章開頭那樣,這個註冊介面是慢慢展開的。所以我們在fab按鈕過渡動畫結束後,不是簡單的對cardView設定View.VISIBLE就可以。我們使用揭露動畫來實現:

Animator mAnimator = ViewAnimationUtils.createCircularReveal(cardView,cardView.getWidth()/2
    ,0,0,cardView.getHeight());
mAnimator.setDuration(500);
mAnimator.setInterpolator(new AccelerateInterpolator());
mAnimator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationStart(Animator animation) {
        super.onAnimationStart(animation);
        cardView.setVisibility(View.VISIBLE);
    }
});
mAnimator.start();
複製程式碼

揭露動畫參考文章:

使用Circular Reveal為你的應用新增揭露動畫效果

所以我們這麼使用後效果變成了:

專案需求討論 — 用Transition做一個漂亮的登入介面

第四步返回登入介面:

這裡有二種方式:

  1. 按了手機上的返回鍵
  2. 按了那個fab按鈕返回

我們的fab鍵從左邊移動到了上邊,然後如果你按返回鍵,你會發現自動fab鍵會先執行相應的自動回去動畫,然後activity再關閉。比如你直接對fab鍵設定了點選事件:

btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
       finish();
    }
});
複製程式碼

直接呼叫finish()方法的話,你就會發現,沒有fab鍵返回的動畫,而是直接第二個activity關閉,顯示第一個activity的見面。這樣很不友善。

我們知道預設按返回鍵是呼叫了:

@Override
public void onBackPressed() {
    super.onBackPressed();
}
複製程式碼

說明呼叫onBackPressed會呼叫退出動畫效果後再finish();

參考文章:

最常用的Activity的onBackPressed()與finish()的區別.

所以我們知道了,我們點選fab鍵返回的時候不能直接finish,而是最後一步是呼叫super.onBackPressed();

所以我們最終是先讓註冊介面慢慢消失,消失後呼叫super.onBackPressed();

//覆寫返回鍵操作,
//執行註冊介面消失動畫,
//然後再執行super.onBackPressed();
@Override
public void onBackPressed() {
    animateRevealClose();
}
//fab的點選事件與上面一樣
btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        animateRevealClose();
    }
});
//註冊介面介面慢慢消失,然後呼叫super.onBackPressed()
//然後fab鍵會執行動畫回到原始位置,然後第二個Activity關閉。
//然後顯示了第一個Activity
public void animateRevealClose(){
    
    Animator mAnimator = ViewAnimationUtils.createCircularReveal(cardView,cardView.getWidth()/2
            ,0,cardView.getHeight(),0);
    mAnimator.setDuration(500);
    mAnimator.setInterpolator(new AccelerateInterpolator());
    mAnimator.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            super.onAnimationEnd(animation);
            cardView.setVisibility(View.GONE);
            btn.setImageResource(R.drawable.plus);
            RegisterMainActivity.super.onBackPressed();
        }
    });
    mAnimator.start();
}

複製程式碼

最後實現就是這樣子了:

專案需求討論 — 用Transition做一個漂亮的登入介面


結語

哪裡錯誤了,大家留言回覆哦,多謝支援。o( ̄︶ ̄)o

大佬如果能幫我解答下下面二個問題,非常感謝:

  1. 我在使用arcMotion的時候,小米5(6.0)與華為(7.0),呈現的曲線效果差別很大,(gif圖是小米的,所以fab鍵移動的時候更像是直線,但是華為就很明顯的是曲線)不知道是什麼原因,知道的可以告訴我下。

專案需求討論 — 用Transition做一個漂亮的登入介面
網上的文章清一色都是要求app的主題設定裡面這個屬性要是true,但是我設成了false,為什麼也是沒問題的。比如activity之間的共享元素動畫也是一樣執行的。測試手機是小米5(6.0)與華為(7.0)。

相關文章