2017-01-25 1 views
2

J'ai remarqué que lorsque OnElementPropertyChanged est déclenché sur un VisualElement comme un BoxView, les propriétés de la vue de la plateforme sous-jacente ne sont pas mises à jour à ce moment-là.Comment puis-je savoir quand une vue est terminée?

Je veux savoir quand la vue de la plate-forme correspondante de VisualElement est terminée rendu, quelque chose comme:

this.someBoxView.ViewHasRendered += (sender, e) => { // Here I would know underlying UIView (in iOS) has finished rendering };

En regardant à travers un code intérieur de Xamarin.Forms, à savoir VisualElementRenderer.cs, il semblerait que je pourrait déclencher un événement après la fin de OnPropertyChanged. Quelque chose comme:

protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) 
{ 
    if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) 
     SetBackgroundColor(Element.BackgroundColor); 
    else if (e.PropertyName == Layout.IsClippedToBoundsProperty.PropertyName) 
     UpdateClipToBounds(); 
    else if (e.PropertyName == PlatformConfiguration.iOSSpecific.VisualElement.BlurEffectProperty.PropertyName) 
     SetBlur((BlurEffectStyle)Element.GetValue(PlatformConfiguration.iOSSpecific.VisualElement.BlurEffectProperty)); 

    // Raise event 
    VisualElement visualElement = sender as VisualElement; 
    visualElement.ViewHasRendered();   
} 

Naturellement il y a un peu plus de complexité à l'ajout d'un événement à la classe VisualElement, car il aurait besoin d'être sous-classé. Mais je pense que vous pouvez voir ce que je veux.

En piquant, j'ai remarqué des propriétés sur VisualElement comme IsInNativeLayout. Mais cela ne semble être implémenté dans Win/WP8. En outre, UpdateNativeWidget sur VisualElementRenderer également, mais je ne peux pas trouver la bonne façon de les exploiter.

Des idées? Très apprécié.

Répondre

2

TL;DR: Fuyez, ne vont pas dans cette voie ...

Sur iOS tout ce qui affiche le contenu à l'écran se produit dans un UIView (ou sous-classe) et drawRect: est la méthode qui fait le dessin. Donc, lorsque drawRect: est terminé, le dessin UIView est terminé.

Remarque: Des animations pourraient se produire et vous pourriez voir des centaines de cycles de rendu terminés. Vous devrez peut-être vous accrocher dans le gestionnaire de complétion de chaque animation pour déterminer quand les choses sont vraiment "rendues".

Note: Le dessin se fait hors de l'écran et en fonction de l'iDevice, l'écran de rafraîchissement Hz, 60FPS pourrait 30fps ou dans le cas de l'iPad Pro est variable (30-60hz) ...

Exemple :

public class CustomRenderer : ButtonRenderer 
{ 
    public override void Draw(CGRect rect) 
    { 
     base.Draw(rect); 
     Console.WriteLine("A UIView is finished w/ drawRect: via msg/selector)"); 
    } 
} 

Sur Android la façon commune de tirer le contenu est par une sous-classe ou View, vous pouvez obtenir une surface, tirer/Bilt via OpenGL à un écran, etc ... et que pourrait pas être dans un View, mais pour y notre cas d'utilisation, pensez View s ..

View s ont Draw méthodes que vous pouvez remplacer, et vous pouvez également brancher dans ViewTreeObserver et surveiller OnPreDraw et OnDraw, etc, etc, etc ... Parfois, vous devez surveiller la Le parent de View (ViewGroup) pour déterminer quand le dessin va être terminé ou quand il est terminé.

De plus, tous les widgets standard sont complètement gonflés via des ressources xml et optimisés pour que vous ne voyiez jamais d'appel de méthode Draw/OnDraw (Remarque: vous devriez toujours recevoir un appel OnPreDraw si vous le forcez).

différents View s/s Widget se comporter différemment et il aucun moyen d'examiner tous les défis que vous aurez à déterminer si un View est vraiment fait « rendu » ...

Exemple:

public class CustomButtonRenderer : Xamarin.Forms.Platform.Android.AppCompat.ButtonRenderer, 
    ViewTreeObserver.IOnDrawListener, ViewTreeObserver.IOnPreDrawListener 
{ 
    public bool OnPreDraw() // IOnPreDrawListener 
    { 
     System.Console.WriteLine("A View is *about* to be Drawn"); 
     return true; 
    } 

    public void OnDraw() // IOnDrawListener 
    { 
     System.Console.WriteLine("A View is really *about* to be Drawn"); 
    } 

    public override void Draw(Android.Graphics.Canvas canvas) 
    { 
     base.Draw(canvas); 
     System.Console.WriteLine("A View was Drawn"); 
    } 

    protected override void Dispose(bool disposing) 
    { 
     Control?.ViewTreeObserver.RemoveOnDrawListener(this); 
     Control?.ViewTreeObserver.RemoveOnPreDrawListener(this); 
     base.Dispose(disposing); 
    } 

    protected override void OnElementChanged(ElementChangedEventArgs<Button> e) 
    {   
     base.OnElementChanged(e); 
     if (e.OldElement == null) 
     { 
      Control?.SetWillNotDraw(false); // force the OnPreDraw to be called :-(
      Control?.ViewTreeObserver.AddOnDrawListener(this); // API16+ 
      Control?.ViewTreeObserver.AddOnPreDrawListener(this); // API16+ 
      System.Console.WriteLine($"{Control?.ViewTreeObserver.IsAlive}"); 
     } 
    } 

} 

Autres:

Remarque: les optimisations de mise en page, la mise en cache de contenu, la mise en cache GPU, l'accélération matérielle activée dans le widget/affichage, etc. peuvent empêcher les méthodes Draw d'être appelées ed ...

Note: L'animation, les effets, etc ... peuvent faire que ces méthodes s'appellent beaucoup, beaucoup, beaucoup fois avant qu'une zone de l'écran soit complètement terminée et prête pour l'interaction de l'utilisateur. Note personnelle: Je suis allé dans cette voie une fois en raison d'une exigence de client fou, et après avoir cogné ma tête sur le bureau pendant un certain temps, un examen de l'objectif réel de cette zone de l'interface utilisateur a été fait et je J'ai réécrit l'exigence et je n'ai plus jamais essayé cela ;-)

+0

- Il m'a fallu une minute pour "cliquer", mais cliquez dessus! J'ai fini par prendre vos conseils et courir vite et fort loin de cette approche. –

2

Je vais répondre à ma propre question, j'espère que la solution aidera quelqu'un qui se battra avec ce problème dans le futur.

Suivez les conseils de @ SushiHangover et ne vous éloignez pas de quelque chose comme ça. (Bien que sa recommandation fonctionne et est saine). Tenter d'écouter/être informé lorsque la plate-forme a fini de rendre une vue, est une idée terrible. Comme le mentionne @SushiHangover, il y a simplement trop de choses qui peuvent mal tourner.

pin code ui

Alors ce qui m'a amené dans cette voie?

J'ai besoin d'une interface utilisateur de code PIN similaire à celle d'iOS pour déverrouiller votre appareil, et dans beaucoup d'autres applications. Quand un utilisateur appuie sur un chiffre sur le pavé, je veux mettre à jour la "boîte" d'affichage correspondante (boîtes au-dessus du pavé). Lorsque l'utilisateur entre le dernier chiffre, je veux que la dernière "boîte" soit remplie, dans mon cas un changement de couleur d'arrière-plan, puis l'exécution pour continuer dans laquelle la vue passerait à l'écran suivant dans le flux de travail.

Un problème est survenu lorsque j'ai essayé de définir la propriété BackgroundColor sur la quatrième case, puis de faire passer l'écran. Cependant, puisque l'exécution n'attend pas que la propriété change les transitions d'écran avant que la modification soit rendue. Naturellement, cela crée une mauvaise expérience utilisateur.

Pour tenter de résoudre le problème, j'ai pensé "Oh, je dois simplement être averti lorsque la vue a été rendue". Pas intelligent, comme je l'ai mentionné.

Après avoir regardé quelques implémentations C d'interfaces similaires, je me rends compte que la solution est très simple. L'interface utilisateur doit attendre un bref instant et permettre à la propriété BackgroundColor de s'afficher.

Solution

private async Task HandleButtonTouched(object parameter)  
{ 
    if (this.EnteredEmployeeCode.Count > 4) 
     return;   

    string digit = parameter as string; 
    this.EnteredEmployeeCode.Add(digit); 
    this.UpdateEmployeeCodeDigits(); 

    if (this.EnteredEmployeeCode.Count == 4) { 
     // let the view render on the platform 
     await Task.Delay (1); 

     this.SignIn(); 
    } 
} 

Un petit retard milliseconde suffit de laisser la vue de terminer le rendu sans avoir à passer dans un trou de lapin géant et d'essayer d'écouter pour elle.

Merci encore à @SushiHangover pour sa réponse détaillée. Tu es génial mon ami! : D