Przewijanie kart

Dzięki Google Glass możesz wzbogacać kartę za pomocą interakcji takich jak przewijanie i animacje.

Przewijanie kart w ramach aktywności

Wyświetlacz i touchpad Google Glass idealnie nadają się do wyświetlania kart przesuwanych, np. na osi czasu Glass. Jeśli tworzysz aktywność, możesz utworzyć ten sam typ efektu, korzystając z widżetu CardScrollView.

  1. Zaimplementuj CardScrollAdapter w celu dostarczenia kart do CardScrollView. Możesz utworzyć standardową hierarchię widoków samodzielnie lub użyć klasy CardBuilder.
  2. Utwórz CardScrollView, w którym dostawcą kart jest CardScrollAdapter.
  3. Ustaw widok treści aktywności na CardScrollView lub wyświetl CardScrollView w układzie.

Oto prosta implementacja, która przewija się za pomocą 3 kart:

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

Interakcja z przewijanymi kartami

Ponieważ CardScrollView rozszerza AdapterView, możesz wdrożyć standardowe detektory Androida.

  1. Wywołaj odziedziczone ustawienie setOnItemClickListener() na urządzeniu CardScrollView.
  2. Zaimplementuj moduł obsługi onItemClick() na potrzeby zdarzenia kliknięcia.

Oto rozszerzenie poprzedniego przykładu, w którym po dotknięciu karty odtwarza się dźwięk kliknięcia:

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

Animowanie przewijanych kart

Dostępne są 3 animacje dla kart przewijanych: Nawigacja, Wstawianie i Usuwanie.

  1. Zaimplementuj funkcję wstawiania lub usuwania na karcie w określonym miejscu w zestawie kart.
  2. Wywołaj animate() i użyj wartości z wyliczenia CardScrollView.Animation.
  3. Aby animacja była płynniejsza, usuń wszelkie odwołania do notifyDataSetChanged(). Metoda animate() obsługuje aktualizowanie widoku zbioru danych.

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

Wskazówki dotyczące wydajności i wdrażania przewijanych kart

Tworząc przewijaki kart, pamiętaj o tych kwestiach dotyczących wyglądu i wydajności.

Cykl życia karty

Aby zwiększyć wydajność, CardScrollView wczytuje tylko podzbiór kart wyświetlanych przez CardScrollAdapter (zwykle te, które są widoczne dla użytkownika, oraz kilka innych). Karta może więc mieć jeden z tych 4 stanów:

  • Odłączenie – w widoku przewijania karty nie jest obecnie wymagana ta karta. Jeśli karta została wcześniej podłączona, a następnie odłączona, metoda onDetachedToWindow() karty powiadomi Cię o tym.
  • Załączono – widok przewijania karty prosi o przesłanie karty z adaptera za pomocą getView(), ponieważ karta jest bliska aktywacji. W takiej sytuacji otrzymasz powiadomienie za pomocą metody karty onAttachedToWindow().
  • Aktywna – karta jest częściowo widoczna dla użytkownika, ale widok przewijania karty nie „wybrał” jej wyświetlenia. W tym przypadku metoda 'isActivated()' zwraca true.
  • Wybrana – karta zajmuje cały ekran użytkownika. Wywołanie getSelectedView() zwraca obecnie wybraną kartę. W tym przypadku metoda isSelected() zwraca wartość true.

Jeśli animujesz widok karty lub wykonujesz inne kosztowne operacje, uruchom i zatrzymaj operacje w onAttachedToWindow() i onDetachedToWindow(), aby zaoszczędzić zasoby.

Recykling karty

Gdy karta zostanie podłączona do odłączonej, karty z nią powiązane mogą zostać wykorzystane ponownie lub użyte przez dołączoną kartę. Recykling widoków ze zaktualizowanymi informacjami jest znacznie skuteczniejszy niż tworzenie nowych widoków.

Aby skorzystać z recyklingu kart, zaimplementuj metody getItemViewType(), getViewTypeCount() i getView() klasy CardScrollAdapter. Następnie możesz skorzystać z niektórych metod komfortowych w klasie CardBuilder, aby wdrożyć recykling w urządzeniu CardScrollAdapter, jak w tym przykładzie:

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

Implementowanie stabilnych identyfikatorów kart

Po wybraniu karty, która ma być wyświetlana użytkownikom, możesz nie chcieć zmieniać bazowego adaptera, aby mieć wpływ na kartę wyświetlaną użytkownikom w tej chwili. Jeśli na przykład użytkownik wyświetla wybraną kartę, a jej karta jest usuwana po lewej stronie, karta może zostać przeniesiona do lewej strony, ponieważ w momencie wystąpienia zmian identyfikator CardScrollAdapter ponownie przypisze identyfikatory do bazowego zbioru danych.

Jeśli w logicznie sensowny sposób przypisujesz unikalnym identyfikatorom karty, możesz utrzymywać spójny identyfikator w podstawowym zbiorze danych, aby zapobiec powyższemu problemowi. Aby to zrobić, zastąp hasStableIds() i ustaw true. Określa to, że system CardScrollAdapter utrzymuje stabilne identyfikatory w zmianach zbioru danych. Dodatkowo zaimplementuj getItemId(), aby zwracał odpowiedni unikalny identyfikator kart w adaptera. Domyślna implementacja zwraca indeks pozycji karty w adapterze, który z natury jest niestabilny.

Pusty komponent CardAdapterAdapter

Jeśli masz puste zbiory danych do adapterów, domyślnym widokiem jest czarny ekran. Jeśli w tych przypadkach chcesz wyświetlić inny widok, nie używaj setEmptyView(). Zamiast tego utwórz jedną kartę w CardScrollAdapter.

Opinia o przeciąganiu w poziomie

Wiele wbudowanych w Google Glass umożliwia korzystanie z funkcji „przeciągania” podczas przesuwania do tyłu i do przodu. Możesz na przykład zobaczyć ten komentarz po przesunięciu zdjęcia.

Jeśli zanurzenie w poziomie nie obsługuje gestów przesuwania w poziomie w celu wykonywania funkcji specyficznych dla aplikacji, dodaj efekt w formie komponentu CardScrollView, który zawiera jedną kartę.

  1. Skopiuj do projektu tę klasę pomocniczą:

    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. Zmień metodę onCreate w swojej aktywności, aby wyświetlić CardScrollView, który zawiera Twój układ.

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