原文地址
Android Shared-Element Transitions for all
我們都希望我們的app有自己特殊的地方奄毡,轉(zhuǎn)場動畫就是一個比較好的方式讓用戶記住我們的應(yīng)用。在Lollipop+ 上的版本實現(xiàn)起來十分的簡單,但是如果想兼容低于5.0的版本麦箍,你或許需要檢查Android系統(tǒng)的版本來做一些功能上的削減,或者你可以勇敢的手動來實現(xiàn)這個轉(zhuǎn)換漩勤,瘋狂的想法具篇,但是我們可以來這么嘗試一下。
共享元素變換步驟
當你想要從一個Activity A轉(zhuǎn)換到Activity B募逞,而且他們共享一個元素(比如是一個view)蛋铆,在這種場景下,最好的用戶體驗可能就是將共享的元素直接變換到最終的地方和大小放接,這會使用戶專注于應(yīng)用而且有一種連貫性的表達刺啦。那么怎么實現(xiàn)這樣的過渡呢?可能需要一些步驟
- Activity A解析共享元素的開始值然后通過intent傳遞給Activity B
- Activity B開始的時候是完全透明的
- Activity B從Bundle里取出值并準備場景
- Activity B開始做共享元素變換的動畫
我將會在這篇文章中展示如何實現(xiàn)這些步驟的細節(jié)和一些示例代碼纠脾。首先,先進行命名,我們將在 Activity A中的共享view稱之為origin view(初始視圖)钧敞,并將Activity B中的共享view稱之為destination view(目標視圖)胳蛮。雖然這兩個view被稱之為共享view,但實際上他們只是碰巧有相同內(nèi)容的完全不相關(guān)的view慧脱。
Activity A解析開始值并傳遞給Activity B
當我們想要創(chuàng)建一個從Activity A到Activity B轉(zhuǎn)換的視覺過渡效果再来,第一步就是解析出兩個Activity中的共享視圖內(nèi)開始和結(jié)束值這樣,后期需要這個數(shù)據(jù)來做變換磷瘤。
在Activity B中我們只能解析后目標視圖的屬性芒篷,對于初始視圖的屬性需要通過intent傳遞過來
Intent intent = new Intent(context, ArticleImageActivity.class);
intent.putExtra(IMAGE_URL_EXTRA, imageUrl);
intent.putExtra(VIEW_INFO_EXTRA, /* start values */ captureValues(originView));
startActivity(intent);
overridePendingTransition(0, 0);
為了去掉默認的轉(zhuǎn)場效果,我們需要在 startActivity
后面調(diào)用一次 overridePendingTransition(0,0)
采缚,然后就能實現(xiàn)我們自己的效果针炉。
這個 captureValues(View)
的方法會將view的大小和位置打包成一個bundle返回出來
private Bundle captureValues(@NonNull View view) {
Bundle b = new Bundle();
int[] screenLocation = new int[2];
view.getLocationOnScreen(screenLocation);
b.putInt(PROPNAME_SCREENLOCATION_LEFT, screenLocation[0]);
b.putInt(PROPNAME_SCREENLOCATION_TOP, screenLocation[1]);
b.putInt(PROPNAME_WIDTH, view.getWidth());
b.putInt(PROPNAME_HEIGHT, view.getHeight());
return b;
}
Activity B設(shè)置背景透明
在startActivity之后就跳轉(zhuǎn)到了Activity B中,但是我們不希望用戶一開始就能看到整個布局直到我們的過渡動畫準備好開始運動扳抽。解決方法十分的簡單篡帕,就是保證這個activity一開始是透明的殖侵,而且將layout都設(shè)置為 INVISIBLE
。
<style name="Transparent" parent="AppTheme">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
</style>
Activity B場景布置
這一步是最為重要的镰烧,首先拢军,確保所有的資源文件準備完畢,假設(shè)這個共享view是一個 ImageView 而且圖片已經(jīng)從URL中加載出來了怔鳖。這個并不難可以使用比如 Picasso, Glide, 或者其他的library茉唉。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_article_image);
...
// start the information passed in the Bundle
extractViewInfoFromBundle(getIntent());
// only now we load the image
Picasso.load(mImageUrl)
.into(mDestinationView, new Callback() {
@Override
public void onSuccess() {
// we've got the image loaded, we can start prepping the scene
onUiReady();
}
@Override
public void onError() {...}
});
...
}
這個 onUiReady()
方法會在所有的資源獲取之后準備界面以及過渡動畫的實現(xiàn),現(xiàn)在已經(jīng)通過intent傳遞過來了初始視圖的屬性值结执,然后我們需要拿到目標視圖的屬性值度陆,所以需要一個在view被layout之后還沒有draw之前的時機,官方給我們提供了一個很好的回調(diào)方式 onPreDraw()
private void onUiReady() {
mDestinationView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
// remove previous listener
mDestinationView.getViewTreeObserver().removeOnPreDrawListener(this);
// prep the scene
prepareScene();
// run the animation
runEnterAnimation();
return true;
}
});
}
在 prepareScene()
中我們需要拿到目標視圖的屬性献幔,并且根據(jù)在屏幕上的位置和大小做一個差值的屬性轉(zhuǎn)換
private void prepareScene() {
// capture the end values in the destionation view
mEndValues = captureValues(mDestinationView);
// calculate the scale and positoin deltas
float scaleX = scaleDelta(mStartValues, mEndValues);
float scaleY = scaleDelta(mStartValues, mEndValues);
int deltaX = translationDelta(mStartValues, mEndValues);
int deltaY = translationDelta(mStartValues, mEndValues);
// scale and reposition the image
mDestinationView.setScaleX(scaleX);
mDestinationView.setScaleY(scaleY);
mDestinationView.setTranslationX(deltaX);
mDestinationView.setTranslationY(deltaY);
}
準備好之后就是設(shè)置過渡動畫了
Activity B過渡動畫
目標視圖現(xiàn)在和初始視圖在同樣的地方大小也一樣懂傀,然后我們做動畫配合千米那設(shè)置的屬性變換來讓它移動到最后的位置,并且縮放到適當?shù)拇笮?/p>
private void runEnterAnimation() {
// We can now make it visible
mDestinationView.setVisibility(View.VISIBLE);
// finally, run the animation
mDestinationView.animate()
.setDuration(DEFAULT_DURATION)
.setInterpolator(DEFAULT_INTERPOLATOR)
.scaleX(1f)
.scaleY(1f)
.translationX(0)
.translationY(0)
.start();
}
在我們需要從Activity B返回到Activity A蜡感,只需要再做一個反轉(zhuǎn)的動畫蹬蚁,你可以在 onBackPressed()
的回調(diào)或者是其他會返回到前面的Activity的地方調(diào)用下面這段代碼。
private void runExitAnimation() {
mDestinationView.animate()
.setDuration(DEFAULT_DURATION)
.setInterpolator(DEFAULT_INTERPOLATOR)
.scaleX(scaleX)
.scaleY(scaleY)
.translationX(deltaX)
.translationY(deltaY)
.withEndAction(new Runnable() {
@Override
public void run() {
finish();
overridePendingTransition(0, 0);
}
}).start();
}
到這里整個步驟就結(jié)束了郑兴,而且這種方式能兼容所有的Android版本犀斋,不限于5.0以上,下面的采取這種方式實踐的效果杈笔。
備注
推薦一個圖片手勢操作的library闪水,其中包含了這個共享元素的使用,可以看一下具體的實現(xiàn)方式