Android5.0之後為我們提供了許多炫酷的介面過渡效果,其中共享元素過渡也是很有亮點的一個效果,但這個效果只能在Android5.0之後使用,那今天我們就來將共享元素過渡效果相容到Android4.0,讓5.0之前的手機也可以體驗這麼炫酷的效果吧。
  
  A transition animation compatible Library.
  
  相容Android5.0之後轉場動畫至Android4.0。
  
  github地址:https://github.com/zhangke3016/TranslationCompat
  
  依慣例,首先來說下本文的行文思路吧:
  
  一、頁面過渡相容庫的使用
  
  二、頁面過渡相容庫實現原理淺析
  
  三、用相容庫將開源專案MaterialLogin動畫效果相容至Android4.0
  
  MaterialLogin
  
  原專案地址:MaterialLogin將動畫效果相容至Android4.0
  
  Translation
  
  Translation
  
  一、頁面過渡相容庫的使用
  
  使用這個相容庫也很簡單,首先,在要控制跳轉的頁面呼叫TransitionController.getInstance().startActivity方法來實現跳轉,在其中主要是傳入當前介面要過渡到另一頁面的過渡元素View,以及另一個頁面對應共享元素的View id值。
  
  然後,在跳轉到的第二個頁面呼叫TransitionController.getInstance().show方法來實現元素的過渡,傳入引數也很簡單。
  
  最後呢,在頁面返回的時候,呼叫TransitionController.getInstance().exitActivity方法即可。
  
  這樣一個完整的介面過渡動畫基本就可以使用了,當然,為了讓實現的效果更炫酷,加入了對過渡動畫狀態的監聽,可以在動畫結束時加入自己的操作,為方便起見,相容庫包含圓形元素過渡:呼叫ViewAnimationCompatUtils.createCircularReveal方法既可實現元素以圓形展開和收起,使用方式和ViewAnimationUtils類一致,以及矩形元素過渡:呼叫:View www.feihuayl.cn AnimationCompatUtils.createRectReveal方法既可實現元素以矩形方式以左、上、右、下四個方向展開。
  
  具體程式碼如下:
  
  //引數一:當前Activity
  
  //引數二:跳轉意圖
  
  //引數三:當前頁面跳轉至下一頁面的View
  
  //引數四:下一頁面關聯的View id
  
  TransitionController.getInstance().startActivity(this,new Intent(this, RegisterActivity.class),fab,R.id.fab);
  
  //跳轉後頁面呼叫:
  
  TransitionController.getInstance().show(this,getIntent());
  
  可在show方法呼叫之前設定監聽:
  
  TransitionController.getInstance().setEnterListener(new TransitionCustomListener() {
  
  @Override
  
  public void onTransitionStart(Animator animator) {
  
  }
  
  @Override
  
  public void onTransitionEnd(Animator www.feiyunyl.cn animator) {
  
  }
  
  @Override
  
  public void onTransitionCancel(Animator animator) {
  
  }
  
  });
  
  //介面退出的時候呼叫
  
  TransitionController.getInstance().www.yghrcp88.cn exitActivity(PageDetailActivity.this);
  
  //增加介面圓形轉換動畫
  
  // 用法及引數和ViewAnimationUtils一致
  
  ViewAnimationCompatUtils.createCircularReveal(cvAdd, cvAdd.getWidth()/2,0, fab.getWidth() / 2, cvAdd.getHeight());
  
  //增加介面矩形轉換動畫
  
  Animator mAnimator = ViewAnimationCompatUtils.createRectReveal( nsv, 0, nsv.getHeight(),ViewAnimationCompatUtils.RECT_TOP);
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  9
  
  10
  
  11
  
  12
  
  13
  
  14
  
  15
  
  16
  
  17
  
  18
  
  19
  
  20
  
  21
  
  22
  
  23
  
  24
  
  25
  
  26
  
  27
  
  28
  
  29
  
  30
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  9
  
  10
  
  11
  
  12
  
  13
  
  14
  
  15
  
  16
  
  17
  
  18
  
  19
  
  20
  
  21
  
  22
  
  23
  
  24
  
  25
  
  26
  
  27
  
  28
  
  29
  
  30
  
  二、頁面過渡相容庫實現原理淺析
  
  先講了這個相容庫的用法,現在來聊聊它是怎麼實現的,可以把主要實現細分六步:
  
  1、獲取跳轉頁面過渡元素的位置
  
  2、將跳轉過渡元素的位置傳給下一個頁面
  
  3、在跳轉到的頁面獲取位置資訊並建立相同寬高大小的元素和其覆蓋螢幕的父容器,並將新建立的元素新增到父容器中,而父容器新增至根檢視中
  
  4、獲取跳轉到的頁面元素截圖並將其設為建立元素的背景
  
  5、將當前新元素位置與跳轉到頁面對比獲取縮放比例與移動距離並開始動畫,結束後將父容器隱藏
  
  6、介面返回時將建立的父容器重新新增至下一個頁面動畫實現,將建立的元素以動畫形式返回初始位置,結束後移除父容器
  
  1、獲取跳轉頁面過渡元素的位置
  
  //rect 來儲存共享元素位置資訊
  
  Rect rect = new Rect();
  
  // 獲取元素位置資訊
  
  view.getGlobalVisibleRect(rect);
  
  1
  
  2
  
  3
  
  4
  
  1
  
  2
  
  3
  
  4
  
  2、將跳轉過渡元素的位置傳給下一個頁面
  
  // 將位置資訊附加到 intent 上
  
  intent.setSourceBounds(rect);
  
  intent.putExtra(TRANSITION_NEXT_ID, nextShowViewId);
  
  1
  
  2
  
  3
  
  1
  
  2
  
  3
  
  3、在跳轉到的頁面獲取位置資訊並建立相同寬高大小的元素和其覆蓋螢幕的父容器,並將新建立的元素新增到父容器中,而父容器新增至根檢視中
  
  View virtalView = new View(activity);
  
  Bitmap cacheBitmap = BitmapUtil.getCacheBitmapFromView(next_view);
  
  // 獲取上一個介面中,元素的寬度和高度
  
  final int mOriginWidth = mRect.right - mRect.left;
  
  final int mOriginHeight = mRect.bottom - mRect.top;
  
  getBundleInfo(next_view,mOriginWidth,mOriginHeight,mRect);
  
  //建立覆蓋螢幕的父容器
  
  mContainer = new FrameLayout(activity);
  
  FrameLayout.LayoutParams mContainerParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
  
  //父容器新增至根檢視中
  
  parent.addView(mContainer,mContainerParams);
  
  if (mBgColor!=-1)
  
  mContainer.setBackgroundColor(ContextCompat.getColor(activity, mBgColor));
  
  FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(mOriginWidth, mOriginHeight);
  
  params.setMargins(mRect.left, mRect.top - BarUtils.getActionBarHeight(activity) -getStatusBarHeight(activity), mRect.right, mRect.bottom);
  
  virtalView.setBackgroundDrawable(new BitmapDrawable(activity.getResources(), cacheBitmap));
  
  //建立相同寬高大小的元素
  
  virtalView.setLayoutParams(params);
  
  //將新建立的元素新增到父容器中
  
  mContainer.addView(virtalView);
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  9
  
  10
  
  11
  
  12
  
  13
  
  14
  
  15
  
  16
  
  17
  
  18
  
  19
  
  20
  
  21
  
  22
  
  23
  
  24
  
  25
  
  26
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  9
  
  10
  
  11
  
  12
  
  13
  
  14
  
  15
  
  16
  
  17
  
  18
  
  19
  
  20
  
  21
  
  22
  
  23
  
  24
  
  25
  
  26
  
  4、獲取跳轉到的頁面元素截圖並將其設為建立元素的背景
  
  //獲取跳轉到的頁面元素截圖
  
  Bitmap cacheBitmap = BitmapUtil.getCacheBitmapFromView(next_view);
  
  //將其設為建立元素的背景
  
  virtalView.setBackgroundDrawable(http://www.zhenloyl88.cn/ new BitmapDrawable(activity.getResources(), cacheBitmap));
  
  /**
  
  * 獲取一個 View 的快取檢視
  
  *
  
  * @param view
  
  * @return
  
  */
  
  public static Bitmap getCacheBitmapFromView(View view) {
  
  final boolean drawingCacheEnabled = true;
  
  view.setDrawingCacheEnabled(drawingCacheEnabled);
  
  view.buildDrawingCache(drawingCacheEnabled);
  
  final Bitmap drawingCache = view.getDrawingCache();
  
  Bitmap bitmap;
  
  if (drawingCache != null) {
  
  bitmap = Bitmap.createBitmap(drawingCache);
  
  view.setDrawingCacheEnabled(false);
  
  } else {
  
  bitmap = null;
  
  }
  
  return bitmap;
  
  }
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  9
  
  10
  
  11
  
  12
  
  13
  
  14
  
  15
  
  16
  
  17
  
  18
  
  19
  
  20
  
  21
  
  22
  
  23
  
  24
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  9
  
  10
  
  11
  
  12
  
  13
  
  14
  
  15
  
  16
  
  17
  
  18
  
  19
  
  20
  
  21
  
  22
  
  23
  
  24
  
  5、將當前新元素位置與跳轉到頁面對比獲取縮放比例與移動距離並開始動畫,結束後將父容器隱藏
  
  //將當前新元素位置與跳轉到頁面對比獲取縮放比例與移動距離
  
  getBundleInfo(next_view,mOriginWidth,mOriginHeight,mRect);
  
  //開始動畫
  
  runEnterAnim(virtalView,next_view,mContainer);
  
  /**
  
  * 計算縮放比例,以及位移距離
  
  *
  
  * @param
  
  */
  
  private void getBundleInfo(View mView,int mOriginWidth,int mOriginHeight,Rect mRect) {
  
  // 計算縮放比例
  
  mScaleBundle.putFloat(SCALE_WIDTH, (float) mView.getWidth() / mOriginWidth);
  
  mScaleBundle.putFloat(SCALE_HEIGHT, (float) mView.getHeight() / mOriginHeight);
  
  Rect rect = new Rect();
  
  mView.getGlobalVisibleRect(rect);
  
  // 計算位移距離
  
  mTransitionBundle.putFloat(TRANSITION_X, (rect.left+(rect.right - rect.left) / 2) - (mRect.left + (mRect.right - mRect.left) / 2));
  
  mTransitionBundle.putFloat(TRANSITION_Y, (rect.top + (rect.bottom - rect.top) / 2) - (mRect.top + (mRect.bottom - mRect.top) / 2));
  
  }
  
  /**
  
  * 模擬入場動畫
  
  */
  
  private void runEnterAnim(View next_view,final View realNextView,final FrameLayout mContainer) {
  
  next_view.animate()
  
  .setInterpolator(new LinearInterpolator())
  
  .setDuration(300)
  
  .scaleX(mScaleBundle.getFloat(SCALE_WIDTH))
  
  .scaleY(mScaleBundle.getFloat(SCALE_HEIGHT))
  
  .translationX(mTransitionBundle.getFloat(TRANSITION_X))
  
  .translationY(mTransitionBundle.getFloat(TRANSITION_Y))
  
  .setListener(new Animator.AnimatorListener() {
  
  @Override
  
  public void onAnimationStart(Animator animation) {
  
  realNextView.setVisibility(View.GONE);
  
  if (mTransitionCustomListener!=null){
  
  mTransitionCustomListener.onTransitionStart(animation);
  
  }
  
  }
  
  @Override
  
  public void onAnimationEnd(Animator animation) {
  
  mContainer.setVisibility(View.GONE);
  
  realNextView.setVisibility(View.VISIBLE);
  
  if (mTransitionCustomListener!=null){
  
  mTransitionCustomListener.onTransitionEnd(animation);
  
  }
  
  }
  
  @Override
  
  public void onAnimationCancel(Animator animation) {
  
  if (mTransitionCustomListener!=null){
  
  mTransitionCustomListener.onTransitionCancel(animation);
  
  }
  
  }
  
  @Override
  
  public void onAnimationRepeat(Animator animation) {
  
  }
  
  });
  
  }
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  9
  
  10
  
  11
  
  12
  
  13
  
  14
  
  15
  
  16
  
  17
  
  18
  
  19
  
  20
  
  21
  
  22
  
  23
  
  24
  
  25
  
  26
  
  27
  
  28
  
  29
  
  30
  
  31
  
  32
  
  33
  
  34
  
  35
  
  36
  
  37
  
  38
  
  39
  
  40
  
  41
  
  42
  
  43
  
  44
  
  45
  
  46
  
  47
  
  48
  
  49
  
  50
  
  51
  
  52
  
  53
  
  54
  
  55
  
  56
  
  57
  
  58
  
  59
  
  60
  
  61
  
  62
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  9
  
  10
  
  11
  
  12
  
  13
  
  14
  
  15
  
  16
  
  17
  
  18
  
  19
  
  20
  
  21
  
  22
  
  23
  
  24
  
  25
  
  26
  
  27
  
  28
  
  29
  
  30
  
  31
  
  32
  
  33
  
  34
  
  35
  
  36
  
  37
  
  38
  
  39
  
  40
  
  41
  
  42
  
  43
  
  44
  
  45
  
  46
  
  47
  
  48
  
  49
  
  50
  
  51
  
  52
  
  53
  
  54
  
  55
  
  56
  
  57
  
  58
  
  59
  
  60
  
  61
  
  62
  
  6、介面返回時將建立的父容器重新新增至下一個頁面動畫實現,將建立的元素以動畫形式返回初始位置,結束後移除父容器
  
  /**
  
  * 模擬退場動畫
  
  */
  
  public void exitActivity(final Activity activity) {
  
  if (nResId!=-1 && mContainer!=null){
  
  //先將建立的父容器從上一個頁面移除
  
  ((ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT)).removeView(mContainer);
  
  activity.finish();
  
  activity.overridePendingTransition(0,0);
  
  //將建立的父容器重新新增至下一個頁面
  
  ((ViewGroup) mFirstActivity.findViewById(Window.ID_ANDROID_CONTENT)).addView(mContainer);
  
  mContainer.setVisibility(View.VISIBLE);
  
  //開始動畫
  
  mContainer.getChildAt(0).animate()
  
  .setInterpolator(new LinearInterpolator())
  
  .setDuration(300)
  
  .scaleX(1)
  
  .scaleY(1)
  
  .translationX(0)
  
  .translationY(0)
  
  .setListener(new Animator.AnimatorListener() {
  
  @Override
  
  public void onAnimationStart(Animator animation) {
  
  mFirstView.setVisibility(View.INVISIBLE);
  
  }
  
  @Override
  
  public void onAnimationEnd(Animator animation) {
  
  mFirstView.setVisibility(View.VISIBLE);
  
  mContainer.setVisibility(View.GONE);
  
  //結束後移除父容器
  
  ((ViewGroup) mFirstActivity.findViewById(Window.ID_ANDROID_CONTENT)).removeView(mContainer);
  
  mContainer.removeAllViews();
  
  mContainer = null;
  
  mFirstView = null;
  
  mFirstActivity =null;
  
  }
  
  @Override
  
  public void onAnimationCancel(Animator animation) {
  
  }
  
  @Override
  
  public void onAnimationRepeat(Animator animation) {
  
  }
  
  });
  
  }else{
  
  activity.finish();
  
  activity.overridePendingTransition(0,0);
  
  }
  
  }
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  9
  
  10
  
  11
  
  12
  
  13
  
  14
  
  15
  
  16
  
  17
  
  18
  
  19
  
  20
  
  21
  
  22
  
  23
  
  24
  
  25
  
  26
  
  27
  
  28
  
  29
  
  30
  
  31
  
  32
  
  33
  
  34
  
  35
  
  36
  
  37
  
  38
  
  39
  
  40
  
  41
  
  42
  
  43
  
  44
  
  45
  
  46
  
  47
  
  48
  
  49
  
  50
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  7
  
  8
  
  9
  
  10
  
  11
  
  12
  
  13
  
  14
  
  15
  
  16
  
  17
  
  18
  
  19
  
  20
  
  21
  
  22
  
  23
  
  24
  
  25
  
  26
  
  27
  
  28
  
  29
  
  30
  
  31
  
  32
  
  33
  
  34
  
  35
  
  36
  
  37
  
  38
  
  39
  
  40
  
  41
  
  42
  
  43
  
  44
  
  45
  
  46
  
  47
  
  48
  
  49
  
  50
  
  三、用相容庫將開源專案MaterialLogin動畫效果相容至Android4.0
  
  這裡就簡單說下相容MaterialLogin的實現,
  
  首先,介面跳轉,呼叫TransitionController.getInstance().startActivity(this,new Intent(this, RegisterActivity.class),fab,R.id.fab);方法既可,
  
  之後,跳轉至註冊頁面,呼叫TransitionController.getInstance().setEnterListener設定動畫監聽,在過渡動畫結束時,呼叫ViewAnimationCompatUtils.createCircularReveal顯示圓形展開效果,最後返回呼叫TransitionController.getInstance().exitActivity(RegisterActivity.this);,炫酷的登入頁面就實現啦。