Android Transition学习笔记

概述

Android 4.4.2 (API level 19)引入Transition框架,之后很多APP上都使用该框架做出很酷炫的效果,如 Google Play Newsstand app

Google Play Newsstand app

在app中适当得使用上Transition能带来较好的用户体验,视频中介绍了该框架的基本使用以及其中核心的一些类和方法,只有学会这些基本的API才能在之后的Activity/Fragment过渡定制一些自己想要的效果。

先看官网的一张关系图

官网关系图

图中有三个核心的类,分别是Scene、Transition和TransitionManager,下面对这个三个核心类展开分析。

Scene

Scene场景

Scene场景,用于保存布局中所有View的属性值,创建Scene的方式可以通过getSceneForLayout方法

比如:

1
2
mScene0 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene0, getContext());
mScene1 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene1, getContext());

也可以直接new Scene(ViewGroup sceneRoot, View layout)

1
2
3
4
View view0 = inflater.inflate(R.layout.scene0, container, false);
View view1 = inflater.inflate(R.layout.scene1, container, false);
mScene0 = new Scene(mSceneRoot, view0);
mScene1 = new Scene(mSceneRoot, view1);

两种方式都需要传SceneRoot,即该场景的根节点。

Transition

Transition过渡动画

Transition过渡动画,前面创建了两个场景,分别保存了视图的一些属性,比如Visibility、position等,Transition就是对于这些属性值的改变定义过渡的效果。从上图可以看到系统内置了一些常用的Transition,Transition的创建可以通过加载xml,如:

res/transition/fade_transition.xml

1
<fade xmlns:android="http://schemas.android.com/apk/res/android" />

然后在代码中:

1
2
3
Transition mFadeTransition =
TransitionInflater.from(this).
inflateTransition(R.transition.fade_transition);

或者直接在代码中:

1
Transition mFadeTransition = new Fade();

TransitionManager

TransitionManeger用于将Scene和Transition联系起来,它提供了一系列的方法如setTransition(Scene fromScene, Scene toScene, Transition transition)指明起始场景和结束场景、他们的过渡动画是什么,go(Scene scene, Transition transition),到指定的场景所使用的过渡动画是什么,beginDelayedTransition(ViewGroup sceneRoot, Transition transition),在当前场景到下一帧的过渡效果是什么。比如这里使用go()方法,效果:

TransitionManager

注意这里两个Scene中红绿两个方块除了位置和大小不一样,id是一致的,transition记录下两个Scene前后属性值,根据属性值的改变执行过渡动画,默认情况下对SceneRoot下的所有View执行动画效果,我们可以通过Transition.addTarget和removeTarget方法选择性添加或移除执行动画的View。

常用API

有时候我们只想改变当前已展示的视图层级中View的状态,可以通过beginDelayedTransition实现,下面列举系统内置的Transition的使用。

AutoTransition

AutoTransition默认的动画效果,对应xml tag为autoTransition

其实是以下几个动画组合顺序执行:

1
2
3
4
5
6
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:transitionOrdering="sequential">
<fade android:fadingMode="fade_out" />
<changeBounds />
<fade android:fadingMode="fade_in" />
</transitionSet>

在代码中使用:

1
2
3
4
5
6
TransitionManager.beginDelayedTransition(mRoot, new AutoTransition());
if (mTextView.getVisibility() != View.VISIBLE) {
mTextView.setVisibility(View.VISIBLE);
} else {
mTextView.setVisibility(View.GONE);
}

ChangeBounds

ChangeBounds对应xml tag为changeBounds,根据前后布局界限的变化执行动画

1
2
3
4
5
6
7
8
TransitionManager.beginDelayedTransition(mRoot, new ChangeBounds());
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mTarget.getLayoutParams();
if ((lp.gravity & Gravity.START) == Gravity.START) {
lp.gravity = Gravity.BOTTOM | Gravity.END;
} else {
lp.gravity = Gravity.TOP | Gravity.START;
}
mTarget.setLayoutParams(lp);

ChangeClipBounds

ChangeClipBounds对应xml tag为changeClipBounds,作用对象:View的getClipBounds()值

1
2
3
4
5
6
7
Rect BOUNDS = new Rect(20, 20, 100, 100);
TransitionManager.beginDelayedTransition(mRoot, new ChangeClipBounds());
if (BOUNDS.equals(ViewCompat.getClipBounds(mImageView))) {
ViewCompat.setClipBounds(mImageView, null);
} else {
ViewCompat.setClipBounds(mImageView, BOUNDS);
}

ChangeClipBounds

ChangeImageTransform

对应xml tag为changeImageTransform,作用对象:ImageView的matrix

1
2
TransitionManager.beginDelayedTransition(mRoot, new ChangeImageTransform());
mImageView.setScaleType(ImageView.ScaleType.XXX);

ChangeImageTransform

ChangeScroll

对应xml tag为changeScroll,作用对象:View的scroll属性值

1
2
TransitionManager.beginDelayedTransition(mRoot, new ChangeScroll());
mTarget.scrollBy(-100, -100);

ChangeScroll

ChangeTransform

对应xml tag 为changeTransform,作用对象:View的scale和rotation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
TransitionManager.beginDelayedTransition(mRoot, new ChangeTransform());
if (mContainer2.getChildCount() > 0) {
mContainer2.removeAllViews();
showRedSquare(mContainer1);
} else {
mContainer1.removeAllViews();
showRedSquare(mContainer2);
mContainer2.getChildAt(0).setRotation(45);
}
private void showRedSquare(FrameLayout container) {
final View view = LayoutInflater.from(getContext())
.inflate(R.layout.red_square, container, false);
container.addView(view);
}

ChangeTransform

Explode

对应xml tag为explode,作用对象:View的Visibility

1
2
3
4
5
TransitionManager.beginDelayedTransition(mRoot, new Explode());
int vis = mViews.get(0).getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE;
for (View view : mViews) {
view.setVisibility(vis);
}

Explode

Fade

对应xml tag为fade,作用对象:View的Visibility

可以在初始化时指定IN或者OUT分别对应淡入和淡出,也可以通过fade.setMode方法设置,若不指定默认为淡入淡出效果

1
2
3
4
5
TransitionManager.beginDelayedTransition(mRoot, new Fade());
int vis = mViews.get(0).getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE;
for (View view : mViews) {
view.setVisibility(vis);
}

Fade

Slide

对应xml tag为slide,作用对象:View的Visibility

可以初始化时传入Gravity.XX,也可以通过slide.setSlideEdge方法设置,默认方向为Gravity.BOTTOM

1
2
3
4
5
TransitionManager.beginDelayedTransition(mRoot, new Slide());
int vis = mViews.get(0).getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE;
for (View view : mViews) {
view.setVisibility(vis);
}

Slide

TransitionSet

对应xml tag为transitionSet

可以在代码中创建transitionSet如:

1
2
3
4
5
6
7
8
9
10
11
12
mTransition = new TransitionSet();
mTransition.addTransition(new ChangeImageTransform());
mTransition.addTransition(new ChangeTransform());
TransitionManager.beginDelayedTransition(mOuterFrame, mTransition);
if (mInnerFrame.getChildCount() > 0) {
mInnerFrame.removeAllViews();
addImageView(mOuterFrame, ImageView.ScaleType.CENTER_CROP, mPhotoSize);
} else {
mOuterFrame.removeViewAt(1);
addImageView(mInnerFrame, ImageView.ScaleType.FIT_XY,
FrameLayout.LayoutParams.MATCH_PARENT);
}

也可以通过加载xml布局创建transitionSet:

xml布局长这样:

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:transitionOrdering="together">
<changeImageTransform/>
<changeTransform/>
</transitionSet>

通过transitionOrdering属性设置动画执行的顺序,together表示同时执行,sequential表示顺序执行,在代码中可以调用TransitionSet的setOrdering(int)方法,属性值传ORDERING_SEQUENTIAL或者ORDERING_TOGETHER

在代码中:

1
2
3
4
5
6
7
8
9
10
mTransition = (TransitionSet) TransitionInflater.from(getContext()).inflateTransition(R.transition.transition);
TransitionManager.beginDelayedTransition(mOuterFrame, mTransition);
if (mInnerFrame.getChildCount() > 0) {
mInnerFrame.removeAllViews();
addImageView(mOuterFrame, ImageView.ScaleType.CENTER_CROP, mPhotoSize);
} else {
mOuterFrame.removeViewAt(1);
addImageView(mInnerFrame, ImageView.ScaleType.FIT_XY,
FrameLayout.LayoutParams.MATCH_PARENT);
}

这里结合changeImageTransform和changeTransform,效果如下:

TransitionSet

PathMotion

PathMotion

Transition的辅助工具,以path的方式指定过渡效果,两个具体实现类ArcMotion和PatternPathMotion,看下ArcMotion的效果

1
2
3
4
5
6
7
8
9
10
mTransition = new AutoTransition();
mTransition.setPathMotion(new ArcMotion());
TransitionManager.beginDelayedTransition(mRoot, mTransition);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mTarget.getLayoutParams();
if ((lp.gravity & Gravity.START) == Gravity.START) {
lp.gravity = Gravity.END | Gravity.BOTTOM;
} else {
lp.gravity = Gravity.START | Gravity.TOP;
}
mTarget.setLayoutParams(lp);

PathMotion

它的运动轨迹是条曲线,有兴趣的可以研究下它的实现算法,在源码中有个很萌的图如下:

PathMotion

自定义Transition

除了系统内置的Transition,我们还可以自定义Transition效果,需要继承Transition

1
2
3
4
5
6
7
8
9
10
public class CustomTransition extends Transition {
@Override
public void captureStartValues(TransitionValues values) {}
@Override
public void captureEndValues(TransitionValues values) {}
@Override
public Animator createAnimator(ViewGroup sceneRoot,
TransitionValues startValues,
TransitionValues endValues) {}
}

其工作原理是在captureStartValues和captureEndValues中分别记录View的属性值,官网建议确保属性值不冲突,属性值的命名格式参考:

1
package_name:transition_name:property_name

在createAnimator中创建动画,对比属性值的改变执行动画效果,如自定义修改颜色动画效果:

自定义Transition

在两个Scene中使用自定义过渡动画,效果如下:

自定义Transition

Note

1.Android 版本在4.0(API Level 14)到4.4.2(API Level 19)使用Android Support Library’s

2.对于 SurfaceView可能不起效果,因为SurfaceView的实例是在非UI线程更新的,因此会造成和其他视图动画不同步。

3.某些特定的转换类型在应用到TextureView时可能不会产生所需的动画效果。

4.继承自AdapterView的如ListView,与该框架不兼容。

5.不要对包含文本的视图的大小进行动画

Thanks to

Google Demo

Github Demo传送门


作者:HuYounger
本文转载自:http://rkhcy.github.io


下篇

在上篇笔记中对于Transition的框架和常用的API使用进行了分析,Transition最常用的是在界面过渡方面,本文继续学习Transition在界面过渡上的使用。在界面过渡上,Transition分为不带共享元素的Content Transition和带共享元素的ShareElement Transition。

Content Transition

先看下content transition的一个例子,在Google Play Games上的应用:

Content Transition

在经过学习后我们也可以设计出类似的效果,首先需要了解在界面过渡中涉及到的一些重要方法,从ActivtyA调用startActivity方法唤起ActivityB,到ActivityB按返回键返回ActivityA涉及到与Transition有关的方法

Content Transition

  • ActivityA.exitTransition()

  • ActivityB.enterTransition()

Content Transition

  • ActivityB.returnTransition()

  • ActivityA.reenterTransition()

因此,只要我们在对应的方法中设置了Transition就可以了。在默认没有设置对应Transition的情况下,Material-theme应用的exitTransition为null,enterTransition为Fade,如果reenterTransition和returnTransition未设定,则分别对应exitTransition和enterTransition。

使用

在style中添加android:windowContentTransitions 属性启用窗口内容转换(Material-theme应用默认为true),指定该Activity的Transition

1
2
3
4
5
6
7
8
9
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- enable window content transitions -->
<item name="android:windowContentTransitions">true</item>
<!-- specify enter and exit transitions -->
<!-- options are: explode, slide, fade -->
<item name="android:windowEnterTransition">@transition/change_image_transform</item>
<item name="android:windowExitTransition">@transition/change_image_transform</item>
</style>

也可以在代码中指定

1
2
3
4
5
6
// inside your activity (if you did not enable transitions in your theme)
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
// set an enter transition
getWindow().setEnterTransition(new Explode());
// set an exit transition
getWindow().setExitTransition(new Explode());

然后启动Acticity

1
2
startActivity(intent,
ActivityOptions.makeSceneTransitionAnimation(this).toBundle());

例子

这里在代码中指定ActivityA的exitTransition:

1
2
3
4
5
6
private void setupTransition() {
Slide slide = new Slide(Gravity.LEFT);
slide.setDuration(1000);
slide.setInterpolator(new FastOutSlowInInterpolator());
getWindow().setExitTransition(slide);
}

使用xml方式指定ActivityB的enterTransition:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<slide
android:duration="1000"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:slideEdge="bottom">
<targets>
<target android:targetId="@id/content_container"/>
</targets>
</slide>
<slide
android:duration="1000"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:slideEdge="top">
<targets>
<target android:targetId="@id/image_container"/>
</targets>
</slide>
</transitionSet>

运行效果如下:

上图动画有两个问题:

  1. ActivityA的exitTransition还没完全走完ActivityB的enterTransition就执行了,ActivityB的returnTransition还没完全走完ActivityA的reenterTransition就执行了;
  2. 状态栏和导航栏的动画不太协调;

问题1是因为默认情况下enter/return transition会比exit/reenter transition完全结束前稍微快一点运行,如果想让前者完全运行完后者再进来,可以在代码中调用Window的

1
2
setWindowAllowEnterTransitionOverlap(false)
setWindowAllowReturnTransitionOverlap(false)

或者在xml中设置

1
2
<item name="android:windowAllowEnterTransitionOverlap">false</item> 
<item name="android:windowAllowReturnTransitionOverlap">false</item>

运行如下:

再看下问题2,默认情况下状态栏和标题栏也会参与动画(如果有导航栏也会,测试机默认木有导航栏),当我们把xxxoverlap属性设为false后就看得比较明显了,如果不想让它们参与动画通过excludeTarget()将其排除,在代码中:

1
2
3
4
5
6
7
8
9
private void setupTransition() {
Slide slide = new Slide(Gravity.LEFT);
slide.setDuration(1000);
slide.setInterpolator(new FastOutSlowInInterpolator());
slide.excludeTarget(android.R.id.statusBarBackground, true);
slide.excludeTarget(android.R.id.navigationBarBackground, true);
slide.excludeTarget(R.id.appbar,true);
getWindow().setExitTransition(slide);
}

或者在xml中:

1
2
3
4
5
6
7
8
9
10
11
<slide xmlns:android="http://schemas.android.com/apk/res/android"
android:slideEdge="left"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="1000">
<targets>
<!-- if using a custom Toolbar container, specify the ID of the AppBarLayout -->
<target android:excludeId="@id/appbar" />
<target android:excludeId="@android:id/statusBarBackground"/>
<target android:excludeId="@android:id/navigationBarBackground"/>
</targets>
</slide>

效果如下:

执行结果

具体流程

ActivityA startActivity()

  1. 确定需要执行exit Transition的target View
  2. Transition的captureStartValues()获取target View Visibility的值(此时为VISIBLE)
  3. 将target View Visibility的值设为INVISIBLE
  4. Transition的captureEndValues()获取target View Visibility的值(此时为INVISIBLE)
  5. Transition的createAnimator()根据前后Visibility的属性值变化创建动画

ActivityB Activity 开始

  1. 确定需要执行enter Transition的target View
  2. Transition的captureStartValues()获取获取target View Visibility的,初始化为INVISIBLE
  3. 将target View Visibility的值设为VISIBLE
  4. Transition的captureEndValues()获取target View Visibility的值(此时为VISIBLE)
  5. Transition的createAnimator()根据前后Visibility的属性值变化创建动画

ShareElement Transition

shareElement Transition的例子

shareElement Transition的例子

shareElement Transition指的是共享元素从activity/fragment到其他activity/fragment时的动画

shareElement Transition

有了上面的分析看名字应该也猜得出方法对应的功能了,如果没有设置exit/enter shared element transitions,默认为 @android:transition/move,上面的Content Transition是根据Visibility的变化创建动画,而shareElement Transition是根据大小,位置,和外观的变化创建动画,如chanageBounds、changeTransform、ChangeClipBounds、 ChangeImageTransform等,具体API使用和效果可以参考上篇。指定shareElement Transition可以通过代码形式:

1
2
3
4
getWindow().setSharedElementEnterTransition();
getWindow().setSharedElementExitTransition();
getWindow().setSharedElementReturnTransition();
getWindow().setSharedElementReenterTransition();

也可以通过xml形式:

1
2
3
4
5
6
7
8
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- specify shared element transitions -->
<item name="android:windowSharedElementEnterTransition">
@transition/change_image_transform</item>
<item name="android:windowSharedElementExitTransition">
@transition/change_image_transform</item>
</style>

然后启动Acticity

1
2
3
4
5
6
Intent intent = new Intent(this, DetailsActivity.class);
// Pass data object in the bundle and populate details activity.
intent.putExtra(DetailsActivity.EXTRA_CONTACT, contact);
ActivityOptionsCompat options = ActivityOptionsCompat.
makeSceneTransitionAnimation(this, (View)ivProfile, "profile");
startActivity(intent, options.toBundle());

在布局文件中对于要共享的View添加android:transitionName且保持一致,如果要共享的View有点多,可以通过Pair,Pair<View,String> 存储着共享View和View的名称,使用如下

1
2
3
4
5
6
7
8
Intent intent = new Intent(context, DetailsActivity.class);
intent.putExtra(DetailsActivity.EXTRA_CONTACT, contact);
Pair<View, String> p1 = Pair.create((View)ivProfile, "profile");
Pair<View, String> p2 = Pair.create(vPalette, "palette");
Pair<View, String> p3 = Pair.create((View)tvName, "text");
ActivityOptionsCompat options = ActivityOptionsCompat.
makeSceneTransitionAnimation(this, p1, p2, p3);
startActivity(intent, options.toBundle());

例子

在ActivityB的theme中添加SharedElementEnterTransition

1
2
3
<item name="android:windowSharedElementEnterTransition">
@transition/change_image_transform
</item>

change_image_transform.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<changeBounds
android:duration="1000"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
<changeImageTransform
android:duration="1000"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</transitionSet>

执行效果:

动画效果

具体流程

从图上看,好像图片是从一个ActivityA”传递”到另一个ActivityB,实际上真正负责绘制都发生在ActivityB上:

  1. ActivityA调用startActivity()后ActivityB处于透明状态
  2. Transition收集ActivityA中共享View的初识状态,并传递给ActivityB
  3. Transition收集ActivityB中共享View的最终状态
  4. Transition根据状态改变创建动画
  5. Transition隐藏ActivityA,随着ActivityB的共享View运动到指定位置,ActivityB的背景在ActivityA上淡入,并随着动画完成而完全可见。

我们可以通过修改Activity背景淡入淡出时间来验证,在ActivityB中加入

1
getWindow().setTransitionBackgroundFadeDuration(2000);

为了更直观,把ActivityA的exitTransition先注释掉,运行效果:

动画效果

可以看到,ActivityB确实像盖在ActivityA上,这里用到了 ViewOverlay,原理简单来说就是在其他View上draw,共享View利用该技术可以实现画在其他View上。我们可以通过Window的setSharedElementsUseOverlay(false)来关闭该功能,不过这样一来会使最终结果和你预想的不一致,默认该值为true。

延迟加载

上面分析Transition会获取共享视图前后的状态值来创建动画,如果我们的图片是网上下载的,那么很有可能图片的准确大小需要下载下来才能确定,Activity Transitions API提供了一对方法暂时推迟过渡,直到我们确切地知道共享元素已经被适当的渲染和放置。在onCreate中调用postponeEnterTransition()(API >= 21)或者supportPostponeEnterTransition()(API < 21)延迟过渡;当图片的状态确定后,调用startPostponedEnterTransition()(API >= 21)或supportStartPostponedEnterTransition()(API < 21)恢复过渡,常见处理:

1
2
3
4
5
6
7
8
9
10
11
12
// ... load remote image with Glide/Picasso here
supportPostponeEnterTransition();
ivBackdrop.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
ivBackdrop.getViewTreeObserver().removeOnPreDrawListener(this);
supportStartPostponedEnterTransition();
return true;
}
}
);

Thanks to

Material-Animations
Getting Started with Activity & Fragment Transitions (part 1-4)
What are all these dang Transitions
Shared Element Activity Transition
Define custom animations
Presentation-ContentTransition


作者:HuYounger
本文转载自:http://rkhcy.github.io


评论

Ajax Android AndroidStudio Animation Anroid Studio AppBarLayout Babel Banner Buffer Bulma ByteBuffer C++ C11 C89 C99 CDN CMYK COM1 COM2 CSS Camera Raw, 直方图 Chrome Class ContentProvider CoordinatorLayout C语言 DML DOM Dagger Dagger2 Darktable Demo Document DownloadManage ES2015 ESLint Element Error Exception Extensions File FileProvider Flow Fresco GCC Git GitHub GitLab Gradle Groovy HTML5 Handler HandlerThread Hexo Hybrid I/O IDEA IO ImageMagick IntelliJ Intellij Interpolator JCenter JNI JS Java JavaScript JsBridge Kotlin Lab Lambda Lifecycle Lint Linux Looper MQTT MVC MVP Maven MessageQueue Modbus Momentum MySQL NDK NIO NexT Next Nodejs ObjectAnimator Oracle VM Permission PhotoShop Physics Python RGB RS-232 RTU Remote-SSH Retrofit Runnable RxAndroid RxJava SE0 SSH Spring SpringBoot Statubar Style Task Theme Thread Tkinter UI UIKit UML VM virtualBox VS Code VUE ValueAnimator ViewPropertyAnimator Vue Vue.js Web Web前端 Workbench api apk bookmark by关键字 cli compileOnly computed css c语言 databases demo hexo hotfix html iOS icarus implementation init jQuery javascript launchModel logo merge methods mvp offset photos pug query rxjava2 scss servlet shell svg tkinter tomcat transition unicode utf-8 vector virtual box vscode watch webpack 七牛 下载 中介者模式 串口 临潼石榴 主题 书签 事件 享元模式 仓库 代理模式 位运算 依赖注入 修改,tables 光和色 内存 内核 内部分享 函数 函数式编程 分支 分析 创建 删除 动画 单例模式 压缩图片 发布 可空性 合并 同向性 后期 启动模式 命令 命令模式 响应式 响应式编程 图层 图床 图片压缩 图片处理 图片轮播 地球 域名 基础 增加 备忘录模式 外观模式 多线程 大爆炸 天气APP 太白山 头文件 奇点 字符串 字符集 存储引擎 宇宙 宏定义 实践 属性 属性动画 岐山擀面皮 岐山肉臊子 岐山香醋 工具 工厂模式 年终总结 开发技巧 异常 弱引用 恒星 打包 技巧 指令 指针 插件 插值 摄影 操作系统 攻略 故事 数据库 数据类型 数组 文件 新功能 旅行 旋转木马 时序图 时空 时间简史 曲线 杂谈 权限 枚举 架构 查询 标准库 标签选择器 样式 核心 框架 案例 桥接模式 检测工具 模块化 模板 模板引擎 模板方法模式 油泼辣子 泛型 洛川苹果 浅色状态栏 渲染 源码 源码分析 瀑布流 热修复 版本 版本控制 状态栏 状态模式 生活 留言板 相册 相对论 眉县猕猴桃 知识点 码云 磁盘 科学 笔记 策略模式 类图 系统,发行版, GNU 索引 组件 组合模式 绑定 结构 结构体 编码 网易云信 网格布局 网站广播 网站通知 网络 美化 联合 脚手架 膨胀的宇宙 自定义 自定义View 自定义插件 蒙版 虚拟 虚拟机 补码 补齐 表单 表达式 装饰模式 西安 观察者模式 规范 视图 视频 解耦器模式 设计 设计原则 设计模式 访问者模式 语法 责任链模式 贪吃蛇 转换 软件工程 软引用 运算符 迭代子模式 适配器模式 选择器 通信 通道 配置 链表 锐化 错误 键盘 闭包 降噪 陕西地方特产 面向对象 项目优化 项目构建 黑洞
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×