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

С помощью 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));
    }