Android過渡動畫,發現掘金小祕密

不知道你們有沒有發現,Android版的掘金有下面這個小小動畫:點擊做者頭像跳轉到做者的詳情頁,而做者頭像會從當前界面經過動畫過渡到詳情頁界面。java

知識貧乏限制了個人視野,真心想不到這怎麼實現的?android

最近在寫動畫方面文章時候,從網上找到了答案:Activity過渡動畫中的共享元素過渡web

本文的初衷,是和你們一塊兒掃盲,若是對你有用,歡迎點贊,讓更多的小夥伴多學點知識。小小的動畫,隱藏着巨大的知識點;怪不得面試造火箭,工做擰螺絲,這是知識儲備,雖然可能一生也用不上。面試

系列好文推薦網絡

Android屬性動畫,看完這篇夠用了吧app

Android矢量圖動畫:每人送一輛掘金牌小黃車框架

1、Activity切換過渡動畫

Activity過渡動畫包含進入過渡退出過渡共享元素過渡三個動畫,它們一樣僅支持Android 5.0+版本。編輯器

一)、共享元素過渡動畫

共享元素過渡指的兩個Activity共享的視圖如何在兩個Activity之間進行過渡。例如上面的Gif圖,共享視圖就是ImageViewide

共享元素也分一個元素和多個元素。函數

定義共享元素過渡效果步驟以下:

  1. 在兩個 Activity定義兩個相同類型的View;
  2. 給兩個 View設置相同的 transitionName屬性;
  3. 經過 ActivityOptions.makeSceneTransitionAnimation()函數生成 Bundle對象;
  4. startActivity()函數傳遞 bundle對象。

栗子講解,清晰易懂:

  1. 分別在 activity_first.xmlactivity_second.xml佈局文件定義 ImageView組件,並將 transitionName屬性設爲 activityTransform
<!--activity_first.xml文件內容-->
 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:background="@color/white"  android:orientation="vertical">   <ImageView  android:id="@+id/ivImage"  android:layout_width="wrap_content"  android:layout_height="wrap_content"  android:src="@mipmap/ic_one"  android:transitionName="activityTransform" />   <TextView  android:id="@+id/tvText"  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:layout_marginTop="10dp"  android:gravity="center"  android:text="我是第一個Activity"  android:textColor="@color/c_333"  android:textSize="18sp" /> </LinearLayout>  <!--activity_second.xml文件內容--> <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:orientation="vertical">   <ImageView  android:id="@+id/ivImage"  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:layout_alignParentBottom="true"  android:adjustViewBounds="true"  android:src="@mipmap/ic_one"  android:transitionName="activityTransform" />   <TextView  android:id="@+id/tvText"  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:layout_above="@id/ivImage"  android:layout_marginBottom="10dp"  android:gravity="center"  android:text="我是第2個Activity"  android:textColor="@color/c_333"  android:textSize="18sp" /> </RelativeLayout> 複製代碼

預覽圖 activityTransform屬性也能夠經過代碼設置。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 ivImage.transitionName="activityTransform" } 複製代碼
  1. FirstActivity中給 ImageView設置點擊事件,跳轉到第二個Activity。
ivImage.setOnClickListener {
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//判斷Android版本  val bundle =  ActivityOptions.makeSceneTransitionAnimation(this, ivImage, "activityTransform")  .toBundle()  startActivity(Intent(this, SecondActivity::class.java), bundle)  } else {  startActivity(Intent(this, SecondActivity::class.java))  } } 複製代碼

代碼中,先判斷當前Android版本是否大於等於5.0,大於或等於Android 5.0的話就設置共享元素動畫,小於5.0 就正常啓動第二個Activity

經過ActivityOptions.makeSceneTransitionAnimation()建立啓動Activity過渡的一些參數,makeSceneTransitionAnimation()函數第一個參數爲Activity對象;第二個參數爲共享元素組件,這裏設置爲idivImageImageView視圖;第三個參數爲transitionName屬性的值,這裏是activityTransform。在調用AcivityOptions對象toBundle函數,包裝成Bundle對象。

效果圖:

多個共享元素過渡

多個共享元素過渡也很簡單,只須要調用makeSceneTransitionAnimation()函數的另一個重載函數便可。

  1. 在前面XML佈局的基礎上,給 TextView增長 transitionName屬性: textTransform
<!--activity_first.xml文件內容-->
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:background="@color/white"  android:orientation="vertical">   <ImageView  android:id="@+id/ivImage"  android:layout_width="wrap_content"  android:layout_height="wrap_content"  android:src="@mipmap/ic_one"  android:transitionName="activityTransform" />   <TextView  android:id="@+id/tvText"  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:layout_marginTop="10dp"  android:gravity="center"  android:transitionName="textTransform"  android:text="我是第一個Activity"  android:textColor="@color/c_333"  android:textSize="18sp" /> </LinearLayout>  <!--activity_second.xml文件內容--> <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:orientation="vertical">   <ImageView  android:id="@+id/ivSecondImage"  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:layout_alignParentBottom="true"  android:adjustViewBounds="true"  android:src="@mipmap/ic_one"  android:transitionName="activityTransform" />   <TextView  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:transitionName="textTransform"  android:layout_above="@id/ivSecondImage"  android:layout_marginBottom="10dp"  android:gravity="center"  android:text="我是第2個Activity"  android:textColor="@color/c_333"  android:textSize="18sp" /> </RelativeLayout> 複製代碼
  1. 構建多個 Pair對象,並傳遞給 makeSceneTransitionAnimation()函數,啓動 Activity
ivImage.setOnClickListener {
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {   val imagePair=Pair<View,String>(ivImage,"activityTransform")  val textPair=Pair<View,String>(ivImage,"textTransform")   val bundle =  ActivityOptions.makeSceneTransitionAnimation(this,  imagePair,textPair).toBundle()   startActivity(Intent(this, SecondActivity::class.java), bundle)  } else {  startActivity(Intent(this, SecondActivity::class.java))  } } 複製代碼

這裏主要是經過將共享視圖和transitionName屬性的值包裝到Pair對象,其餘操做和一個共享元素的操做步驟並沒有區別。

效果圖:

深坑提醒

有時從RecyclerView界面進入到詳情頁,因爲詳情頁加載延遲,可能出現沒有效果。例如ImageView從網絡加載圖片,可能A界面到B界面沒效果,B回到A界面有效果。

解決步驟:

  1. setContentView後添加下面代碼,延遲加載過渡動畫。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 postponeEnterTransition() } 複製代碼
  1. 在共享元素視圖加載完畢,或者圖片加載完畢後調用下面代碼,開始加載過渡動畫。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 startPostponedEnterTransition() } 複製代碼

例如我是在Glide加載完再調用:

Glide.with(mContext)
 .asBitmap()  .load(value?.avatar ?: "")  .listener(object : RequestListener<Bitmap> {  override fun onResourceReady(resource: Bitmap?, model: Any?, target: Target<Bitmap>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {  animatorCallback?.invoke()//回調開始加載過渡動畫  return false  }   override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Bitmap>?, isFirstResource: Boolean): Boolean {  animatorCallback?.invoke()//回調開始加載過渡動畫  return false  }  })  .apply(RequestOptions.circleCropTransform())  .placeholder(R.mipmap.ic_default)  .error(R.mipmap.ic_default)  .into(authorBinding!!.ivAvatar) 複製代碼

你們也能夠考慮下面代碼:

shareElement.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
 override fun onPreDraw(): Boolean {  shareElement!!.viewTreeObserver.removeOnPreDrawListener(this)  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {  animatorCallback?.invoke()  }  return true  }  }) 複製代碼

二)、進入過渡與退出過渡動畫

與共享元素相反的,就是Activity進入與退出過渡動畫,兩個Activity之間在沒有共享的視圖狀況下進行動畫切換。下面先看三種動畫效果圖:爆炸式效果淡入淡出式效果滑動式效果

  • 爆炸式:將視圖移入場景中心或從中移出;
  • 滑動式:將視圖從場景的其中一個邊緣移入或移出;
  • 爆炸式:經過更改視圖的不透明度,在場景中添加視圖或從中移除視圖;

第一個界面採用Fade淡入淡出效果,第二個界面採用了Explode爆炸效果。

先後兩個界面都採用了Slide滑入滑出效果。

利用Android現有的過渡框架,實現起來是很簡單的,步驟以下:

  1. ActivityonCreate()方法中調用 setContentView()前設置啓用窗口過渡屬性;
window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS)
複製代碼
  1. 建立過渡效果對象 SlideExplodeFade;
val slide=Slide()
slide.slideEdge=Gravity.START slide.duration=300//效果時長,通常Activity切換時間很短,不建議設置過長 複製代碼

若是是Slide效果,能夠設置slideEdge屬性來指定滑動方向,默認是Gravity.BOTTOM

  1. 將過渡效果設置給window相關屬性,設置;
//退出當前界面的過渡動畫
window.exitTransition = slide //進入當前界面的過渡動畫 window.enterTransition = slide //從新進入界面的過渡動畫 window.reenterTransition = slide 複製代碼
  1. 調用第二個 Activity界面,使用過渡效果。
startActivity(
 Intent(this, SecondActivity::class.java),  ActivityOptions.makeSceneTransitionAnimation(this).toBundle()) 複製代碼

那麼ActivityOnCreate()方法看起來是這樣子的。

override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {  window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS)  window.allowEnterTransitionOverlap=false  Slide().apply {  duration = 300  excludeTarget(android.R.id.statusBarBackground, true)  excludeTarget(android.R.id.navigationBarBackground, true)  }.also {  window.exitTransition = it  window.enterTransition = it  window.reenterTransition = it  }  }   setContentView(R.layout.activity_first)   ivContent.setOnClickListener {  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {  startActivity(  Intent(this, SecondActivity::class.java),  ActivityOptions.makeSceneTransitionAnimation(this).toBundle()  )  }  }  } 複製代碼

上面代碼中調用 了excludeTarget()方法將狀態欄和導航欄排除在過渡動畫效果以外。不然會跟着一塊兒起動畫效果,不是很美觀。

正常狀況,退出與進入過渡動畫會有一小段交叉的過程,而window.allowEnterTransitionOverlap=false就是禁止交叉,只有退出過渡動畫結束後纔會再顯示進入過渡動畫。

若是第二個Activityfinish掉後,回到第一個Activity界面也想有過渡效果,就不要手動調用finish(),能夠調用finishAfterTransition ()方法。

三)、兼容Android 5.0前

若是Android 5.0前也想要有切換動畫怎麼辦?

  1. res/anim文件夾下建立想要的效果:
<alpha 
 xmlns:android="http://schemas.android.com/apk/res/android"  android:interpolator="@interpolator/decelerate_quad"  android:fromAlpha="0.0"  android:toAlpha="1.0"  android:duration="@android:integer/config_longAnimTime" /> 複製代碼
  1. 在啓動 Activity後調用 overridePendingTransition()方法。
val intent = Intent(this, TestActivity2::class.java)
startActivity(intent) overridePendingTransition(R.anim.fade_in, R.anim.fade_out) 複製代碼

overridePendingTransition()方法第一個參數爲下一個界面進入動畫,第二個參數爲當前界面退出動畫。

到這裏,Activity的切換使用過渡動畫基本就結束了。有朋友可能會問,只有Activity切換才能應用過渡效果麼?

2、佈局變化過渡動畫

在上一節要理解一個概念:場景。佈局的顯示與隱藏能夠理解分別爲一個場景,過渡動畫就是解決場景切換帶來的生硬視覺感覺。Activity切換過渡動畫指在兩個Activity之間,而佈局變化過渡動畫,是指同個Activity之間View的變化過渡動畫。

一)、手動建立Scene

手動建立場景的話,須要咱們本身建立起始和結束場景,利用現有的過渡效果來達到兩個場景的切換。默認狀況下,當前界面就是起始場景。

  1. 建立起始場景和結束場景的 xml佈局。起始場景和結束場景須要有相同根元素,例以下面代碼 idflConatentFrameLayout佈局。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:orientation="vertical">   <TextView  android:id="@+id/tvText"  android:text="內容過渡動畫"  android:gravity="center"  android:textSize="18sp"  android:layout_width="match_parent"  android:layout_height="50dp"/>   <FrameLayout  android:id="@+id/flContent"  android:layout_weight="1"  android:layout_width="match_parent"  android:layout_height="0dp">  <include layout="@layout/layout_first_scene"/>  </FrameLayout>  </LinearLayout> 複製代碼

初始視圖,第一個場景,佈局layout_first_scene.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:orientation="vertical">  <TextView  android:id="@+id/tvFirst"  android:textSize="18sp"  android:layout_marginTop="100dp"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:gravity="center_horizontal|top"  android:text="感謝你們閱讀文章" /> </LinearLayout> 複製代碼

第二個場景,佈局layout_second_scene.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:orientation="vertical">   <TextView  android:textSize="18sp"  android:layout_marginTop="100dp"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:gravity="center_horizontal|top"  android:text="我是新小夢\n歡迎你們點贊支持一下" /> </LinearLayout> 複製代碼
  1. 建立起始場景和結束場景。
val firstScene = Scene.getSceneForLayout(flContent, R.layout.layout_first_scene, this)
val secondScene = Scene.getSceneForLayout(flContent, R.layout.layout_sencod_scene, this) 複製代碼

默認狀況下,過渡動畫應用整個場景,若是場景某個View不參加,能夠經過過渡效果對象removeTarget()方法進行移除。

Slide(Gravity.TOP).removeTarget(tvNoJoin)
複製代碼
  1. 點擊時,進行場景過渡。
tvText.setOnClickListener {
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {  if (isFirst) {  TransitionManager.go(secondScene, Slide(Gravity.TOP))  }else{  TransitionManager.go(firstScene, Slide(Gravity.TOP))  }  isFirst=!isFirst  } } 複製代碼

TransitionManager.go()第一個參數表示結束場景,第二個參數表示當前場景退出時過渡效果,當前場景就是初始場景。

效果圖:

二)、系統自動建立Scene

這種狀況,咱們調用TransitionManager.beginDelayedTransition(sceneRoot)函數時,系統會自動記錄當前sceneRoot節點下全部要進行動畫的視圖做爲起始節點,下一幀中再次記錄sceneRoot子節點下全部 起始場景進行動畫狀態的視圖做爲結束場景。這種通常用來改變視圖的屬性,而後進行動畫過渡,如View的寬高。

栗子

定義只有一個正方形的View,經過改變正方形的寬高爲原來的2倍,來看看動畫效果。

  1. activity_text.xml佈局文件,定義 idsceneRoot的根節點,也是場景的根節點。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:id="@+id/sceneRoot"  android:background="@color/colorPrimary">   <View  android:id="@+id/vSquare"  android:layout_width="100dp"  android:layout_height="100dp"  android:layout_centerHorizontal="true"  android:layout_marginTop="50dp"  android:background="@color/white" /> </RelativeLayout> 複製代碼
  1. TestActivityOnCreate方法中調用下面代碼,將正方形的寬高設置200dp。
vSquare.setOnClickListener {
 TransitionManager.beginDelayedTransition(sceneRoot)  vSquare.layoutParams.apply {  width = dp2px(200f, this@TestActivity)  height = dp2px(200f, this@TestActivity)  }.also {  vSquare.layoutParams = it  } } 複製代碼

效果圖:

3、過渡動畫效果

上面的動畫效果,都是採用系統內置的,那具體有哪些動畫效果,或支持自定義麼?

過渡效果類都繼承自Transition類,Transition類持有場景切動畫的相關信息,子類的主要做用是捕獲屬性值(例如起始值和結束值)和如何演奏動畫。從這裏也能夠看出,過渡動畫也是屬性動畫的一個擴展與應用。

一)、內置過渡動畫

系統支持將任何擴展Visibility類的過渡做爲進入或退出過渡,內置繼承自Visibility的類有ExplodeSlideFade;支持共享元素過渡的有:

  • changeScroll 爲目標視圖滑動添加動畫效果
  • changeBounds 爲目標視圖佈局邊界的變化添加動畫效果
  • changeClipBounds 爲目標視圖裁剪邊界的變化添加動畫效果
  • changeTransform 爲目標視圖縮放和旋轉方面的變化添加動畫效果
  • changeImageTransform 爲目標圖片尺寸和縮放方面的變化添加動畫效果

代碼示例:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 TransitionSet().apply {  addTransition(ChangeImageTransform())  addTransition(ChangeBounds())  addTransition(Fade(Fade.MODE_IN))  }.also {  window.sharedElementEnterTransition=it  } } 複製代碼

TransitionSet對象是動畫的合集,能夠將多個過渡效果組織起來。

也能夠經過XML佈局來實現,在res/transition文件夾建立``:

<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"  android:duration="300"  android:transitionOrdering="together">  <changeImageTransform />  <changeBounds />  <fade /> </transitionSet> 複製代碼

transitionSetfade...等等的一些屬性和 Android矢量圖動畫:每人送一輛掘金牌小黃車文章 講到的一些屬性大同小異,這裏再也不復述。

代碼調用:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 TransitionInflater.from(this).inflateTransition(R.transition.transition_set).also {  window.sharedElementEnterTransition=it  } } 複製代碼

效果圖:

當現有的過渡效果不知足平常需求時,能夠經過繼承Transition,定製本身的動畫特效。

二)、自定義過渡動畫

子類繼承Transition類,並重寫其三個方法。

class MyTransition : Transition() {
 override fun captureStartValues(transitionValues: TransitionValues?) {}   override fun captureEndValues(transitionValues: TransitionValues?) {}   override fun createAnimator(  sceneRoot: ViewGroup?,  startValues: TransitionValues?,  endValues: TransitionValues?  ): Animator {  return super.createAnimator(sceneRoot, startValues, endValues)  }  } 複製代碼

captureStartValues()captureEndValues()方法是必須實現的,捕獲動畫的起始值和結束值,而createAnimator()方法,是用來建立自定義的動畫。

參數TransitionValues能夠理解是用來存儲View的一些屬性值,參數sceneRoot爲根視圖。

自定義過渡效果感興趣能夠參考:Android自定義Transition動畫

好啦,過渡動畫就講到這裏~

參考文章:

官網文檔

酷炫的Activity切換動畫,打造更好的用戶體驗

【碼字不易,點個贊,往後好查看】

本文使用 mdnice 排版

相關文章
相關標籤/搜索