Verständnisprobleme beim Layout

  • 46 Antworten
  • Neuester Beitrag
Diskutiere Verständnisprobleme beim Layout im Android App Entwicklung im Bereich Betriebssysteme & Apps.
deka

deka

Experte
Hallo zusammen,
ich verzweifle langsam mit meinem Layout. Undzwar mache ich momentan eine Quiz-App und diese ist von den Funktionen so gut wie fertig. Ich komme jedoch mit dem Layout überhaupt nicht weiter. Habe schon unzählige Male die Doku von Google durchgelesen, vieles recherchiert, aber das richtige Vorgehen möchte mir einfach nicht einleuchten. Von meinen Kenntnissen würde ich mich als Fortgeschritten bezeichnen (bin selber Java-Entwickler).

Folgende Situation:
Das Hauptlayout ist ein Relativelayout. Hier habe ich folgendes ImageView platziert:
Code:
<ImageView
        android:id="@+id/iv_image"
        android:layout_width="150dp"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_below="@id/tv_topic"/>
Die Bilder habe ich allesamt im drawable Verzeichnis. Das heißt ich habe immer nur ein Examplar und keine Aufteilung in ldpi, mdpi usw. gemacht. Aus diesem Blog Perfect Resource Image Size & DPI for any Android device | Tivix habe ich die Info, dass es ausreichend ist, wenn man ein hochauflösendes Bild bereitstellt, da das System je nach Gerät selber runterskaliert.

Auf meinem LG G2 (5,2 Zoll und 480 density) sieht es wie folgt aus:

QuickMemo+_2017-02-20-19-37-26.png

So soll es auch auf allen anderen Geräten aussehen. D.h. das Bild links soll den Inhalt komplett ausfüllen und keine Ränder zeigen.

Auf meinem OnePlus 3T (5,5 Zoll und 420 density) sieht es leider ziemlich mies aus:

2.PNG

Meine Frage ist jetzt was ich hier falsch mache.
a) Liegt es daran, dass ich nur EIN Bild im drawable Verzeichnis habe?
b) Ist das Bild für das 5,5 Zoll Gerät zu klein von der Auflösung?

Falls es a) ist: Reicht es aus, wenn ich das gleiche Bild in die jeweiligen Ordner (ldpi, mdpi usw.) kopiere?
b) kann ich mir ehrlich gesagt kaum vorstellen, da die Auflösung mit 400x645 recht hoch ist.
Zusätzlich gefällt mir auch nicht, wie klein das ganze auf dem zweiten Gerät aussieht. Da ich RelativeLayout verwende kann ich auch nicht mit layout_weight arbeiten (wobei ich nicht weiß, ob das die Lösung wäre. Und bei dem PercentageRelativeLayout bin ich mir nicht sicher, ob es das Problem lösen würde, dass es auf jeden Bildschirm genau gleich aufgeteilt ist. Wie beschrieben zeigt das erste Bild, wie es überall aussehen sollte. Ist also quasi meine Grundlage.

In der Google Doc fehlt mir auch beispielsweise die Info wie ich die baseline-Auflösung herausfinde. Die geben in ihrem Beispiel an, dass sie für das medium-density 48x48 verwenden. Das wäre dann die baseline. Aber ich verstehe nicht wie ich die richtige Auflösung für meinen Fall herausfinde. Bei mir beträgt die Auflösung des Bildes 400x645.

Mein Designer möchte die Woche noch die genaue Auflösung der Bilder wissen. Diese möchte er dann kaufen. Nun stehe ich leider auch in dieser Sache auf dem Schlauch.

Ich hoffe ihr könnt mir hier ein wenig weiterhelfen. Wäre euch wirklich sehr sehr dankbar.
 
swa00

swa00

Moderator
Teammitglied
Halo deka,

willkommen im Layout-Frust :)

Nun , Dein "Fehler" ist kein Fehler, sondern eigentlich rein technisch richtig.
Android stellt das Image im Höhen/Seitenverhältnis so dar , wie das Original ist.

Da aber die Displays unterschiedliche Höhen/Seitenverhältnisse haben - und vor allem eine andere DPI,
kann das Image niemals überall gleich dargestellt werden.
(Es sei denn es würde verzerrt sein)

Abhilfe hierfür wäre , dass Du selbst die Berechnung der Breite des Banners durchführst und
damit wärst du Dir sicher , dass es auch unten anliegt.
Hat aber wiederum den Nachteil , das dein Content der Quizfrage natürlich wiederum schmaler wird.

Android ist recht rudimentär im Layout , - da ist selbst Handanlegen angesagt .






.
 
Jaiel

Jaiel

Experte
Also die normale Vorgehensweise ist, dass man nicht eine einzige Layoutdatei hat sondern mehrere für die verschiedenen Auflösungen. Ich persönlich würde es auch im Code machen, damit ich nicht unzählige Layoutdateien anlegen muss. Das führt aber zu sehr viel Schreibarbeit, glaub mir!

Android ist da wirklich sehr rudimentär wie swa00 sagt. 150dp...was genau heist das schon? Auf deinen OnePlus nehmen die 150dp halt weniger Platz auf dem Screen ein als auf deinem LG.

Falls es dich zufriedenstellt:

Code:
<ImageView
        android:id="@+id/iv_image"
        android:layout_width="150dp"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_below="@id/tv_topic"

        android:scaleType="centerCrop"

/>
Das skaliert dein Bild damit es reinpasst und behält das Seitenverhältnis bei.

Was du aber wirklich möchtest ist eine Layoutdatei für höherauflösende Display die andere Werte annehmen. So dass zum Beispiel für die Breite der Imageview 200dp auf deinem OnePlus genutzt wird statt die 150dp die auf deinem LG benutzt werden.

Auch das mit dem 1 Bild für alle Auflösungen ist eigentlich nur Faulheit und am Ende frisst es halt den Akku und CPU Zeit auf dem Gerät des Endbenutzers weg.

OT: Ich wundere mich warum Android immer noch nicht mit Prozentangaben in Layoutdateien funktioniert. Das würde vielen das Leben erleichtern mMn!
 
Zuletzt bearbeitet:
deka

deka

Experte
Vielen Dank euch beiden für die Antworten.
@Jaiel
Das heißt, ich lege jetzt die Layoutdatei in die Verzeichnisse small, normal und large. xlarge brauche ich nicht, da ich keine Tablets unterstütze. Und in der Layout-Datei passe ich dann lediglich die dps für die Elemente. Für normal beispielsweise 150dp und für large 200dp. Meine beiden Geräte gehören jedoch von der Displaygröße beide zu normal. Das heißt, Android würde für diesen Fall immer auf die Layoutdatei im normal Verzeichnis zugreifen. Habe ich das richtig verstanden?
Das würde ja immer noch zum gleichen Resultat führen.

Es gibt ja noch das PercentageRelativeLayout. Hast du damit schon mal gearbeitet?
 
swa00

swa00

Moderator
Teammitglied
Hallo deka,

bevor du Dich ein wenig verwirren lässt.

Das mag vielleicht ein Weg sein , ist aber nicht im Sinne des Erfinders und deckt auch nur ein Bruchteil
der Devices ab .

Diesen Weg habe ich bis dato bei KEINEM meiner Apps angewendet.
Das wäre eher ein try & hope , hat aber nichts mit fundiertem Coden zu tun

Wenn Du es richtig haben möchtest , dann musst du dir die DPI des Devices in Abhängigkeit der
Breite und Höhe ermitteln und dann dein Layout setzen .

Code:
 DisplayMetrics metrics = new DisplayMetrics();
 WindowManager wm = (WindowManager) mContext.getSystemService(WINDOW_SERVICE);
 wm.getDefaultDisplay().getMetrics(metrics);
 yInches = metrics.heightPixels / metrics.ydpi;
 xInches = metrics.widthPixels / metrics.xdpi;
 DISPLAY_HEIGHT = metrics.heightPixels;
 DISPLAY_WIDTH = metrics.widthPixels;
 DISPLAY_INCH = (float) Math.sqrt(xInches * xInches + yInches * yInches);
Danach bist du auch dann in Lage , deine Berechnungen durchzuführen
Ob Prozentual oder in mm , spielt keine Rolle

Bsp

Code:
public int mm2pixel (double mm)
{
 return (int) ((double) ((double)metrics.xdpi * (mm * (1.0F / 25.4F)));
}
Wobei zu berücksichtigen ist , dass bei manchen Displays die DPI für x und y unterschiedlich sind.

Dann kann Jaeil's Vorschlag nicht funktionieren

Tip : Das ganze dann in eine SingleTon - Klasse und nach setContentView einmalig initialisieren
 
Zuletzt bearbeitet:
deka

deka

Experte
Ja, hoffen das es so passt geht in der Entwicklung in der Regel immer schief...
Kannst du mal bitte dein Vorgehen genau beschreiben. Also wie gehst du genau vor?

Was mir im Moment fehlt ist wirklich der rote Faden. Ich weiß einfach nicht an welcher Stelle ich anfangen soll weil es mir überhaupt nicht klar ist wie ich vorgehen muss. So eine Schritt-für-Schritt-Anleitung wäre wirklich goldwert.

Die einzigen Sachen die ich weiß:
- Ich möchte die Displaygrößen small, normal und large unterstützen (da xlarge zu den Tabletgrößen zählt)
- Ich muss alle sechs densities unterstützen (von ldpi bis xxxhdpi)
- Meine App soll auf jedem Gerät exakt gleich aussehen und da will ich nichts dem Zufall überlassen
 
swa00

swa00

Moderator
Teammitglied
Hast du meinen Code oben schon gesehen ( kam später ) oder benötigst du dazu noch eine Erklärung ?

small, large oder was auch immer lässt du ausser acht, das sind Eieruhren :)
 
deka

deka

Experte
Habe es mir mal angschaut und versucht nachzuvollziehen.
Wo platziere ich diese Berechnung? Und muss das dann bei jedem Start der App berechnet werden oder nur bei der ersten Ausführung der App?
Ich verstehe nicht ganz wie ich dann weitermache wenn ich die ganzen Werte (Codeblock 1) berechnet habe. Das ganze Layout-Zeug ist ja in der xml definiert. Greife ich dann beispielsweise auf ein bestimmtes Element (z.B. ImageView) zu und setze dynamische die Größe davon?
 
swa00

swa00

Moderator
Teammitglied
Ok, ich gehe darauf ein ::


Die Berechnung (Ermittlung der Werte) brauchst du nur einmal beim Start der app auszuführen.
Damit dir diese Werte über die gesammte APp zu verfügung stehen , habe ich oben angeraten , diese in eine SingleTon Klasse zu setzen.

Hast nun dein ImageView auf der linken Seite und möchtest dieses so Anpassen, dass es immer dein Bitmap
in der Höhe bündig darstellt, dann ermittelst du erst den Faktor dieses images.

float fak = HöheBitmap / BreiteBitmap.

Danach setzt du dein ImageView in der Grösse

imageview.getLayoutParams().height = (int) displayheight;
imageview.getLayoutParams().width = (int) ((float) displayheight * (float) fak);
imageview.setX (0);
imageView.setY (0);

Und das Gleiche machst du dann mit deinem "Fragenholder" mit dem Rest rechts.
 
Zuletzt bearbeitet:
Jaiel

Jaiel

Experte
Ich fühle mich etwas dumm muss ich sagen!

Ich sehe heute zum ersten Mal diese PercentRelativeLayout. Damit sollte es doch besser klappen. Probier mal aus.

Im Code ist das viel zu viel Arbeit. XML sollte eher benutzt werden weil es eleganter ist.

Beispiel aus einer App die ich kürzlich als Hausaufgabe geschrieben habe. So ungefähr sieht das dann aus wenn man die Objekte im Code initialisieren möchte:

Code:
    private void setUpLayouts(int width, int height, final Activity main)
    {

        contactsMenuFrame =(FrameLayout)main.findViewById(R.id.contactsouterparent);
        contactsMenuFrame.setLayoutParams(new FrameLayout.LayoutParams((int) (0.75f * width), (int) (0.75f * height)));
        contactsMenuFrame.setX(0.125f * width);
        contactsMenuFrame.setY(0.125f * height);

        //hier wird die GridView für die Kontakte initialisiert
        contactsParent =(GridView)main.findViewById(R.id.contactsparent);
        contactsParent.setLayoutParams(new FrameLayout.LayoutParams((int) (0.75f * width), (int) (0.65f * height)));
        contactsParent.setVerticalSpacing((int) (0.0125f * width));
        contactsParent.setHorizontalSpacing((int) (0.0125f * width));
        contactsParent.setAdapter(new ContactAdapter(main));
        contactsParent.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
                activateContactDetailMenu(selectedPerson = (Person) (contactsParent.getAdapter().getItem(position)));
            }
        });

        //contact detail layouts werden hier initialisiert
        contactDetailFrame = (FrameLayout) main.findViewById(R.id.contactdetailparent);
        contactDetailFrame.setLayoutParams(new FrameLayout.LayoutParams((int) (0.75f * width), (int) (0.75f * height)));
        contactDetailFrame.setX(0.125f * width);
        contactDetailFrame.setY(0.125f * height);

        contactDetailPhoto = (ImageView) main.findViewById(R.id.contactphoto);
        contactDetailPhoto.setLayoutParams(new FrameLayout.LayoutParams((int) (0.65f * width), (int) (0.47f * height)));
        contactDetailPhoto.setX(0.05f * width);
        contactDetailPhoto.setY(0.02f * height);

        contactDetailName = (TextView) main.findViewById(R.id.contactnamedetail);
        contactDetailName.setLayoutParams(new FrameLayout.LayoutParams((int) (0.7f * width), (int) (0.08f * height)));
        contactDetailName.setX(0.025f * width);
        contactDetailName.setY(0.51f * height);

        contactDetailPhone = (TextView) main.findViewById(R.id.contactphonedetail);
        contactDetailPhone.setLayoutParams(new FrameLayout.LayoutParams((int) (0.7f * width), (int) (0.08f * height)));
        contactDetailPhone.setX(0.025f * width);
        contactDetailPhone.setY(0.58f * height);

        //neuer kontakt layout werden heir initialisiert
        newContactFrame =(FrameLayout) main.findViewById(R.id.newcontact);
        newContactFrame.setLayoutParams(new FrameLayout.LayoutParams((int) (0.75f * width), (int) (0.75f * height)));
        newContactFrame.setX(0.125f * width);
        newContactFrame.setY(0.125f * height);

        newContactPhoto = (ImageView) main.findViewById(R.id.newcontactphoto);
        newContactPhoto.setLayoutParams(new FrameLayout.LayoutParams((int) (0.65f * width), (int) (0.47f * height)));
        newContactPhoto.setX(0.05f * width);
        newContactPhoto.setY(0.02f * height);
        newContactPhoto.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if(ContextCompat.checkSelfPermission(main,
                    Manifest.permission.READ_EXTERNAL_STORAGE)==PackageManager. PERMISSION_DENIED)
            {
                Toast.makeText(main,"Keine Erlaubnis gegeben um auf externen Speicher zuzugreifen!",Toast.LENGTH_SHORT).show();
                return;
            }
            Intent galleryIntent = new Intent(Intent.ACTION_PICK,
                    android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
            // Start the Intent
            main.startActivityForResult(galleryIntent, 123);
        }
    });

        newName = (EditText) main.findViewById(R.id.newname);
        newName.setLayoutParams(new FrameLayout.LayoutParams((int) (0.7f * width), (int) (0.06f * height)));
        newName.setX(0.025f * width);
        newName.setY(0.51f * height);

        newContactNum = (EditText) main.findViewById(R.id.newphonenum);
        newContactNum.setLayoutParams(new FrameLayout.LayoutParams((int) (0.7f * width), (int) (0.06f * height)));
        newContactNum.setX(0.025f * width);
        newContactNum.setY(0.58f * height);

        //edit kontakt layout werden heir initialisiert
        editContactFrame =(FrameLayout) main.findViewById(R.id.editcontact);
        editContactFrame.setLayoutParams(new FrameLayout.LayoutParams((int) (0.75 * width), (int) (0.75 * height)));
        editContactFrame.setX(0.125f * width);
        editContactFrame.setY(0.125f * height);

        editContactPhoto = (ImageView) main.findViewById(R.id.editcontactphoto);
        editContactPhoto.setLayoutParams(new FrameLayout.LayoutParams((int) (0.65 * width), (int) (0.47 * height)));
        editContactPhoto.setX(0.05f * width);
        editContactPhoto.setY(0.02f * height);
        editContactPhoto.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(ContextCompat.checkSelfPermission(main,
                    Manifest.permission.READ_EXTERNAL_STORAGE)==PackageManager. PERMISSION_DENIED)
                return;
                Intent galleryIntent = new Intent(Intent.ACTION_PICK,
                        android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                // Start the Intent
                main.startActivityForResult(galleryIntent, 123);
            }
        });

        editContactName = (EditText) main.findViewById(R.id.editname);
        editContactName.setLayoutParams(new FrameLayout.LayoutParams((int) (0.70 * width), (int) (0.06 * height)));
        editContactName.setX(0.025f * width);
        editContactName.setY(0.51f * height);

        editContactNum = (EditText) main.findViewById(R.id.editphonenum);
        editContactNum.setLayoutParams(new FrameLayout.LayoutParams((int) (0.70 * width), (int) (0.06 * height)));
        editContactNum.setX(0.025f * width);
        editContactNum.setY(0.58f * height);


    }

Soweit wie ich das bisher überblicken kann wäre aber das PercentRelativeLayout genau das richtige (für mich) gewesen und ich hätte es viel eleganter in Layoutdateien unterbringen können statt hunderte von Zeilen code.

Offiziell empfiehlt Google halt aber die Layouts in small bis xlarge und ldpi bis xxxhdpi zu verwalten. Im Endeffekt sind es doch nur verschiedene Zahlen die eingetragen werden müssen und einige Male Testen und Anpassen.

Ich bin wie man sehen kann aber auch ein Freund davon alle Initialisierungen bezüglich Dimension und Position im Code zu machen. Wobei mich das neu entdeckte Layout aber auf jeden fall dazu bringen wird es in Zukunft zu benutzen...
 
swa00

swa00

Moderator
Teammitglied
@Jaiel
warum dann so kompliziert oben ? - Du machst es doch genau so wie ich :)

@deka : das müsste dir schon helfen .- oder ?
 
deka

deka

Experte
Das ist echt sehr viel Code. Muss das erst mal genau anschauen und nachvollziehen.
Das heißt ihr beide verwendet dann immer nur genau eine Layoutdatei und macht die Anpassungen per Code? Also eine Layoutdatei im layout-Verzeichnis.

Ich werde mir auch nochmal das PercentageLayout genauer anschauen. Vielleicht lässt es sich damit schon lösen. Möchte, falls möglich, alles in der xml haben.
 
swa00

swa00

Moderator
Teammitglied
Das heißt ihr beide verwendet dann immer nur genau eine Layoutdatei und macht die Anpassungen per Code? Also eine Layoutdatei im layout-Verzeichnis.
ich schon , ich verwende den Layout - Editor nur um wild meine Elemente durcheinander reinzuziehen.
das Gefrickel mit den Platzierungen erspare ich mir
 
deka

deka

Experte
Und wie sieht es mit den Bilder aus? Hast du da nur ein hochauflösendes? Oder so wie es Google vorschreibt?
 
swa00

swa00

Moderator
Teammitglied
Nein , ich versuche immer in der geringsten Auflösung (png) zu bleiben , so dass es bei einem 4K display nicht verpixelt
aussieht .

ABER Vorsicht :
Du solltest die Images auf alle Fälle im /res/drawable-nodpi plazieren NICHT im /drawable.

Sonst bekommst du schnell ein memory-problem .

Hintergrund : Android versucht jedes Images an die dpi des Display anzupassen , da sind schnell mal
hunderte von MB weg.
Damit Android diese Berechnung nicht durchführt , platzierst du sie in dem oben angegebenen Ordner

(Ein häufiger Fehler , den Anfänger machen , wenn sie ein Image als Hintergrund wählen)
 
deka

deka

Experte
@swa00
Aber wenn du die geringste Auflösung nimmst, dann tut doch Android bei größeren Bildschirmen hochskalieren. Oder verstehe ich das falsch? Ich dachte nämlich man nimmt das maximal auflösende Bild welches man verwenden möchte und dieses wird dann dementsprechend runterskaliert. Beim Runterskalieren verpixelt es dann nach meinem Verständnis nicht. Nur in die andere Richtung.
 
swa00

swa00

Moderator
Teammitglied
Nein , ich versuche immer in der geringsten Auflösung (png) zu bleiben , so dass es bei einem 4K display nicht verpixelt
aussieht .
ja hast du falsch verstanden - schon die Auflösung, die bei einem 4k noch ok aussieht
 
Kardroid

Kardroid

Stammgast
Hallo, ich mische jetzt mal auch mit, obwohl ich euren ganzen Code mir nicht angesehen habe.

Die wichtigste Seite für dein Layout-Problem ist diese hier:
Supporting Multiple Screens | Android Developers

Ein wichtiges Zitat ist das:
A set of four generalized sizes: small, normal, large, and xlarge
Note:
Beginning with Android 3.2 (API level 13), these size groups are deprecated in favor of a new technique for managing screen sizes based on the available screen width.
Wir haben in produktiven Apps im Store sogar "layout-sw360dp"-Ordner. Das funktioniert also nicht nur bei Tablets und könnte auch bei dir funktionieren.

Zu den Bildern: Auf der Seite, die ich oben genannt habe steht bei Best Practice (Supporting Multiple Screens | Android Developers)
Use size and density-specific resources
Wir benutzen wirklich für jede DPI-Größe auch eigene Bilder. Stell dir vor, du hast ein Burger Menü Icon, welches in XHDPI aus 3 mal 3 Pixel höhe besteht,
Wenn es jetzt automatisch auf MDPI-skalliert wird, könnte es sein, dass die Striche viel zu dick erscheinen. Bei einzelnen Bildern pro DPI kann man bei uns sie einfach 1 Pixel hoch machen.

Ich hoffe meine Erklärung ist verständlich.
 
swa00

swa00

Moderator
Teammitglied
Moin Moin ,

A set of four generalized sizes: small, normal, large, and xlarge
Das ist allerdings Wunschdenken , denn viele Devices besitzen mittlerweile unterschiedliche
DPI Werte in Höhe und Breite.
Und dann kommt ein rundes Element einem Ei gleich. (Selbst Samsung und Lenovo bekommen das hin)

Ich hätte mir nämlich auch nicht die Mühe gemacht , wenn ich bei einem Projekt völlig
gegen die Wand gelaufen wäre :-(

Jetzt bin ich auf der sichern Seite und meine Layouts sehen immer exakt so aus , wie sie sollen.

P.S ich übertreibe es sogar noch und setze meine Layouts in nur zwei verschiedene Grössen
(> 6.5 Inch) und berechne meine Positionen nur noch in Millimeter,

Dann kann ich sicher sein , dass der Knopf auf jedem Device definitiv drückbar bleibt
 
Kardroid

Kardroid

Stammgast
swa00 schrieb:
Moin Moin ,

A set of four generalized sizes: small, normal, large, and xlarge
Das ist ja auch deprecated.
Edit: Aber wie machst du das mit den SP bei Texten? Also ich finde Android gibt schon alles in die Hand, ohne großen Code beim Layout schreiben zu müssen.
 
Ähnliche Themen - Verständnisprobleme beim Layout Antworten Datum
1