С помощью 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()
, чтобы возвращать соответствующий уникальный идентификатор для карт в вашем адаптере. Реализация по умолчанию возвращает индекс положения карты в адаптере, который по своей природе нестабилен.
Пустой CardScrollAdapter
Если у вас есть пустой набор данных для адаптеров, по умолчанию отображается черный экран. Если вы хотите показать другое представление в этих случаях, не используйте 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)); }