Barre de défilement des cartes

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

Cartes défilantes dans les activités

L'écran et le pavé tactile des lunettes Glass 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 CardScrollAdapter pour fournir des cartes au CardScrollView. Vous pouvez créer vous-même une hiérarchie des vues standard ou utiliser la classe CardBuilder.
  2. Créez un CardScrollView qui utilise CardScrollAdapter comme fournisseur de cartes.
  3. Définissez la vue du contenu de votre activité sur CardScrollView ou affichez 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 défilantes

Étant donné que 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 d'appui.

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 défilantes

Trois animations sont disponibles pour le défilement des cartes: Navigation, Insertion et Suppression.

  1. Implémentez une action d'insertion ou de suppression d'une fiche à une position spécifiée dans l'ensemble de cartes.
  2. Appelez animate() et utilisez une valeur de l'énumération CardScrollView.Animation.
  3. Pour afficher une animation plus fluide, 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 l'implémentation pour le défilement des cartes

Tenez compte des implications suivantes en termes de conception et de performances lorsque vous créez des pages de défilement des cartes.

Cycle de vie d'une carte

Pour améliorer les performances, un CardScrollView ne charge qu'un sous-ensemble des fiches fournies par un CardScrollAdapter (en général, celles qui sont visibles par l'utilisateur, et quelques autres). Pour cette raison, une carte peut être dans l'un des quatre états généraux suivants:

  • Dissocié : la vue de défilement de la carte n'a pas besoin de cette fiche pour le moment. Vous êtes averti par la méthode onDetachedToWindow() de la carte si une carte a été associée, puis dissociée.
  • Associé : la vue de défilement de la carte demande la fiche à l'adaptateur avec getView(), car la carte est sur le point d'être "activée". Dans ce cas, vous en êtes informé via la méthode onAttachedToWindow() de la carte.
  • Activé : la fiche est partiellement visible par l'utilisateur, mais la vue de défilement de la carte n'a pas "sélectionné" la fiche à présenter à l'utilisateur. Dans ce cas, la méthode 'isActivated()' renvoie true.
  • Selected (Sélectionnés) : la fiche occupe la totalité de l'écran de l'utilisateur. L'appel de getSelectedView() renvoie la carte actuellement sélectionnée. Dans ce cas, la méthode isSelected() renvoie "true".

Si vous animez la vue de votre carte ou effectuez 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, l'objet de vue associé à la carte peut être recyclé et utilisé par une carte en cours d'association. Il est beaucoup plus efficace de recycler des vues avec des informations mises à jour que de créer des vues.

Pour tirer parti 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 affichée pour les 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, elle peut potentiellement se déplacer vers la gauche, car CardScrollAdapter réattribue les ID à l'ensemble de données sous-jacent lorsque des modifications se produisent, par défaut.

S'il est logique d'attribuer des identifiants uniques à vos cartes, vous pouvez conserver un identifiant cohérent dans l'ensemble de données sous-jacent pour éviter ce problème. Pour ce faire, remplacez hasStableIds() et renvoyez true. Cela indique au système que CardScrollAdapter conserve des ID stables en cas de modification 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.

Vider CardScrollAdapter

Lorsque vous disposez d'un ensemble de données vide pour les adaptateurs, un écran noir s'affiche par défaut. Si vous souhaitez afficher une vue différente dans ces cas, n'utilisez pas setEmptyView(). Créez plutôt une seule fiche dans votre CardScrollAdapter.

Commentaires sur le tirage horizontal

De nombreuses immersions intégrées sur les lunettes Glass fournissent un retour d'entrave lorsque vous balayez l'écran vers l'arrière ou vers l'avant sans effectuer d'action. Par exemple, vous pouvez voir ce feedback 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 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));
    }