Android交互實現——圖片共享元素轉場及手勢拖動返回

最近需要在Android上實現一個iOS上很常見的交互效果,在界面的轉場之間共享圖片,比較常見的場景就是當需要在一個列表中查看某個圖片大圖的時候,列表頁和詳情頁之間圖片的移動共享,彷彿兩個界面中的圖片都是共享同一張,同時圖片詳情頁面支持手勢拖動圖片退出,拖動的時候圖片會產生位移和縮放的效果。

描述起來有點吃力,還是先看看我實現的效果圖吧。

那麼這個效果是怎麼實現的呢?

實現思路

首先這個效果分爲兩部分,圖片在兩個界面之間移動共享+手勢拖動圖片返回,只要搞定這兩個部分就可以輕鬆實現上面的效果。

1、共享元素

那麼界面之間的圖片共享怎麼做?不用擔心,谷歌官方已經爲我們實現了這個功能,我們直接使用就可以了。

共享元素在Android5.0以後是谷歌官方推出的一種新的界面轉場方式。不瞭解的可以看一下這篇博客,介紹的比較詳細——

酷炫的Activity切換動畫,打造更好的用戶體驗

2、手勢拖動圖片返回

手勢拖動圖片返回需要我們自定義一個View,並重寫其中的onInterceptTouchEvent()和onTouchEvent()方法來針對手指在屏幕上的手勢進行一系列的操作,比如拖動時的圖片位移和縮放,判斷手鬆開時是退出界面還是恢復圖片至原狀態等等。因爲之前寫過一篇仿今日頭條圖片滑動退出的Demo,所以只需要把那個拿過來稍微修改一下就可以了。

Android仿今日頭條圖片滑動退出效果

具體實

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。

到此就實現了文章開頭時的交互效果了,是不是挺簡單的呢?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章