web-dev-qa-db-fra.com

Comment imiter la listeAfficher les articles collants sur l'application Contacts de Lollipop?

Contexte

Google a récemment publié une nouvelle version d'Android, qui dispose d'une application de contact ressemblant à ceci:

enter image description here

Le problème

Je dois imiter ce genre de liste, mais je ne peux pas savoir comment bien le faire.

Cela consiste en 3 composants principaux:

  1. les titres collants, qui restent aussi longtemps que le contact principal a le nom qui commence par cette lettre.

  2. Les photos circulaires ou cercle + lettre (pour les contacts qui n'ont pas de photos)

  3. le PagerTitleStrip, qui a un espacement uniforme entre les éléments et essaie de les afficher tous à l'écran (ou permet de les faire défiler si nécessaire).

Ce que j'ai trouvé

  1. Il existe de nombreuses bibliothèques tierces, mais elles gèrent les en-têtes collants, qui font partie des éléments de la liste même. Ils n'ont pas le titre à gauche, mais en haut des éléments.

    Ici, le côté gauche se déplace différemment du côté droit. Cela peut coller, et s'il n'y a pas besoin de coller (parce que la section a un élément, par exemple), il défile.

  2. J'ai remarqué que pour les photos circulaires, je peux utiliser la nouvelle classe (?) Appelée " RoundedBitmapDrawableFactory " (qui crée " RoundedBitmapDrawable "). . Cela me permettra probablement de mettre une photo dans une forme circulaire bien. Cependant, cela ne fonctionnera pas pour les lettres (utilisées pour les contacts qui n’ont pas de photo), mais je pense que je peux mettre un textView au-dessus et définir un arrière-plan de couleur.

    En outre, j'ai remarqué que pour pouvoir utiliser correctement "RoundedBitmapDrawable" (pour le rendre véritablement circulaire), je dois lui fournir un bitmap de taille carrée. Sinon, il aura une forme étrange.

  3. J'ai essayé d'utiliser " setTextSpacing " pour minimiser l'espace entre les éléments, mais cela ne semble pas fonctionner. Je ne pouvais pas non plus trouver un moyen de personnaliser/personnaliser le PagerTitleStrip pour qu'il ressemble à celui de l'application Contacts.

    J'ai aussi essayé d'utiliser " PagerTabStrip ", mais cela n'a pas non plus aidé.

La question

Comment pouvez-vous imiter la manière dont Google a implémenté cet écran?

Plus précisement:

  1. Comment puis-je faire en sorte que le côté gauche se comporte comme sur l'application Contacts?

  2. Est-ce la meilleure façon de mettre en œuvre la photo circulaire? Dois-je vraiment recadrer le bitmap dans un carré avant d'utiliser le dessinable spécial? Existe-t-il des directives de conception pour les couleurs à utiliser? Existe-t-il un moyen plus officiel d'utiliser la cellule de texte en cercle?

  3. Comment concevez-vous le PagerTitleStrip pour avoir le même look et la même sensation que sur l'application Contacts?


Projet Github

EDIT: pour # 1 et # 2, j'ai fait un projet sur Github, here . Malheureusement, j'ai aussi trouvé un bogue important, alors j'ai aussi publié à ce sujet ici .

17
android developer

ok, j'ai réussi à résoudre tous les problèmes sur lesquels j'ai écrit:

1.J'ai modifié le fonctionnement de la bibliothèque tierce (je ne me souviens pas d'où j'ai obtenu la bibliothèque, mais celui-ci est très similaire), en modifiant la disposition de chaque ligne , afin que l'en-tête soit à gauche du contenu lui-même. C'est juste une question de fichier XML de mise en page et vous avez pratiquement terminé. Je vais peut-être publier une bibliothèque Nice pour ces deux solutions.

2.C'est la vue que j'ai faite. Ce n'est pas une implémentation officielle (je n'en ai trouvé aucune), alors j'ai fait quelque chose par moi-même. Cela peut être plus efficace, mais au moins c'est assez facile à comprendre et aussi très flexible:

public class CircularView extends ViewSwitcher {
    private ImageView mImageView;
    private TextView mTextView;
    private Bitmap mBitmap;
    private CharSequence mText;
    private int mBackgroundColor = 0;
    private int mImageResId = 0;

    public CircularView(final Context context) {
        this(context, null);
    }

    public CircularView(final Context context, final AttributeSet attrs) {
        super(context, attrs);
        addView(mImageView = new ImageView(context), new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
                LayoutParams.MATCH_PARENT, Gravity.CENTER));
        addView(mTextView = new TextView(context), new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
                LayoutParams.MATCH_PARENT, Gravity.CENTER));
        mTextView.setGravity(Gravity.CENTER);
        if (isInEditMode())
            setTextAndBackgroundColor("", 0xFFff0000);
    }

    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        final int measuredWidth = getMeasuredWidth();
        final int measuredHeight = getMeasuredHeight();
        if (measuredWidth != 0 && measuredHeight != 0)
            drawContent(measuredWidth, measuredHeight);
    }

    @SuppressWarnings("deprecation")
    private void drawContent(final int measuredWidth, final int measuredHeight) {
        ShapeDrawable roundedBackgroundDrawable = null;
        if (mBackgroundColor != 0) {
            roundedBackgroundDrawable = new ShapeDrawable(new OvalShape());
            roundedBackgroundDrawable.getPaint().setColor(mBackgroundColor);
            roundedBackgroundDrawable.setIntrinsicHeight(measuredHeight);
            roundedBackgroundDrawable.setIntrinsicWidth(measuredWidth);
            roundedBackgroundDrawable.setBounds(new Rect(0, 0, measuredWidth, measuredHeight));
        }
        if (mImageResId != 0) {
            mImageView.setBackgroundDrawable(roundedBackgroundDrawable);
            mImageView.setImageResource(mImageResId);
            mImageView.setScaleType(ScaleType.CENTER_INSIDE);
        } else if (mText != null) {
            mTextView.setText(mText);
            mTextView.setBackgroundDrawable(roundedBackgroundDrawable);
            // mTextView.setPadding(0, measuredHeight / 4, 0, measuredHeight / 4);
            mTextView.setTextSize(measuredHeight / 5);
        } else if (mBitmap != null) {
            mImageView.setScaleType(ScaleType.FIT_CENTER);
            mImageView.setBackgroundDrawable(roundedBackgroundDrawable);
            mBitmap = ThumbnailUtils.extractThumbnail(mBitmap, measuredWidth, measuredHeight);
            final RoundedBitmapDrawable roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(getResources(),
                    mBitmap);
            roundedBitmapDrawable.setCornerRadius((measuredHeight + measuredWidth) / 4);
            mImageView.setImageDrawable(roundedBitmapDrawable);
        }
        resetValuesState(false);
    }

    public void setTextAndBackgroundColor(final CharSequence text, final int backgroundColor) {
        resetValuesState(true);
        while (getCurrentView() != mTextView)
            showNext();
        this.mBackgroundColor = backgroundColor;
        mText = text;
        final int height = getHeight(), width = getWidth();
        if (height != 0 && width != 0)
            drawContent(width, height);
    }

    public void setImageResource(final int imageResId, final int backgroundColor) {
        resetValuesState(true);
        while (getCurrentView() != mImageView)
            showNext();
        mImageResId = imageResId;
        this.mBackgroundColor = backgroundColor;
        final int height = getHeight(), width = getWidth();
        if (height != 0 && width != 0)
            drawContent(width, height);
    }

    public void setImageBitmap(final Bitmap bitmap) {
        setImageBitmapAndBackgroundColor(bitmap, 0);
    }

    public void setImageBitmapAndBackgroundColor(final Bitmap bitmap, final int backgroundColor) {
        resetValuesState(true);
        while (getCurrentView() != mImageView)
            showNext();
        this.mBackgroundColor = backgroundColor;
        mBitmap = bitmap;
        final int height = getHeight(), width = getWidth();
        if (height != 0 && width != 0)
            drawContent(width, height);
    }

    private void resetValuesState(final boolean alsoResetViews) {
        mBackgroundColor = mImageResId = 0;
        mBitmap = null;
        mText = null;
        if (alsoResetViews) {
            mTextView.setText(null);
            mTextView.setBackgroundDrawable(null);
            mImageView.setImageBitmap(null);
            mImageView.setBackgroundDrawable(null);
        }
    }

    public ImageView getImageView() {
        return mImageView;
    }

    public TextView getTextView() {
        return mTextView;
    }

}

3.J'ai trouvé une bibliothèque Nice qui le fait, appelée PagerSlidingTabStrip . Je n'ai pas trouvé de moyen officiel de dénommer l'indigène.

Vous pouvez également consulter l'exemple de Google, disponible directement dans Android-Studio, appelé "SlidingTabLayout". Cela montre comment c'est fait.

EDIT: une meilleure bibliothèque pour # 3 est here , appelée aussi "PagerSlidingTabStrip".

6
android developer

Vous pouvez faire ce qui suit:

  1. Sur le côté le plus à gauche de votre RecyclerView, crée un TextView qui tiendra l'index de lettre;
  2. En haut de la vue Recycler (dans la mise en page qui le recouvre), placez un TextView afin de couvrir celui que vous avez créé à l'étape 1, il s'agira du texte persistant.
  3. Ajoutez un OnScrollListener dans votre RecyclerView. Sur la méthode onScrolled (), définissez le TextView créé à l'étape 2 pour le texte de référence extrait de firstVisibleRow. Jusqu'ici, vous aurez un index stiky, sans les effets de la transition;
  4. Pour ajouter l'effet de transition d'ouverture/fermeture en fondu, développez une logique qui vérifie si l'élément précédent de currentFirstVisibleItem est le dernier de la liste de lettres précédente ou si le second second élément visible est le premier de la nouvelle lettre. Sur la base de ces informations, rendre l'indicateur visible/invisible et l'index de ligne l'opposé, en ajoutant à ce dernier l'effet alpha.

       if (recyclerView != null) {
        View firstVisibleView = recyclerView.getChildAt(0);
        View secondVisibleView = recyclerView.getChildAt(1);
    
        TextView firstRowIndex = (TextView) firstVisibleView.findViewById(R.id.sticky_row_index);
        TextView secondRowIndex = (TextView) secondVisibleView.findViewById(R.id.sticky_row_index);
    
        int visibleRange = recyclerView.getChildCount();
        int actual = recyclerView.getChildPosition(firstVisibleView);
        int next = actual + 1;
        int previous = actual - 1;
        int last = actual + visibleRange;
    
        // RESET STICKY LETTER INDEX
        stickyIndex.setText(String.valueOf(getIndexContext(firstRowIndex)).toUpperCase());
        stickyIndex.setVisibility(TextView.VISIBLE);
    
        if (dy > 0) {
            // USER SCROLLING DOWN THE RecyclerView
            if (next <= last) {
                if (isHeader(firstRowIndex, secondRowIndex)) {
                    stickyIndex.setVisibility(TextView.INVISIBLE);
                    firstRowIndex.setVisibility(TextView.VISIBLE);
                    firstRowIndex.setAlpha(1 - (Math.abs(firstVisibleView.getY()) / firstRowIndex.getHeight()));
                    secondRowIndex.setVisibility(TextView.VISIBLE);
                } else {
                    firstRowIndex.setVisibility(TextView.INVISIBLE);
                    stickyIndex.setVisibility(TextView.VISIBLE);
                }
            }
        } else {
            // USER IS SCROLLING UP THE RecyclerVIew
            if (next <= last) {
                // RESET FIRST ROW STATE
                firstRowIndex.setVisibility(TextView.INVISIBLE);
    
                if ((isHeader(firstRowIndex, secondRowIndex) || (getIndexContext(firstRowIndex) != getIndexContext(secondRowIndex))) && isHeader(firstRowIndex, secondRowIndex)) {
                    stickyIndex.setVisibility(TextView.INVISIBLE);
                    firstRowIndex.setVisibility(TextView.VISIBLE);
                    firstRowIndex.setAlpha(1 - (Math.abs(firstVisibleView.getY()) / firstRowIndex.getHeight()));
                    secondRowIndex.setVisibility(TextView.VISIBLE);
                } else {
                    secondRowIndex.setVisibility(TextView.INVISIBLE);
                }
            }
        }
    
        if (stickyIndex.getVisibility() == TextView.VISIBLE) {
            firstRowIndex.setVisibility(TextView.INVISIBLE);
        }
    }
    

J'ai développé un composant qui fait la logique ci-dessus, il peut être trouvé ici: https://github.com/edsilfer/sticky-index

0
Aleks Nine