A货码厂带你进坑:仿某阅小说APP书籍打开效果
原厂效果图
博主的翻车图集
翻车效果1-打开正常,返回会突然归位,停止后又执行向右翻转
翻车效果2-打开ImageView慢慢放大翻开,图片切已经放大了;返回时视图也是正常的,图片也是先缩小了。
翻车效果3-打开Activity,未等动画完成,马上关闭,出现了图片飘走的效果。
看到上面的翻车效果是不是感同身受!
原厂效果拆解:
- 由ShareElementTransition即Activity共享元素过渡动画(即两个Activity或Fragment之间切换时的共享元素)来完成整体效果;
- 封面视图沿着Y轴在X=0的位置执行旋转动画,小说内容区域使用缩放动画;(坑,在这里!!!)
- 关闭阅读器时,执行反向旋转动画,并改变小说在书架的位置。
实现过程(被坑的过程):
0. 图片旋转
对博主来说,图片旋转的是比较陌生的,就从图片旋转效果开始实现。
一顿操作猛如虎,在百度找到一个Animation,实现了图片旋转的动画。内心小激动一番,距离实现效果进了一大步,So easy!!!
结果,在开始写自定义过渡动画时发现,实现过渡动画需要使用Animator,不是Animation。晴天霹雳啊,重新开始吧!
1. Activity共享元素过渡动画
Android 默认提供共享元素过渡:
- changeBounds - 为目标视图布局边界的变化添加动画效果。
- changeClipBounds - 为目标视图裁剪边界的变化添加动画效果。
- changeTransform - 为目标视图缩放和旋转方面的变化添加动画效果。
- changeImageTransform - 为目标图片尺寸和缩放方面的变化添加动画效果。
上面已经分析过原厂效果,需要分别实现两个视图的动画,封面视图旋转缩放以及小说内容视图缩放。先从简单的开始,实现“小说内容视图缩放”。缩放效果官方就有案例,这里就直接参考(Ctrl+c)Android官方的代码
/**
* 启动Activity
*/
class StartActivity {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
ivBook.setOnClickListener { startActivity(it) }
}
private fun openActivity(view: View) {
view.transitionName = "open_book"
val i = Intent(this, EndActivity::class.java)
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(
this,
Pair(view, "open_reader")
)
startActivity(i, options.toBundle())
}
}
/**
* 结束Activity
*/
class EndActivity {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
tvReader.transitionName = "open_reader"
window.sharedElementEnterTransition = createTransition()
window.sharedElementReturnTransition = createTransition()
}
private fun createTransition(): Transition {
val changeBounds = ChangeBounds()
changeBounds.targetIds.add(R.id.tv_reader)
changeBounds.duration = 1000L
return changeBounds
}
}
是不是很简单,这就实现了一个普通的过渡动画。
2. 自定义共享元素过渡动画
想要实现打开书本的动画效果,那就必须自定义动画。
要实现自定义过渡动画,需要继承Transition,然后实现一个Animator。
public class TurnPageTransition extends Transition {
@Override
public void captureStartValues(TransitionValues values) {
// 捕获起始值。
// 过渡动画添加了几个目标视图(targetIds.add(R.id.xx))就会执行多少次这个方法。下面的captureEndValues()和createAnimator()也是如此。
}
@Override
public void captureEndValues(TransitionValues values) {
// 捕获结束值
}
@Override
public Animator createAnimator(ViewGroup sceneRoot,
TransitionValues startValues,
TransitionValues endValues) {
// 创建Animator
}
}
图片旋转动画Animator实现
Android提供了ObjectAnimator,通过View的rotationY(沿Y轴旋转)属性,然后改变旋转轴心的位置可以轻而易举实现我们想要的翻页(翻车)动画。
博主是开玩笑的吧,这个不是我们想要的啊。
我们预期的效果应该是下面这样的:
那打开效果是正常的,为什么返回效果却是出现了跳动BUG呢。主要是由于我们旋转视图时,视图的x,y的位置是没有变化的,所以需要在捕获结束值的方法里做视图位置的偏移,向左偏移一个视图的宽度。
public class TurnPageTransition extends Transition {
private static final String PROPNAME_ROTATION = "com.example.learnactivitytransition:rotation:rotationY";
private static final String PROPNAME_RECT = "com.example.learnactivitytransition:rotation:rect";
// 是否是关闭动画 true=关闭动画,false=打开动画
private final boolean isClose;
public TurnPageTransition(boolean isClose) {
this.isClose = isClose;
}
@Override
public void captureStartValues(TransitionValues transitionValues) {
// 记录旋转的开始值角度。打开的是-180,关闭的开始值是0.
transitionValues.values.put(PROPNAME_ROTATION, isClose ? -180 : 0);
// 记录视图的四个点的值
Rect startRect = new Rect();
View view = transitionValues.view;
startRect.left = view.getLeft();
startRect.top = view.getTop();
startRect.right = view.getRight();
startRect.bottom = view.getBottom();
transitionValues.values.put(PROPNAME_RECT, startRect);
}
@Override
public void captureEndValues(TransitionValues transitionValues) {
// 记录旋转的结束值角度。打开的是0,关闭的是-180。
transitionValues.values.put(PROPNAME_ROTATION, isClose ? 0 : -180);
Rect endRect = new Rect();
View view = transitionValues.view;
endRect.left = view.getLeft();
endRect.top = view.getTop();
endRect.right = view.getRight();
endRect.bottom = view.getBottom();
if (isClose) {
/*
*填坑操作-修复结束动画位置跳动BUG,向左偏移视图宽度的值
*/
int width = view.getMeasuredWidth();
endRect.left -= width;
endRect.right -= width;
}
transitionValues.values.put(PROPNAME_RECT, endRect);
}
@Override
public Animator createAnimator(ViewGroup sceneRoot,
TransitionValues startValues,
TransitionValues endValues) {
if (null == startValues || null == endValues) {
return null;
}
final View startView = startValues.view;
final View endView = endValues.view;
List<Integer> targetIds = getTargetIds();
Animator animator = null;
if (targetIds.contains(startView.getId())) {
final Rect startRect = (Rect) startValues.values.get(PROPNAME_RECT);
final Rect endRect = (Rect) endValues.values.get(PROPNAME_RECT);
final Integer start = (Integer) startValues.values.get(PROPNAME_ROTATION);
final Integer end = (Integer) endValues.values.get(PROPNAME_ROTATION);
animator = create(startView, start, end, startRect, endRect);
}
return animator;
}
public AnimatorSet create(View view,
Integer startRotationY, Integer endRotationY,
Rect startRect, Rect endRect
) {
float cameraDistance = view.getContext().getResources().getDisplayMetrics().density * 3000;
int height = view.getMeasuredHeight();
view.setCameraDistance(cameraDistance);
view.setPivotX(0);
if (isClose) view.setPivotY(0.45f * height);
else view.setPivotY(0.2f * height);
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(view, "rotationY", startRotationY, endRotationY),
ObjectAnimator.ofInt(view, "left", startRect.left, endRect.left),
ObjectAnimator.ofInt(view, "top", startRect.top, endRect.top),
ObjectAnimator.ofInt(view, "right", startRect.right, endRect.right),
ObjectAnimator.ofInt(view, "bottom", startRect.bottom, endRect.bottom)
);
set.setDuration(getDuration());
return set;
}
}
3. 完整效果
到目前为止我们已经完成了所需动画的代码,只需整合完善,我们就可以实现一个完整的效果了。
博主说好完整的效果呢,怎么动画中的图片旋转缩放跟视图就不同步了呢?
正常的效果应该是这样的:
博主一时半会也没有搞明白为什么,陷入了沉思。
正当迷茫之际又回去看了官方文档,发现changeImageTransform-为目标图片尺寸和缩放方面的变化添加动画效果。这不正是需要的功能吗!上代码:
/**
* 结束Activity
*/
class EndActivity {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
tvReader.transitionName = "open_reader"
ivCover.transitionName = "open_book"
window.sharedElementEnterTransition = createTransition()
window.sharedElementReturnTransition = createTransition()
}
private fun createTransition(): Transition {
val transitionSet = TransitionSet()
val changeBounds = ChangeBounds()
changeBounds.targetIds.add(R.id.tv_reader)
// 填坑操作-修复动画中图片缩放不同步的BUG。
val changeImageTransform = ChangeImageTransform()
changeImageTransform.targetIds.add(R.id.iv_cover)
val turnPageTransition = TurnPageTransition(isClose)
turnPageTransition.targetIds.add(R.id.iv_cover)
transitionSet.addTransition(changeBounds)
transitionSet.addTransition(changeImageTransform)
transitionSet.addTransition(turnPageTransition)
transitionSet.duration = TRANSITION_DURATION
return transitionSet
}
}
4. 最后的坑
WTF博主还有最后的坑!其实就是最开看到的封面飞走的BUG:
当我们在打开Activity时,动画还没有结束便点击返回,结束Activity,就会发生视图飘走的bug。
这里简单的处理,就是监听动画的回调,在动画结束前让返回键不生效即可。这里就贴代码了。在最后直接送上源码。
最终效果
看着还是有模有样的吧,给自己赞一个。
写代码总会遇到坑,只有在坑里才会不断促使自己不断的卷 卷卷 卷卷卷。最后回头去看,原来也就这样。
感谢你观看这里,一起共勉。