Android完美解析setContentView 你真的理解setContentView吗?「建议收藏」

Android完美解析setContentView 你真的理解setContentView吗?「建议收藏」导读:本篇文章的前半部分为源码分析,后半部分为一个例子,在例子中我们会遇到一些问题,从而回答前半部分留下的问题!

大家好,又见面了,我是你们的朋友全栈君。

导读:

本篇文章的前半部分为源码分析,后半部分为一个例子,在例子中我们会遇到一些问题,从而回答前半部分留下的问题!

源码分析:

说到Activity的setContentView,咱们直接找到一个Activity中的setContentView点进去看看!

public void setContentView(View view) {
        getWindow().setContentView(view);
        initActionBar();
    }

点进来之后我们发现它里边调用了getWindow.setContentView,我们点击getWindow看看里面是什么!

 public Window getWindow() {
        return mWindow;
    }

返回了一个Window对象,这个mWindow就是Window的子类PhoneWindow,每一个Activity都有一个PhoneWindow对象,至于他们是怎么联系起来的我们就不去研究了,好了现在我们来到了第一层!
这里写图片描述
我们在PhoneWindow中找到了setContentView的实现

public class PhoneWindow extends Window implements MenuBuilder.Callback { 
   
    //...
    //...
    //...
    //老大
    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }
    //老二
    @Override
    public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    //和老三
    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mContentParent.addView(view, params);
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

    //...
    //...
    //...
}

我们看到首先判断了mContentParent,那么这个mContentParent是个什么呢?当mContentParent为空的时候,会执行installDecor()方法,那么我们肯定是到installDecor中去找答案咯,点进去!

private void installDecor() {
            if (mDecor == null) {
                mDecor = generateDecor();
                //...
                }
            }
            if (mContentParent == null) {
                mContentParent = generateLayout(mDecor);

                        //...
                    }
                }
            }
    }

我把代码能删的都给删了,我们看见mContentParent为空的时候,会执行generateLayout()方法,同时需要传入一个mDecor,那么mDecor是什么东西呢,我们往上面看,mDecor是通过generateDecor()方法创建出来的,那我们自然得先到generateDecor()中一探究竟!

protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }

new了一个DecorView对象,DecorView就是我们界面中最顶层的View了,这个View的结构是这样的!
这里写图片描述

DecorView继承于FrameLayout,然后它有一个子view即LinearLayout,方向为竖直方向,其内有两个FrameLayout,上面的FrameLayout即为TitleBar之类的,下面的FrameLayout即为我们的ContentView,所谓的setContentView就是往这个FrameLayout里面添加我们的布局View的!现在我们可以画出第二层了!
这里写图片描述
好了,现在mDecor有了,终于可以进入到generateLayout(mDecor);看看了!

protected ViewGroup generateLayout(DecorView decor) {
//...

//省略一些设置Window样式的代码,直接来看我们最关心的代码!
 ViewGroup contentParent =(ViewGroup)findViewById(ID_ANDROID_CONTENT);
                    //... 
                    return contentParent;
                }
         }

ID_ANDROID_CONTENT就是R.id.content,就是这个FrameLayout
这里写图片描述
我们看到contentParent就是这个FrameLaout!所以这下我们清楚了,mContentParent就是这个FrameLayout,就是我们的ContentView,现在回到PhoneWindow中的setContentView方法中!

public class PhoneWindow extends Window implements MenuBuilder.Callback { 
   
    //...
    //...
    //...
    //老大
    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }
    //老二
    @Override
    public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    //和老三
    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mContentParent.addView(view, params);
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

    //...
    //...
    //...
}

我们先来看老大,首先会先判断mContentParent是否为空,如果为空说明我们还没有DecorView,然后调用installDecor,之后我们的DecorView就准备好了,mContentParent也指向了我们的ContentView,由于是新建的,我们的mContentParent中肯定没有子View,如果不是新建的,我们要先把mContentParent中的子View全部清干净。接下来通过反射加载到我们传入的布局,接着下面会通过调用getCallBack得到一个CallBack对象cb,其实这个cb就是我们的Activity,接着会调用Activity的onContentChanged方法,这个方法是一个空实现,在后面的例子中我们会用到这个方法!
通过这一系列的过程,我们自己的View就被加载到那个FrameLayout中了,至此我们的布局就显示到屏幕上了!

老二和老三也非常的清晰,我们不是传入布局的id,而是传入一个View,mContentParent通过addView(view)来加载布局,那么这个和老大通过反射加载布局有什么区别吗? 答案肯定是有!我会通过一个例子来说明!

例子:

我们现在就来模拟一个需求,比如用户在MainActivity填写一个表单,这个表单有姓名和电话两个字段,当用户填完之后我们要进行提交,但是在提交之前我们希望有一个确认表单的页面来让用户确认一下信息是否填对,如果需要修改可以点击重填来修改,如果没问题就点击提交,然后跳到SecondActivity提示提交成功。

有问题版本

首先我们先来看一个有问题的版本,首先我们进入到填写表单的页面,填写完之后点击提交进入确认表单页面,然后点击重填,发现回来之后姓名栏和手机栏都是空的,然而我们确实在onContentChanged中为他们赋值了,不管了,再次填写,填完了点击提交,发现提交也点不了了,怎么点都没有反应!这是怎么回事呢!我们带着问题来看代码!
这里写图片描述

public class MainActivity extends Activity implements OnClickListener{ 

private static final int LAYOUT_FILL = 0;
private static final int LAYOUT_CONFIRM = 1;
private EditText et_name;
private EditText et_phone;
private Button bt_ok;
private TextView tv_name;
private TextView tv_phone;
private Button bt_refilling;
private Button bt_confirm;
private String name;
private String phone;
private int currentLayout;
private LayoutInflater mInflater;
private View confirmView;
private InputMethodManager imm;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//调用setContentView(int id) 加载填写表单布局
setContentView(R.layout.activity_main);
initViews();
//注册监听器
registerListeners();
//初始化当前布局为填写表单布局
currentLayout = LAYOUT_FILL;
}
/** * 初始化布局 */
private void initViews() {
imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
//初始化布局加载器
mInflater = LayoutInflater.from(this);
//加载确认表单页面
confirmView =mInflater.inflate(R.layout.activity_confirm, null);
//拿到填写表单页面中的控件
et_name = (EditText)findViewById(R.id.et_name);
et_phone = (EditText)findViewById(R.id.et_phone);
bt_ok = (Button)findViewById(R.id.bt_ok);
//拿到确认表单页面中的控件
tv_name = (TextView)confirmView.findViewById(R.id.tv_confirm_name);
tv_phone = (TextView)confirmView.findViewById(R.id.tv_confirm_phone);
bt_refilling = (Button)confirmView.findViewById(R.id.bt_refilling);
bt_confirm = (Button)confirmView.findViewById(R.id.bt_comfirm);
}
/** * 注册监听器 */
private void registerListeners() {
bt_ok.setOnClickListener(this);
bt_refilling.setOnClickListener(this);
bt_confirm.setOnClickListener(this);
}
/** * 点击事件 */
@Override
public void onClick(View v) {
if (currentLayout == LAYOUT_FILL) {
//如果当前页面是填写表单页面
switch (v.getId()) {
case R.id.bt_ok://点击提交按钮
if (TextUtils.isEmpty(et_name.getText().toString())) {
Toast.makeText(MainActivity.this, "姓名不能为空", Toast.LENGTH_SHORT).show();
return;
}
if (TextUtils.isEmpty(et_phone.getText().toString())) {
Toast.makeText(MainActivity.this, "手机号不能为空",Toast.LENGTH_SHORT).show();
return;
}
//隐藏软键盘
imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
//将EditText内容保存到变量中
name = et_name.getText().toString();
phone = et_phone.getText().toString();
//将当前页面改为确认表单页面
currentLayout = LAYOUT_CONFIRM;
//调用setContentView(View view),显示确认表单页面
setContentView(confirmView);
break;
}
}else if (currentLayout == LAYOUT_CONFIRM) {
//如果当前页面为确认表单页面
switch (v.getId()) {
case R.id.bt_refilling://重填按钮
//调用setContentView(int id),显示填写表单页面
setContentView(R.layout.activity_main);
//将当前页面改为填写表单页面
currentLayout = LAYOUT_FILL;
break;
case R.id.bt_comfirm://确认表单页面的最终提交按钮
//跳到SecondActivity
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
startActivity(intent);
break;
}
}
}
/** * 调用了setContentView后会回调此方法 */
@Override
public void onContentChanged() {
super.onContentChanged();
if (currentLayout == LAYOUT_CONFIRM) {
//如果当前页面是确认表单页面
if (!TextUtils.isEmpty(name)) {
//如果填写表单页面中的姓名不为空,我们将姓名一栏setText上
tv_name.setText(name);
}
if (!TextUtils.isEmpty(phone)) {
//如果填写表单页面中的电话不为空,我们将电话一栏setText上
tv_phone.setText(phone);
}
}else if (currentLayout == LAYOUT_FILL) {
//如果当前页面是填写表单页面
//如果是第一次启动这个页面,我们判断name和phone是空,所以就不做任何的操作
//如果是从确认表单页面点击重填按钮再次返回到填写表单页面时,我们就将刚刚填过
//的信息再次填上,省的用户再重新填一遍
if (!TextUtils.isEmpty(name)) {
et_name.setText(name);
}
if (!TextUtils.isEmpty(phone)) {
et_phone.setText(phone);
}
}
}
}

那么问题就出现在了setContentView上面,我们在点击了重填按钮后,我们的setContentView使用的是老大,即setContentView(int id),回想刚才我们分析的源码,老大是通过反射拿到我们的view,而每次反射拿到的view都不是同一个view,也就是说我们在onCreate中setContentView(R.layout.activity_main)和在点击了重填后setContentView(R.layout.activity_main)实际上是两个View,那么通过findviewById拿到的控件也是两套不同的控件了,所以我们点击了重填后,我们确实是给tv_name和tv_phone赋值了,但是我们显示的View不是原来那个View了,是新的View,那么新的View里面的tv_name和tv_phone是空的!所以显示为空!点击提交按钮也是一个道理!我们给原来的bt_ok设置了监听器,而新的View的bt_ok是没有设置过监听器的,所以点击是没有效果的!说了这么多!有很多重复的话,就是为了给说明白这件事!这个就是老大与老二老三的不同之处!!

修改后:

这里写图片描述

public class MainActivity extends Activity implements OnClickListener{ 

private static final int LAYOUT_FILL = 0;
private static final int LAYOUT_CONFIRM = 1;
private EditText et_name;
private EditText et_phone;
private Button bt_ok;
private TextView tv_name;
private TextView tv_phone;
private Button bt_refilling;
private Button bt_confirm;
private String name;
private String phone;
private int currentLayout;
private LayoutInflater mInflater;
private View confirmView;
private View fillView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//我们将setContentView放到initViews里面去调用
initViews();
//注册监听器
registerListeners();
//初始化当前布局为填写表单布局
currentLayout = LAYOUT_FILL;
}
/** * 初始化布局 */
private void initViews() {
//初始化布局加载器
mInflater = LayoutInflater.from(this);
//加载填写表单页面
fillView = mInflater.inflate(R.layout.activity_main, null);
//加载确认表单页面
confirmView =mInflater.inflate(R.layout.activity_confirm, null);
//调用setContentView(View view)方法,传入一个View
setContentView(fillView);
//拿到填写表单页面中的控件
et_name = (EditText)fillView.findViewById(R.id.et_name);
et_phone = (EditText)fillView.findViewById(R.id.et_phone);
bt_ok = (Button)fillView.findViewById(R.id.bt_ok);
//拿到确认表单页面中的控件
tv_name = (TextView)confirmView.findViewById(R.id.tv_confirm_name);
tv_phone = (TextView)confirmView.findViewById(R.id.tv_confirm_phone);
bt_refilling = (Button)confirmView.findViewById(R.id.bt_refilling);
bt_confirm = (Button)confirmView.findViewById(R.id.bt_comfirm);
}
/** * 注册监听器 */
private void registerListeners() {
bt_ok.setOnClickListener(this);
bt_refilling.setOnClickListener(this);
bt_confirm.setOnClickListener(this);
}
/** * 点击事件 */
@Override
public void onClick(View v) {
if (currentLayout == LAYOUT_FILL) {
//如果当前页面是填写表单页面
switch (v.getId()) {
case R.id.bt_ok://点击提交按钮
if (TextUtils.isEmpty(et_name.getText().toString())) {
Toast.makeText(MainActivity.this, "姓名不能为空", Toast.LENGTH_SHORT).show();
return;
}
if (TextUtils.isEmpty(et_phone.getText().toString())) {
Toast.makeText(MainActivity.this, "手机号不能为空",Toast.LENGTH_SHORT).show();
return;
}
//将EditText内容保存到变量中
name = et_name.getText().toString();
phone = et_phone.getText().toString();
//将当前页面改为确认表单页面
currentLayout = LAYOUT_CONFIRM;
//调用setContentView(View view),显示确认表单页面
setContentView(confirmView);
break;
}
}else if (currentLayout == LAYOUT_CONFIRM) {
//如果当前页面为确认表单页面
switch (v.getId()) {
case R.id.bt_refilling://重填按钮
//调用setContentView(View view),显示填写表单页面
setContentView(fillView);
//将当前页面改为填写表单页面
currentLayout = LAYOUT_FILL;
break;
case R.id.bt_comfirm://确认表单页面的最终提交按钮
//跳到SecondActivity
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
startActivity(intent);
break;
}
}
}
/** * 调用了setContentView后会回调此方法 */
@Override
public void onContentChanged() {
super.onContentChanged();
if (currentLayout == LAYOUT_CONFIRM) {
//如果当前页面是确认表单页面
if (!TextUtils.isEmpty(name)) {
//如果填写表单页面中的姓名不为空,我们将姓名一栏setText上
tv_name.setText(name);
}
if (!TextUtils.isEmpty(phone)) {
//如果填写表单页面中的电话不为空,我们将电话一栏setText上
tv_phone.setText(phone);
}
}else if (currentLayout == LAYOUT_FILL) {
//如果当前页面是填写表单页面
//如果是第一次启动这个页面,我们判断name和phone是空,所以就不做任何的操作
//如果是从确认表单页面点击重填按钮再次返回到填写表单页面时,我们就将刚刚填过
//的信息再次填上,省的用户再重新填一遍
if (!TextUtils.isEmpty(name)) {
et_name.setText(name);
}
if (!TextUtils.isEmpty(phone)) {
et_phone.setText(phone);
}
}
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/153249.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 数字化转型中的大数据治理架构

    数字化转型中的大数据治理架构一、数字化时代大数据向服务化发展数字化时代,我们的数据来源比以前更广了。第一,之前传统企业政府的IT系统主要是面向内部使用,产生了一些信息,现在已经面向外部使用了;第二,更多行为信息、社交信息都会变成企业的数据;第三,我们有很多非结构化的数据,比如媒体、视频数据等;第四,还有物联网传感器方面的数据等。这些数据大部分是非结构化的,如媒体数据、视频数据,包括物联网传感器等信息,这些信息远比以前更加难以…

    2022年10月23日
  • IT该忍者神龟Jquery小工具easyUI物业摘要召回

    IT该忍者神龟Jquery小工具easyUI物业摘要召回

  • Python的random函数用法详解[通俗易懂]

    Python的random函数用法详解[通俗易懂]在random模块下提供了如下常用函数:random.seed(a=None,version=2):指定种子来初始化伪随机数生成器。random.randrange(start,stop[,stop]):返回从start开始到stop结束、步长为step的随机数。其实就相当于choice(range(start,stop,step))的效果,只不过实际底层并不生成区间对象。random.randint(a,b):生成一个范围为a≤N≤b的随机数。其等同于ra

  • flask 的 jsonify 自动排序问题

    flask 的 jsonify 自动排序问题背景·Python在写接口的时候有时需要返回json格式的数据给客户端·最简单的方式就是用flask的jsonify,能直接将字典格式化为json的形式进行传输例如fromflaskimportjsonify………defreturn_success(data):”””返回成功信息”””returnjsonify(data)通过以上jsonify的方式大部分需求是能够搞定的问题引发但是有时候我们要传递的json格式可能

  • Eclipse中快速输入System.out.println()的快捷键

    Eclipse中快速输入System.out.println()的快捷键善用Eclipse组合键,可以提高输入效率。Step1:Eclipse的参数设置面板,工具栏窗口-》首选项-》常规-》键-》按类别筛选,编辑类别下找到“内容辅助”,英文即“ContentAssist”。检查该项是不是绑定了“Alt+/”,如果不是换成这个快捷键的组合,点击确定按钮。效果:1、例如:输入“tr”,然后按组合键

  • Ext中apply及applyIf方法的应用

    Ext中apply及applyIf方法的应用Ext中apply及applyIf方法的应用        apply及applyIf方法都是用于实现把一个对象中的属性应用于另外一个对象中,相当于属性拷贝。不同的是apply将会覆盖目标对象中的属性,而applyIf只拷贝目标对象中没有而源对象中有的属性。apply方法的签名为“apply(Objectobj,Objectconfig,Ob

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号