Перемещение раздутого макета/представления в новую позицию анимированным способом

Я раздуваю макет, чтобы показать его на экране. Теперь я хочу частично переместить этот макет за пределы экрана. Я попробовал это, используя .animate().translationX(-500) на надутом макете. Он сдвинулся с экрана именно так, как я хотел:

До:

перед перемещением

После:

после переезда

Но теперь у меня проблема в том, что область, которую изначально занимал макет, недоступна для кликов (например, нельзя прокручивать между домашними экранами, работает только за пределами области, отмеченной синим цветом).

не кликабельная область

Как я могу решить эту проблему, чтобы синяя область была кликабельной после перемещения, но оставшаяся область макета (красная область) все еще могла регистрировать клики (например, если я хотел переместить ее обратно с помощью onClickListener)? Я думаю, что мне нужно работать с функцией updateViewLayout() функции windowManager, но я не знаю, что я должен изменить в параметрах.

Редактировать 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 по-прежнему запускается в той же позиции, что и до «перемещения» макета.

Теперь мне нужно найти способ переместить реальный вид в желаемую позицию в сочетании с анимацией. Любые идеи, как это сделать?

Изменить 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;
         }
    }
});

Я также попытался изменить значение x для LayoutParams и вызвать wm.updateViewLayou(ll, updatedParameters), что сработало нормально. Когда я переместил оверлей на 100 пикселей вправо, область, вызывающая onClickListener, также сдвинулась на 100 пикселей вправо. Проблема в том, что я смог переместить наложение только в пределах реального экрана, и мне нужно переместить его за пределы экрана. Связал отрицательные значения, но это тоже не сработало.

По сути, мне нужна навигация по ящикам, но с ограниченной высотой (по крайней мере, я думаю, что мне это нужно). Посмотрим, смогу ли я заставить что-то работать должным образом.

Редактировать 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 на втором. введите здесь описание изображения введите здесь описание изображения


person 0x1234    schedule 27.07.2016    source источник


Ответы (2)


Я думаю, что простой способ решить эту проблему - анимировать изменение свойства «ширина» вместо перевода вашего макета.

Надеюсь, поможет

person dosssik    schedule 27.07.2016
comment
Но не покажет ли этот метод все содержимое макета, просто сжатое вместе, чтобы соответствовать новой ширине? - person 0x1234; 27.07.2016
comment
да, это изменит размер вашего макета. Вам нужно точно скрыть часть вашего макета? Не могли бы вы показать фрагмент вашего кода? Анимационная часть - person dosssik; 27.07.2016
comment
Да, если бы я изменил только свойство ширины, это не выглядело бы хорошо, когда я добавлю контент позже. Я добавлю свой код в стартовый пост позже. - person 0x1234; 27.07.2016
comment
Вы можете попробовать вызвать .invalidate() для всех дочерних представлений вашего макета после завершения аниматоина. Я не уверен, но думаю, это обновит фактическое положение. - person dosssik; 28.07.2016
comment
Спасибо за предложение. Я обновил стартовый пост. - person 0x1234; 28.07.2016
comment
думаю, вам нужно вызвать invalidate() ПОСЛЕ того, как анимация будет завершена. и вы должны вызывать его для каждого дочернего представления. Теперь он вызывается одновременно с запуском анимации. Используете ли вы параметр Показать границы макета из параметров разработчика? Иногда очень помогает.. - person dosssik; 28.07.2016
comment
В очередной раз обновил свой пост. Спасибо за подсказку с настройкой dev. - person 0x1234; 28.07.2016
comment
Вау, это очень странное поведение. Попробуйте setOnclickListener(null) для вашей кнопки, когда анимация начнется, и установите снова, когда анимация завершится. Конечно, это не решение, но может помочь локализовать проблему. - person dosssik; 29.07.2016
comment
Я только что опубликовал решение. Но все равно спасибо за помощь :) - person 0x1234; 29.07.2016

Я спросил создателя руководства, которое использовал для создания таких SYSTEM_ALERT_WINDOW окон, и он мне помог.

Вот код:

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) такого не бывает. Не знаю, из-за эмулятора или по другой причине.

person 0x1234    schedule 29.07.2016