View,androidui基础

本文详细探讨了Android UI开发中关于View.getContext()的使用,以及不同Context类型的差异,包括Activity的setContentView()与addContentView()的区别,AppCompatActivity与Activity在setContentView()上的实现细节。在AppCompatActivity的生命周期方法onCreate()中,AppCompatDelegate通过installViewFactory()方法实现了对LayoutInflater的定制,从而影响到View的创建。在创建过程中,AppCompatDelegateImplV9的createViewFromTag()方法中,使用TintContextWrapper包裹Context,实现了对颜色主题的支持。
摘要由CSDN通过智能技术生成

1.1 View.getContext()

Context context = imageView.getContext();
if (context instanceof Activity) {
Activity activity = (Activity)context;
// …
}

从上面的代码举例中可以看到,从 imageView 控件里获取到 context ,转化为 Activity 来继续操作。这个 imageView 是来自 XML 布局中的一个控件,但在实际项目运行时有的手机并未走到转换类型的 if 分支里去,表明这个 context 并非 Activity 类型。这个就很奇怪了,为什么呢?

/**

  • Simple constructor to use when creating a view from code.
  • @param context The Context the view is running in, through which it can
  •    access the current theme, resources, etc.
    

*/
public View(Context context) {
mContext = context;
//…省略
}

@ViewDebug.CapturedViewProperty
public final Context getContext() {
return mContext;
}

我们点进去看下 View.getContext() 方法,返回 mContext 成员变量,而且 mContext 赋值只有在构造函数里。依据印象,这个 imageView 是写在 XML 中的,在 setContextView(R.layout.xxx) 时候,实际调用的应该就是 PhoneWindow 里的 setContextView() 方法,那构建使用的 context 应该就是 Activity 类型啊?

这时候我又回去仔细 Debug 了一回,发现出现问题的都是在 5.0 以下的手机里。所以上面的印象是有问题的,在 5.0 以下,这个 imageView.getContext() 获取到的 context 类型不是我一开始以为的 Activity 类型,而是 TintContextWrapper 类型。

1.2 Context 类型

这个 TintContextWrapper 是什么 Wrapper ?我印象中 Context 的继承关系中没有这个啊。 关于 Context 类型 www.jianshu.com/p/94e0f9ab3… 的讲解,不清楚的小伙伴可以自行搜索下,这里就不展开了,网上能讲清楚的也不少,这里贴个图看下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vRPsR006-1638084177819)(https://user-gold-cdn.xitu.io/2018/1/9/160dac4188ef008e?imageView2/0/w/1280/h/960/ignore-error/1)]

确实也没有这个 TintContextWrapper 这个类型,从名字看应该也是个 Wrapper 类型的 Context ,还和 Tint 有关系。那剩下的线索还有这个 imageView ,再 Debug 一次,发现这个 imageView 的类型也不是原先在 XML 中定义的 ImageView 类型,而是 AppCompatImageView 类型。

猛然醒悟,控件所在的 Activity 是继承自 AppCompatActivity ,这个 context 类型的变化一定是和 v7 包里的 AppCompatActivity 有关系。之前所谓的印象已经出了两次错误,何不读源码解惑?

注意:下面的文章并不是完全依照查问题时的顺序来的,而是阅读完相关源码后,整理出来的相关知识点。已经清楚的小伙伴可以挑着阅读。

二、Activity 中 setContentView() 与 addContentView() 的区别

如果多次调用 setContentView() ,则之后每次都会清空 mContentParent 容器。然后组装资源 layoutResID

如果多次调用 addContentView() ,则之后每次都会将 View 添加到 mContentParent 容器中。最后产生 View 的叠加效果。

这个 mContentParent 存在于 PhoneWindow 中。

// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
ViewGroup mContentParent;

三、AppCompatActivity 和 Activity 的 setContentView() 方法的区别?

setContentView() 方法有两类,其中一类的必要参数是 XML 布局 id ,另一类的必要参数是 View 类型。

setContentView(@LayoutRes int layoutResID)

setContentView(View view)

这里我们以参数为 View 类型的代码讨论。

3.1 Activity

3.1.1 Activity.setContentView()

// Activity代码
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}

public Window getWindow() {
return mWindow;
}

Activity 中 setContentView() 代码,获取 windowsetContentView()

// Window代码
public abstract void setContentView(View view);

而这个 window 其实就是 PhoneWindow ,看下面的代码。

// Activity代码
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window) {
//…省略

mWindow = new PhoneWindow(this, window);

//…省略
}

3.1.2 PhoneWindow.setContentView()

@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}

@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
// 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) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}

if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params);
}
//…省略
}

代码第12行,确保 mContentParent 已经初始化过。

第14行,如果没有 FEATURE_CONTENT_TRANSITIONS ,先清空 mContentParent 里内容。

第22行, mContentParentview 当子孩子添加进来。

第17行,如果有 FEATURE_CONTENT_TRANSITIONS ,调用 transitionTo(newScene) 。这部分不展开了,最终也是调用以下代码,逻辑步骤都是一样的。

//Scene 代码
//mSceneRoot 就是刚才的 mContentParent
//mLayout 就是 setContentView 方法传进来的 view

public Scene(ViewGroup sceneRoot, View layout) {
mSceneRoot = sceneRoot;
mLayout = layout;
}

public void enter() {
// Apply layout change, if any
if (mLayoutId > 0 || mLayout != null) {
// empty out parent container before adding to it
getSceneRoot().removeAllViews();

if (mLayoutId > 0) {
LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
} else {
mSceneRoot.addView(mLayout);
}
}
//…省略
}

3.2 AppCompatActivity

可以看到 Activity 中 setContentView() 流程还是比较简单的,基本上就是调用了PhoneWindow 里的相应方法。下面我们来看看 AppCompatActivity 中有什么特别的。

3.2.1 AppCompatActivity.setContentView() 方法

// Ap

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值