篇章目标要点
目前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主要代码
- 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);
- 设置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;
}
});
}
- Activity拉起View主要代码
这部分与第2点是逆过程,原理和代码基本类似,在此不赘述了。需要特别提醒的是回到View执行位移动效时,需要确保View属性动画轨迹完全可见,需要在添加悬浮窗View时为之设置一个尺寸为全屏的容器,待属性动画结束之后再将其布局大小缩小为目标大小。
存在问题
- View向Activity进行切换时执行动画的时机控制还是存在一些bug,有时出现了Activity中相应的View已经显示了才执行动画的case,虽然概率较低,该处的时机需要再优化一下。
- 代码目前较为凌乱,需要封装一下成为使用较为简单的基类和接口
学习心得
本篇文章只是初步提出了悬浮窗View与Activity之间互相跳转的共享元素动效的基本思路和基本实现,作为自己学习过程的笔记信息。还有较多不足之处,各位读者如有更好的想法,欢迎提出一起讨论。下一步是完善本思路存在的问题,以及进行View与View之间的共享元素动效实现。