我有一个相当复杂的情况,我需要在通过WindowManager
添加的自定义视图中处理事件,或者将其传递到基础窗口(如果它在所需区域之外)。所需区域是containerView
,在该区域可以从根视图本身开始变小,或者可以具有相等的宽度/高度。
视图的尺寸为28x28,但可以长到60x60。增长部分由ValueAnimator
完成,其中当前宽度和目标宽度由ValueAnimator.getAnimatedValue()
确定(在这种情况下,介于28和60之间)。如果单击了该窗口,或者单击了可能小于该窗口本身的目标视图,则该窗口需要使用该事件。
布局示例如下:
<FrameLayout android:layout_width="wrap_content"
android:layout_height="wrap_content">
<FrameLayout android:id="@+id/containerView"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_gravity="center">
<!-- rest of the view, not important -->
<!-- the containerView can have 28x28 size or
60x60 size -->
</FrameLayout>
</FrameLayout>
动画视图是用android:id="@+id/containerView"
定义的视图。
我已经尝试使用常规的布局参数来附加视图,以使窗口布局具有动态性:
WindowManager manager = context.getSystemService(WindowManager.class);
View rootView = LayoutInflater.from(context).inflate(resId, null, false);
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
params.flags = FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH;
manager.addView(rootView, params);
而且这个类似的代码块以28x28的尺寸添加了视图,这不是问题。但是,在根据状态变化(在containerView
上)设置为60x60大小的动画时,动画会闪烁很多。我猜是因为视图本身和窗口都需要重新调整大小而发生的。我尝试使用setLayerType(HARDWARE, null)
,但似乎没有用。然后,我发现了另一个解决方法,该方法是在开始动画之前通过为其赋予固定的width-height值来直接增加窗口的大小,如下所示:
params.width = dpToPx(60);
params.height = dpToPx(60);
manager.updateViewLayout(rootView, params);
然后,我开始播放不断增长的动画,该动画逐渐改变containerView
的宽度和高度。通过这种方式,即使在低端设备上,动画也可以流畅,因此我认为这是一个很好的优化。
问题始于窗口尺寸的改变。您看到,containerView
必须具有属性android:layout_gravity="center"
才能将视图定位到窗口的中心。但是,增加窗口的宽度和高度会更改视图的位置。为了克服这个问题,我决定通过执行类似的操作来编写另一种方法:
// This method is inside the root view, which contains
// the WindowManager.LayoutParams as its layout params.
private void setWindowSize(int widthPx, int heightPx)
{
WindowManager.LayoutParams params = getLayoutParams(); // ignore cast
int oldWidth = params.width;
int oldHeight = params.height;
int differenceWidth = widthPx - oldWidth;
int differenceHeight = heightPx - oldHeight;
// Position the view relatively to the window so
// it should look like its position is not changed
// due to containerView's center layout_gravity.
params.x -= differenceWidth / 2;
params.y -= differenceHeight / 2;
params.width = widthPx;
params.height = heightPx;
// Update itself since this is already the root view.
manager.updateViewLayout(this, params);
}
上面的代码导致动画发生位置变化。因此,我搜索了是否可以禁用此动画,并发现an answer here似乎可以与Android 10模拟器一起使用。但是,我认为这不是一种可靠的方法,因为大多数制造商都会更改框架类的源代码以实现自己的主题等。因此,我正在寻找一种更可靠的方法。该更改还会由于containerView.onLayout()
操作而引起闪烁,这大概是在执行manager.updateViewLayout()
之后发生的,该更改在左上角显示一帧,在第二帧的中央,对眼睛可见。
目前,我只能想到一些防止这些错误的方法:
1)仅在某些状态下处理触摸事件(例如,截取containerView
的坐标)
2)在接收到MotionEvent.ACTION_OUTSIDE
后使视图不可触摸,这将指示触摸事件发生在视图边界之外。
第一个缺点:如果该视图在所有情况下都是可单击的,则从根视图开始变为可单击的,并且从该视图接收到触摸事件后,该事件不会转移到其他窗口(也称为基础应用程序)引起问题。
第二个对我来说似乎是一个好方法,但是事件MotionEvent.ACTION_OUTSIDE
不包含任何特定的x或y坐标,因此无法确定该事件是否发生在窗口边界内。如果可能的话,我会在布局参数中添加FLAG_NOT_TOUCHABLE
并更新视图,如果要处理触摸,则删除该标志。
所以,我的问题是:
添加了WindowManager
的自定义视图是否可以选择基于[我不知道,从dispatchTouchEvent()
返回false或其他原因来进一步传送事件?或者,是否有一种方法可以接收带有特定屏幕坐标的所有触摸事件,甚至在我们的应用程序之外也可以,因此我可以根据它更改窗口标志?
非常感谢您的帮助,非常感谢。
我有一个相当复杂的情况,我需要在通过WindowManager添加的自定义视图中处理事件,或者如果它们在所需窗口之外,则将其传递给基础窗口...
FLAG_NOT_FOCUSABLE
和FLAG_NOT_TOUCHABLE
,并且由于禁用了触摸,因此事件将在窗口下方传递。