Utilisation de forceLayout (), requestLayout () et invalidate ()

185

Je suis un peu confus sur les rôles des forceLayout(), requestLayout()et des invalidate()méthodes de la Viewclasse.

Quand seront-ils appelés?

sdabet
la source

Réponses:

357

Pour mieux comprendre les réponses fournies par François BOURLIEUX et Dalvik, je vous suggère de jeter un œil à ce superbe diagramme du cycle de vie des vues par Arpit Mathur : entrez la description de l'image ici

Bartek Lipinski
la source
28
Je vois souvent requestLayout être appelé directement après l'appel d'invalidation, je vois même que cela se produit dans le code source d'Android pour des choses comme TextView, mais selon ce diagramme, cela est redondant, non? Alors, y a-t-il un but à cela?
tcox
5
Eh bien, c'est une question intéressante, et pour être honnête, je ne sais pas vraiment pourquoi ils appellent les deux méthodes par exemple TextView. Je pensais qu'ils voulaient peut-être dessiner le Viewpour la dernière fois avant de modifier ses paramètres liés à la mise en page, mais cela n'a pas vraiment de sens si nous pensons que ces appels sont invoqués dans un ordre différent (et ils appellent invalidate()juste après requestLayout()dans TextViewainsi que). Peut-être que cela mérite une autre question sur StackOverflow :)?
Bartek Lipinski
14
(1/2) : Je ne pense pas que vous compreniez ces deux méthodes ( invalidate()et requestLayout()) correctement. Le but de ces méthodes est de dire Viewquelle sorte d' invalidation (comme vous l'avez appelée) s'est produite. Ce n'est pas comme si le Viewdécidait du chemin à suivre après avoir appelé l'une de ces méthodes. La logique derrière le choix du View-lifecycle-path est le choix de la méthode appropriée pour s'appeler. S'il y a quelque chose lié au changement de taille - requestLayout()devrait être appelé, s'il n'y a qu'un changement visuel sans changement de taille - vous devriez appeler invalidate().
Bartek Lipinski
11
(2/2): Si vous changez la taille de votre Viewd'une certaine manière, par exemple, vous obtenez le courantLayoutParams de a Viewet vous les modifiez, mais n'appelez PAS l' un requestLayoutou l' autre setLayoutParams(qui appelle en requestLayoutinterne), alors vous pouvez appeler invalidate()autant que vous le souhaitez, et le Viewne passera pas par le processus de mise en page de la mesure et ne changera donc pas sa taille. Si vous ne dites pas Viewque sa taille a changé (avec un requestLayoutappel de méthode), alors le Viewsupposera que ce n'est pas le cas, et le onMeasureet onLayoutne sera pas appelé.
Bartek Lipinski
2
@tcox plus ici -> stackoverflow.com/questions/35279374/…
Sotti
125

invalidate()

L'appel invalidate()est effectué lorsque vous souhaitez planifier un rafraîchissement de la vue. Il en résultera onDrawéventuellement un appel (bientôt, mais pas immédiatement). Un exemple de quand une vue personnalisée l'appellerait est quand une propriété de couleur de texte ou d'arrière-plan a changé.

La vue sera redessinée mais la taille ne changera pas.

requestLayout()

Si quelque chose dans votre vue change et affecte la taille, vous devez appeler requestLayout(). Cela déclenchera onMeasureet onLayoutnon seulement pour ce point de vue , mais tout le chemin jusqu'à la ligne pour les vues parent.

Il requestLayout()n'est pas garanti qu'unonDraw appel aboutisse à un (contrairement à ce que le diagramme de la réponse acceptée implique), il est donc généralement combiné avec invalidate().

invalidate();
requestLayout();

Un exemple de ceci est lorsqu'une étiquette personnalisée voit sa propriété de texte modifiée. L'étiquette changerait de taille et devrait donc être remesurée et redessinée.

forceLayout()

Lorsqu'il y a un requestLayout()qui est appelé sur un groupe de vues parent, il n'est pas nécessaire de remesurer et de relayer ses vues enfants. Cependant, si un enfant doit être inclus dans la remesure et le relais, vous pouvez faire appel forceLayout()à l'enfant. forceLayout()ne fonctionne sur un enfant que s'il se produit en conjonction avec un requestLayout()sur son parent direct. L'appel forceLayout()en lui-même n'aura aucun effet car il ne déclenche pas d' requestLayout()arborescence de vues.

Lisez ces questions et réponses pour une description plus détaillée de forceLayout().

Une étude plus approfondie

Suragch
la source
1
mais cela n'aurait-il pas plus de sens d'appeler d'abord requestLayout () puis d'invalider ()?
scholt
2
@scholt, pour autant que je sache, l'ordre n'a pas d'importance, vous pouvez donc appeler requestLayout()avant invalidate()si vous le souhaitez. Ni l'un ni l'autre ne fait de mise en page ou ne dessine immédiatement. Au contraire, ils définissent des drapeaux qui finiront par entraîner un relais et un redessiner.
Suragch
27

Ici vous pouvez trouver une réponse: http://developer.android.com/guide/topics/ui/how-android-draws.html

Pour moi, un appel à invalidate()actualise uniquement la vue et un appel à requestLayout()actualiser la vue et à calculer la taille de la vue à l'écran.

François BOURLIEUX
la source
3
Que diriez-vous de forceLayout () alors?
sdabet
@fiddler, cette méthode définit juste deux indicateurs: PFLAG_FORCE_LAYOUT et PFLAG_INVALIDATED
suitianshi
7
@suitianshi Quelles sont alors les conséquences de la pose des drapeaux?
Sergey
1
@Sergey La conséquence semble être que cet indicateur remplace le cache de mesure (les vues utilisent le cache pour mesurer plus rapidement dans le futur pour la même MeasureSpec); voir la ligne 18783 de View.java ici: github.com/android/platform_frameworks_base/blob/master/core/…
RhetoricalRuvim
3

vous utilisez invalidate () sur une vue que vous souhaitez redessiner, il rendra son onDraw (Canvas c) invoqué, et requestLayout () effectuera à nouveau le rendu de la mise en page (phase de mesure et phase de positionnement). Vous devez l'utiliser si vous modifiez la taille de la vue enfant au moment de l'exécution, mais uniquement dans des cas particuliers comme les contraintes de la vue parent (par là, je veux dire que la hauteur ou la largeur du parent est WRAP_CONTENT et correspond donc à mesurer les enfants avant de pouvoir les envelopper à nouveau)

Nativ
la source
3

Cette réponse n'est pas correcte forceLayout().

Comme vous pouvez le voir dans le code,forceLayout() il marque simplement la vue comme "nécessite un relais" mais il ne programme ni ne déclenche ce relais. Le relais ne se produira qu'à un moment donné dans le futur, le parent de la vue a été aménagé pour une autre raison.

Il y a aussi un problème beaucoup plus important lors de l'utilisation forceLayout()et requestLayout():

Disons que vous avez appelé forceLayout()une vue. Désormais, lors de l'appel requestLayout()à un descendant de cette vue, Android appellera récursivement requestLayout()les ancêtres de ce descendant. Le problème est que cela arrêtera la récursion à la vue sur laquelle vous avez appelé forceLayout(). Ainsi, l' requestLayout()appel n'atteindra jamais la racine de la vue et ne planifiera donc jamais une passe de mise en page. Un sous-arbre entier de la hiérarchie de vues attend une mise en page et l'appel requestLayout()sur une vue de ce sous-arbre ne provoquera pas de mise en page. N'appeler que requestLayout()sur n'importe quelle vue en dehors de ce sous-arbre rompra le charme.

Je considérerais que l'implémentation de forceLayout()(et comment elle affecte requestLayout()est cassée et vous ne devriez jamais utiliser cette fonction dans votre code.

fluidsonic
la source
1
Salut! Comment avez-vous inventé ce sort ? Est-ce documenté quelque part?
azizbekian
1
Malheureusement, ce n'est pas documenté et probablement même pas prévu. J'ai compris cela en examinant le code Viewet en rencontrant le problème moi-même et en le déboguant.
fluidsonic
Ensuite, je suis curieux de savoir quel est le but de l' forceLayout()API: il ne force pas réellement une mise en page, mais change simplement un drapeau qui est observé dansonMeasure() , mais onMeasure()ne sera pas appelé à moins requestLayout()qu'un explicite ne View#measure()soit appelé. Cela signifie que cela forceLayout()devrait être associé requestLayout(). D'un autre côté, pourquoi alors jouer forceLayout()si j'ai encore besoin de jouer requestLayout()?
azizbekian
@azizbekian non, vous n'êtes pas obligé. requestLayout()fait tout ce qui fait forceLayout()aussi.
fluidsonic
1
@Suragch a raté celui-là, merci! Analyse très intéressante. L'utilisation prévue de a du forceLayoutsens. Donc, au final, c'est juste très mal nommé et documenté.
fluidsonic
0

invalidate()---> onDraw()depuis le fil de l'interface utilisateur

postInvalidate()---> onDraw()du fil d'arrière-plan

requestLayout()---> onMeasure()et onLayout()ET PAS nécessairement onDraw()

  • IMPORTANT : l'appel de cette méthode n'affecte pas l'enfant de la classe appelée.

forceLayout()---> onMeasure()et onLayout() JUST SI le parent direct a appelé requestLayout().

Ehsan Mashhadi
la source