Jak osiągnąć nakładający się / ujemny margines w układzie ograniczeń?

118

Czy możliwe jest uzyskanie ujemnego marginesu na układ wiązań, aby uzyskać nakładanie się? Próbuję, aby obraz był wyśrodkowany w układzie i miał widok tekstu, tak aby nakładał się na x dp. Próbowałem ustawić ujemną wartość marginesu, ale bez powodzenia. Byłoby wspaniale, gdyby był sposób na osiągnięcie tego.

więź
źródło
Wytyczne mogą pomóc, nie próbowałem.
Eugen Pechanec

Odpowiedzi:

214

Wyjaśnienie: Poniższa odpowiedź pozostaje aktualna, ale chcę wyjaśnić kilka rzeczy. Oryginalne rozwiązanie umieści widok z de facto ujemnym przesunięciem w stosunku do innego widoku, jak podano, i pojawi się w układzie, jak pokazano.

Innym rozwiązaniem jest użycie właściwości translationY, zgodnie z sugestią Amira Khorsandi tutaj . Wolę to rozwiązanie jako prostsze z jednym zastrzeżeniem: tłumaczenie następuje po układzie , więc widoki ograniczone do widoku przesuniętego nie będą zgodne z tłumaczeniem.

Na przykład poniższy kod XML wyświetla dwa TextViews bezpośrednio pod obrazem. Każdy widok jest ograniczony od góry do dołu z widokiem, który pojawia się bezpośrednio nad nim.

wprowadź opis obrazu tutaj

<androidx.constraintlayout.widget.ConstraintLayout 
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:tint="#388E3C"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/ic_action_droid" />

    <TextView
        android:id="@+id/sayName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Say my name."
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        app:layout_constraintTop_toBottomOf="@+id/imageView"
        app:layout_constraintEnd_toEndOf="@+id/imageView"
        app:layout_constraintStart_toStartOf="@+id/imageView" />

    <TextView
        android:id="@+id/sayIt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Say it."
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        app:layout_constraintEnd_toEndOf="@+id/sayName"
        app:layout_constraintStart_toStartOf="@+id/sayName"
        app:layout_constraintTop_toBottomOf="@id/sayName" />
</androidx.constraintlayout.widget.ConstraintLayout>

A teraz przetłumaczyć „Say My Name” TextView przez 50dpokreślając

android:translationY="-50dp"

Daje to następujące efekty:

wprowadź opis obrazu tutaj

W „Say my name” TextView przesunął się zgodnie z oczekiwaniami, ale „Say it” TextView nie zastosował go jak moglibyśmy się spodziewać. Dzieje się tak, ponieważ tłumaczenie następuje po układzie . Mimo że widok przesuwa układ posta, nadal można go kliknąć w nowej pozycji.

Więc, IMO, idź z translationX i translationY dla ujemnych marginesów w ConstraintLayout, jeśli powyższe zastrzeżenie nie wpływa na twój układ; w przeciwnym razie użyj widżetu przestrzeni, jak opisano poniżej.


Oryginalna odpowiedź

Chociaż nie wydaje się, że ujemne marże będą obsługiwane w programie ConstraintLayout, istnieje sposób na osiągnięcie tego efektu za pomocą dostępnych i obsługiwanych narzędzi. Oto obraz, na którym tytuł obrazu nakłada się 22dpod dołu obrazu - w rzeczywistości -22dpmargines:

wprowadź opis obrazu tutaj

Osiągnięto to za pomocą Spacewidżetu z dolnym marginesem równym żądanemu przesunięciu. SpaceWidżet czym ma dolną zamocowaną na dnie ImageView. Teraz wszystko, co musisz zrobić, to ograniczyć górną część TextViewz tytułem obrazu do dołu Spacewidżetu. TextViewZostanie umieszczony w dolnej części Spacewidoku ignorując margines, który został ustawiony.

Poniżej znajduje się kod XML, który zapewnia ten efekt. Zaznaczę, że używam, Spaceponieważ jest lekki i przeznaczony do tego typu zastosowań, ale mógłbym użyć innego typu Viewi uczynić go niewidocznym. (Prawdopodobnie będziesz jednak musiał dokonać korekt.) Możesz również zdefiniować a Viewz zerowymi marginesami i wysokością żądanego marginesu wstawki i ograniczyć górną część TextViewdo górnej krawędzi wstawki View.

Jeszcze innym podejściem byłoby nałożenie TextViewgórnej części ImageViewprzez wyrównanie górnych / dolnych / lewych / prawych i dokonanie odpowiednich korekt marginesów / dopełnienia. Zaletą podejścia przedstawionego poniżej jest to, że ujemny margines można utworzyć bez wielu obliczeń. To wszystko, co oznacza, że ​​istnieje kilka sposobów podejścia do tego.

Aktualizacja: szybką dyskusję i prezentację tej techniki można znaleźć w poście na blogu Google Developers Medium .

Ujemny margines dla ConstraintLayoutXML

<android.support.constraint.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@mipmap/ic_launcher" />

    <android.support.v4.widget.Space
        android:id="@+id/marginSpacer"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginBottom="22dp"
        app:layout_constraintBottom_toBottomOf="@+id/imageView"
        app:layout_constraintLeft_toLeftOf="@id/imageView"
        app:layout_constraintRight_toRightOf="@id/imageView" />

    <TextView
        android:id="@+id/editText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Say my name"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/marginSpacer" />
</android.support.constraint.ConstraintLayout>
Cheticamp
źródło
Nie działa w moim przypadku, ImageViewjest wyśrodkowany na rodzicu, TextViewnakłada się powyżej ImageView. Następnie Spacepowoduje błąd, gdy aplikacja wraca z tła. Myślę, że 0dp, 0dp sprawia pewne problemy. A co z po prostu użyj Guideline.
iluzjaJJ
W ten sposób nie może działać, gdy widok ma ograniczenia z rodzicem.
R4j
Dlaczego potrzebuję miejsca? Próbowałem bez miejsca i mam ten sam wynik.
kuzdu
@kuzdu chodzi tutaj o to, że dolna część przestrzeni jest ograniczona na dole imageView, margines przesuwa przestrzeń w górę, a następnie górna część widoku tekstu jest ograniczona do dołu przestrzeni, która tworzy tę „połowę nakładki „efekt, który staramy się osiągnąć. Napisz odpowiedź tutaj, jeśli udało Ci się ją uzyskać bez spacji, abyśmy mogli zobaczyć ograniczenia, których użyłeś w swoich dwóch widokach.
Lucas P.
Jesteś Heisenberg ...
Nahro
62

Innym sposobem jest użycie translationXlub w translationYten sposób:

  <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" 
                android:translationX="25dp"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"/>

będzie działać jak android:layout_marginRight="-25dp"

Amir Khorsandi
źródło
29
Należy o tym pamiętać, ponieważ rzeczywiste położenie widoku pozostaje w pierwotnym położeniu (przed tłumaczeniem), zostanie przetłumaczone tylko wizualnie. Tym samym zdarzenia onClick będą wywoływane tylko ze starej pozycji.
goemic
Oświadczenie @ goemic wydaje się być błędne, ponieważ nie jest to animacja pośrednia, ale animacja właściwości. (proszę poprawić mnie, jeśli się mylę)
Henry
zła praktyka, ponieważ nie obsługuje układów RTL
Salam El-Banna
27

Ujemne marże nigdy nie były oficjalnie obsługiwane w RelativeLayout. Ujemne marginesy nie będą obsługiwane w ConstraintLayout. […]

- Romain Guy 8 czerwca 2016 r

Postępuj zgodnie z tymi dwoma kwestiami:

https://code.google.com/p/android/issues/detail?id=212499 https://code.google.com/p/android/issues/detail?id=234866

Eugen Pechanec
źródło
ich pomysł może być taki, że przy ograniczeniu nie potrzebujesz ujemnego marginesu. Może to być prawdą, jeśli masz wszystkie elementy w jednym układzie. Czasami jednak chcesz logicznie pogrupować elementy, takie jak na pasku poleceń utworzonym z przycisków, co ma swoje zalety w zakresie ponownego użycia, przejrzystości i hermetyzacji. W tym momencie grupa będzie miała kształt prostokąta i to właśnie ograniczenie tego kształtu sprawia, że ​​ujemne marginesy są ponownie przydatne i potrzebne.
AndrewBloom
Ujemne marginesy naśladują dokładnie to samo pojęcie linii bazowej w tekście, będąc tekstem złożonym kształtem w prostokątnym kontenerze
AndrewBloom
14

To jest to, co odkryłem po godzinach prób znalezienia rozwiązania.

Rozważmy dwa obrazy, obraz1 i obraz2. Obraz2 ma być umieszczony na górze image1, umieszczonym w prawym dolnym rogu.

Przykład nakładających się widoków wizerunek

Możemy użyć widżetu Space do nakładania się widoków.

Powiąż cztery boki widżetu Przestrzeń odpowiednio z czterema bokami obrazu1. W tym przykładzie ogranicz lewą stronę elementu image2 z prawą stroną widżetu Space i górną stroną image2 z dolną stroną widżetu Space. Spowoduje to powiązanie obrazu2 z widżetem Przestrzeń, a ponieważ widżet Przestrzeń jest ograniczony ze wszystkich stron, możemy zdefiniować wymagane odchylenie poziome lub pionowe, które przesunie obraz2 zgodnie z wymaganiami.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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"
tools:context=".Player">
 <ImageView
     android:id="@+id/image1"
     android:layout_width="250dp"
     android:layout_height="167dp"
     android:src="@android:color/holo_green_dark"
     app:layout_constraintEnd_toEndOf="parent"
     app:layout_constraintStart_toStartOf="parent"
     app:layout_constraintTop_toTopOf="parent" />
 <Space
     android:id="@+id/space"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     app:layout_constraintBottom_toBottomOf="@+id/image1"
     app:layout_constraintEnd_toEndOf="@+id/image1"
     app:layout_constraintHorizontal_bias="0.82"
     app:layout_constraintStart_toStartOf="@+id/image1"
     app:layout_constraintTop_toTopOf="@+id/image1"
     app:layout_constraintVertical_bias="0.62" />
 <ImageView
     android:id="@+id/image2"
     android:layout_width="82dp"
     android:layout_height="108dp"
     android:src="@android:color/holo_green_light"
     app:layout_constraintStart_toEndOf="@+id/space"
     app:layout_constraintTop_toBottomOf="@+id/space" />
 </android.support.constraint.ConstraintLayout>

Dodatkowo, aby umieścić obraz2 na środku dołu obrazu1, możemy ograniczyć lewą i prawą stronę obrazu2 odpowiednio lewą i prawą stroną widżetu Space. Podobnie, możemy umieścić obraz2 w dowolnym miejscu, zmieniając ograniczenia obrazu2 za pomocą widżetu Space.

Swati Navalkar
źródło
Dziękuję za Twoją odpowiedź!
user3193413
11

Znalazłem sposób, aby to zrobić o wiele prostsze.

Zasadniczo należy mieć ImageView, a następnie w widoku tekstu dodać górne ograniczenie, aby dopasować górne ograniczenie obrazu i po prostu dodać górny margines TextView, aby dopasować, aby uzyskać zachowanie typu -ve marginesu.

więź
źródło
9
chyba że wysokość obrazu jest zmienna
Stas Shusha
Nadal by działał. Musisz tylko uważać na to, gdzie chcesz, aby górny widżet nakładał się na obraz, ustawienie marginesu z prawej lub dołu może być lepszą opcją lub możesz użyć odchylenia, z których wszystkie odpowiadają na pytanie .
Gabriel Vasconcelos
4

To pomoże wielu

W moim przypadku chcę mieć taki projekt:

po

Oznacza, że ​​chcę, aby mój obraz był wyświetlany w połowie ich szerokości, więc w zasadzie potrzebuję ujemnego marginesu połowy rzeczywistej szerokości obrazu, ale cały mój układ w układzie z ograniczeniami i układzie z ograniczeniami nie dopuszcza ujemnego marginesu, więc osiągnąłem to za pomocą poniższego kodu

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:scaleType="centerCrop"
        android:src="@drawable/ic_launcher_background"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/guideline"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_begin="50dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

Tak więc ImageView zakończy się na początku wytycznych. a efekt jest taki sam, jak ujemna marża na początku 50 dp.

A także, jeśli szerokość widoku nie jest stała i jest wyrażona w procentach, dzięki czemu można umieścić wytyczne w procentach i osiągnąć dowolny efekt

Miłego kodowania :)

Vijay Makwana
źródło
0

Musisz tylko użyć widżetu Space w swoim układzie

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<Space
    android:id="@+id/negative_margin"
    android:layout_width="16dp"
    android:layout_height="16dp"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintRight_toLeftOf="parent"/>

<Button
    android:id="@+id/button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Widget who needs negative margin"
    app:layout_constraintTop_toBottomOf="@+id/negative_margin"
    app:layout_constraintLeft_toLeftOf="@+id/negative_margin" />

Hagar Magdy
źródło
0

To stare pytanie, ale bardzo często zadawane. Najszybszym sposobem osiągnięcia tego jest ograniczenie górnej i dolnej części widoku, do którego chcesz zakotwiczyć, na przykład:

        <androidx.appcompat.widget.AppCompatImageView
        android:layout_width="55dp"
        android:layout_height="55dp"
        app:layout_constraintBottom_toBottomOf="@+id/parent_view_id"
        app:layout_constraintTop_toBottomOf="@+id/parent_view_id"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent" />

Spowoduje to wyśrodkowanie go w dolnej linii widoku, wyśrodkowany w poziomie.

Ahmed Awad
źródło
-1

Prosty sposób.

Nie jestem pewien, jak najlepiej.

Po prostu zawiń za pomocą LinearLayout

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
 <View
 android:layout_width="wrap_content"
 android:layout_marginLeft="-20dp"
 android:layout_height="wrap_content"/>
</LinearLayout>
최봉재
źródło
Dodaje to trochę więcej zagnieżdżenia, ALE działa do animowania widoków z góry, w przeciwieństwie do innych rozwiązań tutaj, które zakładają, że Widok zostanie wyświetlony w całości. Dzięki!
Leo wspiera Monikę Cellio
2
Ta odpowiedź wymaga znacznie więcej informacji. Nie jest jasne, w jaki sposób umieszczenie czegoś w LinearLayout może służyć do nakładania się.
Robert Liberatore