С помощью Glass вы можете создавать разнообразные возможности взаимодействия со своими карточками, такие как прокрутка и анимация.
Прокрутка карточек в действиях
Дисплей и сенсорная панель Glass отлично подходят для отображения перелистываемых карточек, например, на временной шкале Glass. Если вы создаете действие, вы можете создать эффект того же типа с помощью виджета CardScrollView
.
- Реализуйте
CardScrollAdapter
для предоставления карточекCardScrollView
. Вы можете построить стандартную иерархию представлений самостоятельно или использовать классCardBuilder
. - Создайте
CardScrollView
, который используетCardScrollAdapter
в качестве поставщика карточек. - Установите представление содержимого вашего действия как
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.
- Вызовите унаследованный
setOnItemClickListener()
в вашемCardScrollView
. - Реализуйте обработчик
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);
}
});
}
Анимация прокрутки карточек
Для прокрутки карточек доступны три анимации: навигация, вставка и удаление.
- Реализуйте действие вставки или удаления на карточке в указанной позиции в наборе карточек.
- Вызовите
animate()
и используйте значение из перечисленияCardScrollView.Animation
. Чтобы отобразить более плавную анимацию, удалите все ссылки на
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
, который содержит одну карточку.
Скопируйте следующий вспомогательный класс в свой проект:
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; } } }
Измените метод
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)); }