欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

浅析Android Dialog中setContentView()方法

程序员文章站 2023-11-24 11:51:16
概述 dialog在android中是一个很优秀的工具。在使用dialog时,我们一般都会自定义要显示的内容布局。dialog自带了三个方法来支持自定义内容布局。...

概述

dialog在android中是一个很优秀的工具。在使用dialog时,我们一般都会自定义要显示的内容布局。dialog自带了三个方法来支持自定义内容布局。

 public void setcontentview (int layoutresid);
 public void setcontentview (view view);
 public void setcontentview (view view, viewgroup.layoutparams params);

这三个方法内部的实现原理都是一样的,只是其封装深度不同而已。三个方法可以说分别照顾了不同定制深度的开发者。

setcontentview()流程

直接查看dialog的源代码,如下图1所示。

浅析Android Dialog中setContentView()方法

【图1】

上图中mwindow在dialog类中的定义如下:

 import android.view.window;
 window mwindow;

那么,它从何而来呢?如下图2所示。

浅析Android Dialog中setContentView()方法

【图2】

由上图2可知,在构造dialog对象时,这个mwindow的值也被确定。它由policymanager提供。再往下跟系统代码。

makenewwindow(context)方法的实现如下:

  // the static methods to spawn new policy-specific objects
  public static window makenewwindow(context context) {
   return spolicy.makenewwindow(context);
  }

还得继续往下跟。

import com.andorid.policy.internal.policy.impl.policy;
/**
 * {@hide}
 */
public class policy implements ipolicy {
 //...

 public window makenewwindow(context context) {
  return new phonewindow(context);
 }
}

到这,貌似就差不多看到尽头了,原来我们调用的setcontentview就是在这个phonewindow类中被实现的。继续跟进。

import com.android.internal.policy.impl.phonewindow;
/**
 * android-specific window.
 * <p>
 * todo: need to pull the generic functionality out into a base class
 * in android.widget.
 */
public class phonewindow extends window implements menubuilder.callback {
 //...
}

setcontentview(int)

这个方法的代码实现如下图3所示。

浅析Android Dialog中setContentView()方法

【图3】

整个的实现流程乍一看还算简单明了。我们传入的布局参数最后就是被加载到上图所示的那个 mcontentparent 中的。这个mcontentparent是一个viewgroup类对象。

浅析Android Dialog中setContentView()方法

在上图所示的代码中第367行作了空判断,可见这个对象的实例的创建与installdecor()方法有关系。这个方法的实现较为复杂,这里我们只看mcontentparent的实例化过程。

浅析Android Dialog中setContentView()方法

【图4】

这个generatelayout()方法的实现过程很繁杂。我们没有必要去把每一行的代码都看懂。只需要知道在它内部是这样创建mcontentparent对象的就好了

浅析Android Dialog中setContentView()方法

浅析Android Dialog中setContentView()方法

最后会把这个contentparent作为结果返回即可。然后再回到图3,在图中所示代码处第378行完成了将我们传入的布局加载进系统容器中的操作。

setcontentview(view)与setcontentview(view, viewgroup.layoutparams)

这种方式设置内容布局比较灵活。一般用于布局中有需要在java代码中做特殊操作的布局。如设置监听等。其具体实现代码如下图5所示。

浅析Android Dialog中setContentView()方法

【图5】

这个代码,并没什么特别的,它的目的都已经明明白白的表现在代码上了,就不再赘述了。

setcontentview(view)无法设置布局尺寸的问题

使用setcontentview(int)的方式时,可以直接通过在layout的根容器中指定宽、高来设置布局的尺寸。这里得注意,我指的是在根视图中直接指定宽度多少像素,高度多少像素这种直白的写法才可以控制布局的尺寸。若直接设为match_parent,那么它的效果等同于wrap_content。为什么会是这样的结果呢?本人并没有深究它的原因,但本人猜测(且后续并未去证实)这与mcontentparent加载了布局后重新确定整个视图的尺寸的过程脱不了干系。我们来翻翻viewgroup的代码,在viewgroup中,有如下图6所示的一段代码。

浅析Android Dialog中setContentView()方法

【图6】

对于一个viewgroup及其子类来说,它的measurespec要么是exactly要么是at_most,我记不清这里头的具体关系了。但上图6所示的代码也已经非常直白了。对于match_parent,它确实是按照wrap_content的方式来处理的。这也就解释了上面所说的“令人费解”的情况了。虽然这个只是本人的猜测,但我估计也是八九不离十了。

而使用setcontentview(view)的方式时,无论layout中根容器的宽高是什么,都按照wrap_content的方式来走。这是为什么?我们先回去看看图5所示代码中这个方法的实现。可以发现,phonewindow给了一个默认的viewgroup.layoutparams对象。并且宽、高的值不偏不倚,正好是match_parent。因此,当使用这种方式时,无论layout中根容器的宽高如何设置,它都表现成按内容的尺寸来适配布局的效果。因此,想要能控制对话框布局的尺寸,还是老老实实自己建一个有指定宽高值的layoutparams对象给phonewindow对象吧,不要指望人家帮你擦屁股,擦不干净的~~

那么,到了这里,我们再来简单探究一下,setcontentview(int)又是如何做到可以直接设置尺寸的。我们回去图3,看看这个方法的实现代码中,第378行。它在映射xml时,第二个参数传的直接是mcontentparent。比较一下我们平时使用映射布局函数时,讲道理,直接传一个null的比较多吧,或者说,或许我们平时都很少注意到这个参数。我们去layoutinflater中转转。

 import android.view.layoutinflater;

inflate()方法的代码还是挺长的,这里就不详细贴了,我们只挑有代表性的来看。

public view inflate(xmlpullparser parser, viewgroup root, boolean attachtoroot)

// ...

final attributeset attrs = xml.asattributeset(parser);

// ...

// create layout params that match root, if supplied
params = root.generatelayoutparams(attrs);

// ...

// temp is the root view that was found in the xml
final view temp = createviewfromtag(root, name, attrs, false);

// ...

if (root != null && attachtoroot) {
 root.addview(temp, params);
}
// ...

够清晰了吧。在这里,layoutinflater在映射xml布局时主动去解析了所有的属性。当然会包括外层容器的属性。然后根据解析的结果生成一个layoutparams对象,最后,再将要内容布局联合这个即时创建的layoutparams对象一同添加到mcontentparent容器中去,其实就相当于调用setcontentview(view, layoutparams)方法。所以在文章开头我才说到dialog的三个设置内容布局的方法本质是一样的,只是其封装深度不同而已。

设置dialog的背景为完全透明

dialog默认有一个灰色的背景,首先这个背景巨丑,其次背景的存在还影响我们对对话框ui的定制。

浅析Android Dialog中setContentView()方法    浅析Android Dialog中setContentView()方法

在activity中,可以通过在创建dialog时传入一个无背景对话框的风格样式给构造器,以构造出无灰色背景的对话框出来。也可以通过java代码控制对话框的背景色为透明色。还可以先show()对话框,然后再给它setcontentview()来达到无背景色的对话框的目的。

1、 通过风格样式

 <style name="transbg" parent="@android:theme.dialog">
  <item name="android:windowbackground">@android:color/transparent"</item>
 </style> 

 alertdialog dialog = new alertdialog.builder(mcontext, r.style.transbg).create();
 //或
 dialog dlg = new dialog(mcontext, r.style.transbg);
 //等 

2、通过java代码控制

所谓背景,其实就是phonewindow的背景。我们只需要设置phonewindow的背景为透明,就能达到我们想要的结果了。

// ...
alertdialog dialog = builder.create();
dialog.show();
dialog.setcontentview(view);
//方式1,使用透明的colordrawable对象。
dialog.getwindow().setbackgrounddrawable(new colordrawable(0));
//方式2,使用一张透明的drawable图片。
dialog.getwindow().setbackgrounddrawableresource(r.drawable.transparent);

3、先显示对话框再设置布局

这种方式只在activity中有效果。

 alertdialog dialog = builder.create();
 dialog.show();
 dialog.setcontentview(view);

至于service中为什么没有效果,本人怀疑是由于在service中要想弹出对话框,只能将它设为系统级对话框,需要加多的一段代码导致的。但其具体原理还没有去研究过。

dialog.getwindow().settype(windowmanager.layoutparams.type_system_alert);

在service中。只能通过上述第1、第2种方式来实现背景透明的目的。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持!