卡片捲軸

您可以透過 Google Glass 與捲動互動等動畫互動。

在活動中捲動資訊卡

Glass 螢幕和觸控板非常適合顯示可滑動的資訊卡,就像 Glass 時間軸一樣。建構活動時,您可以使用 CardScrollView 小工具建立相同類型的效果。

  1. 實作 CardScrollAdapter 以提供資訊卡至 CardScrollView。您可以自行建立標準檢視區塊階層,或使用 CardBuilder 類別。
  2. 建立使用 CardScrollAdapter 做為卡片供應商的 CardScrollView
  3. 將活動的內容檢視畫面設為 CardScrollView,或是在版面配置中顯示 CardScrollView

以下提供幾個簡易的導入方式,可在 3 張資訊卡中捲動:

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. 呼叫 CardScrollView 上繼承的 setOnItemClickListener()
  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() 並停止作業,以節省資源。

卡片回收

當資訊卡從卸離時,您可以回收和使用與資訊卡相關聯的檢視畫面物件。使用新資訊回收回收視圖比建立新檢視更有效率。

如要充分運用卡片回收功能,請實作 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 中,藉此提供這項固定效果。

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