Barra de desplazamiento de la tarjeta

Con Glass, puedes crear interacciones enriquecedoras con tus tarjetas, como el desplazamiento y las animaciones.

Tarjetas de desplazamiento en las actividades

La pantalla y el panel táctil de Glass son ideales para mostrar tarjetas deslizables, como en el cronograma de Glass. Si estás compilando una actividad, puedes crear el mismo tipo de efecto con el widget CardScrollView.

  1. Implementa un CardScrollAdapter para suministrar tarjetas al CardScrollView. Puedes compilar una jerarquía de vistas estándar tú mismo o usar la clase CardBuilder.
  2. Crea una CardScrollView que use CardScrollAdapter como proveedor de las tarjetas.
  3. Configura la vista de contenido de tu actividad como CardScrollView o muestra la CardScrollView en un diseño.

Esta es una implementación simple que se desplaza por tres tarjetas:

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);
        }
    }
}

Cómo interactuar con las tarjetas de desplazamiento

Dado que CardScrollView extiende AdapterView, puedes implementar los objetos de escucha estándar de Android.

  1. Llama a la función heredada setOnItemClickListener() en tu CardScrollView.
  2. Implementa un controlador onItemClick() para el evento de toque.

Esta es una extensión del ejemplo anterior que reproduce un sonido de toque cuando presionas una tarjeta:

    @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);
            }
        });
    }

Cómo animar tarjetas de desplazamiento

Hay tres animaciones disponibles para las tarjetas de desplazamiento: Navegación, Inserción y Eliminación.

  1. Implementa una acción de insertar o borrar una tarjeta en una posición específica del conjunto de tarjetas.
  2. Llama a animate() y usa un valor de la enumeración CardScrollView.Animation.
  3. Para mostrar una animación más fluida, quita cualquier referencia a notifyDataSetChanged(). El método animate() controla la actualización de la vista del conjunto de datos.

    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);
    }
    

Sugerencias de implementación y rendimiento para las tarjetas de desplazamiento

Ten en cuenta las siguientes implicaciones de diseño y rendimiento cuando crees desplazadores de tarjetas.

Ciclo de vida de la tarjeta

Para aumentar el rendimiento, un objeto CardScrollView solo carga un subconjunto de las tarjetas que proporciona un objeto CardScrollAdapter (en general, las que son visibles para el usuario y algunas más). Por este motivo, las tarjetas pueden tener cualquiera de los siguientes cuatro estados generales:

  • Separada: La vista de desplazamiento de la tarjeta no necesita la tarjeta en este momento. El método onDetachedToWindow() de la tarjeta te notifica si una tarjeta se conectó anteriormente y, luego, se desconectó.
  • Adjuntada: La vista de desplazamiento de la tarjeta solicita la tarjeta al adaptador con getView(), ya que está cerca de "activarse". Cuando esto sucede, el método onAttachedToWindow() de la tarjeta te notifica.
  • Activada: La tarjeta es parcialmente visible para el usuario, pero la vista de desplazamiento de la tarjeta no se seleccionó de modo que se muestre al usuario. El método 'isActivated()' muestra true en este caso.
  • Seleccionada: La tarjeta ocupa toda la pantalla del usuario. Llamar a getSelectedView() muestra la tarjeta seleccionada actualmente. En este caso, el método isSelected() muestra el valor verdadero.

Si estás animando la vista de tu tarjeta o realizando otras operaciones costosas, inicia y detén las operaciones en onAttachedToWindow() y onDetachedToWindow() para ahorrar recursos.

Reciclaje de tarjetas

Cuando una tarjeta pasa de estar conectada a la separación, el objeto de vista asociado con la tarjeta se puede reciclar y se usa en una tarjeta que se adjunta. Reciclar vistas con información actualizada es mucho más eficiente que crear vistas nuevas.

Para aprovechar el reciclaje de tarjetas, implementa los métodos getItemViewType(), getViewTypeCount() y getView() de la clase CardScrollAdapter. Luego, deberás usar algunos de los métodos útiles de la clase CardBuilder para implementar el reciclaje en tu CardScrollAdapter, como en el siguiente ejemplo:

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);
}

Cómo implementar ID de tarjetas estables

Cuando se selecciona una tarjeta y se muestra a los usuarios, es posible que no quieras que los cambios en el adaptador subyacente afecten la tarjeta que los usuarios ven en ese momento. Por ejemplo, si un usuario está viendo una tarjeta seleccionada y se quita una tarjeta a la izquierda de esa tarjeta, es posible que la tarjeta que está viendo el usuario se desplace hacia la izquierda, ya que, de forma predeterminada, CardScrollAdapter reasigna los ID al conjunto de datos subyacente.

Si tiene lógica que asignar ID únicos a tus tarjetas, puedes mantener un ID coherente en el conjunto de datos subyacente para evitar el problema mencionado con anterioridad. Para ello, anula hasStableIds() y muestra true. Esto especifica al sistema que CardScrollAdapter mantiene ID estables en todos los cambios del conjunto de datos. Además, implementa getItemId() a fin de mostrar el ID único apropiado para las tarjetas de tu adaptador. La implementación predeterminada muestra el índice de posición de la tarjeta en el adaptador, que es inherentemente inestable.

CardScrollAdapter vacío

Cuando tengas un conjunto de datos vacío para adaptadores, la vista predeterminada mostrará una pantalla negra. Si quieres mostrar una vista diferente en estos casos, no uses setEmptyView(). En su lugar, crea una sola tarjeta en tu CardScrollAdapter.

Comentarios sobre el ajuste horizontal

Muchas de las inmersiones integradas en Glass brindan información útil para deslizar, por lo que no se realiza ninguna acción. Por ejemplo, puedes ver estos comentarios cuando deslizas el dedo después de tomar una foto.

Si tu inmersión no usa gestos de deslizamiento horizontal para realizar funciones específicas de la aplicación, proporciona este efecto de ajuste al unir tu diseño dentro de una CardScrollView que contenga una tarjeta.

  1. Copia la siguiente clase de ayuda en tu proyecto:

    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. Modifica el método onCreate en tu actividad para mostrar el CardScrollView que contiene tu diseño.

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