Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android之自定義Transition動畫

Android之自定義Transition動畫

編輯:關於Android編程

曾經(或者現在)很多人說起Android和iOS都會拿Android的UI設計來開黑, “你看看人家iOS的設計, 再來看看Android的, 差距怎麼就這麼大呢?”, 對於這種說辭, 可以一句話來總結一下”他們還停留在4.X之前的時代”. 自從Android5.0推出Material Design設計規范後, Android在設計上早已甩那個萬年不變的iOS好幾十條街!

以上純屬個人看法, 請勿開黑~~, 下面進入今天的主題.

還記得我曾經有篇文章(你所不知道的Activity轉場動畫——ActivityOptions)是來介紹Android新的轉場動畫的(對於ActivityOptions還不太熟悉的朋友現在可以打開上面的文章先來了解下), 那篇文章中介紹的Android預定義的幾個轉場雖然在效果上已經很贊了, 但是還是很難滿足我們在開發中遇到的各種需求, 那怎麼辦? View不能滿足需求, 我們可以自定義, Transition也是一樣~~, 所以這篇文章我們就來介紹一下如何自定義Transition動畫.

熟悉原理

在開始自定義之前, 我們首先來簡單的了解一下Transition轉場動畫的原理, 大家在看到你所不知道的Activity轉場動畫——ActivityOptions這篇文章時, 對Android提供的這種新的轉場動畫都震撼到了, 但是肯定有很多人對它的原理不是很請求, 尤其是Scene場景動畫, 一個ImageView怎麼就變著變著跳轉到其他的Activity了呢? 其實它的原理很簡單,Transition動畫其實就是拿著第一頁某個view的信息去第二頁的某個view上做的動畫, 這樣我們在視覺上就會產生一個漸變的錯覺~

玩玩Transition

在稍微了解了一下原理之後, 我們就來玩玩Transition了, 如何自定義一個Transition呢? 跟自定義view我們需要繼承View或者ViewGroup一樣, 這裡我們需要繼承Transition類.

public class MyTransition extends Transition {}

有兩個抽象方法必須要要重寫,

public class MyTransition extends Transition {

    @Override
    public void captureStartValues(TransitionValues transitionValues) {

    }

    @Override
    public void captureEndValues(TransitionValues transitionValues) {

    }
}

除了這兩個必須要重寫的方法, 我們還要重寫一個createAnimator方法來自定義動畫, 於是, 我們要自定義一個Transition, 一個類的結構肯定是肯定是這樣的.

public class MyTransition extends Transition {

    @Override
    public void captureStartValues(TransitionValues transitionValues) {

    }

    @Override
    public void captureEndValues(TransitionValues transitionValues) {

    }

    @Override
    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, final TransitionValues endValues) {
    }
}

ok, 下面我們來詳細說一下這三個方法都是用來干嘛的.
首先captureStartValues, 從字面上來看是用來收集開始信息的, 什麼開始信息? 當然是動畫的開始信息了. 那同樣的captureEndValues是用來收集動畫結束的信息的. 收集完了信息,就要通過createAnimator來創建個Animator供系統調用了.

再來看看TransitionValues這個陌生的類, 這個類其實很簡單, 只有兩個成員變量view和values, view指的是我們要從哪個view上收集信息, values是用來存放我們收集到的信息的. 比如: 在captureStartValues裡, transitionValues.view指的就是我們在開始動畫的界面上的那個view, 在captureEndValues指的就是在目標界面上的那個view.

好了, 上面幾個方法的作用介紹完畢後, 我們馬上就來完成一個進入消息內容的動畫效果, 還是老規矩, 在開始代碼之前, 我們先來看看效果.

\<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPrCmLCDU2nVidW50dcnPwrzGwdPQteO30b6iLCDQp7n7srvVprXYLCC01bvu18W/tCwgu/LV37/J0tTU2s7E1cLX7rrztcTBtL3T19S8us/C1Nixvs7EtcRkZW1v1LTC69fUvLrUy9DQv7R+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L3A+DQo8cD48Y29kZT48Y29kZT48Y29kZT48Y29kZT48Y29kZT48Y29kZT48Y29kZT48Y29kZT48Y29kZT48Y29kZT48Y29kZT48Y29kZT48Y29kZT48Y29kZT48Y29kZT48Y29kZT48Y29kZT7X0M+4uduy7NCnufssIM7Sw8e/ydLU1dK1vcG9tKa2r7utLjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9wPg0KPGJsb2NrcXVvdGU+DQoJPGNvZGU+PGNvZGU+PGNvZGU+PGNvZGU+PGNvZGU+PGNvZGU+PGNvZGU+PGNvZGU+PGNvZGU+PGNvZGU+PGNvZGU+PGNvZGU+PGNvZGU+PGNvZGU+PGNvZGU+PGNvZGU+PGNvZGU+taXQ0MTayN2008v81NrB0LHt1tC1xM671sPSxravtb2958PmtcTX7snPw+YuIM/7z6K1xMTayN3TybWl0NDW8L2l1bm/qi4g1eLBvbj2tq+7rcrHy7PQ8ta00NC1xCA8L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvYmxvY2txdW90ZT4NCjxwPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPs2ouf3Jz8PmtcS31s72LCDO0sPHtPPWwr/J0tS1w7P2LCDPwsPmLCDO0sPH0OjSqsrVvK+1xNDFz6LT0DxzdHJvbmc+dmlld9TavefD5rXEzrvWwzwvc3Ryb25nPrrNPHN0cm9uZz52aWV3tcS437bI0MXPojwvc3Ryb25nPiwgy/nS1M7Sw8fPyMC0tqjS5dK7z8LQ6NKqytW8r7XE0MXPojwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9wPg0KPHByZSBjbGFzcz0="brush:java;"> public class MyTransition extends Transition { private static final String TOP = "top"; private static final String HEIGHT = "height"; // ... }

然後我們開始收集動畫開始需要的信息

public class MyTransition extends Transition {

    private static final String TOP = "top";
    private static final String HEIGHT = "height";

    @Override
    public void captureStartValues(TransitionValues transitionValues) {
        View view = transitionValues.view;
        Rect rect = new Rect();
        view.getHitRect(rect);

        transitionValues.values.put(TOP, rect.top);
        transitionValues.values.put(HEIGHT, view.getHeight());

        Log.d("qibin", "start:" + rect.top + ";" + view.getHeight());
    }
}

首先, 我們通過transitionValues.view拿到我們要收集信息的目標view, 然後我們可以通過getHitRect可以拿到它在ListView中的上下左右信息, 最後我們通過transitionValues.values.put(TOP, rect.top)來保存一下他距離父布局上面的距離, 當然我們還需要通過transitionValues.values.put(HEIGHT, view.getHeight())來保存動畫初始的高度.

收集完動畫開始的信息, 我們再來收集動畫結束的信息, 依葫蘆畫瓢, 很快就能寫出下面的代碼.

public class MyTransition extends Transition {

    private static final String TOP = "top";
    private static final String HEIGHT = "height";

    @Override
    public void captureEndValues(TransitionValues transitionValues) {
        transitionValues.values.put(TOP, 0);
        transitionValues.values.put(HEIGHT, transitionValues.view.getHeight());

        Log.d("qibin", "end:" + 0 + ";" + transitionValues.view.getHeight());
    }
}

這裡的代碼和上面並無差別, 動畫結束後, view距離上面的距離應該是0, 不過這裡需要注意的是captureStartValues方法裡的transitionValues.view是我們頁面跳轉開始那個界面上的view, 而captureEndValues方法裡的transitionValues.view是我們跳轉目標上的view, 所以這兩個方法裡獲取到的view的高度肯定是不一樣的.

好了, 在完成信息收集之後, 我們就來寫動畫效果了,

public class MyTransition extends Transition {
  @Override
  public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, final TransitionValues endValues) {
      if (startValues == null || endValues == null) { return null;}

      final View endView = endValues.view;

      final int startTop = (int) startValues.values.get(TOP);
      final int startHeight = (int) startValues.values.get(HEIGHT);
      final int endTop = (int) endValues.values.get(TOP);
      final int endHeight = (int) endValues.values.get(HEIGHT);

      ViewCompat.setTranslationY(endView, startTop);
      endView.getLayoutParams().height = startHeight;
      endView.requestLayout();

      ValueAnimator positionAnimator = ValueAnimator.ofInt(startTop, endTop);
      if (mPositionDuration > 0) { positionAnimator.setDuration(mPositionDuration);}
      positionAnimator.setInterpolator(mPositionInterpolator);

      positionAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
          @Override
          public void onAnimationUpdate(ValueAnimator valueAnimator) {
              int current = (int) valueAnimator.getAnimatedValue();
              ViewCompat.setTranslationY(endView, current);
          }
      });

      ValueAnimator sizeAnimator = ValueAnimator.ofInt(startHeight, endHeight);
      if (mSizeDuration > 0) { sizeAnimator.setDuration(mSizeDuration);}
      sizeAnimator.setInterpolator(mSizeInterpolator);

      sizeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
          @Override
          public void onAnimationUpdate(ValueAnimator valueAnimator) {
              int current = (int) valueAnimator.getAnimatedValue();
              endView.getLayoutParams().height = current;
              endView.requestLayout();
          }
      });

      AnimatorSet set = new AnimatorSet();
      set.play(sizeAnimator).after(positionAnimator);

      return set;
  }
}

在說原理的時候, 我們提到過, 這一系列的動畫其實在我們跳轉後的界面上完成的, 所以這裡的動畫我們也是在目標view上完成. 上面兩個方法中收集到的信息, 我們需要在這裡用到, 所以我們通過以下代碼來獲取收集到的信息.

final int startTop = (int) startValues.values.get(TOP);
final int startHeight = (int) startValues.values.get(HEIGHT);
final int endTop = (int) endValues.values.get(TOP);
final int endHeight = (int) endValues.values.get(HEIGHT);

startValues和endValues都是createAnimator的參數.
接著幾行莫名奇妙的代碼

ViewCompat.setTranslationY(endView, startTop);
endView.getLayoutParams().height = startHeight;
endView.requestLayout();

是因為我們的動畫順序是先移動, 後展開, 首先把view的高度設置為前一個界面上view的高度是為了防止在移動的過程中view的高度是他自身的高度的.
接著我們創建了兩個動畫, 這兩個動畫很好理解, 一個位移的,一個是展開的, 不過這裡我們給了動畫一個時長和插值器, 這兩個信息是公開給調用者去設置的.
最後我們創建一個AnimatorSet, 在這個動畫集合中, 我們先來完成sizeAnimator然後開始positionAnimator, 最後返回該動畫集合. 自定義Transition完畢.

使用自定義Transition

上面我們完成了Transition的自定義, 這裡我們就來用一下它, 首先我們要在應用的主題中指定可以使用場景過度動畫.

true

看過你所不知道的Activity轉場動畫——ActivityOptions這篇文章的朋友都應該清楚, 我們還需要給我們兩個activity中的view一個transitionName, 這裡就不貼代碼了, 然後我們就來看看如何做跳轉.

public class MainActivity extends AppCompatActivity {

    private ListView mListView;
    private Adapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mListView = (ListView) findViewById(R.id.list);
        mAdapter = new Adapter();
        mListView.setAdapter(new Adapter());
        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView adapterView, View view, int position, long id) {
                startActivity(view, mAdapter.getItem(position));
            }
        });
    }
       public void startActivity(View view, String content) {
           Intent intent = new Intent(this, MessageActivity.class);
           intent.putExtra("msg", content);

           ActivityOptionsCompat compat = ActivityOptionsCompat
                   .makeSceneTransitionAnimation(this, view, view.getTransitionName());
           ActivityCompat.startActivity(this, intent, compat.toBundle());
       }
}

跳轉的代碼大家都可以在你所不知道的Activity轉場動畫——ActivityOptions這篇文章中找到, 這裡就不解釋了, 我們主要還是來看看在目標activity中怎麼應用動畫.

public class MessageActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.message_layout);
        setTitle("Content");

        TextView msgTextView = (TextView) findViewById(R.id.msg);
        msgTextView.setText(getIntent().getStringExtra("msg"));

        executeTransition();
    }

    public void executeTransition() {
        MyTransition transition = new MyTransition();
        transition.setPositionDuration(300);
        transition.setSizeDuration(300);
        transition.setPositionInterpolator(new FastOutLinearInInterpolator());
        transition.setSizeInterpolator(new FastOutSlowInInterpolator());
        transition.addTarget("message");

        getWindow().setSharedElementEnterTransition(transition);
    }

    @Override
    public void onBackPressed() {
        finish();
    }
}

來看executeTransition方法, 在這個方法中, 首頁我們構建了一個我們自定義的transition, 然後各種配置, 解析來的一行代碼,

transition.addTarget("message");

這個message就是我們前面提到的transitionName, 最後我們通過

getWindow().setSharedElementEnterTransition(transition);

來設置進入的動畫.
ok, 現在我們來看看效果

\

閃爍問題

看到效果後, 細心的朋友可能發現, 在動畫執行的過程中我們的NavigationBar會產生一個閃爍的效果, 這個效果不是我們想要的,出現這個問題的原因是共享元素動畫是在整個窗口的view上執行的, 在這裡找到了解決方案. 他的解決辦法是: 首先將NavigationBar也作為動畫的一部分, 然後在目標activity中延遲動畫的執行. google給我們提供了兩個方法來用, postponeEnterTransition()和startPostponedEnterTransition()方法來延遲動畫的執行.

所以, 現在我們的跳轉代碼應該是這樣的.

public void startActivity(View view, String content) {
    View statusBar = findViewById(android.R.id.statusBarBackground);
    View navigationBar = findViewById(android.R.id.navigationBarBackground);

    List> pairs = new ArrayList<>();
    pairs.add(Pair.create(statusBar, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME));
    pairs.add(Pair.create(navigationBar, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME));
    pairs.add(Pair.create(view, view.getTransitionName()));

    Intent intent = new Intent(this, MessageActivity.class);
    intent.putExtra("msg", content);

    ActivityOptionsCompat compat = ActivityOptionsCompat
            .makeSceneTransitionAnimation(this, pairs.toArray(new Pair[pairs.size()]));
    ActivityCompat.startActivity(this, intent, compat.toBundle());
}

在目標activity中執行的動畫的代碼也應該是這樣的.

public void executeTransition() {
    postponeEnterTransition();

    final View decorView = getWindow().getDecorView();
    getWindow().getDecorView().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            decorView.getViewTreeObserver().removeOnPreDrawListener(this);
            supportStartPostponedEnterTransition();
            return true;
        }
    });

    MyTransition transition = new MyTransition();
    transition.setPositionDuration(300);
    transition.setSizeDuration(300);
    transition.setPositionInterpolator(new FastOutLinearInInterpolator());
    transition.setSizeInterpolator(new FastOutSlowInInterpolator());
    transition.addTarget("message");

    getWindow().setSharedElementEnterTransition(transition);
}

到現在, 我們就完美解決了閃爍的問題~. ok, 到這裡, 大家應該可以隨意的自定義Transition動畫啦~最後需要demo的朋友可以到https://github.com/qibin0506/TransitionAnimator來下載.

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved