本項(xiàng)目git: https://github.com/razerdp/ZoomViewActivity
【下篇】一起擼個(gè)微信圖片瀏覽的BaseActivity吧(下)——過渡動畫的實(shí)現(xiàn)
項(xiàng)目預(yù)覽圖:

距離上次更新博客有兩三個(gè)月了。。。。太懶了orz...
在微信的日常使用中,我們點(diǎn)擊圖片放大的時(shí)候都有一個(gè)動畫效果,這個(gè)動畫效果過渡看起來很自然,在5.0之后,擁有ShareElement之后做到這個(gè)還是比較好做的,然而目前在我們的日常開發(fā)中,大多數(shù)app兼容都是下限為4.0而不是5.0,所以要實(shí)現(xiàn)這個(gè)動畫效果就需要我們花費(fèi)一點(diǎn)心思了。
事實(shí)上,在朋友圈項(xiàng)目中,我們就實(shí)現(xiàn)過這樣的一個(gè)圖片瀏覽動畫,詳情點(diǎn)我→《一起擼個(gè)朋友圈吧 - 圖片瀏覽(中)【圖片瀏覽器】》
然而在這里的實(shí)現(xiàn)按照我目前的看法,是不太完美的,原因有二:
- 圖片瀏覽視圖跟時(shí)間線(timeline)處于同一Activity,即便我將它移到一個(gè)代理類里面,但還是顯得依賴性很大。
- 基于第一點(diǎn),不便于其他Activity使用
總的來說,就是一個(gè)定制性的類,不太符合我們的“通用性”思想。
于是,再稍微整理和封裝之后,我們就有了今天的這個(gè)項(xiàng)目。
在說明之前,先聲明一下目前仍然有的不足:
- 對于圖片的scaleType支持不好
- 暫時(shí)沒有針對多圖瀏覽(ViewPager)做優(yōu)化
暫且算是幾個(gè)issue吧,有空再處理一下。
廢話說完,那么就正式開始我們的項(xiàng)目吧。
【Step 1】思考
在朋友圈項(xiàng)目中,最難的那一部分——即如何做到圖片放大縮小已經(jīng)是解決了(感謝官方代碼-V-),那么現(xiàn)在我們遇到的難題有兩個(gè):
- 如何做到順利的過渡到新的Activity中
- 圖片縮小的時(shí)候如何正確的回歸到前一個(gè)Activity的小圖中
在朋友圈項(xiàng)目里,我們知道做到這種圖片的由小到大的過渡實(shí)際上是一個(gè)障眼法,就是大圖一開始不可見并且以小圖的大小開始顯示,并做放大和位移動畫達(dá)到一種視覺上看起來像是從小圖放大的感覺。
而這兩者的實(shí)際核心在于得到View的繪制區(qū)域,也就是getGlobalVisibleRect()方法,在朋友圈項(xiàng)目里,我把大圖和時(shí)間線放到同一個(gè)Activity的原因就是因?yàn)榧词筕iew不可見,但只要執(zhí)行到resume后,就可以拿到繪制區(qū)域。
但如果放到一個(gè)新的Activity里,我們就沒法這么做了,因?yàn)樵?strong>onCreate()里面,我們并無法拿到View的屬性信息,也就拿不到繪制區(qū)域了。
然而當(dāng)初做點(diǎn)擊展開控件的時(shí)候(鏈接→)《一起擼個(gè)朋友圈吧(step5) - 控件篇【點(diǎn)擊展開】》 我講述過TextView的onPreDraw()方法,那時(shí)候我留意到TextView實(shí)現(xiàn)了OnPreDrawListener,便以為只有某些View實(shí)現(xiàn)了這個(gè)方法,其他View使用的話是無效的,直到最近查閱了View的資料之后,才發(fā)現(xiàn)其實(shí)無論是什么View,都會在ViewRootImpl的performTraversals()方法里檢測onPreDraw(cancelDraw),而在執(zhí)行這個(gè)方法之前,實(shí)際上已經(jīng)是measure過了,所以這個(gè)方法對于任何View都是有效的。
有了這一點(diǎn),我們就可以解決上面的問題了,在onPreDraw里面得到繪制區(qū)域,然后計(jì)算比率之后進(jìn)行動畫的展開就可以實(shí)現(xiàn)進(jìn)入Activity的時(shí)候開展動畫。
回到我們的第一個(gè)問題,解決了動畫播放之后,我們還要解決的是如何打開窗口的時(shí)候不進(jìn)行動畫,關(guān)于這一點(diǎn)是在再簡單不過了,我們只需要startActivity后執(zhí)行overridePendingTransition(0, 0);就可以禁用窗口切換動畫了。
至此,我們第一個(gè)問題解決的思路如下:
- 目標(biāo)ImageView實(shí)現(xiàn)onPreDrawListener,并在里面獲取getGlobalVisibleRect。
- startActivity禁用動畫(特指位移動畫,實(shí)際上Alpha動畫還是可以接受的),使用戶的焦點(diǎn)集中在圖片中而不集中在Activity過場動畫中。
然后第二個(gè)問題,在朋友圈項(xiàng)目中也講解過,在view點(diǎn)擊的時(shí)候就把view的rect傳過來,最后執(zhí)行退出動畫時(shí)回歸原來的位置即可。
【Step 2】封裝
如題,我們的標(biāo)題名字叫做BaseActivity,因此我們的目的很簡單,就是讓子類輕松實(shí)現(xiàn)這個(gè)效果,并且可以更好的拓展,而不要說只能是固定的一個(gè)Activity。
因此我們的BaseActivity需要實(shí)現(xiàn)以下幾個(gè)功能:
- 得到目標(biāo)View,即最終放大的View
- 播放進(jìn)場動畫/退場動畫
- 實(shí)現(xiàn)核心算法,并保證私有,對內(nèi)保護(hù)
- 判斷是否進(jìn)行動畫
綜上所述,我們暫時(shí)可以寫出如下的代碼結(jié)構(gòu):
public abstract class BaseScaleElementAnimaActivity<V extends ImageView> extends AppCompatActivity {
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//數(shù)據(jù)初始化
initData();
}
@Override public void setContentView(@LayoutRes int layoutResID) {
super.setContentView(layoutResID);
//針對目標(biāo)View的初始化
initImageView();
}
private void initData() {
}
private void initImageView() {
}
//進(jìn)場過渡動畫(放大)
private void playEnterAnima() {
}
//退場過渡動畫(縮?。? private void playExitAnima() {
}
@Override public void finish() {
super.finish();
overridePendingTransition(0, android.R.anim.fade_out);
}
//子類限制
protected abstract V getAnimaedImageView();
//子類限制(此處用的Glide)
protected abstract void onLoadingPicture(SimpleTarget targetImageView, String url);
//放大/縮小比例計(jì)算
private float[] calculateRatios(Rect startBounds, Rect finalBounds) {
}
//startActivity方法
public static void startWithScaleElementActivity(Activity from,
@Nullable String picUrl,
@Nullable Rect fromRect,
Class<? extends BaseScaleElementAnimaActivity> clazz) {
Intent intent = new Intent(from, clazz);
intent.putExtra("url", picUrl);
intent.putExtra("fromRect", fromRect);
from.startActivity(intent);
//禁用過渡動畫
from.overridePendingTransition(0, 0);
}
}
對于子類而言,它并不需要知道如何實(shí)現(xiàn)放大/縮小動畫,它只需要提供最終展示的View和在什么時(shí)候載入圖片的時(shí)機(jī)(本項(xiàng)目采用Glide,其他圖片框架請自行替換設(shè)計(jì))。
所以在父類的onCreate中,我們需要拿到前一個(gè)Activity點(diǎn)擊的View的繪制區(qū)域以及圖片url,在setContentView中,我們需要子類提供目標(biāo)View,其余操作都放在父類執(zhí)行。
在使用該功能的Activity時(shí)必須采用對應(yīng)的靜態(tài)方法,畢竟咱們有點(diǎn)特殊是吧。。。
【第一章節(jié)完,下一章開始實(shí)現(xiàn)動畫】