1

I have a View in a Constraint Layout and I would like that at the very beginning it should be outside of the screen (and then later slowly move into the scree from right to left). Now, I kind of need something like negative bias or margins.

I had a look at this question How to achieve overlap/negative margin on Constraint Layout?. The accepted answer using android:layout_marginTop="-25dp" does not have any effect (altough the top of the view is constrained and I use"androidx.constraintlayout:constraintlayout:2.1.3").

I tried the second most upvoted answer and used the code:

view.setTranslationX(view.getWidth() - 20);

This actually works. However, the problem is that when the Fragment is created you first see that the view is not on the left for a short period of time. This is not what I want. I would like to have the view beyond the right rim of the layout at the very very beginning such that it can later move into the layout.

Do you have any idea how I can do that? Ideally I would like to do this programmatically.

Update: Here is the code of the XML layout where a negative margin does not have any effect:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/game_test_background"
    tools:context=".MainActivity"
    android:id="@+id/constraintLayout">


    <ImageView
        android:id="@+id/imageView_RedRectange_Test"
        android:layout_width="0dp"
        android:layout_height="30dp"
        android:layout_marginTop="-1250dp"

        app:layout_constraintWidth_percent="0.25"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.048"
        app:srcCompat="@drawable/red_rectangle" />


    <Button
        android:id="@+id/button"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:text="Button"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHeight_percent="0.102"
        app:layout_constraintHorizontal_bias="0.373"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.745"
        app:layout_constraintWidth_percent="0.12" />

</androidx.constraintlayout.widget.ConstraintLayout>
VanessaF
  • 515
  • 11
  • 36
  • There is something about your code or your layout that is stopping negative margins from working. Post the code/xml (or both) that is not working. – Cheticamp Feb 23 '22 at 18:11
  • Support for negative margins was added to ConstraintLayout in the 2.1 alpha release. I see that you say you have version 2.1.3 in your gradle config, but maybe you're including another library that has a conflicting version of constraintlayout? – Ben P. Feb 23 '22 at 18:46
  • You could actually use constraints to position the view off-screen. For example, set `app:layout_constraintRight_toLeftOf="parent"` to make the view sit outside the left edge of the parent. Then you could change the constraints at runtime and use animation to slide the view on screen. – Ben P. Feb 23 '22 at 18:47
  • @Cheticamp: Thanks for your comment. I added the XML layout file for which a negative margin does not have any effect on the ImageView – VanessaF Feb 24 '22 at 17:36
  • @BenP.: Thanks for your comment. What do you exactly mean by using animations to slide in? I just use a constraint set and change the horizontalBias to move the element with the following line `constraintSet.setHorizontalBias(view.getId(), horizontalBias);`. – VanessaF Feb 24 '22 at 17:39
  • @VanessaF Unfortunately, your layout is showing some weird behavior that is somehow tied to the presence of the button. (Truly weird.) If I can understand better how to reproduce this behavior, I will post something about it. In the meantime, you can accomplish what you want with a _Space_ widget or translation. You can avoid the flickering of the view by adjusting the translation of a layout listener or a predraw listener. – Cheticamp Feb 24 '22 at 18:12
  • @Cheticamp: Thanks for your answer and effort. I really appreciate it. How exactly can I avoid the flickering? – VanessaF Feb 24 '22 at 18:19
  • I missed that this is in a fragment. You are letting the layout display before changing the translation. Change the translation to move the view in onCreateView(). – Cheticamp Feb 24 '22 at 18:38
  • @Cheticamp: Thanks for your comment. Unfortunately, when I set `view.setTranslationX(view.getWidth() - 20);` into the onCreateView() method as you suggested, nothing happens. The view does not move. Is there another approach how to set an element outside of the layout? setTranslation has big disadvantages. It does not immediately shift the element. – VanessaF Feb 24 '22 at 20:17
  • @VanessaF I'm confused. Are you moving the view from left to right or top down? The margin you set to the top makes me think it is top down, but you talk about translation of x which would move the view horizontally. In any case, you will have to remove the negative margin because of this weird problem with ConstraintLayout. – Cheticamp Feb 24 '22 at 21:22
  • @Cheticamp: Thanks for your answer. I would like to move the view horizontally. I managed to do this by using `constraintSet.setHorizontalBias(view.getId(), horizontalBias);` and alter the horitzontalBias. However, I would like the view to start from the outside of the screen such that it can move from right to left and then eventually disappear at the left side outside the screen. – VanessaF Feb 25 '22 at 17:54

2 Answers2

3

Okay so to have a negative margin you can use translateX, translateY or TranslationZ.

in xml like so:

<Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Hello World!"
    android:translationX="-60dp"
    android:translationY="-90dp"
    android:translationZ="-420dp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

or programmatically like so:

View view = ...;
view.setTranslationX(-60);
view.setTranslationY(-90);
view.setTranslationZ(-420);

Then in order to slowly bring it in from right to left you can use the animate() method like so:

View view = ...;
view.animate().setDuration(1000).translationX(-600).start();
Brandon
  • 146
  • 8
  • Exactly what I did here: https://youtu.be/8SYyxKkO_sc – Sam Chen Feb 23 '22 at 19:53
  • @Beast: Thanks for your answer. As written in my post, I do not want to use setTranslation because the element is not moved out of the screens at the very beginning. – VanessaF Feb 24 '22 at 17:26
  • @Beast: Is it possible to set the value inside the methdo "setTranslationX" as a percentage of the width? – VanessaF Mar 03 '22 at 18:02
0

There is a problem with setting the width of the button using app:layout_constraintWidth_percent when the ImageView has a negative margin. The problem should go away if you can set a definite width to the button (instead of 0dp).

The problem should also resolve if you set app:layout_constraintWidth_percent to a value such that the text of the button shows completely on one line.

Here is a simplified layout to demonstrate this issue. The ConstraintLayout has two views that are simply constrained to the parent to appear in vertical center of the layout. These two views have no dependencies on each other. In addition, the ImageView has a top margin of -250dp, so it should appear above the layout's vertical center.

<androidx.constraintlayout.widget.ConstraintLayout   
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:background="@android:color/holo_green_light">  
  
    <ImageView  
        android:id="@+id/redRectangle"  
        android:layout_width="100dp"  
        android:layout_height="30dp"  
        android:layout_marginTop="-250dp"  
        android:layout_marginEnd="16dp"  
        app:layout_constraintBottom_toBottomOf="parent"  
        app:layout_constraintEnd_toEndOf="parent"  
        app:layout_constraintTop_toTopOf="parent"  
        app:srcCompat="@drawable/red_rectangle" />  
  
     <Button  
            android:id="@+id/button"  
            android:layout_width="0dp"  
            android:layout_height="wrap_content"  
            android:text="Button"  
            android:textSize="18sp"  
            app:layout_constraintBottom_toBottomOf="parent"  
            app:layout_constraintEnd_toEndOf="parent"  
            app:layout_constraintStart_toStartOf="parent"  
            app:layout_constraintTop_toTopOf="parent"  
            app:layout_constraintWidth_default="percent"  
            app:layout_constraintWidth_percent="0.12" />  
  
</androidx.constraintlayout.widget.ConstraintLayout>

Here is what happens then the width of the ImageView is changed from 0dp to a non-zero value of 1dp. When the width is set to 0dp, the negative margin seems to be ignored. It is only when the width is set to a non-zero value that the ImageView is correctly placed.

enter image description here

Changing the width of the button should have no effect on the placement of the ImageView; however, the button only appears in the proper position when the button has a non-zero width.

Here is what happens when the app:layout_constraintWidth_percent is increased so that the word "Button" is not cutoff.

enter image description here

Again, the placement of the ImageView should be independent of the width of the button. Instead, the button only appears in the correct position when the app:layout_constraintWidth_percent is set such that the word "Button" is not cutoff.

This is only an issue with negative margins. Positive margins work as expected.

This is a strange problem, so you may want to use one of the other solutions mentioned.

(ConstraintLayout version 2.1.3)

Cheticamp
  • 61,413
  • 10
  • 78
  • 131
  • Thanks for your answe Cheticamp. So this looks like a bug in Android as far as I see it. Actually, I want to use `app:layout_constraintWidth_percent` because I would like to use the constraintLayout for different devices and when you use dp then the size of the elements are not device independent. Your wrote "This is a strange problem, so you may want to use one of the other solutions mentioned" --> what other methods are you referring to where I can still use `app:layout_constraintWidth_percent` and not dp as the size of elements? – VanessaF Feb 25 '22 at 17:58
  • @VanessaF You can keep everything except the negative margin which doesn't work with the constraint width attribute. I would go with the x-translation as a solution that was recommended in the other comments and answers. – Cheticamp Feb 25 '22 at 19:50
  • Thanks for your comment. As stated before, setting the x-translation is not a good solution as the element is not shifted immediately when the layout is created but just a short time afterwards. So at the very beginning you see the UI element not shifted on the screen and just a few milliseconds later it is shifted outside the screen. So this is not a valid solution for me. The UI element should be outside of the screen from the very beginning (and then later slowly move from right to left) – VanessaF Feb 26 '22 at 08:58
  • Furthermore, I have to shift the element by using `constraintSet.setHorizontalBias(view.getId(), horizontalBias);` and not the animation because I have to know at every time where exactly the element is on the screen (this is actually a game). I have multiple UI elements that should start from the right of the screen and successively move to the left (one after another). This should be continuesly done for about 2 minutes and it should happen autmatically and not when pressing on the button. This is why your suggested updated solution - altough really appreciated - is not a possible for me. – VanessaF Feb 26 '22 at 09:05
  • You wrote "This is a strange problem, so you may want to use one of the other solutions mentioned" --> what other methods are you referring to where I can still use `app:layout_constraintWidth_percent` and not dp as the size of elements? Ais stated before (and in my question) using `view.setTranslationX(view.getWidth() - 20);` is not a good option as the shifting is not done immediately. – VanessaF Feb 27 '22 at 09:30
  • Any comments to my last comment? You were talking about other methods? Which methods (other then setting the translation) are you referring to? – VanessaF Mar 03 '22 at 17:21
  • @VanessaF I was referring to the translation. You don't have to use animation for the tranlation. You can use [setTranslationX(float)](https://developer.android.com/reference/android/view/View#setTranslationX(float)) without animation. You can use `View.getX()` to get its placement. That same goes for the y-axis. The animation was just for illustration. – Cheticamp Mar 03 '22 at 18:25
  • Thanks for your comment. Can I use the setTranslationX with a percentage of the screen as with `constraintSet.setHorizontalBias(view.getId(), horizontalBias);`? I would like to use a percentage in the constraint layout to make the positioning independant from the device. The suggested method has as a parameter "float: The horizontal position of this view relative to its left position, in pixels." It's in pixels which is not device independant, so I don't want to use it. I also don't want to use dp because dp is also not device independant. Just the percentage is device independant. – VanessaF Mar 04 '22 at 16:19
  • @VanessaF `setTranslationX()` only works with px, but you can always translate screen percentages to pixels on any device you run on. You can discover the width of the screen (or view, if that's what you want) and compute the percentage of width as percentage_of_width = px / screen_width. px will then be percentage_of_width * screen_width. – Cheticamp Mar 04 '22 at 17:52
  • Thanks for your answer and effort. I really appreciate it. Yes, I understand that I can do it as you said with the percentages. However, there is still the big disadvantage of the setTranslationX approach, that I have already mentioned several times, of the deplayed shifting. When I use setTranslationX the view is not immediately shifted. You can see it for a short period of time in its original position (defined by the constraints in the constraint layout) and just after a short time it is shifted. This does not look good and thus I don't want to use the setTranslation appraoch – VanessaF Mar 04 '22 at 18:05
  • @VanessaF I thought we covered that but, if it's not working for you, you'll have to try another way. I am out of ideas. – Cheticamp Mar 04 '22 at 18:12
  • Thanks for your comment. No, it is not working. I tried you suggested approach from above "You are letting the layout display before changing the translation. Change the translation to move the view in onCreateView()" and nothing happens when using the setTranslationX method. If I just use my code without your advice then the view is shifted but just after a short period of time which does not look good. Is there no option in constraint layout to use `constraintSet.setHorizontalBias(view.getId(), horizontalBias);` with a negative horizontalBias? – VanessaF Mar 04 '22 at 18:22