Android共享元素轉(zhuǎn)場動畫兼容實踐

原文地址

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)方式

https://github.com/alexvasilkov/GestureViews

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蒙具,一起剝皮案震驚了整個濱河市球榆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌禁筏,老刑警劉巖持钉,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異篱昔,居然都是意外死亡每强,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門州刽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來空执,“玉大人,你說我怎么就攤上這事穗椅”姘恚” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵匹表,是天一觀的道長门坷。 經(jīng)常有香客問我宣鄙,道長,這世上最難降的妖魔是什么默蚌? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任冻晤,我火速辦了婚禮,結(jié)果婚禮上绸吸,老公的妹妹穿的比我還像新娘鼻弧。我一直安慰自己,他們只是感情好惯裕,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布温数。 她就那樣靜靜地躺著绣硝,像睡著了一般蜻势。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鹉胖,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天握玛,我揣著相機與錄音,去河邊找鬼甫菠。 笑死挠铲,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的寂诱。 我是一名探鬼主播拂苹,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼痰洒!你這毒婦竟也來了瓢棒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤丘喻,失蹤者是張志新(化名)和其女友劉穎脯宿,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泉粉,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡连霉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了嗡靡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片跺撼。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖讨彼,靈堂內(nèi)的尸體忽然破棺而出歉井,到底是詐尸還是另有隱情,我是刑警寧澤点骑,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布酣难,位于F島的核電站谍夭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏憨募。R本人自食惡果不足惜紧索,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望菜谣。 院中可真熱鬧珠漂,春花似錦、人聲如沸尾膊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冈敛。三九已至待笑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抓谴,已是汗流浹背暮蹂。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留癌压,地道東北人仰泻。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像滩届,于是被迫代替她去往敵國和親集侯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容