├── .gitignore
├── .idea
├── .name
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── gradle.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── Blog.md
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── imczy
│ │ └── customactivitytransition
│ │ ├── CommentActivity.java
│ │ ├── MainActivity.java
│ │ └── transition
│ │ ├── ChangeColor.java
│ │ ├── ChangePosition.java
│ │ ├── CommentEnterTransition.java
│ │ ├── ShareElemEnterRevealTransition.java
│ │ ├── ShareElemReturnChangePosition.java
│ │ └── ShareElemReturnRevealTransition.java
│ └── res
│ ├── drawable-xhdpi
│ ├── bottom_bg.9.png
│ ├── ic_bottom_comment_normal.png
│ ├── icon_back_pressed.png
│ └── inputline.9.png
│ ├── layout
│ ├── activity_coment.xml
│ └── activity_main.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── image
├── back_error.gif
├── demo_preview.gif
├── reveal.gif
└── top_bottom_bar_transY.gif
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | CustomAndroidActivityTransition
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
22 |
23 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Blog.md:
--------------------------------------------------------------------------------
1 | # 自定义 AndroidActivity 转场动画
2 | 在 Android 5.0上 google 官方给我提供了不少好看方便使用的专场动画
3 |
4 | 原生提供的普通转场动画
5 | - fade 渐隐渐现
6 | - slid 各元素先后滑动进入
7 | - Explode 分裂成连个部分以前进入
8 |
9 | 分享元素的转场动画
10 | - changeBound 这个是最长使用的 改变View 大小和位置
11 | - changeClipBounds 改变 Clip 边界的大小
12 | - changeImageTransform 改变ImageView 的大小 和 martix
13 | - ChangeTransform 改变普通的 View 一些Scalex 值
14 | - ChangeScroll 改变滑动位置
15 |
16 | 以上都是原生的. 但是面对一些复杂的转场动画,google 提供的这几个还是不够, 很多时候都需要自己定义转场动画.
17 | 例如下转场动画, 使用原生的这些动画很难实现:
18 |
19 | 
20 |
21 | ###上面那个转场动画主要分为两部分:
22 | 1. 顶部 title bar 和底部输入框的 进入 返回动画
23 | 2. 第一个页面的评论圆球通过揭露效果显示到第二页面的内容详情页面
24 |
25 |
26 | ##第一个问题, 顶部 title bar 和顶部输入框进入,返回动画
27 | 一开始我想使用 官方元素的slid或Explode来实现, 但是基本都难以实现
28 | 后面只能自己写一个转场动画,转场动画也简单.
29 | 只需要继承 `Visibility` 或 `Transition` 其中 `Visibility`是继承自`Transition`的
30 |
31 | 如果转场动画只是某个 View 出现或消失, 那么可以考虑继承 `Visibility`
32 | 如果是累 ShareElem这样的转场动画, 那么就需要继承 `Transition`
33 |
34 | 回到重点, 我们需要实现 , 第一个问题:
35 | > 顶部 title bar 和底部输入框的 进入 返回动画
36 |
37 | 继承`Visibility` 有4个方法需要我们重写:
38 | 1. `public void captureStartValues(TransitionValues transitionValues)` 这里保存计算动画初始状态的一个属性值
39 | 2. `public void captureEndValues(TransitionValues transitionValues)` 这里保存计算动画结束状态的一个属性值
40 | 3. `public Animator onAppear(ViewGroup sceneRoot, final View view, TransitionValues startValues, TransitionValues endValues)`
41 | 如果是进入动画 即显示某个 View 则会执行这个方法
42 | 4. `public Animator onDisappear(ViewGroup sceneRoot, final View view, TransitionValues startValues, TransitionValues endValues)`
43 | 如果是退出 , 即不在显示某个 VIew 则会执行这个方法
44 |
45 | 我们可以看到顶部 title bar 和 底部输入框 输入框的动画其实非常简单, 仅仅只有`TranslationY` 这一个动画.
46 | 我们只需要在`captureStartValues`,`captureEndValues`两个方法中分别计算和保存开始和解释位置需要的位移
47 | 然后在 `onAppear`和`onDisappear` 方法创建动画即可
48 |
49 | 具体请查看代码:
50 | ```
51 | public class CommentEnterTransition extends Visibility {
52 | private static final String TAG = "CommentEnterTransition";
53 |
54 | private static final String PROPNAME_BOTTOM_BOX_TRANSITION_Y = "custom_bottom_box_enter_transition:change_transY:transitionY";
55 | private static final String PROPNAME_TOP_BAR_TRANSITION_Y = "custom_top_bar_transition:change_transY:transitionY";
56 |
57 | private View mBottomView;
58 | private View mTopBarView;
59 | private Context mContext;
60 |
61 | public CommentEnterTransition(Context context, View topBarView, View bottomView) {
62 | mBottomView = bottomView;
63 | mTopBarView = topBarView;
64 | mContext = context;
65 | }
66 |
67 |
68 | @Override
69 | public void captureStartValues(TransitionValues transitionValues) {
70 | super.captureStartValues(transitionValues);
71 | mBottomView.measure(0, 0);
72 | int transY = mBottomView.getMeasuredHeight();
73 |
74 | // 保存 计算初始值
75 | transitionValues.values.put(PROPNAME_BOTTOM_BOX_TRANSITION_Y, transY);
76 | transitionValues.values.put(PROPNAME_TOP_BAR_TRANSITION_Y, -mContext.getResources().getDimensionPixelOffset(R.dimen.top_bar_height));
77 | }
78 |
79 | @Override
80 | public void captureEndValues(TransitionValues transitionValues) {
81 | super.captureEndValues(transitionValues);
82 |
83 | // 保存计算结束值
84 | transitionValues.values.put(PROPNAME_BOTTOM_BOX_TRANSITION_Y, 0);
85 | transitionValues.values.put(PROPNAME_TOP_BAR_TRANSITION_Y, 0);
86 | }
87 |
88 |
89 | @Override
90 | public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
91 | return super.createAnimator(sceneRoot, startValues, endValues);
92 |
93 | }
94 |
95 | @Override
96 | public Animator onAppear(ViewGroup sceneRoot, final View view,
97 | TransitionValues startValues, TransitionValues endValues) {
98 |
99 | if (null == startValues || null == endValues) {
100 | return null;
101 | }
102 |
103 | // 这里去除 之前 存储的 初始值 和 结束值, 然后执行东湖
104 | if (view == mBottomView) {
105 | int startTransY = (int) startValues.values.get(PROPNAME_BOTTOM_BOX_TRANSITION_Y);
106 | int endTransY = (int) endValues.values.get(PROPNAME_BOTTOM_BOX_TRANSITION_Y);
107 |
108 | if (startTransY != endTransY) {
109 | ValueAnimator animator = ValueAnimator.ofInt(startTransY, endTransY);
110 | // 注意这里不能使用 属性动画, 使用 ValueAnimator 然后在更新 View 的对应属性
111 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
112 | @Override
113 | public void onAnimationUpdate(ValueAnimator animation) {
114 | Object value = animation.getAnimatedValue();
115 | if (null != value) {
116 | view.setTranslationY((Integer) value);
117 | }
118 | }
119 | });
120 | return animator;
121 | }
122 | } else if (view == mTopBarView) {
123 |
124 | int startTransY = (int) startValues.values.get(PROPNAME_TOP_BAR_TRANSITION_Y);
125 | int endTransY = (int) endValues.values.get(PROPNAME_TOP_BAR_TRANSITION_Y);
126 |
127 | if (startTransY != endTransY) {
128 | ValueAnimator animator = ValueAnimator.ofInt(startTransY, endTransY);
129 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
130 | @Override
131 | public void onAnimationUpdate(ValueAnimator animation) {
132 | Object value = animation.getAnimatedValue();
133 | if (null != value) {
134 | view.setTranslationY((Integer) value);
135 | }
136 | }
137 | });
138 | return animator;
139 | }
140 | }
141 | return null;
142 | }
143 |
144 | @Override
145 | public Animator onDisappear(ViewGroup sceneRoot, final View view,
146 | TransitionValues startValues, TransitionValues endValues) {
147 | if (null == startValues || null == endValues) {
148 | return null;
149 | }
150 |
151 | // 这里执行 返回动画, 这里金 初始值 和技术值 对调了,这样动画, 就就和原来动画想反了
152 | if (view == mBottomView) {
153 | int startTransY = (int) endValues.values.get(PROPNAME_BOTTOM_BOX_TRANSITION_Y);
154 | int endTransY = (int) startValues.values.get(PROPNAME_BOTTOM_BOX_TRANSITION_Y);
155 |
156 | if (startTransY != endTransY) {
157 | ValueAnimator animator = ValueAnimator.ofInt(startTransY, endTransY);
158 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
159 | @Override
160 | public void onAnimationUpdate(ValueAnimator animation) {
161 | Object value = animation.getAnimatedValue();
162 | if (null != value) {
163 | view.setTranslationY((Integer) value);
164 | }
165 | }
166 | });
167 | return animator;
168 | }
169 | } else if (view == mTopBarView) {
170 | int startTransY = (int) endValues.values.get(PROPNAME_TOP_BAR_TRANSITION_Y);
171 | int endTransY = (int) startValues.values.get(PROPNAME_TOP_BAR_TRANSITION_Y);
172 |
173 | if (startTransY != endTransY) {
174 | ValueAnimator animator = ValueAnimator.ofInt(startTransY, endTransY);
175 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
176 | @Override
177 | public void onAnimationUpdate(ValueAnimator animation) {
178 | Object value = animation.getAnimatedValue();
179 | if (null != value) {
180 | view.setTranslationY((Integer) value);
181 | }
182 | }
183 | });
184 | return animator;
185 | }
186 | }
187 | return null;
188 | }
189 | }
190 |
191 | ```
192 |
193 |
194 | 最后把自定义的转场动画设置上即可:
195 | ```
196 | getWindow().setEnterTransition(new CommentEnterTransition(this, mTitleBarTxt, mBottomSendBar));
197 | ```
198 |
199 | 再看看效果:
200 |
201 | 
202 |
203 |
204 |
205 | ## 第二个问题, 分享元素的揭露展开以及收缩动画
206 | 首先一点可以明确指示一个分享元素的转场动画,
207 | 一开始想用原始的 ChangeBounds 来简单实现但是有一个问题就是从圆形变成矩形的这个过程太过生硬
208 | 后来没办法只能自己自动转存动画.
209 |
210 | 我们在来分析看看上面的那个转场动画
211 | 1. 揭露效果: 从第一个页面的圆球开始, 圆球慢慢的方法, 直到整个动画结束
212 | 2. 看似 View 是在随着动画的过程慢慢放大
213 | 3. 似乎还有曲线位移的动画?
214 |
215 | 其实如果真的自己写过转场动画的话, 第二个可以排除了, 分享元素的转场动画一开始 view 就已经变成第二个页面中的View 了, 所以 View 没有放大过程
216 | ok 我们首先来解决第一个问题, 揭露动画. 这个是 Android 5.0 以后原生提供的一个动画使用起来特别简单:
217 | ```
218 | ViewAnimationUtils.createCircularReveal(view, centerX, centerY,startRadius, endRadius);
219 | ```
220 |
221 | 其中
222 | `centerX`, `centerY`是揭露动画圆心的位置;
223 | `startRadius`, `endRadius`则是开始时圆球的半径 和结束时圆球的半径
224 |
225 | 那么加来我们就需要 继承 `Transition` 来实现揭露动画了.
226 | 继承 `Transition` 需要重写以下 3个方法:
227 | 1. `public void captureStartValues(TransitionValues transitionValues)` 这里能够获取到 上一个页面的对应的 View 一些属性值
228 | 2. `public void captureEndValues(TransitionValues transitionValues)` 这里能够获取到 即将要打开的对应的页面的对应 View 的一些属性值
229 | 3. `public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues)` 这里创建对应的转场动画
230 |
231 |
232 | ok, 那么为了实现 上面的揭露动画, 我们需要一开始获取的值有:
233 | 1. 上一个页面的圆形 View 的宽度, 作为揭露动画圆形的最开始的半径
234 | 2. 即将要打开的页面的 对应 View 的对角线的长度, 作为揭露动画圆形的最终半径
235 | 3. 获取揭露动画的圆心位置, 这里我们去 View 的中间位置
236 |
237 | ok 那么就可实现一个简单的转场揭露动画了, 先看看代码, 然后再看看效果
238 | 代码:
239 | ```
240 | public class ShareElemEnterRevealTransition extends Transition {
241 | private static final String TAG = "ShareElemEnterRevealTransition";
242 |
243 | private static final String PROPNAME_RADIUS = "custom_reveal:change_radius:radius";
244 |
245 | private boolean hasAnim = false;
246 |
247 | private View animView;
248 |
249 | public ShareElemEnterRevealTransition(View animView) {
250 | this.animView = animView;
251 | }
252 |
253 | @Override
254 | public void captureStartValues(TransitionValues transitionValues) {
255 | transitionValues.values.put(PROPNAME_RADIUS, transitionValues.view.getWidth() / 2);
256 | }
257 |
258 | @Override
259 | public void captureEndValues(TransitionValues transitionValues) {
260 | View view = transitionValues.view;
261 | float widthSquared = view.getWidth() * view.getWidth();
262 | float heightSquared = view.getHeight() * view.getHeight();
263 | int radius = (int) Math.sqrt(widthSquared + heightSquared) / 2;
264 | transitionValues.values.put(PROPNAME_RADIUS, radius);
265 | }
266 |
267 | @Override
268 | public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
269 |
270 | if (null == startValues || null == endValues) {
271 | return null;
272 | }
273 | final View view = endValues.view;
274 | int startRadius = (int) startValues.values.get(PROPNAME_RADIUS);
275 | int endRadius = (int) endValues.values.get(PROPNAME_RADIUS);
276 |
277 | if (view == animView) {
278 | Animator reveal = createAnimator(view, startRadius, endRadius);
279 | hasAnim = true;
280 | return reveal;
281 | }
282 |
283 | return null;
284 | }
285 |
286 | private Animator createAnimator(View view, float startRadius, float endRadius) {
287 | int centerX = view.getWidth() / 2;
288 | int centerY = view.getHeight() / 2;
289 | Animator reveal = ViewAnimationUtils.createCircularReveal(view, centerX, centerY,
290 | startRadius, endRadius);
291 | return new NoPauseAnimator(reveal);
292 | }
293 | }
294 | ```
295 |
296 | 效果:
297 |
298 | 
299 |
300 | 貌似差距有点大,没有颜色的渐变, 同时貌似接通动画的圆形是在第二页面 View 的圆心, 没有移动的感觉
301 | ok , 接下来需要处理的有:
302 | 1. 移动 View
303 | 2. 背景颜色渐变
304 |
305 | 通过上面的代码示例, 做这两件事情应该不复杂, 下面直接上代码:
306 |
307 | 改变颜色:
308 | ```
309 | public class ChangeColor extends Transition {
310 | private static final String TAG = "ChangeColor";
311 |
312 | private static final String PROPNAME_BACKGROUND = "customtransition:change_color:backgroundcolor";
313 |
314 |
315 | int mStartColor;
316 | int mEndColor;
317 |
318 | public ChangeColor(int startColor, int endColor) {
319 | this.mStartColor = startColor;
320 | this.mEndColor = endColor;
321 | }
322 |
323 | private void captureValues(TransitionValues values) {
324 | values.values.put(PROPNAME_BACKGROUND, values.view.getBackground());
325 | }
326 |
327 | @Override
328 | public void captureStartValues(TransitionValues transitionValues) {
329 | captureValues(transitionValues);
330 | transitionValues.values.put(PROPNAME_BACKGROUND, mStartColor);
331 | }
332 |
333 | @Override
334 | public void captureEndValues(TransitionValues transitionValues) {
335 | transitionValues.values.put(PROPNAME_BACKGROUND, mEndColor);
336 | }
337 |
338 | @Override
339 | public Animator createAnimator(ViewGroup sceneRoot,
340 | TransitionValues startValues, TransitionValues endValues) {
341 | if (null == startValues || null == endValues) {
342 | return null;
343 | }
344 | final View view = endValues.view;
345 |
346 | int startColor = (int) startValues.values.get(PROPNAME_BACKGROUND);
347 | int endColor = (int) endValues.values.get(PROPNAME_BACKGROUND);
348 |
349 | if (startColor != endColor) {
350 | ValueAnimator animator = ValueAnimator.ofObject(new ArgbEvaluator(), startColor, endColor);
351 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
352 | @Override
353 | public void onAnimationUpdate(ValueAnimator animation) {
354 | Object value = animation.getAnimatedValue();
355 | if (null != value) {
356 | view.setBackgroundColor((Integer) value);
357 | }
358 | }
359 | });
360 | return animator;
361 | }
362 | return null;
363 | }
364 | }
365 |
366 | ```
367 |
368 | 改变位置(曲线运动):
369 |
370 | ```
371 | public class ChangePosition extends Transition {
372 | private static final String TAG = "ChangePosition";
373 |
374 | private static final String PROPNAME_POSITION = "custom_position:change_position:position";
375 |
376 |
377 | public ChangePosition() {
378 | // 这里通过曲线的方式 来改变位置
379 | setPathMotion(new PathMotion() {
380 | @Override
381 | public Path getPath(float startX, float startY, float endX, float endY) {
382 | Path path = new Path();
383 | path.moveTo(startX, startY);
384 |
385 | float controlPointX = (startX + endX) / 3;
386 | float controlPointY = (startY + endY) / 2;
387 |
388 | // 这里是一条贝塞尔曲线的路基, (controlPointX, controlPointY) 表示控制点
389 | path.quadTo(controlPointX, controlPointY, endX, endY);
390 | return path;
391 | }
392 | });
393 | }
394 |
395 | private void captureValues(TransitionValues values) {
396 | values.values.put(PROPNAME_POSITION, values.view.getBackground());
397 |
398 | Rect rect = new Rect();
399 | values.view.getGlobalVisibleRect(rect);
400 | values.values.put(PROPNAME_POSITION, rect);
401 | }
402 |
403 |
404 | @Override
405 | public void captureStartValues(TransitionValues transitionValues) {
406 | captureValues(transitionValues);
407 | }
408 |
409 | @Override
410 | public void captureEndValues(TransitionValues transitionValues) {
411 | captureValues(transitionValues);
412 | }
413 |
414 | @Override
415 | public Animator createAnimator(ViewGroup sceneRoot,
416 | TransitionValues startValues, TransitionValues endValues) {
417 | if (null == startValues || null == endValues) {
418 | return null;
419 | }
420 |
421 | if (startValues.view.getId() > 0) {
422 | Rect startRect = (Rect) startValues.values.get(PROPNAME_POSITION);
423 | Rect endRect = (Rect) endValues.values.get(PROPNAME_POSITION);
424 |
425 | final View view = endValues.view;
426 |
427 | Path changePosPath = getPathMotion().getPath(startRect.centerX(), startRect.centerY(), endRect.centerX(), endRect.centerY());
428 |
429 | int radius = startRect.centerY() - endRect.centerY();
430 |
431 | ObjectAnimator objectAnimator = ObjectAnimator.ofObject(view, new PropPosition(PointF.class, "position", new PointF(endRect.centerX(), endRect.centerY())), null, changePosPath);
432 | objectAnimator.setInterpolator(new FastOutSlowInInterpolator());
433 |
434 | return objectAnimator;
435 | }
436 | return null;
437 |
438 | }
439 |
440 | static class PropPosition extends Property {
441 |
442 | public PropPosition(Class type, String name) {
443 | super(type, name);
444 | }
445 |
446 | public PropPosition(Class type, String name, PointF startPos) {
447 | super(type, name);
448 | this.startPos = startPos;
449 | }
450 |
451 | PointF startPos;
452 |
453 | @Override
454 | public void set(View view, PointF topLeft) {
455 |
456 | int x = Math.round(topLeft.x);
457 | int y = Math.round(topLeft.y);
458 |
459 | int startX = Math.round(startPos.x);
460 | int startY = Math.round(startPos.y);
461 |
462 | int transY = y - startY;
463 | int transX = x - startX;
464 |
465 | // 这里控制 View 移动
466 | view.setTranslationX(transX);
467 | view.setTranslationY(transY);
468 | }
469 |
470 | @Override
471 | public PointF get(View object) {
472 | return null;
473 | }
474 | }
475 |
476 | }
477 |
478 | ```
479 |
480 | 上面改变位置的 使用 Path 动画, 使得 View 能够以贝塞尔曲线的方式进行位移
481 |
482 | ok 上面基本上就把 enter 的动画处理完了, 但是返回还是有点问题.
483 | 看下图:
484 | 
485 |
486 | 返回的时候 View 大小已经变成了后面个页面 View 的大小了, 然后由于大小的限制揭露动画基本也看不出效果.
487 | 所以分享元素的返回动画我们也要做一些细微的调整.
488 |
489 | 关于改变 View大小的这个问题, 我看了下 ChangeBounds 的源码, 然后发现, 他们是通过调用 View一个隐藏方法:
490 | ```
491 | /**
492 | * Same as setFrame, but public and hidden. For use in {@link android.transition.ChangeBounds}.
493 | * @hide
494 | */
495 | public void setLeftTopRightBottom(int left, int top, int right, int bottom) {
496 | setFrame(left, top, right, bottom);
497 | }
498 | ```
499 |
500 | 后面发现有适配问题, 这个方法只在5.1或以上才有, 在5.0 上面没有, 然后又看了下 5.0 的ChangeBounds 源码, 发现在 5.0 上改变 View 大小是通过以下方式实现的:
501 | ```
502 | view.setLeft(left);
503 | view.setRight(right);
504 | view.setTop(top);
505 | view.setBottom(bottom);
506 | ```
507 |
508 | 额 其实 5.0 的这个 set 方法在 5.1或以上也是可以使用的.
509 | 所以改变 View 大小这个, 可以选择 5.1 以上用反射 调用 setLeftTopRightBottom 方法, 也可以选择 都直接使用set 方法
510 |
511 | 下面贴上返回动画一些代码:
512 | 改变位置:
513 | ```
514 | public class ShareElemReturnChangePosition extends Transition {
515 | private static final String TAG = "ShareElemReturnChangePosition";
516 |
517 | private static final String PROPNAME_POSITION = "custom_position:change_position:position";
518 |
519 |
520 | public ShareElemReturnChangePosition() {
521 | setPathMotion(new PathMotion() {
522 | @Override
523 | public Path getPath(float startX, float startY, float endX, float endY) {
524 | Path path = new Path();
525 | path.moveTo(startX, startY);
526 |
527 | float controlPointX = (startX + endX) / 3;
528 | float controlPointY = (startY + endY) / 2;
529 |
530 | path.quadTo(controlPointX, controlPointY, endX, endY);
531 | return path;
532 | }
533 | });
534 | }
535 |
536 | private void captureValues(TransitionValues values) {
537 | values.values.put(PROPNAME_POSITION, values.view.getBackground());
538 |
539 | Rect rect = new Rect();
540 | values.view.getGlobalVisibleRect(rect);
541 | values.values.put(PROPNAME_POSITION, rect);
542 | }
543 |
544 |
545 | @Override
546 | public void captureStartValues(TransitionValues transitionValues) {
547 | captureValues(transitionValues);
548 | }
549 |
550 | @Override
551 | public void captureEndValues(TransitionValues transitionValues) {
552 | captureValues(transitionValues);
553 | }
554 |
555 | @Override
556 | public Animator createAnimator(ViewGroup sceneRoot,
557 | TransitionValues startValues, TransitionValues endValues) {
558 | if (null == startValues || null == endValues) {
559 | return null;
560 | }
561 |
562 | if (startValues.view.getId() > 0) {
563 | Rect startRect = (Rect) startValues.values.get(PROPNAME_POSITION);
564 | Rect endRect = (Rect) endValues.values.get(PROPNAME_POSITION);
565 |
566 | final View view = endValues.view;
567 |
568 |
569 | Rect rect = new Rect();
570 | view.getGlobalVisibleRect(rect);
571 |
572 | Path changePosPath = getPathMotion().getPath(startRect.centerX(), startRect.centerY(), endRect.centerX(), endRect.centerY() - endRect.height() / 2);
573 |
574 | ObjectAnimator objectAnimator = ObjectAnimator.ofObject(view, new PropPosition(PointF.class, "position", new PointF(startRect.centerX(), startRect.centerY())), null, changePosPath);
575 | objectAnimator.setInterpolator(new FastOutSlowInInterpolator());
576 |
577 | return objectAnimator;
578 | }
579 | return null;
580 |
581 | }
582 |
583 | static class PropPosition extends Property {
584 |
585 | public PropPosition(Class type, String name) {
586 | super(type, name);
587 | }
588 |
589 | public PropPosition(Class type, String name, PointF startPos) {
590 | super(type, name);
591 | this.startPos = startPos;
592 | }
593 |
594 | PointF startPos;
595 |
596 | @Override
597 | public void set(View view, PointF topLeft) {
598 |
599 | int x = Math.round(topLeft.x);
600 | int y = Math.round(topLeft.y);
601 |
602 | int startX = Math.round(startPos.x);
603 | int startY = Math.round(startPos.y);
604 |
605 | int transY = y - startY;
606 | int transX = x - startX;
607 |
608 | Rect rect = new Rect();
609 | view.getGlobalVisibleRect(rect);
610 |
611 | view.setTranslationX(transX);
612 | view.setTranslationY(transY);
613 | }
614 |
615 | @Override
616 | public PointF get(View object) {
617 | return null;
618 | }
619 | }
620 | }
621 | ```
622 |
623 | 揭露动画:
624 | ```
625 | public class ShareElemReturnRevealTransition extends Transition {
626 | private static final String TAG = "ShareElemReturnRevealTransition";
627 |
628 | private static final String PROPNAME_BACKGROUND = "custom_reveal:change_radius:radius";
629 |
630 | private boolean hasAnim = false;
631 |
632 | private View animView;
633 |
634 | private Rect startRect;
635 | private Rect endRect;
636 |
637 |
638 | public ShareElemReturnRevealTransition(View animView) {
639 | this.animView = animView;
640 | startRect = new Rect();
641 | endRect = new Rect();
642 | }
643 |
644 | @Override
645 | public void captureStartValues(TransitionValues transitionValues) {
646 | View view = transitionValues.view;
647 | float widthSquared = view.getWidth() * view.getWidth();
648 | float heightSquared = view.getHeight() * view.getHeight();
649 | int radius = (int) Math.sqrt(widthSquared + heightSquared) / 2;
650 |
651 | transitionValues.values.put(PROPNAME_BACKGROUND, radius);
652 | transitionValues.view.getGlobalVisibleRect(startRect);
653 |
654 | }
655 |
656 | @Override
657 | public void captureEndValues(TransitionValues transitionValues) {
658 | transitionValues.view.getLocalVisibleRect(endRect);
659 |
660 | transitionValues.values.put(PROPNAME_BACKGROUND, transitionValues.view.getWidth() / 2);
661 | }
662 |
663 | @Override
664 | public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, final TransitionValues endValues) {
665 |
666 | if (null == startValues || null == endValues) {
667 | return null;
668 | }
669 |
670 | final View view = endValues.view;
671 | int startRadius = (int) startValues.values.get(PROPNAME_BACKGROUND);
672 | int endRadius = (int) endValues.values.get(PROPNAME_BACKGROUND);
673 |
674 | // 在执行返回动画的时候, View 默认的被控制 为 前一个页面的 ShareElem 的打消了
675 | // 所以这里 需要改变的 View 大小 才能 正常的使用揭露动画
676 | // 反射调用
677 | relfectInvoke(view,
678 | startRect.left,
679 | startRect.top,
680 | startRect.right,
681 | startRect.bottom
682 | );
683 |
684 | Animator reveal = createAnimator(view, startRadius, endRadius);
685 |
686 | // 在动画的最后 被我们放大后的 View 会闪一些 这里可以有防止那种情况发生
687 | reveal.addListener(new AnimatorListenerAdapter() {
688 | @Override
689 | public void onAnimationEnd(Animator animation) {
690 | super.onAnimationEnd(animation);
691 | view.setClipBounds(new Rect(0, 0, 1, 1));
692 | view.setVisibility(View.GONE);
693 | }
694 | });
695 | return reveal;
696 |
697 | }
698 |
699 | private Animator createAnimator(View view, float startRadius, float endRadius) {
700 | int centerX = view.getWidth() / 2;
701 | int centerY = view.getHeight() / 2;
702 |
703 | Animator reveal = ViewAnimationUtils.createCircularReveal(view, centerX, centerY,
704 | startRadius, endRadius);
705 | return new ShareElemEnterRevealTransition.NoPauseAnimator(reveal);
706 | }
707 |
708 | // setLeftTopRightBottom 需要反射执行, 该方法能够控制 View 的大小以及位置 在 ChangeBounds 类中有调用
709 | private void relfectInvoke(View view, int left, int top, int right, int bottom) {
710 |
711 | Class clazz = view.getClass();
712 | try {
713 | Method m1 = clazz.getMethod("setLeftTopRightBottom", new Class[]{int.class, int.class, int.class, int.class});
714 | m1.invoke(view, left, top, right, bottom);
715 | } catch (Exception e) {
716 | e.printStackTrace();
717 |
718 | // 5.0版本 没有 setLeftTopRightBottom 这个方法 使用一下方法 ,额 其实 5.0 以上也可以用这些方法?
719 | view.setLeft(left);
720 | view.setRight(right);
721 | view.setTop(top);
722 | view.setBottom(bottom);
723 | }
724 |
725 | }
726 |
727 | }
728 |
729 | ```
730 |
731 | ok 下面贴上 Activity 中如何使用这些动画:
732 |
733 | ```
734 | public class CommentActivity extends AppCompatActivity {
735 |
736 | private static final String TAG = "CommentActivity";
737 |
738 |
739 | View mBottomSendBar;
740 | View mTitleBarTxt;
741 | View mCommentBox;
742 |
743 |
744 | @Override
745 | protected void onCreate(Bundle savedInstanceState) {
746 | super.onCreate(savedInstanceState);
747 | setContentView(R.layout.activity_coment);
748 | mCommentBox = findViewById(R.id.comment_box);
749 | mTitleBarTxt = findViewById(R.id.txt_title_bar);
750 | mBottomSendBar = findViewById(R.id.bottom_send_bar);
751 |
752 | setTransition();
753 |
754 | }
755 |
756 | private void setTransition() {
757 | // 顶部 title 和底部输入框的进入动画
758 | getWindow().setEnterTransition(new CommentEnterTransition(this, mTitleBarTxt, mBottomSendBar));
759 |
760 | getWindow().setSharedElementEnterTransition(buildShareElemEnterSet());
761 | getWindow().setSharedElementReturnTransition(buildShareElemReturnSet());
762 |
763 | }
764 |
765 | /**
766 | * 分享 元素 进入动画
767 | * @return
768 | */
769 | private TransitionSet buildShareElemEnterSet() {
770 | TransitionSet transitionSet = new TransitionSet();
771 |
772 | Transition changePos = new ChangePosition();
773 | changePos.setDuration(300);
774 | changePos.addTarget(R.id.comment_box);
775 | transitionSet.addTransition(changePos);
776 |
777 | Transition revealTransition = new ShareElemEnterRevealTransition(mCommentBox);
778 | transitionSet.addTransition(revealTransition);
779 | revealTransition.addTarget(R.id.comment_box);
780 | revealTransition.setInterpolator(new FastOutSlowInInterpolator());
781 | revealTransition.setDuration(300);
782 |
783 | ChangeColor changeColor = new ChangeColor(getResources().getColor(R.color.black_85_alpha), getResources().getColor(R.color.white));
784 | changeColor.addTarget(R.id.comment_box);
785 | changeColor.setDuration(350);
786 |
787 | transitionSet.addTransition(changeColor);
788 |
789 | transitionSet.setDuration(900);
790 |
791 | return transitionSet;
792 | }
793 |
794 | /**
795 | * 分享元素返回动画
796 | * @return
797 | */
798 | private TransitionSet buildShareElemReturnSet() {
799 | TransitionSet transitionSet = new TransitionSet();
800 |
801 | Transition changePos = new ShareElemReturnChangePosition();
802 | changePos.addTarget(R.id.comment_box);
803 | transitionSet.addTransition(changePos);
804 |
805 | ChangeColor changeColor = new ChangeColor(getResources().getColor(R.color.white), getResources().getColor(R.color.black_85_alpha));
806 | changeColor.addTarget(R.id.comment_box);
807 | transitionSet.addTransition(changeColor);
808 |
809 |
810 | Transition revealTransition = new ShareElemReturnRevealTransition(mCommentBox);
811 | revealTransition.addTarget(R.id.comment_box);
812 | transitionSet.addTransition(revealTransition);
813 |
814 | transitionSet.setDuration(900);
815 |
816 | return transitionSet;
817 | }
818 | }
819 | ```
820 |
821 |
822 | ##结语
823 | ok 关于自定义过场动画基本就说完了
824 | 这里没有将具体如果使用过场动画, 也没有有说 EnterTransition 和 ReturnTransition 这些关系什么的,
825 | 还有如何最基本使用过场动画什么的, 这些Android 官网上都有中文文档, 就不多提了
826 |
827 | ### [github 项目连接](https://github.com/crianzy/CustomAndroidActivityTransition)
828 |
829 |
830 |
831 |
832 |
833 |
834 |
835 |
836 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 自定义 AndroidActivity 转场动画
2 | 在 Android 5.0上 google 官方给我提供了不少好看方便使用的专场动画
3 |
4 | 原生提供的普通转场动画
5 | - fade 渐隐渐现
6 | - slid 各元素先后滑动进入
7 | - Explode 分裂成连个部分以前进入
8 |
9 | 分享元素的转场动画
10 | - changeBound 这个是最长使用的 改变View 大小和位置
11 | - changeClipBounds 改变 Clip 边界的大小
12 | - changeImageTransform 改变ImageView 的大小 和 martix
13 | - ChangeTransform 改变普通的 View 一些Scalex 值
14 | - ChangeScroll 改变滑动位置
15 |
16 | 以上都是原生的. 但是面对一些复制的转场动画,google 提供的这几个还是不够, 很多时候都需要自己定义转场动画.
17 | 例如下转场动画, 使用原生的这些动画很难实现:
18 |
19 | 
20 |
21 | 具体实现方式请查看源码和[讲解文章](Blog.md)
22 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.3"
6 |
7 | defaultConfig {
8 | applicationId "com.imczy.customactivitytransition"
9 | minSdkVersion 21
10 | targetSdkVersion 23
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | testCompile 'junit:junit:4.12'
25 | compile 'com.android.support:appcompat-v7:23.4.0'
26 | }
27 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/chenzhiyong/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/imczy/customactivitytransition/CommentActivity.java:
--------------------------------------------------------------------------------
1 | package com.imczy.customactivitytransition;
2 |
3 | import android.os.Bundle;
4 | import android.support.v4.view.animation.FastOutSlowInInterpolator;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.transition.Transition;
7 | import android.transition.TransitionSet;
8 | import android.view.View;
9 |
10 | import com.imczy.customactivitytransition.transition.ChangeColor;
11 | import com.imczy.customactivitytransition.transition.ChangePosition;
12 | import com.imczy.customactivitytransition.transition.CommentEnterTransition;
13 | import com.imczy.customactivitytransition.transition.ShareElemEnterRevealTransition;
14 | import com.imczy.customactivitytransition.transition.ShareElemReturnChangePosition;
15 | import com.imczy.customactivitytransition.transition.ShareElemReturnRevealTransition;
16 |
17 | /**
18 | * Created by chenzhiyong on 16/6/6.
19 | */
20 | public class CommentActivity extends AppCompatActivity {
21 |
22 | private static final String TAG = "CommentActivity";
23 |
24 |
25 | View mBottomSendBar;
26 | View mTitleBarTxt;
27 | View mCommentBox;
28 |
29 |
30 | @Override
31 | protected void onCreate(Bundle savedInstanceState) {
32 | super.onCreate(savedInstanceState);
33 | setContentView(R.layout.activity_coment);
34 | mCommentBox = findViewById(R.id.comment_box);
35 | mTitleBarTxt = findViewById(R.id.txt_title_bar);
36 | mBottomSendBar = findViewById(R.id.bottom_send_bar);
37 |
38 | setTransition();
39 |
40 | }
41 |
42 | private void setTransition() {
43 | // 顶部 title 和底部输入框的进入动画
44 | getWindow().setEnterTransition(new CommentEnterTransition(this, mTitleBarTxt, mBottomSendBar));
45 |
46 | getWindow().setSharedElementEnterTransition(buildShareElemEnterSet());
47 | getWindow().setSharedElementReturnTransition(buildShareElemReturnSet());
48 |
49 | }
50 |
51 | /**
52 | * 分享 元素 进入动画
53 | * @return
54 | */
55 | private TransitionSet buildShareElemEnterSet() {
56 | TransitionSet transitionSet = new TransitionSet();
57 |
58 | Transition changePos = new ChangePosition();
59 | changePos.setDuration(300);
60 | changePos.addTarget(R.id.comment_box);
61 | transitionSet.addTransition(changePos);
62 |
63 | Transition revealTransition = new ShareElemEnterRevealTransition(mCommentBox);
64 | transitionSet.addTransition(revealTransition);
65 | revealTransition.addTarget(R.id.comment_box);
66 | revealTransition.setInterpolator(new FastOutSlowInInterpolator());
67 | revealTransition.setDuration(300);
68 |
69 | ChangeColor changeColor = new ChangeColor(getResources().getColor(R.color.black_85_alpha), getResources().getColor(R.color.white));
70 | changeColor.addTarget(R.id.comment_box);
71 | changeColor.setDuration(350);
72 |
73 | transitionSet.addTransition(changeColor);
74 |
75 | transitionSet.setDuration(900);
76 |
77 | return transitionSet;
78 | }
79 |
80 | /**
81 | * 分享元素返回动画
82 | * @return
83 | */
84 | private TransitionSet buildShareElemReturnSet() {
85 | TransitionSet transitionSet = new TransitionSet();
86 |
87 | Transition changePos = new ShareElemReturnChangePosition();
88 | changePos.addTarget(R.id.comment_box);
89 | transitionSet.addTransition(changePos);
90 |
91 | ChangeColor changeColor = new ChangeColor(getResources().getColor(R.color.white), getResources().getColor(R.color.black_85_alpha));
92 | changeColor.addTarget(R.id.comment_box);
93 | transitionSet.addTransition(changeColor);
94 |
95 |
96 | Transition revealTransition = new ShareElemReturnRevealTransition(mCommentBox);
97 | revealTransition.addTarget(R.id.comment_box);
98 | transitionSet.addTransition(revealTransition);
99 |
100 | transitionSet.setDuration(900);
101 |
102 | return transitionSet;
103 | }
104 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/imczy/customactivitytransition/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.imczy.customactivitytransition;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.v4.app.ActivityOptionsCompat;
6 | import android.support.v4.util.Pair;
7 | import android.support.v7.app.AppCompatActivity;
8 | import android.view.View;
9 |
10 | public class MainActivity extends AppCompatActivity {
11 |
12 | View mCommentImg;
13 |
14 | @Override
15 | protected void onCreate(Bundle savedInstanceState) {
16 | super.onCreate(savedInstanceState);
17 | setContentView(R.layout.activity_main);
18 |
19 | mCommentImg = findViewById(R.id.comment_img);
20 |
21 | mCommentImg.setOnClickListener(new View.OnClickListener() {
22 | @Override
23 | public void onClick(View v) {
24 | Intent intent = new Intent(MainActivity.this, CommentActivity.class);
25 | transitionTo(intent);
26 | }
27 | });
28 | }
29 |
30 |
31 | void transitionTo(Intent i) {
32 | ActivityOptionsCompat transitionActivityOptions = ActivityOptionsCompat.makeSceneTransitionAnimation(this, new Pair<>(mCommentImg, "comment"));
33 | startActivity(i, transitionActivityOptions.toBundle());
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/imczy/customactivitytransition/transition/ChangeColor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.imczy.customactivitytransition.transition;
18 |
19 | import android.animation.Animator;
20 | import android.animation.ArgbEvaluator;
21 | import android.animation.ValueAnimator;
22 | import android.transition.Transition;
23 | import android.transition.TransitionValues;
24 | import android.view.View;
25 | import android.view.ViewGroup;
26 |
27 | public class ChangeColor extends Transition {
28 | private static final String TAG = "ChangeColor";
29 |
30 | private static final String PROPNAME_BACKGROUND = "customtransition:change_color:backgroundcolor";
31 |
32 |
33 | int mStartColor;
34 | int mEndColor;
35 |
36 | public ChangeColor(int startColor, int endColor) {
37 | this.mStartColor = startColor;
38 | this.mEndColor = endColor;
39 | }
40 |
41 | private void captureValues(TransitionValues values) {
42 | values.values.put(PROPNAME_BACKGROUND, values.view.getBackground());
43 | }
44 |
45 | @Override
46 | public void captureStartValues(TransitionValues transitionValues) {
47 | captureValues(transitionValues);
48 | transitionValues.values.put(PROPNAME_BACKGROUND, mStartColor);
49 | }
50 |
51 | @Override
52 | public void captureEndValues(TransitionValues transitionValues) {
53 | transitionValues.values.put(PROPNAME_BACKGROUND, mEndColor);
54 | }
55 |
56 | @Override
57 | public Animator createAnimator(ViewGroup sceneRoot,
58 | TransitionValues startValues, TransitionValues endValues) {
59 | if (null == startValues || null == endValues) {
60 | return null;
61 | }
62 | final View view = endValues.view;
63 |
64 | int startColor = (int) startValues.values.get(PROPNAME_BACKGROUND);
65 | int endColor = (int) endValues.values.get(PROPNAME_BACKGROUND);
66 |
67 | if (startColor != endColor) {
68 | ValueAnimator animator = ValueAnimator.ofObject(new ArgbEvaluator(), startColor, endColor);
69 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
70 | @Override
71 | public void onAnimationUpdate(ValueAnimator animation) {
72 | Object value = animation.getAnimatedValue();
73 | if (null != value) {
74 | view.setBackgroundColor((Integer) value);
75 | }
76 | }
77 | });
78 | return animator;
79 | }
80 | return null;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/app/src/main/java/com/imczy/customactivitytransition/transition/ChangePosition.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.imczy.customactivitytransition.transition;
18 |
19 | import android.animation.Animator;
20 | import android.animation.ObjectAnimator;
21 | import android.graphics.Path;
22 | import android.graphics.PointF;
23 | import android.graphics.Rect;
24 | import android.support.v4.view.animation.FastOutSlowInInterpolator;
25 | import android.transition.PathMotion;
26 | import android.transition.Transition;
27 | import android.transition.TransitionValues;
28 | import android.util.Property;
29 | import android.view.View;
30 | import android.view.ViewGroup;
31 |
32 | public class ChangePosition extends Transition {
33 | private static final String TAG = "ChangePosition";
34 |
35 | private static final String PROPNAME_POSITION = "custom_position:change_position:position";
36 |
37 |
38 | public ChangePosition() {
39 | // 这里通过曲线的方式 来改变位置
40 | setPathMotion(new PathMotion() {
41 | @Override
42 | public Path getPath(float startX, float startY, float endX, float endY) {
43 | Path path = new Path();
44 | path.moveTo(startX, startY);
45 |
46 | float controlPointX = (startX + endX) / 3;
47 | float controlPointY = (startY + endY) / 2;
48 |
49 | // 这里是一条贝塞尔曲线的路基, (controlPointX, controlPointY) 表示控制点
50 | path.quadTo(controlPointX, controlPointY, endX, endY);
51 | return path;
52 | }
53 | });
54 | }
55 |
56 | private void captureValues(TransitionValues values) {
57 | values.values.put(PROPNAME_POSITION, values.view.getBackground());
58 |
59 | Rect rect = new Rect();
60 | values.view.getGlobalVisibleRect(rect);
61 | values.values.put(PROPNAME_POSITION, rect);
62 | }
63 |
64 |
65 | @Override
66 | public void captureStartValues(TransitionValues transitionValues) {
67 | captureValues(transitionValues);
68 | }
69 |
70 | @Override
71 | public void captureEndValues(TransitionValues transitionValues) {
72 | captureValues(transitionValues);
73 | }
74 |
75 | @Override
76 | public Animator createAnimator(ViewGroup sceneRoot,
77 | TransitionValues startValues, TransitionValues endValues) {
78 | if (null == startValues || null == endValues) {
79 | return null;
80 | }
81 |
82 | if (startValues.view.getId() > 0) {
83 | Rect startRect = (Rect) startValues.values.get(PROPNAME_POSITION);
84 | Rect endRect = (Rect) endValues.values.get(PROPNAME_POSITION);
85 |
86 | final View view = endValues.view;
87 |
88 | Path changePosPath = getPathMotion().getPath(startRect.centerX(), startRect.centerY(), endRect.centerX(), endRect.centerY());
89 |
90 | int radius = startRect.centerY() - endRect.centerY();
91 |
92 | ObjectAnimator objectAnimator = ObjectAnimator.ofObject(view, new PropPosition(PointF.class, "position", new PointF(endRect.centerX(), endRect.centerY())), null, changePosPath);
93 | objectAnimator.setInterpolator(new FastOutSlowInInterpolator());
94 |
95 | return objectAnimator;
96 | }
97 | return null;
98 |
99 | }
100 |
101 | static class PropPosition extends Property {
102 |
103 | public PropPosition(Class type, String name) {
104 | super(type, name);
105 | }
106 |
107 | public PropPosition(Class type, String name, PointF startPos) {
108 | super(type, name);
109 | this.startPos = startPos;
110 | }
111 |
112 | PointF startPos;
113 |
114 | @Override
115 | public void set(View view, PointF topLeft) {
116 |
117 | int x = Math.round(topLeft.x);
118 | int y = Math.round(topLeft.y);
119 |
120 | int startX = Math.round(startPos.x);
121 | int startY = Math.round(startPos.y);
122 |
123 | int transY = y - startY;
124 | int transX = x - startX;
125 |
126 | // 这里控制 View 移动
127 | view.setTranslationX(transX);
128 | view.setTranslationY(transY);
129 | }
130 |
131 | @Override
132 | public PointF get(View object) {
133 | return null;
134 | }
135 | }
136 |
137 | }
138 |
--------------------------------------------------------------------------------
/app/src/main/java/com/imczy/customactivitytransition/transition/CommentEnterTransition.java:
--------------------------------------------------------------------------------
1 | package com.imczy.customactivitytransition.transition;
2 |
3 | import android.animation.Animator;
4 | import android.animation.ValueAnimator;
5 | import android.annotation.TargetApi;
6 | import android.content.Context;
7 | import android.os.Build;
8 | import android.transition.TransitionValues;
9 | import android.transition.Visibility;
10 | import android.util.Log;
11 | import android.view.View;
12 | import android.view.ViewGroup;
13 |
14 | import com.imczy.customactivitytransition.R;
15 |
16 |
17 | /**
18 | * Created by chenzhiyong on 16/6/6.
19 | *
20 | * 注意这里是继承 Visibility
21 | */
22 | public class CommentEnterTransition extends Visibility {
23 | private static final String TAG = "CommentEnterTransition";
24 |
25 | private static final String PROPNAME_BOTTOM_BOX_TRANSITION_Y = "custom_bottom_box_enter_transition:change_transY:transitionY";
26 | private static final String PROPNAME_TOP_BAR_TRANSITION_Y = "custom_top_bar_transition:change_transY:transitionY";
27 |
28 | private View mBottomView;
29 | private View mTopBarView;
30 | private Context mContext;
31 |
32 | public CommentEnterTransition(Context context, View topBarView, View bottomView) {
33 | mBottomView = bottomView;
34 | mTopBarView = topBarView;
35 | mContext = context;
36 | }
37 |
38 |
39 | @Override
40 | public void captureStartValues(TransitionValues transitionValues) {
41 | super.captureStartValues(transitionValues);
42 | mBottomView.measure(0, 0);
43 | int transY = mBottomView.getMeasuredHeight();
44 |
45 | // 保存 计算初始值
46 | transitionValues.values.put(PROPNAME_BOTTOM_BOX_TRANSITION_Y, transY);
47 | transitionValues.values.put(PROPNAME_TOP_BAR_TRANSITION_Y, -mContext.getResources().getDimensionPixelOffset(R.dimen.top_bar_height));
48 | }
49 |
50 | @Override
51 | public void captureEndValues(TransitionValues transitionValues) {
52 | super.captureEndValues(transitionValues);
53 |
54 | // 保存计算结束值
55 | transitionValues.values.put(PROPNAME_BOTTOM_BOX_TRANSITION_Y, 0);
56 | transitionValues.values.put(PROPNAME_TOP_BAR_TRANSITION_Y, 0);
57 | }
58 |
59 |
60 | @Override
61 | public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
62 | return super.createAnimator(sceneRoot, startValues, endValues);
63 |
64 | }
65 |
66 | @Override
67 | public Animator onAppear(ViewGroup sceneRoot, final View view,
68 | TransitionValues startValues, TransitionValues endValues) {
69 |
70 | if (null == startValues || null == endValues) {
71 | return null;
72 | }
73 |
74 | // 这里去除 之前 存储的 初始值 和 结束值, 然后执行东湖
75 | if (view == mBottomView) {
76 | int startTransY = (int) startValues.values.get(PROPNAME_BOTTOM_BOX_TRANSITION_Y);
77 | int endTransY = (int) endValues.values.get(PROPNAME_BOTTOM_BOX_TRANSITION_Y);
78 |
79 | if (startTransY != endTransY) {
80 | ValueAnimator animator = ValueAnimator.ofInt(startTransY, endTransY);
81 | // 注意这里不能使用 属性动画, 使用 ValueAnimator 然后在更新 View 的对应属性
82 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
83 | @Override
84 | public void onAnimationUpdate(ValueAnimator animation) {
85 | Object value = animation.getAnimatedValue();
86 | if (null != value) {
87 | view.setTranslationY((Integer) value);
88 | }
89 | }
90 | });
91 | return animator;
92 | }
93 | } else if (view == mTopBarView) {
94 |
95 | int startTransY = (int) startValues.values.get(PROPNAME_TOP_BAR_TRANSITION_Y);
96 | int endTransY = (int) endValues.values.get(PROPNAME_TOP_BAR_TRANSITION_Y);
97 |
98 | if (startTransY != endTransY) {
99 | ValueAnimator animator = ValueAnimator.ofInt(startTransY, endTransY);
100 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
101 | @Override
102 | public void onAnimationUpdate(ValueAnimator animation) {
103 | Object value = animation.getAnimatedValue();
104 | if (null != value) {
105 | view.setTranslationY((Integer) value);
106 | }
107 | }
108 | });
109 | return animator;
110 | }
111 | }
112 | return null;
113 | }
114 |
115 | @Override
116 | public Animator onDisappear(ViewGroup sceneRoot, final View view,
117 | TransitionValues startValues, TransitionValues endValues) {
118 | if (null == startValues || null == endValues) {
119 | return null;
120 | }
121 |
122 | // 这里执行 返回动画, 这里金 初始值 和技术值 对调了,这样动画, 就就和原来动画想反了
123 | if (view == mBottomView) {
124 | int startTransY = (int) endValues.values.get(PROPNAME_BOTTOM_BOX_TRANSITION_Y);
125 | int endTransY = (int) startValues.values.get(PROPNAME_BOTTOM_BOX_TRANSITION_Y);
126 |
127 | if (startTransY != endTransY) {
128 | ValueAnimator animator = ValueAnimator.ofInt(startTransY, endTransY);
129 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
130 | @Override
131 | public void onAnimationUpdate(ValueAnimator animation) {
132 | Object value = animation.getAnimatedValue();
133 | if (null != value) {
134 | view.setTranslationY((Integer) value);
135 | }
136 | }
137 | });
138 | return animator;
139 | }
140 | } else if (view == mTopBarView) {
141 | int startTransY = (int) endValues.values.get(PROPNAME_TOP_BAR_TRANSITION_Y);
142 | int endTransY = (int) startValues.values.get(PROPNAME_TOP_BAR_TRANSITION_Y);
143 |
144 | if (startTransY != endTransY) {
145 | ValueAnimator animator = ValueAnimator.ofInt(startTransY, endTransY);
146 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
147 | @Override
148 | public void onAnimationUpdate(ValueAnimator animation) {
149 | Object value = animation.getAnimatedValue();
150 | if (null != value) {
151 | view.setTranslationY((Integer) value);
152 | }
153 | }
154 | });
155 | return animator;
156 | }
157 | }
158 | return null;
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/app/src/main/java/com/imczy/customactivitytransition/transition/ShareElemEnterRevealTransition.java:
--------------------------------------------------------------------------------
1 | package com.imczy.customactivitytransition.transition;
2 |
3 | import android.animation.Animator;
4 | import android.animation.TimeInterpolator;
5 | import android.transition.Transition;
6 | import android.transition.TransitionValues;
7 | import android.util.ArrayMap;
8 | import android.view.View;
9 | import android.view.ViewAnimationUtils;
10 | import android.view.ViewGroup;
11 |
12 | import java.util.ArrayList;
13 |
14 | /**
15 | * Created by chenzhiyong on 16/6/7.
16 | * 进入时的揭露动画
17 | */
18 | public class ShareElemEnterRevealTransition extends Transition {
19 | private static final String TAG = "ShareElemEnterRevealTransition";
20 |
21 | private static final String PROPNAME_RADIUS = "custom_reveal:change_radius:radius";
22 |
23 | private boolean hasAnim = false;
24 |
25 | private View animView;
26 |
27 | public ShareElemEnterRevealTransition(View animView) {
28 | this.animView = animView;
29 | }
30 |
31 | @Override
32 | public void captureStartValues(TransitionValues transitionValues) {
33 | transitionValues.values.put(PROPNAME_RADIUS, transitionValues.view.getWidth() / 2);
34 | }
35 |
36 | @Override
37 | public void captureEndValues(TransitionValues transitionValues) {
38 | View view = transitionValues.view;
39 | float widthSquared = view.getWidth() * view.getWidth();
40 | float heightSquared = view.getHeight() * view.getHeight();
41 | int radius = (int) Math.sqrt(widthSquared + heightSquared) / 2;
42 | transitionValues.values.put(PROPNAME_RADIUS, radius);
43 | }
44 |
45 | @Override
46 | public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
47 |
48 | if (null == startValues || null == endValues) {
49 | return null;
50 | }
51 | final View view = endValues.view;
52 | int startRadius = (int) startValues.values.get(PROPNAME_RADIUS);
53 | int endRadius = (int) endValues.values.get(PROPNAME_RADIUS);
54 |
55 | if (view == animView) {
56 | Animator reveal = createAnimator(view, startRadius, endRadius);
57 | hasAnim = true;
58 | return reveal;
59 | }
60 |
61 | return null;
62 | }
63 |
64 | private Animator createAnimator(View view, float startRadius, float endRadius) {
65 | int centerX = view.getWidth() / 2;
66 | int centerY = view.getHeight() / 2;
67 | Animator reveal = ViewAnimationUtils.createCircularReveal(view, centerX, centerY,
68 | startRadius, endRadius);
69 | return new NoPauseAnimator(reveal);
70 | }
71 |
72 |
73 | public static class NoPauseAnimator extends Animator {
74 | private final Animator mAnimator;
75 | private final ArrayMap mListeners =
76 | new ArrayMap();
77 |
78 | public NoPauseAnimator(Animator animator) {
79 | mAnimator = animator;
80 | }
81 |
82 | @Override
83 | public void addListener(AnimatorListener listener) {
84 | AnimatorListener wrapper = new AnimatorListenerWrapper(this, listener);
85 | if (!mListeners.containsKey(listener)) {
86 | mListeners.put(listener, wrapper);
87 | mAnimator.addListener(wrapper);
88 | }
89 | }
90 |
91 | @Override
92 | public void cancel() {
93 | mAnimator.cancel();
94 | }
95 |
96 | @Override
97 | public void end() {
98 | mAnimator.end();
99 | }
100 |
101 | @Override
102 | public long getDuration() {
103 | return mAnimator.getDuration();
104 | }
105 |
106 | @Override
107 | public TimeInterpolator getInterpolator() {
108 | return mAnimator.getInterpolator();
109 | }
110 |
111 | @Override
112 | public ArrayList getListeners() {
113 | return new ArrayList(mListeners.keySet());
114 | }
115 |
116 | @Override
117 | public long getStartDelay() {
118 | return mAnimator.getStartDelay();
119 | }
120 |
121 | @Override
122 | public boolean isPaused() {
123 | return mAnimator.isPaused();
124 | }
125 |
126 | @Override
127 | public boolean isRunning() {
128 | return mAnimator.isRunning();
129 | }
130 |
131 | @Override
132 | public boolean isStarted() {
133 | return mAnimator.isStarted();
134 | }
135 |
136 | @Override
137 | public void removeAllListeners() {
138 | mListeners.clear();
139 | mAnimator.removeAllListeners();
140 | }
141 |
142 | @Override
143 | public void removeListener(AnimatorListener listener) {
144 | AnimatorListener wrapper = mListeners.get(listener);
145 | if (wrapper != null) {
146 | mListeners.remove(listener);
147 | mAnimator.removeListener(wrapper);
148 | }
149 | }
150 |
151 | /* We don't want to override pause or resume methods because we don't want them
152 | * to affect mAnimator.
153 | public void pause();
154 |
155 | public void resume();
156 |
157 | public void addPauseListener(AnimatorPauseListener listener);
158 |
159 | public void removePauseListener(AnimatorPauseListener listener);
160 | */
161 |
162 | @Override
163 | public Animator setDuration(long durationMS) {
164 | mAnimator.setDuration(durationMS);
165 | return this;
166 | }
167 |
168 | @Override
169 | public void setInterpolator(TimeInterpolator timeInterpolator) {
170 | mAnimator.setInterpolator(timeInterpolator);
171 | }
172 |
173 | @Override
174 | public void setStartDelay(long delayMS) {
175 | mAnimator.setStartDelay(delayMS);
176 | }
177 |
178 | @Override
179 | public void setTarget(Object target) {
180 | mAnimator.setTarget(target);
181 | }
182 |
183 | @Override
184 | public void setupEndValues() {
185 | mAnimator.setupEndValues();
186 | }
187 |
188 | @Override
189 | public void setupStartValues() {
190 | mAnimator.setupStartValues();
191 | }
192 |
193 | @Override
194 | public void start() {
195 | mAnimator.start();
196 | }
197 | }
198 |
199 | public static class AnimatorListenerWrapper implements Animator.AnimatorListener {
200 | private final Animator mAnimator;
201 | private final Animator.AnimatorListener mListener;
202 |
203 | public AnimatorListenerWrapper(Animator animator, Animator.AnimatorListener listener) {
204 | mAnimator = animator;
205 | mListener = listener;
206 | }
207 |
208 | @Override
209 | public void onAnimationStart(Animator animator) {
210 | mListener.onAnimationStart(mAnimator);
211 | }
212 |
213 | @Override
214 | public void onAnimationEnd(Animator animator) {
215 | mListener.onAnimationEnd(mAnimator);
216 | }
217 |
218 | @Override
219 | public void onAnimationCancel(Animator animator) {
220 | mListener.onAnimationCancel(mAnimator);
221 | }
222 |
223 | @Override
224 | public void onAnimationRepeat(Animator animator) {
225 | mListener.onAnimationRepeat(mAnimator);
226 | }
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/app/src/main/java/com/imczy/customactivitytransition/transition/ShareElemReturnChangePosition.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.imczy.customactivitytransition.transition;
18 |
19 | import android.animation.Animator;
20 | import android.animation.ObjectAnimator;
21 | import android.graphics.Path;
22 | import android.graphics.PointF;
23 | import android.graphics.Rect;
24 | import android.support.v4.view.animation.FastOutSlowInInterpolator;
25 | import android.transition.PathMotion;
26 | import android.transition.Transition;
27 | import android.transition.TransitionValues;
28 | import android.util.Log;
29 | import android.util.Property;
30 | import android.view.View;
31 | import android.view.ViewGroup;
32 |
33 | public class ShareElemReturnChangePosition extends Transition {
34 | private static final String TAG = "ShareElemReturnChangePosition";
35 |
36 | private static final String PROPNAME_POSITION = "custom_position:change_position:position";
37 |
38 |
39 | public ShareElemReturnChangePosition() {
40 | setPathMotion(new PathMotion() {
41 | @Override
42 | public Path getPath(float startX, float startY, float endX, float endY) {
43 | Path path = new Path();
44 | path.moveTo(startX, startY);
45 |
46 | float controlPointX = (startX + endX) / 3;
47 | float controlPointY = (startY + endY) / 2;
48 |
49 | path.quadTo(controlPointX, controlPointY, endX, endY);
50 | return path;
51 | }
52 | });
53 | }
54 |
55 | private void captureValues(TransitionValues values) {
56 | values.values.put(PROPNAME_POSITION, values.view.getBackground());
57 |
58 | Rect rect = new Rect();
59 | values.view.getGlobalVisibleRect(rect);
60 | values.values.put(PROPNAME_POSITION, rect);
61 | }
62 |
63 |
64 | @Override
65 | public void captureStartValues(TransitionValues transitionValues) {
66 | captureValues(transitionValues);
67 | }
68 |
69 | @Override
70 | public void captureEndValues(TransitionValues transitionValues) {
71 | captureValues(transitionValues);
72 | }
73 |
74 | @Override
75 | public Animator createAnimator(ViewGroup sceneRoot,
76 | TransitionValues startValues, TransitionValues endValues) {
77 | if (null == startValues || null == endValues) {
78 | return null;
79 | }
80 |
81 | if (startValues.view.getId() > 0) {
82 | Rect startRect = (Rect) startValues.values.get(PROPNAME_POSITION);
83 | Rect endRect = (Rect) endValues.values.get(PROPNAME_POSITION);
84 |
85 | final View view = endValues.view;
86 |
87 |
88 | Path changePosPath = getPathMotion().getPath(startRect.centerX(), startRect.centerY(), endRect.centerX(), endRect.centerY() - endRect.height() / 2);
89 |
90 | ObjectAnimator objectAnimator = ObjectAnimator.ofObject(view, new PropPosition(PointF.class, "position", new PointF(startRect.centerX(), startRect.centerY())), null, changePosPath);
91 | objectAnimator.setInterpolator(new FastOutSlowInInterpolator());
92 |
93 | return objectAnimator;
94 | }
95 | return null;
96 |
97 | }
98 |
99 | static class PropPosition extends Property {
100 |
101 | public PropPosition(Class type, String name) {
102 | super(type, name);
103 | }
104 |
105 | public PropPosition(Class type, String name, PointF startPos) {
106 | super(type, name);
107 | this.startPos = startPos;
108 | }
109 |
110 | PointF startPos;
111 |
112 | @Override
113 | public void set(View view, PointF topLeft) {
114 |
115 | int x = Math.round(topLeft.x);
116 | int y = Math.round(topLeft.y);
117 |
118 | int startX = Math.round(startPos.x);
119 | int startY = Math.round(startPos.y);
120 |
121 | int transY = y - startY;
122 | int transX = x - startX;
123 |
124 | view.setTranslationX(transX);
125 | view.setTranslationY(transY);
126 | }
127 |
128 | @Override
129 | public PointF get(View object) {
130 | return null;
131 | }
132 | }
133 |
134 | }
135 |
--------------------------------------------------------------------------------
/app/src/main/java/com/imczy/customactivitytransition/transition/ShareElemReturnRevealTransition.java:
--------------------------------------------------------------------------------
1 | package com.imczy.customactivitytransition.transition;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.TimeInterpolator;
6 | import android.graphics.Rect;
7 | import android.transition.Transition;
8 | import android.transition.TransitionValues;
9 | import android.util.ArrayMap;
10 | import android.view.View;
11 | import android.view.ViewAnimationUtils;
12 | import android.view.ViewGroup;
13 |
14 | import java.lang.reflect.Method;
15 | import java.util.ArrayList;
16 |
17 | /**
18 | * Created by chenzhiyong on 16/6/7.
19 | */
20 | public class ShareElemReturnRevealTransition extends Transition {
21 | private static final String TAG = "ShareElemReturnRevealTransition";
22 |
23 | private static final String PROPNAME_BACKGROUND = "custom_reveal:change_radius:radius";
24 |
25 | private boolean hasAnim = false;
26 |
27 | private View animView;
28 |
29 | private Rect startRect;
30 | private Rect endRect;
31 |
32 |
33 | public ShareElemReturnRevealTransition(View animView) {
34 | this.animView = animView;
35 | startRect = new Rect();
36 | endRect = new Rect();
37 | }
38 |
39 | @Override
40 | public void captureStartValues(TransitionValues transitionValues) {
41 | View view = transitionValues.view;
42 | float widthSquared = view.getWidth() * view.getWidth();
43 | float heightSquared = view.getHeight() * view.getHeight();
44 | int radius = (int) Math.sqrt(widthSquared + heightSquared) / 2;
45 |
46 | transitionValues.values.put(PROPNAME_BACKGROUND, radius);
47 | transitionValues.view.getGlobalVisibleRect(startRect);
48 |
49 | }
50 |
51 | @Override
52 | public void captureEndValues(TransitionValues transitionValues) {
53 | transitionValues.view.getLocalVisibleRect(endRect);
54 |
55 | transitionValues.values.put(PROPNAME_BACKGROUND, transitionValues.view.getWidth() / 2);
56 | }
57 |
58 | @Override
59 | public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, final TransitionValues endValues) {
60 |
61 | if (null == startValues || null == endValues) {
62 | return null;
63 | }
64 |
65 | final View view = endValues.view;
66 | int startRadius = (int) startValues.values.get(PROPNAME_BACKGROUND);
67 | int endRadius = (int) endValues.values.get(PROPNAME_BACKGROUND);
68 |
69 | // 在执行返回动画的时候, View 默认的被控制 为 前一个页面的 ShareElem 的打消了
70 | // 所以这里 需要改变的 View 大小 才能 正常的使用揭露动画
71 | // 反射调用
72 | relfectInvoke(view,
73 | startRect.left,
74 | startRect.top,
75 | startRect.right,
76 | startRect.bottom
77 | );
78 |
79 | Animator reveal = createAnimator(view, startRadius, endRadius);
80 |
81 | // 在动画的最后 被我们放大后的 View 会闪一些 这里可以有防止那种情况发生
82 | reveal.addListener(new AnimatorListenerAdapter() {
83 | @Override
84 | public void onAnimationEnd(Animator animation) {
85 | super.onAnimationEnd(animation);
86 | view.setClipBounds(new Rect(0, 0, 1, 1));
87 | view.setVisibility(View.GONE);
88 | }
89 | });
90 | return reveal;
91 |
92 | }
93 |
94 | private Animator createAnimator(View view, float startRadius, float endRadius) {
95 | int centerX = view.getWidth() / 2;
96 | int centerY = view.getHeight() / 2;
97 |
98 | Animator reveal = ViewAnimationUtils.createCircularReveal(view, centerX, centerY,
99 | startRadius, endRadius);
100 | return new ShareElemEnterRevealTransition.NoPauseAnimator(reveal);
101 | }
102 |
103 | // setLeftTopRightBottom 需要反射执行, 该方法能够控制 View 的大小以及位置 在 ChangeBounds 类中有调用
104 | private void relfectInvoke(View view, int left, int top, int right, int bottom) {
105 |
106 | Class clazz = view.getClass();
107 | try {
108 | Method m1 = clazz.getMethod("setLeftTopRightBottom", new Class[]{int.class, int.class, int.class, int.class});
109 | m1.invoke(view, left, top, right, bottom);
110 | } catch (Exception e) {
111 | e.printStackTrace();
112 |
113 | // 5.0版本 没有 setLeftTopRightBottom 这个方法 使用一下方法 ,额 其实 5.0 以上也可以用这些方法?
114 | view.setLeft(left);
115 | view.setRight(right);
116 | view.setTop(top);
117 | view.setBottom(bottom);
118 | }
119 |
120 | }
121 |
122 | }
123 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/bottom_bg.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crianzy/CustomAndroidActivityTransition/478d7b199956e4ba42b3f1025722f5a9b3c20f10/app/src/main/res/drawable-xhdpi/bottom_bg.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_bottom_comment_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crianzy/CustomAndroidActivityTransition/478d7b199956e4ba42b3f1025722f5a9b3c20f10/app/src/main/res/drawable-xhdpi/ic_bottom_comment_normal.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/icon_back_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crianzy/CustomAndroidActivityTransition/478d7b199956e4ba42b3f1025722f5a9b3c20f10/app/src/main/res/drawable-xhdpi/icon_back_pressed.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/inputline.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crianzy/CustomAndroidActivityTransition/478d7b199956e4ba42b3f1025722f5a9b3c20f10/app/src/main/res/drawable-xhdpi/inputline.9.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_coment.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
21 |
28 |
29 |
39 |
40 |
49 |
50 |
62 |
63 |
64 |
65 |
66 |
74 |
75 |
80 |
81 |
86 |
91 |
96 |
101 |
106 |
111 |
116 |
121 |
126 |
127 |
128 |
129 |
130 |
131 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
15 |
26 |
27 |
33 |
34 |
39 |
40 |
45 |
46 |
51 |
52 |
57 |
58 |
63 |
64 |
69 |
70 |
75 |
76 |
81 |
82 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crianzy/CustomAndroidActivityTransition/478d7b199956e4ba42b3f1025722f5a9b3c20f10/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crianzy/CustomAndroidActivityTransition/478d7b199956e4ba42b3f1025722f5a9b3c20f10/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crianzy/CustomAndroidActivityTransition/478d7b199956e4ba42b3f1025722f5a9b3c20f10/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crianzy/CustomAndroidActivityTransition/478d7b199956e4ba42b3f1025722f5a9b3c20f10/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crianzy/CustomAndroidActivityTransition/478d7b199956e4ba42b3f1025722f5a9b3c20f10/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #0e0e0e
4 | #0e0e0e
5 | #FF4081
6 |
7 | #0e0e0e
8 |
9 | #FFFFFF
10 | #33ffffff
11 | #59ffffff
12 | #99ffffff
13 | #e6ffffff
14 | #000000
15 | #26000000
16 | #59000000
17 | #66000000
18 | #99000000
19 | #bf000000
20 | #cc000000
21 | #d9000000
22 | #e6000000
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 | 45dp
7 |
8 | 1500
9 | 500
10 | 300
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | CustomAndroidActivityTransition
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.1.0'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
23 | }
24 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crianzy/CustomAndroidActivityTransition/478d7b199956e4ba42b3f1025722f5a9b3c20f10/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/image/back_error.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crianzy/CustomAndroidActivityTransition/478d7b199956e4ba42b3f1025722f5a9b3c20f10/image/back_error.gif
--------------------------------------------------------------------------------
/image/demo_preview.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crianzy/CustomAndroidActivityTransition/478d7b199956e4ba42b3f1025722f5a9b3c20f10/image/demo_preview.gif
--------------------------------------------------------------------------------
/image/reveal.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crianzy/CustomAndroidActivityTransition/478d7b199956e4ba42b3f1025722f5a9b3c20f10/image/reveal.gif
--------------------------------------------------------------------------------
/image/top_bottom_bar_transY.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crianzy/CustomAndroidActivityTransition/478d7b199956e4ba42b3f1025722f5a9b3c20f10/image/top_bottom_bar_transY.gif
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------