شريط تمرير البطاقة

باستخدام 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() القيمة صحيح في هذه الحالة.

إذا كنت تحرّك طريقة عرض بطاقتك أو تُجري عمليات أخرى مكلفة، يمكنك بدء العمليات وإيقافها في 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() لإرجاع المعرّف الفريد المناسب للبطاقات في المحوّل. تؤدي عملية التنفيذ التلقائية إلى عرض فهرس موضع البطاقة في المحوّل، وهو أمر غير مستقر بطبيعته.

محوِّل تمرير البطاقة الفارغة

عندما يكون لديك مجموعة بيانات فارغة للمحوّلات، يكون العرض التلقائي هو إظهار شاشة سوداء. إذا أردت إظهار طريقة عرض مختلفة في هذه الحالات، لا تستخدم السمة 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));
    }