自定义视图已添加到WindowManager,根据状态将事件传递到基础窗口

问题描述 投票:0回答:1

我有一个相当复杂的情况,我需要在通过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添加的自定义视图中处理事件,或者如果它们在所需窗口之外,则将其传递给基础窗口...

android android-animation android-windowmanager
1个回答
0
投票
我能够通过应用一个难看的骇客解决问题。我使用了第二个窗口,该窗口本身是全屏窗口,并包含标志FLAG_NOT_FOCUSABLEFLAG_NOT_TOUCHABLE,并且由于禁用了触摸,因此事件将在窗口下方传递。
© www.soinside.com 2019 - 2024. All rights reserved.