View与Activity之间共享元素动效原理

篇章目标要点

目前Android源码自带功能已经提供了Activity向Activity, Fragment向Fragment跳转场景的共享元素动效,均提供了非常不错的体验。但是在实际在开发当中,View面向Activity和View与View之间跳转的应用场景是比较频繁的,这2个场景Android源码并未提供相应的接口使用。本篇文章的目的就是针对这2个场景尝试进行探索,虽然效果还有较大的差距,我向先初步共享下自己的实现思路和基础代码

Android共享元素动效和应用场景

此处简要介绍下Android原生的共享元素动效的用法和场景,Android原生提供的共享元素动效用法基本为2步
第一步,在跳转前后画面中关联的元素layout中设置相同的标签,如下所示

android:transitionName="picture"

第二步,设置跳转参数即可

ActivityOptionsCompat options = ActivityOptionsCompat
        .makeSceneTransitionAnimation(getActivity(),
                imageView,"picture");

受限场景,仅在Activity进入onCreate时动效才能生效,故如希望跳转至目标Activity时每次都有动效,则目标Activity的LaunchMode需要设置为standard.
以下是Android原生提供的几种共享元素动效及适用场景

//新的Activity以某个View的参考点为中心点逐步方法
1.	ActivityOptions.makeScaleUpAnimation   
//适用于Activity之间的跳转,仅有一个共享元素
2.ActivityOptions.makeSceneTransitionAnimation (Activity activity, View sharedElement, String sharedElementName)
//适用于Activity之间的跳转,有多个共享元素
3.ActivityOptions makeSceneTransitionAnimation(Activity activity, Pair<View, String>... sharedElements) 

View 与Activity之间跳转共享元素动效实现

关于悬浮窗View与Activity之间跳转的共享元素动效才是本文的重点内容

1. 实现效果

在这里插入图片描述

2. 实现思路

过程的核心要点就是将关联的共享元素View的原位置和尺寸带入新的视图当中,然后设置新的视图当中相应的共享元素View从原位置开始执行属性动效,提供一种类似从原位置移动过来的感受。注意执行属性动效时,为了避免共享元素View的父布局限制了其超出边界无法显示,故需要在其父类视图设置不切除子视图边界。
在这里插入图片描述

3. View拉起Activity主要代码
  1. Intent中带入原View的位置和尺寸参数
//打包原视图的位置和尺寸参数
private Bundle getTransitionBundle(){
    int[] location = new int[2];
    mImageView.getLocationOnScreen(location);
    Rect rect = new Rect();
    mImageView.getLocalVisibleRect(rect);
    Bundle b = new Bundle();
    b.putIntArray("loc", location);
    b.putInt("width", rect.width());
    b.putInt("height", rect.height());
    Log.d(TAG, "origin x = "+location[0]+", y = "+location[1]+ ", width = "+rect.width()+",height = "+rect.height());
    return b;
}
……
//Intent中附带参数
Intent intent = new Intent(AnimApplication.getInstance().getApplicationContext(), MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtras(getTransitionBundle());
AnimApplication.getInstance().startActivity(intent);

  1. 设置Activity中相应View从原位置执行属性动画
public void enterAnimation(){
    Log.d(TAG, "execute animation");
    if(null == getIntent()){
        Log.d(TAG, "intent is null");
        return;
    }
    Bundle b = getIntent().getExtras();
    if(null == b){
        Log.d(TAG, "bundle is null");
        return;
    }
    mAlbumImageView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            mAlbumImageView.getViewTreeObserver().removeOnPreDrawListener(this);
            //获取bundle传递过来的原视图的位置和尺寸参数
            int[] originLoc = b.getIntArray("loc");
            int originWidth = b.getInt("width");
            int originHeight = b.getInt("height");
            mAlbumImageView.getLocationOnScreen(latestLoc);
            Log.d(TAG, "get album location on screen x = " + latestLoc[0] +", y = "+ latestLoc[1]);
//设置共享元素View的初始大小和位置
            mAlbumImageView.setPivotX(0.5f);
            mAlbumImageView.setPivotY(0.5f);
            mAlbumImageView.setScaleX(originWidth*1.0f/mAlbumImageView.getWidth());
            mAlbumImageView.setScaleY(originHeight*1.0f/mAlbumImageView.getHeight());
            mAlbumImageView.setTranslationX(originLoc[0] - latestLoc[0]);
            mAlbumImageView.setTranslationY(originLoc[1] - latestLoc[1]);
            width = mAlbumImageView.getWidth();
            height = mAlbumImageView.getHeight();
            //设置共享元素View执行属性动画缩放移动到新视图的位置
mAlbumImageView.animate().scaleX(1).scaleY(1).translationX(0).translationY(0).setDuration(500).setInterpolator(new LinearInterpolator()).setListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animator) {
                    Log.d(TAG, "animation start");
                }

                @Override
                public void onAnimationEnd(Animator animator) {
                    Log.d(TAG, "animation end");
                }

                @Override
                public void onAnimationCancel(Animator animator) {

                }

                @Override
                public void onAnimationRepeat(Animator animator) {

                }
            });

            return false;
        }
    });
}

  1. Activity拉起View主要代码
    这部分与第2点是逆过程,原理和代码基本类似,在此不赘述了。需要特别提醒的是回到View执行位移动效时,需要确保View属性动画轨迹完全可见,需要在添加悬浮窗View时为之设置一个尺寸为全屏的容器,待属性动画结束之后再将其布局大小缩小为目标大小。

存在问题

  1. View向Activity进行切换时执行动画的时机控制还是存在一些bug,有时出现了Activity中相应的View已经显示了才执行动画的case,虽然概率较低,该处的时机需要再优化一下。
  2. 代码目前较为凌乱,需要封装一下成为使用较为简单的基类和接口

学习心得

本篇文章只是初步提出了悬浮窗View与Activity之间互相跳转的共享元素动效的基本思路和基本实现,作为自己学习过程的笔记信息。还有较多不足之处,各位读者如有更好的想法,欢迎提出一起讨论。下一步是完善本思路存在的问题,以及进行View与View之间的共享元素动效实现。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值