研究这个问题的主要原因是在一个webapp中添加的h5视图发现不能长按复制,从表象上看,webview及依附的activity都没有问题,确实是匪夷所思了几小时(过中发现原因的过程不是本文重点,跳过).
我正使用的webapp中的activity是公用的而并非webview独享,用的是windoManager的addview方式添加到窗口中,但当我不使用addView而用activity自身的setContentView方式去添加视图时,复制粘贴等菜单就能正确出现了。本文描述的是两种方式的差异。
首先需要明确的是,两个方法属于不同类的方法,SetContentView是Window的方法 ,addView是WindowManager的方法 ,一开始的webview就是使用WindowManager的addView方式添加的,明确了两种写法是导致两种结果的原因后,接下来就是研究究竟是怎么回事了。因为事关视图的差异,写了一个简单的demo模拟一下情况,关键代码:
1 | protected void onCreate(Bundle savedInstanceState) { |
使用SDK自带的HierarchyViewer查找其布局结构:
1 | protected void onCreate(Bundle savedInstanceState) { |
从上两个结构可以得知两者的区别在于根view的区别。对于setContentView,其根view是PhoneWIndow中的DecorView,其后才是webview;但使用addView,整个视图都以webview为根,即当前根view为Webview而非默认的 decorView。
看到这里大概也能猜出点什么来了,此时就要找找源码了。以下都是基于4.0.4源码。
从Activity入手,setContentView的源码部分为:
1 | public void setContentView(View view) { |
其getWindow()拿到的Window对象由Activity中的内部方法定义:
1 | mWindow = PolicyManager.makeNewWindow(this); |
PolicyManager在运行时决定使用何种window类,对于Activity使用的是PhoneWindow,其setContentView方法定义如下:
1 | public void setContentView(View view, ViewGroup.LayoutParams params) { |
如果根布局没有被初始化就开始进行初始化,即执行installDecor(),其最重要的一项就是进行默认DecorView的初始化,其中mContenteParent是ViewGroup类型,所有添加的view都是添加到viewGroup下,而decorView又是默认下又是所有view的根(rootView);查看installDecor()方法可知,decorView是FrameLayout的子类,在installDecor()方法中默认会加载内置的一个布局com.android.internal.R.layout.screen_simple,mContentParent指向的就是该布局的Content部分;如果ViewGroup没有初始化则需要初始化,否则就需要把整个ViewGroup上的view都移除,然后再将指定的view添加到mContentParent的布局中。在上面的例子中就是webview。
回归到主题,原因就很明显,当我们使用addView直接把View添加到window上时,从上面的布局结构可知,webview此时的作用是相当于decorView的作用(即rootView),但它并不是一个布局,它没有加载默认的布局设置(如title,actionBar)等,就是因为这个原因无论怎么长按都不会在理想的响应被触发。
知道原因要解决就很容易了,因为我们要的是系统的触发事件,只是单纯的地view加入到window中相当于是丢失了很多默认的布局和配置,但我们只要把我们想要添加的view添加到window中但又不想直接用setContentView或addContentView的话(可能此activity是一个通用的容器,上边可能会添加各种类型的view,使用setContentView有不妥,add与set两者的差别在于是否在清除所有当前content布局上的view),可以通过自己构建decorView然后添加自定义view的方式去达到我们要的目的。
1 | private FrameLayout mContentParant=null; |
拿到mContentParent后就可以像以前一样把windowManager的addView全部换成mContentView的addView,如果存在windowManager的addView,其添加上的view后会显示在最上层。
写完了