最近需要在Android上實現一個iOS上很常見的交互效果,在界面的轉場之間共享圖片,比較常見的場景就是當需要在一個列表中查看某個圖片大圖的時候,列表頁和詳情頁之間圖片的移動共享,彷彿兩個界面中的圖片都是共享同一張,同時圖片詳情頁面支持手勢拖動圖片退出,拖動的時候圖片會產生位移和縮放的效果。
描述起來有點吃力,還是先看看我實現的效果圖吧。
那麼這個效果是怎麼實現的呢?
實現思路
首先這個效果分爲兩部分,圖片在兩個界面之間移動共享+手勢拖動圖片返回,只要搞定這兩個部分就可以輕鬆實現上面的效果。
1、共享元素
那麼界面之間的圖片共享怎麼做?不用擔心,谷歌官方已經爲我們實現了這個功能,我們直接使用就可以了。
共享元素在Android5.0以後是谷歌官方推出的一種新的界面轉場方式。不瞭解的可以看一下這篇博客,介紹的比較詳細——
2、手勢拖動圖片返回
手勢拖動圖片返回需要我們自定義一個View,並重寫其中的onInterceptTouchEvent()和onTouchEvent()方法來針對手指在屏幕上的手勢進行一系列的操作,比如拖動時的圖片位移和縮放,判斷手鬆開時是退出界面還是恢復圖片至原狀態等等。因爲之前寫過一篇仿今日頭條圖片滑動退出的Demo,所以只需要把那個拿過來稍微修改一下就可以了。
具體實現
1、實現圖片共享
首先,標記好兩個界面之間的共享元素
列表item中
<com.sunfusheng.GlideImageView
android:id="@+id/ivImage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:src="@mipmap/ic_image_loading"
android:transitionName="shareImg" />
圖片詳情頁中
因爲我是在ViewPager中動態生成的ImageView,所以就在java代碼中設置的
img.transitionName = "shareImg"
然後就是圖片item的跳轉代碼
//Activity中封裝的跳轉方法
companion object {
fun start(context: Activity, shareView: View, shareElementName: String, urls: ArrayList<String>, position: Int) {
val intent = Intent(context, ImagesIosActivity::class.java)
intent.apply {
putStringArrayListExtra("urls", urls)
putExtra("position", position)
}
context.startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(context, shareView, shareElementName).toBundle())
}
}
//item中調用的方法
ImagesIosActivity.start(ctx,imageView,"shareImg",item.pics as ArrayList<String>, 0)
至此就可以實現界面之間的圖片共享了。
2、實現圖片詳情頁手勢拖動效果
這個直接修改之前寫過的SlideCloseLayout,主要是修改了拖動時執行的動畫和恢復動畫。
@Override
public boolean onTouchEvent(@NonNull MotionEvent ev) {
final int y = (int) ev.getRawY();
final int x = (int) ev.getRawX();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
previousX = x;
previousY = y;
break;
case MotionEvent.ACTION_MOVE:
int diffY = y - previousY;
int diffX = x - previousX;
//判斷手指向上還是向下移動,關聯手指擡起後的動畫位移方向
this.setTranslationY(diffY);
this.setTranslationX(diffX);
float scalePercent = 1f - (Math.abs(diffY) * 3f / getMeasuredHeight());
Log.i("kkk", "scalePercent = " + scalePercent);
if (scalePercent > 0.3) {
this.setScaleX(scalePercent);
this.setScaleY(scalePercent);
}
if (mBackground != null) {
//透明度跟隨手指的移動距離發生變化
int alpha = (int) (255 * Math.abs(diffY * 1f)) / getHeight();
mBackground.setAlpha(255 - alpha);
//回調給外面做更多操作
mScrollListener.onLayoutScrolling(alpha / 255f);
}
break;
case MotionEvent.ACTION_UP:
int height = this.getHeight();
//滑動距離超過臨界值才執行退出動畫,臨界值爲控件高度1/5
if (Math.abs(getTranslationY()) > (height / 5)) {
//執行退出動畫
mScrollListener.onLayoutClosed();
} else {
//執行恢復動畫
layoutRecoverAnim();
}
}
return true;
}
/**
* 恢復動畫
*/
private void layoutRecoverAnim() {
//從手指擡起的地方恢復到原點
ObjectAnimator recoverYAnim = ObjectAnimator.ofFloat(this, "translationY", this.getTranslationY(), 0);
ObjectAnimator recoverXAnim = ObjectAnimator.ofFloat(this, "translationX", this.getTranslationX(), 0);
ObjectAnimator recoverXScale = ObjectAnimator.ofFloat(this,"scaleX",1);
ObjectAnimator recoverYScale = ObjectAnimator.ofFloat(this,"scaleY",1);
AnimatorSet set = new AnimatorSet();
set.setDuration(100);
set.playTogether(recoverXAnim, recoverYAnim,recoverXScale,recoverYScale);
set.start();
if (mBackground != null) {
//將背景置爲完全不透明
mBackground.setAlpha(255);
mScrollListener.onLayoutScrollRevocer();
}
}
public interface LayoutScrollListener {
//關閉佈局
void onLayoutClosed();
//正在滑動
void onLayoutScrolling(float alpha);
//滑動結束並且沒有觸發關閉
void onLayoutScrollRevocer();
}
注意在Activity中設置Listener的onLayoutClosed()方法時不要使用Activity的finish()方法,因爲它不會執行關閉時的共享元素動畫,所以我們要使用Activity的finishAfterTransition()方法,等待共享元素動畫結束後再finish。
到此就實現了文章開頭時的交互效果了,是不是挺簡單的呢?