Скроллер карт

С помощью Glass вы можете создавать разнообразные возможности взаимодействия со своими карточками, такие как прокрутка и анимация.

Прокрутка карточек в действиях

Дисплей и сенсорная панель Glass отлично подходят для отображения перелистываемых карточек, например, на временной шкале Glass. Если вы создаете действие, вы можете создать эффект того же типа с помощью виджета CardScrollView .

  1. Реализуйте CardScrollAdapter для предоставления карточек CardScrollView . Вы можете построить стандартную иерархию представлений самостоятельно или использовать класс CardBuilder .
  2. Создайте CardScrollView , который использует CardScrollAdapter в качестве поставщика карточек.
  3. Установите представление содержимого вашего действия как CardScrollView или отобразите CardScrollView в макете.

Вот простая реализация, которая прокручивает три карточки:

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

Взаимодействие с прокручиваемыми карточками

Поскольку CardScrollView расширяет AdapterView , вы можете реализовать стандартные прослушиватели Android.

  1. Вызовите унаследованный setOnItemClickListener() в вашем CardScrollView .
  2. Реализуйте обработчик onItemClick() для события касания.

Вот расширение предыдущего примера, которое воспроизводит звук касания при нажатии на карту:

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

Анимация прокрутки карточек

Для прокрутки карточек доступны три анимации: навигация, вставка и удаление.

  1. Реализуйте действие вставки или удаления на карточке в указанной позиции в наборе карточек.
  2. Вызовите animate() и используйте значение из перечисления CardScrollView.Animation .
  3. Чтобы отобразить более плавную анимацию, удалите все ссылки на notifyDataSetChanged() . Метод animate() обрабатывает обновление представления набора данных.

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

Советы по производительности и реализации прокрутки карточек

При создании скроллеров карточек учитывайте следующие аспекты дизайна и производительности.

Жизненный цикл карты

Чтобы повысить производительность, CardScrollView загружает только подмножество карточек, предоставляемых CardScrollAdapter (обычно те, которые видны пользователю, и некоторые другие). По этой причине карта может находиться в любом из этих четырех основных состояний:

  • Отсоединено — для просмотра прокрутки карточек эта карточка в данный момент не требуется. Метод onDetachedToWindow() карты уведомляет вас, если карта ранее была прикреплена, а затем отсоединена.
  • Прикреплено — представление прокрутки карты запрашивает карту у адаптера с помощью getView() , поскольку карта близка к «активации». Когда это произойдет, вы получите уведомление с помощью метода onAttachedToWindow() карты.
  • Активировано — карта частично видна пользователю, но в режиме прокрутки карты она не «выбрана» для отображения пользователю. В этом случае метод isActivated() возвращает true .
  • Выбрано — карточка занимает весь экран пользователя. Вызов getSelectedView() возвращает текущую выбранную карту. В этом случае метод isSelected() возвращает true.

Если вы анимируете вид своей карты или выполняете другие дорогостоящие операции, запускайте и останавливайте операции в onAttachedToWindow() и onDetachedToWindow() для экономии ресурсов.

Утилизация карт

Когда карта переходит из прикрепленного состояния в отсоединенное, объект просмотра, связанный с картой, может быть переработан и использован прикрепляемой картой. Переработка представлений с обновленной информацией гораздо более эффективна, чем создание новых представлений.

Чтобы воспользоваться преимуществами повторного использования карт, реализуйте методы getItemViewType() , getViewTypeCount() и getView() класса CardScrollAdapter . Затем вы используете некоторые удобные методы класса CardBuilder для реализации переработки в CardScrollAdapter , как в следующем примере:

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

Внедрение стабильных идентификаторов карт

Когда карточка выбрана и отображается пользователям, возможно, вы не захотите, чтобы изменения в базовом адаптере влияли на карточку, которую пользователи видят в этот момент. Например, если пользователь просматривает выбранную карточку, а карточка удаляется слева от этой карточки, карточка, которую просматривает пользователь, потенциально может сместиться влево, поскольку CardScrollAdapter переназначает идентификаторы базовому набору данных при возникновении изменений. , по умолчанию.

Если логически имеет смысл присваивать вашим картам уникальные идентификаторы, вы можете поддерживать согласованный идентификатор в базовом наборе данных, чтобы предотвратить вышеупомянутую проблему. Для этого переопределите hasStableIds() и верните true . Это указывает системе, что CardScrollAdapter поддерживает стабильные идентификаторы при изменении набора данных. Кроме того, реализуйте getItemId() для возврата соответствующего уникального идентификатора карт в вашем адаптере. Реализация по умолчанию возвращает индекс положения карты в адаптере, что по своей сути нестабильно.

Пустая картаАдаптер прокрутки

Если у вас есть пустой набор данных для адаптеров, по умолчанию отображается черный экран. Если в этих случаях вы хотите отобразить другое представление, не используйте setEmptyView() . Вместо этого создайте одну карту в CardScrollAdapter .

Обратная связь по горизонтали

Многие встроенные в Glass функции погружения обеспечивают «подтягивающую» обратную связь, когда смахивание назад и вперед не выполняет никаких действий. Например, вы можете увидеть этот отзыв, проведя пальцем по экрану после того, как сделали фотографию.

Если ваше погружение не использует жесты горизонтального смахивания для выполнения функций, специфичных для приложения, обеспечьте этот эффект перетягивания, обернув макет внутри CardScrollView , который содержит одну карточку.

  1. Скопируйте следующий вспомогательный класс в свой проект:

    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. Измените метод onCreate в своей деятельности, чтобы отобразить CardScrollView , содержащий ваш макет.

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