Trình cuộn thẻ

Với Glass, bạn có thể tạo ra các tương tác đa dạng thức với các thẻ như cuộn và ảnh động.

Thẻ cuộn trong các hoạt động

Màn hình Glass và bàn di chuột rất phù hợp để hiển thị các thẻ có thể vuốt, như trong dòng thời gian Google Glass. Nếu đang tạo một hoạt động, bạn có thể tạo cùng một loại hiệu ứng bằng tiện ích CardScrollView.

  1. Triển khai CardScrollAdapter để cung cấp thẻ cho CardScrollView. Bạn có thể tự xây dựng hệ phân cấp thành phần hiển thị chuẩn hoặc sử dụng lớp CardBuilder.
  2. Tạo CardScrollView sử dụng CardScrollAdapter làm nhà cung cấp thẻ.
  3. Đặt thành phần hiển thị nội dung của hoạt động là CardScrollView hoặc hiển thị CardScrollView trong bố cục.

Dưới đây là một cách triển khai đơn giản có thể cuộn qua ba thẻ:

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

Tương tác với thẻ cuộn

CardScrollView mở rộng AdapterView, bạn có thể triển khai trình nghe Android tiêu chuẩn.

  1. Gọi setOnItemClickListener() kế thừa trên CardScrollView của bạn.
  2. Triển khai trình xử lý onItemClick() cho sự kiện nhấn.

Đây là phần mở rộng cho ví dụ trước phát âm thanh nhấn khi bạn nhấn vào thẻ:

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

Tạo ảnh động cho thẻ cuộn

Có 3 ảnh động cho thẻ cuộn: Điều hướng, Chèn và Xoá.

  1. Triển khai thao tác chèn hoặc xoá trên thẻ tại một vị trí đã chỉ định trong nhóm thẻ.
  2. Gọi animate() và sử dụng một giá trị từ enum CardScrollView.Animation.
  3. Để ảnh động xuất hiện mượt mà hơn, hãy xoá mọi tệp tham chiếu đến notifyDataSetChanged(). Phương thức animate() sẽ xử lý việc cập nhật chế độ xem tập dữ liệu của bạn.

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

Mẹo triển khai và hiệu suất cho thẻ cuộn

Hãy lưu ý những ảnh hưởng sau đây về thiết kế và hiệu suất khi tạo trình cuộn thẻ.

Vòng đời thẻ

Để tăng hiệu suất, CardScrollView chỉ tải một tập hợp con của các thẻ mà CardScrollAdapter cung cấp (thường là các thẻ mà người dùng nhìn thấy và một vài thẻ khác). Do đó, một thẻ có thể rơi vào bất kỳ trạng thái chung nào sau đây:

  • Tách biệt – Chế độ xem cuộn thẻ hiện không cần thẻ này. Bạn sẽ nhận được thông báo bằng phương thức onDetachedToWindow() của thẻ nếu trước đó một thẻ đã được đính kèm và sau đó tách ra.
  • Đính kèm – Chế độ xem cuộn thẻ yêu cầu thẻ từ bộ chuyển đổi với getView(), vì thẻ này sắp được "kích hoạt". Bạn sẽ nhận được thông báo bằng phương thức onAttachedToWindow() của thẻ khi việc này xảy ra.
  • Đã kích hoạt – Thẻ mà người dùng nhìn thấy một phần, nhưng chế độ xem cuộn thẻ chưa "chọn" thẻ để hiển thị cho người dùng. Phương thức 'isActivated()' sẽ trả về true trong trường hợp này.
  • Đã chọn – Thẻ đang chiếm toàn bộ màn hình của người dùng. Việc gọi getSelectedView() sẽ trả về thẻ hiện được chọn. Phương thức isSelected() sẽ trả về giá trị đúng trong trường hợp này.

Nếu bạn đang tạo ảnh động cho chế độ xem thẻ của mình hoặc thực hiện các thao tác tốn kém khác, hãy bắt đầu và dừng các thao tác trong onAttachedToWindow()onDetachedToWindow() để tiết kiệm tài nguyên.

Tái chế thẻ

Khi một thẻ được chuyển sang chế độ tách rời, đối tượng chế độ xem liên kết với thẻ này có thể được tái chế và sử dụng trong một thẻ đang được đính kèm. Việc khôi phục chế độ xem bằng thông tin cập nhật sẽ hiệu quả hơn nhiều so với việc tạo chế độ xem mới.

Để tận dụng tính năng tái chế thẻ, hãy triển khai getItemViewType(), getViewTypeCount() và các phương thức getView() của lớp CardScrollAdapter. Sau đó, bạn có thể sử dụng một số phương thức thuận tiện trong lớp CardBuilder để triển khai việc tái chế trong CardScrollAdapter, như trong ví dụ sau:

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

Triển khai mã thẻ ổn định

Khi một thẻ được chọn và hiển thị cho người dùng, bạn có thể không muốn các thay đổi đối với bộ chuyển đổi cơ bản ảnh hưởng đến thẻ mà người dùng thấy tại thời điểm đó. Ví dụ: nếu người dùng đang xem thẻ đã chọn và thẻ bị xoá ở bên trái thẻ đó thì thẻ mà người dùng đang xem có thể chuyển sang trái, vì CardScrollAdapter gán lại mã nhận dạng cho tập dữ liệu cơ bản khi có thay đổi.

Nếu việc gán các mã nhận dạng duy nhất cho thẻ của bạn là hợp lý, bạn có thể duy trì mã nhận dạng nhất quán trong tập dữ liệu cơ bản để ngăn ngừa vấn đề nêu trên. Để thực hiện việc này, hãy ghi đè hasStableIds() và trả về true. Điều này chỉ định cho hệ thống rằng CardScrollAdapter duy trì mã nhận dạng ổn định trong các thay đổi đối với tập dữ liệu. Ngoài ra, hãy triển khai getItemId() để trả về mã nhận dạng duy nhất thích hợp cho các thẻ trong bộ chuyển đổi của bạn. Cách triển khai mặc định sẽ trả về chỉ mục vị trí của thẻ trong bộ chuyển đổi, vốn vốn không ổn định.

CardScrollAdapter trống

Khi bạn có một tập dữ liệu trống cho các bộ chuyển đổi, chế độ xem mặc định sẽ hiển thị màn hình màu đen. Nếu bạn muốn hiển thị một thành phần hiển thị khác trong những trường hợp này, không sử dụng setEmptyView(). Thay vào đó, hãy tạo một thẻ duy nhất trong CardScrollAdapter.

Phản hồi kéo theo chiều ngang

Nhiều trò chơi tích hợp sẵn trên Glass cung cấp phản hồi "kéo" khi vuốt ngược và không thực hiện được thao tác nào. Ví dụ: bạn có thể thấy phản hồi này khi vuốt sau khi chụp ảnh.

Nếu video nhúng của bạn không sử dụng cử chỉ vuốt ngang để thực hiện các hàm dành riêng cho ứng dụng, hãy cung cấp hiệu ứng kéo này bằng cách gói bố cục bên trong CardScrollView chứa một thẻ.

  1. Sao chép lớp trợ giúp sau vào dự án:

    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. Sửa đổi phương thức onCreate trong hoạt động của bạn để hiển thị CardScrollView chứa bố cục.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        // was: setContentView(R.layout.main_activity);
        setContentView(new TuggableView(this, R.layout.main_activity));
    }