最近需要在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。
到此就实现了文章开头时的交互效果了,是不是挺简单的呢?