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