Barre de défilement des cartes

Avec Glass, vous pouvez créer des interactions riches avec vos cartes, telles que des défilements et des animations.

Faire défiler les fiches dans les activités

L'écran Glass et le pavé tactile sont parfaits pour afficher des cartes à faire glisser, comme dans la chronologie Glass. Si vous créez une activité, vous pouvez créer le même type d'effet avec le widget CardScrollView.

  1. Implémentez un objet CardScrollAdapter pour fournir des fiches au CardScrollView. Vous pouvez créer vous-même une hiérarchie d'affichages standard ou utiliser la classe CardBuilder.
  2. Créez un CardScrollView qui utilise CardScrollAdapter en tant que fournisseur de cartes.
  3. Définissez la vue du contenu de votre activité sur CardScrollView ou affichez la CardScrollView dans une mise en page.

Voici une implémentation simple qui fait défiler trois fiches:

public class CardScrollActivity extends Activity {

    private List<CardBuilder> mCards;
    private CardScrollView mCardScrollView;
    private ExampleCardScrollAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        createCards();

        mCardScrollView = new CardScrollView(this);
        mAdapter = new ExampleCardScrollAdapter();
        mCardScrollView.setAdapter(mAdapter);
        mCardScrollView.activate();
        setContentView(mCardScrollView);
    }

    private void createCards() {
        mCards = new ArrayList<CardBuilder>();

        mCards.add(new CardBuilder(this, CardBuilder.Layout.TEXT)
                .setText("This card has a footer.")
                .setFootnote("I'm the footer!"));

        mCards.add(new CardBuilder(this, CardBuilder.Layout.CAPTION)
                .setText("This card has a puppy background image.")
                .setFootnote("How can you resist?")
                .addImage(R.drawable.puppy_bg));

        mCards.add(new CardBuilder(this, CardBuilder.Layout.COLUMNS)
                .setText("This card has a mosaic of puppies.")
                .setFootnote("Aren't they precious?")
                .addImage(R.drawable.puppy_small_1);
                .addImage(R.drawable.puppy_small_2);
                .addImage(R.drawable.puppy_small_3));
    }

    private class ExampleCardScrollAdapter extends CardScrollAdapter {

        @Override
        public int getPosition(Object item) {
            return mCards.indexOf(item);
        }

        @Override
        public int getCount() {
            return mCards.size();
        }

        @Override
        public Object getItem(int position) {
            return mCards.get(position);
        }

        @Override
        public int getViewTypeCount() {
            return CardBuilder.getViewTypeCount();
        }

        @Override
        public int getItemViewType(int position){
            return mCards.get(position).getItemViewType();
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            return mCards.get(position).getView(convertView, parent);
        }
    }
}

Interagir avec des cartes à faire défiler

Comme CardScrollView étend AdapterView, vous pouvez implémenter les écouteurs Android standards.

  1. Appelez la méthode setOnItemClickListener() héritée sur votre CardScrollView.
  2. Implémentez un gestionnaire onItemClick() pour l'événement tactile.

Voici une extension de l'exemple précédent qui émet un son lorsque vous appuyez sur une carte:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        setupClickListener();
        setContentView(mCardScrollView);
    }

    private void setupClickListener() {
        mCardScrollView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
                am.playSoundEffect(Sounds.TAP);
            }
        });
    }

Animer des cartes à faire défiler

Trois animations sont disponibles pour le défilement des fiches: la navigation, l'insertion et la suppression.

  1. Implémente une action d'insertion ou de suppression sur une fiche à une position spécifiée dans l'ensemble de fiches.
  2. Appelez animate() et utilisez une valeur de l'énumération CardScrollView.Animation.
  3. Pour afficher une animation plus régulière, supprimez toutes les références à notifyDataSetChanged(). La méthode animate() gère la mise à jour de la vue de votre ensemble de données.

    private class ExampleCardScrollAdapter extends CardScrollAdapter {
        ...
    
        // Inserts a card into the adapter, without notifying.
        public void insertCardWithoutNotification(int position, CardBuilder card) {
            mCards.add(position, card);
        }
    }
    
    private void insertNewCard(int position, CardBuilder card) {
        // Insert new card in the adapter, but don't call
        // notifyDataSetChanged() yet. Instead, request proper animation
        // to inserted card from card scroller, which will notify the
        // adapter at the right time during the animation.
        mAdapter.insertCardWithoutNotification(position, card);
        mCardScrollView.animate(position, CardScrollView.Animation.INSERTION);
    }
    

Conseils sur les performances et la mise en œuvre des fiches défilantes

Gardez à l'esprit les implications suivantes en termes de conception et de performances lors de la création de défilements de cartes.

Cycle de vie des cartes

Pour améliorer les performances, un CardScrollView ne charge qu'un sous-ensemble des fiches fournies par un CardScrollAdapter (généralement celles que l'utilisateur peut voir et quelques autres). Une carte peut donc présenter l'un des quatre états généraux suivants:

  • Dissociée : la vue "Faire défiler la fiche" n'a pas besoin de cette fiche pour le moment. La méthode onDetachedToWindow() vous informe si une carte a été associée, puis dissociée.
  • Associée : la vue de défilement de la carte demande l'affichage de la carte auprès de l'adaptateur avec getView(), car elle est sur le point d'être activée. Dans ce cas, vous recevez une notification via la méthode onAttachedToWindow() de la carte.
  • Activée : l'utilisateur peut partiellement voir la fiche, mais l'utilisateur n'a pas "sélectionné" la carte à faire défiler. Dans ce cas, la méthode 'isActivated()' renvoie true.
  • Selected (Sélectionnés) : la fiche occupe la totalité de l'écran de l'utilisateur. Si vous appelez la méthode getSelectedView(), la carte actuellement sélectionnée est renvoyée. Dans ce cas, la méthode isSelected() renvoie la valeur "true".

Si vous souhaitez animer la vue de votre carte ou effectuer d'autres opérations coûteuses, démarrez et arrêtez les opérations dans onAttachedToWindow() et onDetachedToWindow() pour économiser des ressources.

Recyclage de cartes

Lorsqu'une carte n'est plus associée à une carte, elle peut être recyclée et utilisée par une autre carte. Le recyclage des vues avec des informations à jour est bien plus efficace que la création de vues.

Pour profiter du recyclage des cartes, implémentez les méthodes getItemViewType(), getViewTypeCount() et getView() de la classe CardScrollAdapter. Vous allez ensuite utiliser certaines des méthodes pratiques de la classe CardBuilder pour implémenter le recyclage dans votre CardScrollAdapter, comme dans l'exemple suivant:

private List<CardBuilder> mCards;
...
/**
 * Returns the number of view types for the CardBuilder class. The
 * CardBuilder class has a convenience method that returns this value for
 * you.
 */
@Override
public int getViewTypeCount() {
    return CardBuilder.getViewTypeCount();
}

/**
 * Returns the view type of this card, so the system can figure out
 * if it can be recycled. The CardBuilder.getItemViewType() method
 * returns it's own type.
 */
@Override
public int getItemViewType(int position){
    return mCards.get(position).getItemViewType();
}

/**
 * When requesting a card from the adapter, recycle the view if possible.
 * The CardBuilder.getView() method automatically recycles the convertView
 * it receives, if possible, or creates a new view if convertView is null or
 * of the wrong type.
 */
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    return  mCards.get(position).getView(convertView, parent);
}

Implémenter des ID de carte stables

Lorsqu'une carte est sélectionnée et présentée aux utilisateurs, vous ne souhaitez peut-être pas que les modifications apportées à l'adaptateur sous-jacent affectent la carte que les utilisateurs voient à ce moment-là. Par exemple, si un utilisateur consulte une fiche sélectionnée et qu'une fiche est supprimée à gauche de cette fiche, celle-ci peut potentiellement être déplacée vers la gauche. En effet, par défaut, CardScrollAdapter réattribue des ID à l'ensemble de données sous-jacent en cas de modification.

Si cela est logique d'attribuer des ID uniques à vos cartes, vous pouvez conserver un ID cohérent dans l'ensemble de données sous-jacent pour éviter le problème mentionné ci-dessus. Pour ce faire, remplacez hasStableIds() et renvoyez true. Cela indique au système que CardScrollAdapter conserve des identifiants stables pour toutes les modifications de l'ensemble de données. En outre, implémentez getItemId() afin de renvoyer l'ID unique approprié pour les cartes de votre adaptateur. L'implémentation par défaut renvoie l'index de position de la carte dans l'adaptateur, qui est instable par nature.

CardScrollAdapter vide

Lorsque l'ensemble de données de vos adaptateurs est vide, la vue par défaut affiche un écran noir. Si vous souhaitez afficher une vue différente dans ce cas, n'utilisez pas setEmptyView(). À la place, créez une carte dans votre CardScrollAdapter.

Commentaires relatifs au tirage à l'horizontale

De nombreuses immersions intégrées dans les Google Glass fournissent des commentaires de type "tirage" lorsque l'utilisateur balaie l'écran vers l'arrière ou vers l'avant sans effectuer d'action. Par exemple, vous pouvez voir ces commentaires lorsque vous balayez l'écran après avoir pris une photo.

Si votre immersion n'utilise pas de gestes de balayage horizontal pour exécuter des fonctions spécifiques à l'application, fournissez cet effet de tirage en encapsulant votre mise en page dans un élément CardScrollView contenant une carte.

  1. Copiez la classe d'assistance suivante dans votre projet:

    public class TuggableView extends CardScrollView {
    
        private final View mContentView;
    
        /**
         * Initializes a TuggableView that uses the specified layout
         * resource for its user interface.
         */
        public TuggableView(Context context, int layoutResId) {
            this(context, LayoutInflater.from(context)
                    .inflate(layoutResId, null));
        }
    
        /**
         * Initializes a TuggableView that uses the specified view
         * for its user interface.
         */
        public TuggableView(Context context, View view) {
            super(context);
    
            mContentView = view;
            setAdapter(new SingleCardAdapter());
            activate();
        }
    
        /**
         * Overridden to return false so that all motion events still
         * bubble up to the activity's onGenericMotionEvent() method after
         * they are handled by the card scroller. This allows the activity
         * to handle TAP gestures using a GestureDetector instead of the
         * card scroller's OnItemClickedListener.
         */
        @Override
        protected boolean dispatchGenericFocusedEvent(MotionEvent event) {
            super.dispatchGenericFocusedEvent(event);
            return false;
        }
    
        /** Holds the single "card" inside the card scroll view. */
        private class SingleCardAdapter extends CardScrollAdapter {
    
            @Override
            public int getPosition(Object item) {
                return 0;
            }
    
            @Override
            public int getCount() {
                return 1;
            }
    
            @Override
            public Object getItem(int position) {
                return mContentView;
            }
    
            @Override
            public View getView(int position, View recycleView,
                    ViewGroup parent) {
                return mContentView;
            }
        }
    }
    
  2. Modifiez la méthode onCreate dans votre activité pour afficher le CardScrollView contenant votre mise en page.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        // was: setContentView(R.layout.main_activity);
        setContentView(new TuggableView(this, R.layout.main_activity));
    }