安卓的共享元素动画是个很给力的存在.
但是可能由于只能运行在5.0或以上的系统上,所以感觉普及率一直不高.
ps.现在看到大部分共享元素动画,都是在rom内置app里看到的.很好理解,自己做的rom,当然能确定是5.0以上的.单个app开发就不是这回事了.
有些开发者会想做一些通用的库,以此想整合代码,让共享元素动画这方面的代码更加简单.但实际使用效果感觉一般.
所以我也只能总结一下要完成共享元素动画,一般的步骤有哪些.
先上图看效果:
一.准备待共享的实体
观看上面的gif可以稍微总结得出.这共享元素所共享的东西可以分为两部分.
1.内容实体 2.控件属性
对于1.内容实体来说,我这里有一张头像图片,一个黑色字的名称,一个灰色字的描述.
所以我建了个bean来存放
public class Person implements Parcelable {
public String name;
public String avatar;
public int defaultAvatarResId;
public String desc;
//下面略
同样也建了个bean来存放
public class SharedElement implements Parcelable {
public int resId;
public int x;
public int y;
public int width;
public int height;
二.给前后两个Activity写xml, 需要有相同的id
因为需要到要获取各控件的属性, 给控件加以动画. 所以需要有前后两Activity中, 那些有共享元素动画的控件, 需要有相同的id加以识别.
三.startActivity里, intent放好需要共享的内容
如上面第一点说的两部分.内容实体Person和控件属性.
然后使用overridePendingTransition把默认切换动画去掉.
Intent intent = new Intent(MainActivity.this, PersonDetailActivity.class);
intent.putExtra(PersonDetailActivity.EXTRA_PERSON, person);
intent.putParcelableArrayListExtra(PersonDetailActivity.EXTRA_ELEMENTS,
ViewUtil.getSharedElement(MainActivity.this, holder.mIvAvatar, holder.mTvName, holder.mTvDesc));
startActivity(intent);
overridePendingTransition(0, 0);
写了个Util方法, 传入控件后得到控件属性列表
public static ArrayList<SharedElement> getSharedElement(Activity aty, View ...views) {
ArrayList<SharedElement> elements = new ArrayList<>();
for (View view : views) {
SharedElement element = new SharedElement();
element.resId = view.getId();
element.width = view.getWidth();
element.height = view.getHeight();
int []location = new int[2];
view.getLocationInWindow(location);
element.x = location[0];
element.y = location[1];
elements.add(element);
}
return elements;
}
四.进入第二个Activity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_person_detail_page);
ButterKnife.bind(this);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
//intent获取内容, 设置到当前页面
Person person = getIntent().getParcelableExtra(EXTRA_PERSON);
if (TextUtils.isEmpty(person.avatar)) {
Glide.with(PersonDetailActivity.this).load(R.drawable.default_avatar_1).into(mIvAvatar);
} else {
Glide.with(PersonDetailActivity.this).load(person.avatar).into(mIvAvatar);
}
mTvName.setText(person.name);
mTvDesc.setText(person.desc);
//共享元素的动画
mElements = getIntent().getParcelableArrayListExtra(EXTRA_ELEMENTS);
animating = true;
for (int i = 0; i< mElements.size(); i++){
final int j = i;
final SharedElement element = mElements.get(i);
final View view = findViewById(element.resId);
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int []location = new int[2];
view.getLocationInWindow(location);
int x = location[0];
int y = location[1];
PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("translationX", element.x-x, 0);
PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("translationY", element.y-y, 0);
PropertyValuesHolder pvh3 = PropertyValuesHolder.ofFloat("scaleX", element.width*1.0f/view.getWidth(), 1f);
PropertyValuesHolder pvh4 = PropertyValuesHolder.ofFloat("scaleY", element.height*1.0f/view.getHeight(), 1f);
view.setPivotX(0);
view.setPivotY(0);
ObjectAnimator.ofPropertyValuesHolder(view, pvh1, pvh2, pvh3, pvh4).setDuration(duration).start();
}
});
}
//其他动画
mFlBackground.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mFlBackground.getViewTreeObserver().removeGlobalOnLayoutListener(this);
TypedValue tv = new TypedValue();
getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true);
int actionBarHeight = getResources().getDimensionPixelSize(tv.resourceId);
ObjectAnimator.ofFloat(mFlBackground, "translationY", -mFlBackground.getHeight()+actionBarHeight, 0).setDuration(duration).start();
}
});
mNsvContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mNsvContent.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int []location = new int[2];
mNsvContent.getLocationInWindow(location);
int y = location[1];
ObjectAnimator animator = ObjectAnimator.ofFloat(mNsvContent, "translationY", ScreenUtil.getScreenHeight(PersonDetailActivity.this)-y, 0).setDuration(duration);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
animating = false;
}
});
animator.start();
}
});
}
分为了几部分.
1.首先把person实体从Intent拿出来, 设置到对应控件.
2.把控件的各属性从Intent拿出来, 在一个for循环里, 通过resId找到对应控件, 根据(x, y)设置平移动画, 根据宽高设置缩放动画. 这里通过PropertyValuesHolder来设置, 它可以用来设置多个一起运行的属性动画.
3.设置一些非共享元素的动画. 从gif图可以看到, 为了整个视觉效果的协调, 我把标题栏和滚动的内容栏也设置了一个平移的动画. 这样新页面全部内容都是有动画演变来的.很和谐哈哈
五.退出第二个Activity的动画
@Override
public void onBackPressed() {
if (animating == true) {
return;
}
//共享元素动画
animating = true;
for (int i=0; i<mElements.size(); i++){
final int j = i;
final SharedElement element = mElements.get(i);
final View view = findViewById(element.resId);
int []location = new int[2];
view.getLocationInWindow(location);
int x = location[0];
int y = location[1];
PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("translationX", 0, element.x-x);
PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("translationY", 0, element.y-y);
PropertyValuesHolder pvh3 = PropertyValuesHolder.ofFloat("scaleX", 1f, element.width*1.0f/view.getWidth());
PropertyValuesHolder pvh4 = PropertyValuesHolder.ofFloat("scaleY", 1f, element.height*1.0f/view.getHeight());
ObjectAnimator.ofPropertyValuesHolder(view, pvh1, pvh2, pvh3, pvh4).setDuration(duration).start();
}
//其他动画
TypedValue tv = new TypedValue();
getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true);
int actionBarHeight = getResources().getDimensionPixelSize(tv.resourceId);
ObjectAnimator.ofFloat(mFlBackground, "translationY", 0, -mFlBackground.getHeight()+actionBarHeight).setDuration(duration).start();
int []location = new int[2];
mNsvContent.getLocationInWindow(location);
int y = location[1];
ObjectAnimator animator = ObjectAnimator.ofFloat(mNsvContent, "translationY", 0, ScreenUtil.getScreenHeight(PersonDetailActivity.this)-y).setDuration(duration);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
PersonDetailActivity.super.onBackPressed();
overridePendingTransition(0, 0);
}
});
animator.start();
}
也分为两部分, 共享元素动画和其他动画
放上github地址
https://github.com/yaodiwei/SharedElementTransition