setContentView怎么实现像activity切换时的动画效果
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了setContentView怎么实现像activity切换时的动画效果相关的知识,希望对你有一定的参考价值。
先用LayoutInflater把每个布局文件转换成View,再把view放到ViewFlipper中,用ViewFlipper控制切换的动画效果。
在Activity 的onCreate()方法中通常会调用setContentView来将一个布局传进去来显示界面,有的时候我们需要拿到这个View来动态的做一些界面处理,比如添加一个子View,这种情况下我们期望有一个与setContentView()对应方法getContentView()方法如下:
拿到这个View之后,就可以将返回的View强制转化成你所传入的布局文件中最外一层的View了。
参考技术A 您好,您先用LayoutInflater把每个布局文件转换成View,再把view放到ViewFlipper中,用ViewFlipper控制切换的动画效果~:D。也可以把您的view加上animation的动画效果。。。用flipper不支持2.0以下的版本的。本回答被提问者和网友采纳
setContentView源码分析
我们先来看一下Android中View视图在Activity中的整个层级关系:
包含关系:Activity中有个成员变量Window,Window是个抽象类,它的实现类是PhoneWindow,PhoneWindow有一个成员变量DecorView.
Phonewindow对象创建的开始
入口:ActivityThread#handleLaunchActivity() ->ActivityThread#performLaunchActivity()
开始创建PhoneWindow : performLaunchActivity() -> Activity.attach()
后续的DecorView的创建主要在SetContentView中,后面会分析到.
// Activity类
final void attach(...) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
// 创建PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...
}
学过Android的都知道,setContentView通常会在Acvitity中的Oncreate方法中调用,那么SetContentView到底做了什么呢?
DecorView的创建
学过Android的都知道,setContentView通常会在Acvitity中的Oncreate方法中调用,那么SetContentView到底做了什么呢?
DecorView创建来龙去脉 Activity#setConTentView -> PhoneWindow#setConTentView -> PhoneWindow#install -> PhoneWindow#generateDecor
代码调用链如下:
// Activity类
public void setContentView(@LayoutRes int layoutResID) {
1
getWindow().setContentView(layoutResID);
2
initWindowDecorActionBar();
}
// PhoneWindow类
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
3
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
// 核心代码,后面重点讲这里面干了什么
mLayoutInflater.inflate(layoutResID, mContentParent);
}
...
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 真正创建decorView的地方
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
...
}
protected DecorView generateDecor(int featureId) {
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
4
return new DecorView(context, featureId, this, getAttributes());
}
总结如下:
1 获取PhoneWindow对象,调用PhoneWindow中的SetContentView方法
2 初始化ActionBar
3 创建DecorView对象实例
4 返回创建的DecorView对象,至此DecorView创建完毕
LayoutInflater.inflate(layoutResID, mContentParent)做了什么
from(mContext) 源码解析
/**
* Obtains the LayoutInflater from the given context.
*/
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater)
//通过 Context 获取服务
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
Context 是一个抽象类,我们找下它的实现类,我们知道在启动 Activity 的时候有一个 Context 上下文,启动 Activity 的入口在 ActivityThread main 函数,我们就从这里开始找
//通过反射调用执行的
public static void main(String[] args) {
...
//主线程消息循环
Looper.prepareMainLooper();
//创建 ActivityThread 对象
ActivityThread thread = new ActivityThread();
//Application,Activity 入口
thread.attach(false);
Looper.loop();
...
throw new RuntimeException("Main thread loop unexpectedly exited");
}
private void attach(boolean system) {
sCurrentActivityThread = this;
mSystemThread = system;
//不是系统级别的应用
if (!system) {
ViewRootImpl.addFirstDrawHandler(new Runnable() {
@Override
public void run() {
ensureJitEnabled();
}
});
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
UserHandle.myUserId());
RuntimeInit.setApplicationObject(mAppThread.asBinder());
final IActivityManager mgr = ActivityManager.getService();
try {
//通过 IActivityManager。aidl 文件 底层通过 Binder 通信,关联 Application
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
...
} else {
代码省略
....
}
在 main 方法中,我们创建了 ActivityThread 对象后,调用了其 attach 函数,并且参数为 false。在 attach 函数中,参数为 false 的情况下是属于非系统应用,会通过 Binder 机制与 AMS 通信,并且最终调用 H 类的 LAUNCH_ACTIVITY - > handleLaunchActivity 函数,我们看下该函数的实现:
/***启动 Activity 代码*/
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
....
1
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
.....
try {
2
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
3
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
appContext.setOuterContext(activity);
4
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
//是否是持久化
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
5
mInstrumentation.callActivityOnCreate(activity, r.state);
}
....
return activity;
}
总结performLaunchActivity
1. 创建 Context 对象
2. 创建 Application 对象
3 获取 Context 对象
4 将 appContext 等对象依附在 Activity 的 attach 函数中
5 调用 Activity 的 onCreate 方法
/***Context 的实现类 ContextImp*/
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
//1.创建 Activity 的 Context 对象, 到这里点击 ContextImpl 是 Context 实现类
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
....
return appContext;
}
/**
* Common implementation of Context API, which provides the base
* context object for Activity and other application components.
*/
class ContextImpl extends Context {
...
}
getSystemService
通过上面我们得知 ContextImpl 是 Context 的实现类,我们继续看源码
public class ContextImpl extends Context{
...
/**
* 通过服务名称代号 Context.XXX 拿到系统各种服务
*/
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
...
}
这里我们发现返回的是 SystemServiceRegistry 类里面的 getSystemService 函数,继续跟:
/**
* Gets a system service from a given context.
*/
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
/**
* 交给子类来实现获取服务
*
*/
static abstract interface ServiceFetcher<T> {
T getService(ContextImpl ctx);
}
/**
* 装服务的容器
*/
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();
到了这里我们知道了通过容器缓存拿到了 LayInflater 服务,那么什么时候注册的?下面我们继续看该类源码
final class SystemServiceRegistry {
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();
// Not instantiable.
private SystemServiceRegistry() { }
static {
.....
1注册 LayoutInflater 服务
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
.....
}
}
static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
private final int mCacheIndex;
public CachedServiceFetcher() {
mCacheIndex = sServiceCacheSize++;
}
@Override
@SuppressWarnings("unchecked")
public final T getService(ContextImpl ctx) {
final Object[] cache = ctx.mServiceCache;
synchronized (cache) {
// Fetch or create the service.
Object service = cache[mCacheIndex];
if (service == null) {
try {
service = createService(ctx);
cache[mCacheIndex] = service;
} catch (ServiceNotFoundException e) {
onServiceNotFound(e);
}
}
return (T)service;
}
}
//交给抽象实现去创建服务
public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
}
通过上面的代码可以知道抽象实现返回的是 new PhoneLayoutInflater(ctx.getOuterContext());
public class PhoneLayoutInflater extends LayoutInflater {
}
过 静态代码块 会注册各种 ServiceFatcher, 这其中就包含了 LayoutInflater Service, 将这些服务以键值对的形式存储在 Map 中, 用户使用时只需要根据 key 来获取对应的 ServiceFetcher, 然后通过 ServiceFetcher 对象的 getService 来获取具体服务对象。当第一次获取时,会调用 ServiceFetcher 的 createService 函数创建服务,然后缓存到一个列表中,下次再取直接从缓存中获取,从而避免了重复创建对象
inflate 源码解析
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
// root 不为 null , 则会从resourec 布局解析到 View ,并添加到 root 中
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \\"" + res.getResourceName(resource) + "\\" ("
+ Integer.toHexString(resource) + ")");
}
//获取 XMl 解析器
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
/**
*
* @param parser xml 解析器
* @param root 解析布局的父视图
* @param attachToRoot 是否将要解析的视图添加到父视图中
* @return
*/
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
//Context 对象
mConstructorArgs[0] = inflaterContext;
//存储父视图
View result = root;
try {
// Look for the root node.
int type;
//找到 root 元素
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
final String name = parser.getName();
1
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
2
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
// 生成布局参数
params = root.generateLayoutParams(attrs);
//如果 attachRoot 为 false,那么将给 temp 设置布局参数
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
3
rInflateChildren(parser, temp, attrs, true);
4 如果 Root 不为空,且 attachToroot 为 true ,那么将 temp 添加到父布局中
if (root != null && attachToRoot) {
root.addView(temp, params);
}
//如果 root == null 且 attachToRoot 为 false 那么直接返回 temp
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
...
return result;
}
}
上述 inflate 方法中,主要有下面几步:
- 解析 xml 中的根标签,如果根标签是 merge ,那么调用 rInflate 进行解析,rInflate 会将所有的子 View 添加到跟标签中
- 根据 Tag 来解析layout 跟视图
- 调动 rInflateChildren 解析 temp 根元素下的所有子 View, 并且将这些子 View 都添加到 temp 下
- 调动 rInflateChildren 解析 temp 根元素下的所有子 View, 并且将这些子 View 都添加到 temp 下
回头我们在看createViewFromTag
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
try {
1. 用户可以通过设置 LayoutInflater 的 factory 来自行解析 View,默认这些 Factory 都为空,可以忽略这段
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
2. 没有 Factory 的情况下通过 onCreateView 或者 createView 创建 View
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
3. 内置 View 控件的解析
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
4 自定义 View 的解析
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
...
}
在上面的 PhoneLayoutInflater 重写了 onCreateView 方法,该方法就是在 View 标签名的前面设置了一个 “android.widget” 前缀,然后传递给 createView 解析。
createView 相当来说还比较理解,如果有前缀,那么就构造 View 的完整路径,并且将该类加载到虚拟机中,然后获取该类的构造函数并且缓存下来,在通过构造函数来创建该 View 的对象,最后将对象返回,这就是解析单个 View 的过程。而我们的窗口中时一个视图树, LayoutInflater 需要解析完这棵树,这个功能就交给 rInflateChildren 方法,看下面代码
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
1. 获取树的深度,优先遍历
final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;
2. 挨个元素解析
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
pendingRequestFocus = true;
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) { // 解析 include 标签
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) { //解析到 merge 标签,抛出异常,因为 merge 标签必须是根视图
throw new InflateException("<merge /> must be the root element");
} else {
3. 根据元素名进行解析,又回去了
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
//递归调用进行解析
rInflateChildren(parser, view, attrs, true);
//将解析到的 View 添加进 ViewGroup 中,也就是它的 parent
viewGroup.addView(view, params);
}
}
...
}
rInflateChildren 通过深度优先遍历来构造视图树,每解析到一个 View 元素就会递归调用 rInflateChildren ,直到这条路径的最后一个元素,然后在回溯过来将每一个 View 元素添加进 parent 中,通过 rInflateChildren 解析之后,整棵树就构建完毕了。当回调了 onResume 之后,setContentView 设置的内容就会出现在屏幕中了。
以上是关于setContentView怎么实现像activity切换时的动画效果的主要内容,如果未能解决你的问题,请参考以下文章
如何添加我在 setcontentview() 函数中创建的 xml 文件
Android-Activity中setContentView流程解析