Android交互实现——图片共享元素转场及手势拖动返回

最近需要在Android上实现一个iOS上很常见的交互效果,在界面的转场之间共享图片,比较常见的场景就是当需要在一个列表中查看某个图片大图的时候,列表页和详情页之间图片的移动共享,仿佛两个界面中的图片都是共享同一张,同时图片详情页面支持手势拖动图片退出,拖动的时候图片会产生位移和缩放的效果。

描述起来有点吃力,还是先看看我实现的效果图吧。

那么这个效果是怎么实现的呢?

实现思路

首先这个效果分为两部分,图片在两个界面之间移动共享+手势拖动图片返回,只要搞定这两个部分就可以轻松实现上面的效果。

1、共享元素

那么界面之间的图片共享怎么做?不用担心,谷歌官方已经为我们实现了这个功能,我们直接使用就可以了。

共享元素在Android5.0以后是谷歌官方推出的一种新的界面转场方式。不了解的可以看一下这篇博客,介绍的比较详细——

酷炫的Activity切换动画,打造更好的用户体验

2、手势拖动图片返回

手势拖动图片返回需要我们自定义一个View,并重写其中的onInterceptTouchEvent()和onTouchEvent()方法来针对手指在屏幕上的手势进行一系列的操作,比如拖动时的图片位移和缩放,判断手松开时是退出界面还是恢复图片至原状态等等。因为之前写过一篇仿今日头条图片滑动退出的Demo,所以只需要把那个拿过来稍微修改一下就可以了。

Android仿今日头条图片滑动退出效果

具体实

1、实现图片共享

首先,标记好两个界面之间的共享元素

列表item中

图片详情页中

因为我是在ViewPager中动态生成的ImageView,所以就在java代码中设置的

 img.transitionName = "shareImg"

然后就是图片item的跳转代码

//Activity中封装的跳转方法
companion object {
        fun start(context: Activity, shareView: View, shareElementName: String, urls: ArrayList, 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, 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。

到此就实现了文章开头时的交互效果了,是不是挺简单的呢?

你可能感兴趣的:(Android交互)