Beim Zeichnen außerhalb der Grenzen des Ansichtsclips mit Android: Wie verhindere ich, dass untergeordnete Ansichten über meiner benutzerdefinierten Ansicht gezeichnet werden?
Lesezeit: 8 Minuten
Daniel Segato
Ich habe eine benutzerdefinierte Android-Ansicht geschrieben, die außerhalb ihrer Beschneidungsgrenzen zeichnen muss.
Das habe ich:
Das passiert, wenn ich auf eine Schaltfläche klicke, sagen wir die rechte Schaltfläche:
Wie verhindere ich, dass die Ansicht unten auf meinem “Griff” gezeichnet wird?
Es folgt ein verwandter Pseudo-Code aus meinem Projekt.
Meine benutzerdefinierte Ansicht MyHandleView zeichne so:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Path p = mPath;
int handleWidth = mHandleWidth;
int handleHeight = mHandleHeight;
int left = (getWidth() >> 1) - handleWidth;
canvas.save();
// allow drawing out of bounds vertically
Rect clipBounds = canvas.getClipBounds();
clipBounds.inset(0, -handleHeight);
canvas.clipRect(clipBounds, Region.Op.REPLACE);
// translate up to draw the handle
canvas.translate(left, -handleHeight);
// draw my background shape
canvas.drawPath(p, mPaint);
canvas.restore();
}
Das Layout sieht in etwa so aus (ich habe es etwas vereinfacht):
Sie können sich das Fragment als eine Ansicht vorstellen, die unten zwei Schaltflächen in einem LinearLayout enthält.
Anstelle des externen RelativeLayout verwende ich wirklich eine Ansicht aus einer Bibliothek: SlidingUpPanelLayout (https://github.com/umano/AndroidSlidingUpPanel). Aber ich habe das Verhalten mit diesem RelativeLayout getestet: dasselbe, was bedeutet, dass die Bibliothek nicht verwandt ist.
Ich sage das nur, um dich das wissen zu lassen Ich kann dort nicht einfach ein FrameLayout platzieren und vermeiden, außerhalb der Schnittgrenzen zu zeichnen.
Ich vermute, dass dies etwas damit zu tun hat, dass, wenn ich die Schaltfläche berühre, sie sich selbst neu zeichnet, aber meine andere Ansicht (die sich an einer anderen Stelle in der Hierarchie befindet) nicht neu gezeichnet wird (dies ist eine Optimierung, und ich bezweifle, dass dies möglich ist). deaktiviert).
Ich möchte in der Lage sein, meine benutzerdefinierte Ansicht ungültig zu machen, wenn eine andere “nahe” (oder irgendeine) Ansicht ungültig wird, daher brauche ich eine Art Listener für die Entwertung der Ansicht, aber mir ist keine bekannt.
Kann jemand helfen?
Ich verstehe immer noch nicht, warum Sie nicht möchten, dass die Unteransicht diese beiden Schaltflächen oben überlappt …
– Pskink
30. Juli 2014 um 18:31 Uhr
Es ist nicht so, dass ich es nicht will. Ich kann nicht. Für die SlideUpPanel-Bibliothek müssen 2 untergeordnete Elemente vorhanden sein. Der erste ist der Hauptinhalt (der mit den zwei Schaltflächen), der zweite ist der Inhalt, der nach oben gleitet. Ich kann ein FrameLayout verwenden und alles richtig platzieren, aber dann rutscht es überhaupt nicht nach oben. Daher wäre die einzige andere Möglichkeit, meine eigene Version des Slide-Up-Panels zu schreiben oder das von mir verwendete zu patchen. Ich möchte es vermeiden, weil es viel Arbeit ist.
– Daniele Segato
31. Juli 2014 um 0:00 Uhr
Daniel Segato
Ich habe die Lösung selbst gefunden, auch wenn das für Auftritte nicht optimal ist.
Einfach hinzufügen:
android:clipChildren="false"
zum RelativeLayout (oder welches Layout Sie auch immer haben).
Dies hat 2 Effekte (vielleicht mehr, das sind die beiden, die mich interessieren): – die ViewGroup schneidet die Zeichnung seiner Kinder nicht ab (offensichtlich) – die ViewGroup prüft beim Betrachten nicht auf Schnittpunkte mit schmutzigen Regionen (ungültig). welche Kinder neu zeichnen
Ich habe den View-Code zum Invalidieren ausgegraben.
Der Prozess läuft ungefähr so ab:
Wenn sich eine Ansicht selbst ungültig macht, wird die Region, die sie normalerweise zeichnet (ein Rechteck), zu einer “schmutzigen Region”, die neu gezeichnet werden muss
Die Ansicht teilt ihrem übergeordneten Element (einer Art ViewGroup) mit, dass es sich selbst neu zeichnen muss
Die Eltern tun dasselbe mit dem Elternteil der Wurzel
jedes Elternteil in der Hierarchieschleife für alle Kinder und prüfen Sie, ob die schmutzige Region einige von ihnen schneidet
Wenn ja, zeichnet es sie auch neu
In Schritt 4 ist Clipping involviert: Die ViewGroup überprüft die Ansichtsgrenzen seines Kindes nur, wenn clipChildren wahr ist: das heißt, wenn Sie es auf false setzen Zeichnen Sie immer alle untergeordneten Elemente neu, wenn eines von ihnen ungültig wird.
Meine View-Hierarchie sah also so aus:
ViewGroup A
|
|----- fragment subtree (containing buttons, map,
| whatever element that I don't want to draw
| on top of my handle)
|
|----- ViewGroup B
|
|---- my handle (which draw outside its clip bounds)
In meinem Fall wird der “Griff” von seiner Bindung abgezogen, auf etwas, das normalerweise von einem Element des Fragment-Unterbaums gezeichnet wird.
Wenn eine Ansicht innerhalb des Fragments ungültig gemacht wird, reicht es seine “schmutzige Region” im Ansichtsbaum nach oben und jede Ansichtsgruppe prüft, ob es einige andere Kinder gibt, die in dieser Region neu gezeichnet werden müssen.
ViewGroup B würde das, was ich zeichne, außerhalb der Clip-Grenzen schneiden, wenn ich es nicht setze clipBounds=”false” darauf.
Wenn irgendetwas im Teilbaum des Fragments ungültig gemacht wird, sieht die Ansichtsgruppe A, dass die schmutzige Region der Ansichtsgruppe B die Region des Teilbaums des Fragments nicht schneidet, und überspringt das Neuzeichnen der Ansichtsgruppe B.
Aber wenn ich ViewGroup A auch sage, keine untergeordneten Elemente zu beschneiden, erhält ViewGroup B immer noch einen Invalidierungsbefehl, der dann ein Neuzeichnen meines Handles bewirkt.
Die Lösung besteht also darin, sicherzustellen, dass es eingestellt ist
android:clipChildren="false"
auf jeder ViewGroup in der Hierarchie über der View, die außerhalb ihrer Grenzen zeichnet, auf die der Inhalt möglicherweise “unter” den Out-of-Bound-Bereich fällt, den Sie zeichnen.
Der offensichtliche Nebeneffekt davon ist, dass jedes Mal, wenn ich eine der Ansichten in ViewGroup A ungültig mache, ein ungültiger Aufruf mit der ungültigen Region an alle darin enthaltenen Ansichten weitergeleitet wird.
Jedoch wird jede Ansicht übersprungen, die nicht den Dirty-Bereich schneidet, der sich innerhalb einer ViewGroup mit clipChildren=”true” (Standard) befindet.
Um Leistungsprobleme zu vermeiden, stellen Sie also sicher, dass Ihre Ansichtsgruppen mit clipChildren=”true” nicht viele “normale” direkte Kinder haben. Und mit “Standard” meine ich, dass sie nicht außerhalb ihrer Sichtgrenzen zeichnen.
Wenn also beispielsweise ViewGroup B in meinem Beispiel viele Ansichten enthält, ziehen Sie in Betracht, alle in einer ViewGroup mit clipChildren=”true” einzuschließen und nur diese Ansichtsgruppe und die eine Ansicht, die außerhalb ihrer Region zeichnet, als direkte untergeordnete Elemente von ViewGroup B zu belassen. Dasselbe gilt für ViewGroup A.
Diese einfache Tatsache stellt sicher, dass keine andere Ansicht neu gezeichnet wird, wenn sie sich nicht in der ungültig gemachten schmutzigen Region befindet, wodurch die erforderlichen Neuzeichnungen minimiert werden.
Ich bin immer noch offen für weitere Überlegungen, wenn jemand eine hat;)
Ich werde also ein wenig warten, bevor ich dies als akzeptierte Antwort markiere.
BEARBEITEN: Viele Geräte behandeln clipChildren=”false” etwas anders. Ich habe festgestellt, dass ich clipChildren=”false” für alle übergeordneten Ansichten meines benutzerdefinierten Widgets festlegen musste, die möglicherweise Elemente in ihrer Hierarchie enthalten, die über den “außerhalb des gebundenen Bereichs” des Widgets hinausragen sollten, oder Sie sehen möglicherweise Ihre benutzerdefinierte Zeichnung zeigt AUF einer anderen Ansicht, die es abdecken sollte. Zum Beispiel hatte ich in meinem Layout eine Navigationsschublade, die meinen “Griff” abdecken sollte. Wenn ich clipChildren=”false” nicht im NavigationDrawer-Layout eingestellt habe, kann es vorkommen, dass mein Griff vor der geöffneten Schublade auftaucht.
EDIT2: Mein benutzerdefiniertes Widget hatte eine Höhe von 0 und wurde “auf” von sich selbst gezeichnet. Hat auf Nexus-Geräten gut funktioniert, aber viele der anderen hatten eine “Optimierung”, die das Zeichnen von Ansichten mit einer Höhe von 0 oder einer Breite von 0 vollständig überspringt. Beachten Sie dies also, wenn Sie eine Komponente schreiben möchten, die über ihre Grenzen hinausgeht: Sie müssen ihr mindestens 1 Pixel Höhe / Breite zuweisen.
keine anderen Abnehmer, äh? Da dies ziemlich gut zu funktionieren scheint, markiere ich dies auch bei Aufführungen als akzeptierte Antwort
– Daniele Segato
2. August 2014 um 0:10 Uhr
@ user1186061 Das bezweifle ich sehr 🙂 Ich habe eine ausführliche Erklärung geschrieben, wie es funktioniert und wann es Sinn macht. Sie befinden sich wahrscheinlich in einer anderen Situation.
– Daniele Segato
25. August 2016 um 7:19 Uhr
Ja vielleicht. Aber danke für das Update, ich weiß es zu schätzen 😉
– KingPinG
25. August 2016 um 17:32 Uhr
Hat für mich funktioniert. Ähnliches Problem. Benutzerdefinierte Ansicht zu einem FrameLayout hinzugefügt. Wenn es aus dem Bildschirm und dann zurück bewegt wurde, blieb es teilweise abgeschnitten. Auch wenn es mit setRotation und setPivotX und setPivotY vom Bildschirm gedreht wird. Als die Drehung es wieder auf den Bildschirm brachte, blieb es abgeschnitten. Obwohl es ‘onDraw()’ aufgerufen wurde. Der Aufruf von setClipChildren(false) auf meinem FrameLayout hat gut funktioniert. Ich habe eine andere Logik, um das “Zeichnen” zu verhindern, wenn es zu weit vom Bildschirm entfernt ist. Vielen Dank.
– David Hubbard
19. Januar 2018 um 16:04 Uhr
Ich habe diese Lösung für um 90 Grad gedrehten Text ausprobiert, der fälschlicherweise abgeschnitten wird, und das hat nicht geholfen. Schade, war gut recherchiert.
– xjcl
6. Juli 2020 um 22:36 Uhr
10545800cookie-checkBeim Zeichnen außerhalb der Grenzen des Ansichtsclips mit Android: Wie verhindere ich, dass untergeordnete Ansichten über meiner benutzerdefinierten Ansicht gezeichnet werden?yes
Ich verstehe immer noch nicht, warum Sie nicht möchten, dass die Unteransicht diese beiden Schaltflächen oben überlappt …
– Pskink
30. Juli 2014 um 18:31 Uhr
Es ist nicht so, dass ich es nicht will. Ich kann nicht. Für die SlideUpPanel-Bibliothek müssen 2 untergeordnete Elemente vorhanden sein. Der erste ist der Hauptinhalt (der mit den zwei Schaltflächen), der zweite ist der Inhalt, der nach oben gleitet. Ich kann ein FrameLayout verwenden und alles richtig platzieren, aber dann rutscht es überhaupt nicht nach oben. Daher wäre die einzige andere Möglichkeit, meine eigene Version des Slide-Up-Panels zu schreiben oder das von mir verwendete zu patchen. Ich möchte es vermeiden, weil es viel Arbeit ist.
– Daniele Segato
31. Juli 2014 um 0:00 Uhr