以动画方式将膨胀的 Layout/View 移动到新位置

Moving inflated Layout/View to a new position in an animated way

我正在膨胀布局以在屏幕上显示它。 现在我想将该布局部分移出屏幕。我尝试在膨胀布局上使用 .animate().translationX(-500)。它完全按照我想要的样子移出了屏幕:

之前:

之后:

但现在我遇到了布局原来占据的区域无法点击的问题(例如无法在主屏幕之间滑动,只能在蓝色标记区域之外工作)。

我该如何解决这个问题,蓝色区域在移动后可以点击,但布局的剩余区域(红色区域)仍然可以注册点击(例如,如果我想使用 onClickListener 将其移回)? 我想我必须使用 windowManagerupdateViewLayout() 函数,但我不知道关于参数我应该更改什么。

编辑 1:

这是代码。翻译由按钮触发:

overlay.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#d8ff0000"
    android:weightSum="1">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="245dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:text="Lorem ipsum "
            android:id="@+id/textView6"
            android:layout_marginLeft="10dp" />

        <Button
            android:layout_width="55dp"
            android:layout_height="wrap_content"
            android:text="X"
            android:id="@+id/button"
            android:layout_gravity="center_horizontal|top"
            android:layout_alignParentTop="true"
            android:layout_toEndOf="@+id/textView6"
            android:layout_marginStart="27dp" />
    </RelativeLayout>
</LinearLayout>

膨胀布局的服务:

OverlayService.java

public class FloatingMenu extends Service{

    private WindowManager wm;
    private LinearLayout ll;
    private boolean isPushedToSide = true;


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        wm = (WindowManager) getSystemService(WINDOW_SERVICE);
        ll = new LinearLayout(this);


        LinearLayout.LayoutParams llParameters = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
        ll.setBackgroundColor(Color.argb(50, 255, 255, 255));
        ll.setLayoutParams(llParameters);

        final WindowManager.LayoutParams parameters = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_PHONE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT);

        parameters.gravity = Gravity.LEFT | Gravity.TOP;

        LayoutInflater li = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
        ll = (LinearLayout) li.inflate(R.layout.overlay, null);
        final Button b = (Button) ll.findViewById(R.id.button);
        b.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (isPushedToSide) {
                    ll.animate().translationX(0);
                    isPushedToSide = false;
                }else {
                    ll.animate().translationX(-450);
                    isPushedToSide = true;
                }
            }
        });
        wm.addView(ll, parameters);
    }
}

编辑 2:

在继续研究的过程中,我发现 .animate().translationX() 只会移动渲染视图的位置,而不是视图本身。这也解释了为什么在使用 .animate().translationX() 之后,onclick 事件仍然在与 "moving" 布局之前相同的位置触发。

现在我需要找到一种结合动画将实际视图移动到我想要的位置的方法。有什么想法吗?

编辑 3: 我发现很多帖子都有类似的问题,如果我是对的,问题的解决方案是使用 ObjectAnimator 而不是 .animate().translationX()。 我尝试在我的代码中替换该部分,因此 onClickListener 现在看起来像这样:

b.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        if (isPushedToSide) {
            //ll.animate().translationX(0);
            ObjectAnimator.ofFloat(ll, "translationX", 0).setDuration(250).start();
            isPushedToSide = false;
        }else {
            //ll.animate().translationX(-450);
            ObjectAnimator.ofFloat(ll, "translationX", -450).setDuration(250).start();
            isPushedToSide = true;
         }
    }
});

ll 是我膨胀的 LinearLayout,包含 Lorem ipsum 文本和 Button。 同样,动画本身工作正常,但行为方式仍然相同。我仍然需要点击按钮的原始位置来触发 onClickListener 并且蓝色区域仍然无法点击。

编辑 4:

正在尝试使用.invalidate()更新实际位置的建议。但是它没有用。不过我不确定使用它是否正确。

b.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        if (isPushedToSide) {
            //ll.animate().translationX(0);
            ObjectAnimator.ofFloat(ll, "translationX", 0).setDuration(250).start();
            ll.invalidate();
            isPushedToSide = false;
        }else {
            //ll.animate().translationX(-450);
            ObjectAnimator.ofFloat(ll, "translationX", -450).setDuration(250).start();
            ll.invalidate();
            isPushedToSide = true;
         }
    }
});

我还尝试更改 LayoutParams 的 x 值并调用 wm.updateViewLayou(ll, updatedParameters),效果很好。当我将叠加层向右移动 100px 时,触发 onClickListener 的区域也会向右移动 100px。问题是我只能在实际屏幕的边界内移动叠加层,而我需要的是将其移出屏幕。绑定负值,但这也不起作用。

我本质上需要的是抽屉导航,但高度有限(至少我认为这是我需要的)。看看我是否可以通过这种方式使某些东西正常工作。

编辑 5:

更正了我的代码,以便在动画结束时调用 .inflate(),但它的行为方式仍然相同:

ObjectAnimator anim = ObjectAnimator.ofFloat(ll, "translationX", 0).setDuration(250);

anim.addListener(new Animator.AnimatorListener() {

    @Override
    public void onAnimationStart(Animator animator) {}

    @Override
    public void onAnimationEnd(Animator animator) {
        b.invalidate();
        text.invalidate();
        frame.invalidate();
        ll.invalidate();
    }

    @Override
    public void onAnimationCancel(Animator animator) {}

    @Override
    public void onAnimationRepeat(Animator animator) {}
});
anim.start();

我还记录了两个显示问题的.gif。我在第二个上将 gravity 设置为 CENTER

我认为解决该问题的简单方法是动画更改 属性 "width" 而不是翻译您的布局。

希望对您有所帮助

我问了我用来创建这样的指南的创建者SYSTEM_ALERT_WINDOW windows,他帮助了我。

代码如下:

public class Fw  extends Service {

    private WindowManager wm;
    private boolean isPushedToSide = true;


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    WindowManager.LayoutParams parameters;
    Button b;
    View inflating_layout;
    LayoutInflater li;
    ValueAnimator animator;

    @Override

    public void onCreate() {
        super.onCreate();

        wm = (WindowManager) getSystemService(WINDOW_SERVICE);

        li = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
        inflating_layout = li.inflate(R.layout.overlay, null);

        b = (Button) inflating_layout.findViewById(R.id.button);

        parameters = new WindowManager.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_PHONE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, PixelFormat.TRANSLUCENT);
        parameters.gravity = Gravity.LEFT | Gravity.TOP;

        isPushedToSide = true;

        b.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View view) {
                if (isPushedToSide) {

                    isPushedToSide = false;

                    animator  = ValueAnimator.ofInt(parameters.x, parameters.x-450);
                    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                        @Override
                        public void onAnimationUpdate(ValueAnimator valueAnimator) {
                            int val = (Integer) valueAnimator.getAnimatedValue();
                            updateView(inflating_layout, val);

                        }
                    });
                    animator.setDuration(250);
                    animator.start();

                } else {

                    animator = ValueAnimator.ofInt(parameters.x, parameters.x+450);
                    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                        @Override
                        public void onAnimationUpdate(ValueAnimator valueAnimator) {
                            int val = (Integer) valueAnimator.getAnimatedValue();
                            updateView(inflating_layout, val);

                        }
                    });
                    animator.setDuration(250);
                    animator.start();


                    isPushedToSide = true;
                }
            }
        });
        wm.addView(inflating_layout, parameters);
    }

    public void updateView(View view, Integer x) {
        if (view != null) {
            if (x != null) parameters.x = x;
            wm.updateViewLayout(view, parameters);
        }
    }
}

现在似乎一切正常。只有滑动动画在屏幕边界处闪烁了一下。以较慢的速度,例如.setDuration(2500) 那不会发生。不知道是模拟器的问题还是其他原因