6
votes

Comment obtenir Gmail comme le zoom par pincement et le défilement de l'en-tête

Comment obtenir un comportement de zoom par pincement comme l'application Gmail? J'ai mis le conteneur d'en-tête dans ScrollView suivi de WebView . Il semble que ce soit un comportement très complexe.

Voici sans zoom.

entrez la description de l'image ici

Lorsque nous pincons le conteneur supérieur Webview fait défiler vers le haut selon le zoom:

entrez la description de l'image ici

Pour l'instant, voici mon initiales:

  <?xml version="1.0" encoding="utf-8"?>
  <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:background="@color/white">

    <RelativeLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:background="@color/white"
       android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@color/white">

        </android.support.v7.widget.Toolbar>
    </android.support.design.widget.AppBarLayout>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/appbar">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <FrameLayout
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:background="@color/colorPrimary"></FrameLayout>

            <WebView
                android:id="@+id/webView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@color/white"
                android:scrollbars="none" />
        </LinearLayout>
    </ScrollView>
  </RelativeLayout>
</FrameLayout>


1 commentaires

Pouvez-vous s'il vous plaît vérifier ma réponse et me faire savoir si cela aide? Merci. :)


3 Réponses :


3
votes

GMail utilise une WebView Chrome avec le zoom par pincement activé. le zoom s'applique uniquement à la vue de fil unique. WebSettings setBuiltInZoomControls () a> est par défaut false et setDisplayZoomControls () est par défaut true . en changeant les deux, le zoom fonctionne et aucune commande de zoom n'est affichée:

webView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
    @Override
    public void onScrollChange(View view, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
        if(scrollY == 0) {
            /* remove the ActionBar's bottom shadow  */
        } else {
            /* apply the ActionBar's bottom shadow */
        }
    }
}

et cette barre d'outils est un style transparent ActionBar , avec le style windowActionBarOverlay défini sur true :

Indicateur indiquant si la barre d'action de cette fenêtre doit superposer le contenu de l'application.


l'ombre inférieure de l'ActionBar est supprimée dans la position de défilement la plus haute. celui-ci écoute les événements de défilement vertical et non les gestes de mise à l'échelle. cet effet fonctionne à peu près comme ceci (initialement cette ombre doit être cachée):

webview.getSettings().setBuiltInZoomControls(true);
webview.getSettings().setDisplayZoomControls(false);

selon la fréquence à laquelle le OnScrollChangeListener est déclenché, même en vérifiant scrollY == 0 et scrollY == 1 peuvent déjà suffire pour activer et désactiver l'ombre.


lors de la mise à l'échelle, cela semble être un ScaleGestureDetector.SimpleOnScaleGestureListener (voir la documentation ), où .getScaleFactor () est utilisé pour animer la position supérieure verticale de la "barre d'outils" secondaire, qui la pousse ensuite en dehors du port de vue visible. et cette "barre d'outils" secondaire semble être un DrawerLayout vertical imbriqué - qui ne peut pas être déplacé manuellement - c'est pourquoi il se déplace aussi en douceur ... un DrawerLayout n'est pas limité à être un tiroir horizontal; et je pense que c'est la réponse.

Modifier : Je suis relativement certain maintenant, qu'il s'agit d'AndroidX avec MDC Motion .


5 commentaires

Ce n'est pas ce que je recherche. Comment mettre l'en-tête, par exemple conteneur de titre de courrier électronique qui va selon le zoom avant, veuillez vérifier le comportement sur l'application Gmail réelle. Je connais très bien la vue Web intégrée au zoom.


@AshishSahu J'ai vérifié l'application GMail et cela répond précisément à votre question - que vous l'aimiez ou non. vous compliquez probablement beaucoup trop les choses, qui sont plutôt simples. il vous suffit d'ajouter la hauteur de la barre d'outils comme remplissage supplémentaire dans le HTML (soit côté serveur - soit côté client avec JavaScript), afin que la barre d'outils ne puisse jamais couvrir le contenu HTML lors du chargement. alors qu'il devrait être évident, que le contenu HTML ne doit pas être initialement localisé, là où se trouve la barre d'outils semi-transparente. c'est parce que les deux, la barre d'outils et le contenu HTML, doivent commencer tout en haut de l'écran.


La barre d'outils n'est pas un problème. veuillez oublier la transparence de la barre d'outils. Le problème est lorsque l'utilisateur pince la vue Web, le titre / en-tête supérieur doit s'éloigner. Je ne sais pas comment vous expliquer correctement. Mais veuillez ignorer la transparence de la barre d'outils.


@AshishSahu a étendu ma réponse, qui couvre maintenant également cet effet. depuis que j'ai utilisé "afficher les limites de mise en page", je suis maintenant certain que la barre d'outils secondaire est un enfant de ce RelativeLayout , et non du contenu dans WebView .


le nœud racine de cette mise en page est même un DrawerLayout ... car le tiroir de navigation y est disponible. de même que la barre d'outils secondaire se comporte, c'est assez similaire au NavigationView d'un DrawerLayout ... juste vertical et la position est contrôlée différemment; mais il est poussé à l'extérieur du port de vue visible de la même manière.



1
votes

Je pense avoir compris votre question. Vous souhaitez pousser la ligne d'objet vers le haut et les autres e-mails vers le bas lorsqu'un e-mail est développé. J'ai essayé de mettre en œuvre l'idée d'afficher un e-mail dans l'application Gmail. Je pense que je suis très proche de la solution car la poussée n'est pas assez fluide. Cependant, je voulais partager la réponse ici pour présenter ma réflexion sur votre question.

J'ai créé un référentiel GitHub à partir duquel vous pouvez voir mon implémentation. J'ai également ajouté un readme pour expliquer l'idée générale.

J'ai essayé d'implémenter le tout en utilisant un RecyclerView avec différents ViewType s. J'ai ajouté un adaptateur qui ressemble à ce qui suit.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <WebView
        android:id="@+id/email_details_web_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/white"
        android:scrollbars="none"
        tools:ignore="WebViewLayout" />
</RelativeLayout>

Et l'élément de liste développé contient un WebView qui a un wrapper qui est wrap_content . Vous trouverez la mise en page suivante dans le list_item_expanded.xml .

public class RecyclerViewWithHeaderFooterAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private static final int HEADER_VIEW = 1;
    private static final int GROUPED_VIEW = 2;
    private static final int EXPANDED_VIEW = 3;

    private ArrayList<Integer> positionTracker; // Take any list that matches your requirement.
    private Context context;
    private ZoomListener zoomListener;

    // Define a constructor
    public RecyclerViewWithHeaderFooterAdapter(Context context, ZoomListener zoomListener) {
        this.context = context;
        this.zoomListener = zoomListener;
        positionTracker = Utilities.populatePositionsWithDummyData();
    }

    // Define a ViewHolder for Header view
    public class HeaderViewHolder extends ViewHolder {
        public HeaderViewHolder(View itemView) {
            super(itemView);
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // Do whatever you want on clicking the item
                }
            });
        }
    }

    // Define a ViewHolder for Expanded view
    public class ExpandedViewHolder extends ViewHolder {
        public ExpandedViewHolder(View itemView) {
            super(itemView);
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // Do whatever you want on clicking the item
                }
            });
        }
    }

    // Define a ViewHolder for Expanded view
    public class GroupedViewHolder extends ViewHolder {
        public GroupedViewHolder(View itemView) {
            super(itemView);
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // Do whatever you want on clicking the item
                }
            });
        }
    }

    // And now in onCreateViewHolder you have to pass the correct view
    // while populating the list item.
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View v;

        if (viewType == EXPANDED_VIEW) {
            v = LayoutInflater.from(context).inflate(R.layout.list_item_expanded, parent, false);
            ExpandedViewHolder vh = new ExpandedViewHolder(v);
            return vh;
        } else if (viewType == HEADER_VIEW) {
            v = LayoutInflater.from(context).inflate(R.layout.list_item_header, parent, false);
            HeaderViewHolder vh = new HeaderViewHolder(v);
            return vh;
        } else {
            v = LayoutInflater.from(context).inflate(R.layout.list_item_grouped, parent, false);
            GroupedViewHolder vh = new GroupedViewHolder(v);
            return vh;
        }
    }

    // Now bind the ViewHolder in onBindViewHolder
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

        try {
            if (holder instanceof ExpandedViewHolder) {
                ExpandedViewHolder vh = (ExpandedViewHolder) holder;
                vh.bindExpandedView(position);
            } else if (holder instanceof GroupedViewHolder) {
                GroupedViewHolder vh = (GroupedViewHolder) holder;
            } else if (holder instanceof HeaderViewHolder) {
                HeaderViewHolder vh = (HeaderViewHolder) holder;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // Now the critical part. You have return the exact item count of your list
    // I've only one footer. So I returned data.size() + 1
    // If you've multiple headers and footers, you've to return total count
    // like, headers.size() + data.size() + footers.size()

    @Override
    public int getItemCount() {
        return DEMO_LIST_SIZE; // Let us consider we have 6 elements. This can be replaced with email chain size
    }

    // Now define getItemViewType of your own.
    @Override
    public int getItemViewType(int position) {
        if (positionTracker.get(position).equals(HEADER_VIEW)) {
            // This is where we'll add the header.
            return HEADER_VIEW;
        } else if (positionTracker.get(position).equals(GROUPED_VIEW)) {
            // This is where we'll add the header.
            return GROUPED_VIEW;
        } else if (positionTracker.get(position).equals(EXPANDED_VIEW)) {
            // This is where we'll add the header.
            return EXPANDED_VIEW;
        }

        return super.getItemViewType(position);
    }

    // So you're done with adding a footer and its action on onClick.
    // Now set the default ViewHolder for NormalViewHolder
    public class ViewHolder extends RecyclerView.ViewHolder {
        // Define elements of a row here
        public ViewHolder(View itemView) {
            super(itemView);
            // Find view by ID and initialize here
        }

        public void bindExpandedView(final int position) {
            // bindExpandedView() method to implement actions
            final WebView webView = itemView.findViewById(R.id.email_details_web_view);
            webView.getSettings().setBuiltInZoomControls(true);
            webView.getSettings().setDisplayZoomControls(false);
            webView.loadUrl("file:///android_asset/sample.html");
            webView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
                @Override
                public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                    zoomListener.onZoomListener(position);
                }
            });
        }
    }
}

J'ai essayé d'ajouter des données factices pour l'expérience et donc la classe Utility a été écrite. Le RecyclerView est configuré pour avoir une mise en page inversée car c'est l'attente courante d'afficher une conversation dans un RecyclerView .

L'idée clé est de scrollToPosition lorsque la WebView est développée. Pour donner l'impression que les articles sont poussés vers le haut et vers le bas pour s'adapter à l'expansion. J'espère que vous avez l'idée.

J'ajoute ici quelques captures d'écran pour vous donner une idée de ce que j'ai pu réaliser jusqu'à présent.

 entrez la description de l'image ici

Veuillez noter que le mécanisme de poussée n'est pas fluide. Je vais y travailler. Cependant, j'ai pensé que je devrais le poster ici car cela pourrait vous aider dans votre réflexion. Je voudrais vous suggérer de cloner le référentiel et d'exécuter l'application pour vérifier l'implémentation globale. Faites-moi savoir s'il y a des commentaires.


2 commentaires

cela semble à peine lié à la vue détaillée du message GMail. il y a certainement deux barres d'outils principales; un collant pour l'application et un mobile pour le message. d'où viennent tous ces éléments "message non développé"?


@MartinZeitler Ceci est juste un espace réservé pour les messages groupés. Parfois, plusieurs e-mails sont enchaînés et groupés, ce qui est indiqué par l'élément de liste «message non développé». Cliquer dessus développera le message qui pourrait avoir le même comportement de zoom par pincement. Je viens d'ajouter un espace réservé pour vous donner une idée que plusieurs éléments d'affichage tels que l'application Gmail sont possibles. Faites-moi savoir si cela dissipe votre confusion.



0
votes

J'ai essayé d'implémenter ce comportement en utilisant webview.setBuiltInZoomControls () et ScaleGestureDetector en remplaçant WebView et en transmettant tous les événements de mouvement au détecteur. Cela fonctionne, mais le détecteur d'échelle et le zoom fonctionnent un peu différemment et l'UX s'avère être terrible.

Si vous regardez attentivement la mise en œuvre du zoom Gmail et un zoom de la vue Web, vous verrez qu'ils sont différents. Je pense que le zoom Gmail est basé sur view.setScaleX () et view.setScaleY () . Vous pouvez obtenir un comportement de base en sous-classant WebView et en suivant ce guide . Vous devrez peut-être également appeler view.setPivotX () et view.setPivotY () . La mise en œuvre de Gmail est plus complexe car elle comporte un défilement et semble faire défiler le contenu vers le haut pendant que vous effectuez un zoom avant. Vous pouvez essayer d'utiliser une bibliothèque qui implémente le conteneur zoomable et prend en charge le défilement, comme celui-ci . Cependant, je n'ai pas pu le faire fonctionner correctement avec WebView .

Dans l'ensemble, c'est une tâche complexe et vous devez jouer vous-même avec la mise en œuvre pour faire des compromis et obtenir une UX similaire mais décente.


0 commentaires