借助 Glass,您可以实现与卡片的丰富互动,例如滚动和动画。
在活动中滚动卡片
Glass 显示屏和触控板非常适合显示可滑动的卡片,例如在 Glass 时间轴中。如果您是在构建 activity,则可以使用 CardScrollView
widget 创建相同类型的效果。
- 实现
CardScrollAdapter
,以便为CardScrollView
提供卡片。您可以自行构建标准视图层次结构,也可以使用CardBuilder
类。 - 创建将
CardScrollAdapter
用作支付卡的CardScrollView
。 - 将 activity 的内容视图设置为
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 监听器。
- 对
CardScrollView
调用继承的setOnItemClickListener()
。 - 为点按事件实现
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()
方法会通知您。 - Attached - 卡片滚动视图使用
getView()
从适配器请求卡片,因为卡片即将“已激活”。发生这种情况时,系统会通过卡的onAttachedToWindow()
方法通知您。 - 已启用 - 用户只能看到部分卡片,但卡片滚动视图尚未“选择”要向用户显示的卡片。在这种情况下,'isActivated()' 方法会返回
true
。 - 已选择 - 卡片会占据用户的整个屏幕。调用
getSelectedView()
会返回当前选定的卡。在这种情况下,isSelected()
方法会返回 true。
如果您要为卡片视图添加动画效果或执行其他开销高昂的操作,请启动和停止 onAttachedToWindow()
和 onDetachedToWindow()
中的操作以节省资源。
卡片回收
当卡从附加状态变为分离状态时,与该卡相关联的视图对象可以被附加的卡回收和使用。与创建新视图相比,利用更新后的信息来回收视图的效率要高得多。
为了利用卡回收利用功能,请实现 CardScrollAdapter
类的 getItemViewType()
、getViewTypeCount()
和 getView()
方法。然后,您可以使用 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);
}
实现稳定的卡 ID
选择卡片并向用户显示后,您可能不希望通过更改底层适配器来影响用户当前看到的卡片。例如,如果用户正在查看一张所选卡片,并且该卡片左侧的一张卡片被移除,那么默认情况下,用户正在查看的卡片可能会向左移动,因为默认情况下,CardScrollAdapter
会将 ID 重新分配给底层数据集。
如果为银行卡分配唯一 ID 有逻辑意义,您可以在基础数据集中保持一致的 ID,以防止出现上述问题。为此,请替换 hasStableIds()
并返回 true
。这向系统表明,CardScrollAdapter
在数据集更改期间会保持稳定的 ID。此外,请实现 getItemId()
,以便为适配器中的卡片返回相应的唯一 ID。默认实现会返回适配器中卡片的位置索引,这本来不稳定。
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; } } }
修改 activity 中的
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)); }