I'm inflating a layout to show it on the screen.
Now I want to move that layout partially off screen. I tried that using .animate().translationX(-500)
on the inflated layout. It moved off screen exactly how I wanted it to look:
Before:
After:
But now I have the problem that the area which the layout originally occupied is not clickable (e.g. can't swipe between homescreens, only works outside the blue marked area).
How can I solve that, so blue area is clickable after moving, but the remaing area of the layout (red area) could still register clicks (e.g. if I wanted to move it back in with an onClickListener)?
I think I have to work with updateViewLayout()
function of the windowManager
, but I don't know what I should change regarding the parameters.
Edit 1:
Here is the code. The translation is triggered by the button:
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>
Service which inflates the layout:
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);
}
}
Edit 2:
While continuing my research I found out that .animate().translationX()
only moves the position where the view is rendered and not the view itself. That also explains why after using .animate().translationX()
the onclick event still gets triggered at the same position as before "moving" the layout.
Now I need to find a way to move the actual view to my desired position combining with an animation. Any ideas how to do that?
Edit 3:
I found a lot of posts with similar problems and if I'm right the solution to the problem is using ObjectAnimator
instead of .animate().translationX()
.
I tried replacing that part in my code, so that the onClickListener
now looks like this:
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
is the LinearLayout I have inflated, containing the Lorem ipsum
text and Button
.
Again the animation itself works fine, but behaves still the same way. I still need to click the original position of the button to fire the onClickListener
and the blue area is still not clickable.
Edit 4:
Was trying out the suggestion to use.invalidate()
to update the actual position. However it didn't work. I'm not sure if used it correct though.
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;
}
}
});
I also tried changing the x value of LayoutParams
and calling wm.updateViewLayou(ll, updatedParameters)
, which worked fine. When I moved the overlay 100px to the right, the area which triggers onClickListener
also gets pushed 100px to the right. The problem is that I was only able to move the overlay within the boundaries of the actual screen and what I need is to move it off screen. Tied negative values, but that didn't work either.
What I essentially need is a drawer navigation but with a limited height (at least that's I think what I need). Gonna see if I can get something to work properly that way.
Edit 5:
Corrected my code so that .inflate()
gets called when the animation has finished, but it still behaves the same way:
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();
I also recorded two .gifs showing the problem. I set the gravity
to CENTER
on the second one.
I asked the creator of the guide I used for creating such SYSTEM_ALERT_WINDOW
windows and he helped me out.
Here is the code:
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);
}
}
}
Now everything seems to work fine. Only the sliding animation is flickering a bit at the border of the screen. With a slower speed e.g. .setDuration(2500)
that doesn't happen. I don't know if it's because of the emulator or another reason.