גלילה בכרטיס

בעזרת Glass תוכלו ליצור אינטראקציות עשירות עם הכרטיסים שלכם, כמו גלילה ואנימציות.

כרטיסי גלילה בתוך פעילויות

מסך ה-Glass ולוח המגע הם מעולים להצגה של כרטיסים שניתנים להחלקה, כמו בציר הזמן של Glass. אם אתם בונים פעילות, אתם יכולים ליצור את אותו סוג אפקט באמצעות הווידג'ט CardScrollView.

  1. מטמיעים CardScrollAdapter כדי לספק כרטיסים ל-CardScrollView. תוכלו ליצור בעצמכם היררכיית תצוגה רגילה או להשתמש בכיתה CardBuilder.
  2. יוצרים CardScrollView שבו הספק CardScrollAdapter הוא הספק של הכרטיסים.
  3. אפשר להגדיר את תצוגת התוכן של הפעילות בתור 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.

  1. התקשרות אל setOnItemClickListener() שעברו בירושה ב-CardScrollView שלך.
  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() כדי לחסוך משאבים.

מיחזור כרטיסים

כשמנתקים את הכרטיס מניתוק, אפשר למחזר את אובייקט התצוגה שמשויך לכרטיס ולהשתמש בכרטיס שמצורף. מיחזור תצוגות עם מידע מעודכן יעיל הרבה יותר מיצירת תצוגות חדשות.

כדי להשתמש במיחזור של כרטיסים, אפשר להשתמש בשיטות 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 שמכיל כרטיס אחד, כדי להרחיב את הפריסה.

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