کارتهای زنده

کارت‌های زنده در بخش حاضر جدول زمانی ظاهر می‌شوند و اطلاعات مربوط به زمان فعلی را نشان می‌دهند.

کارت‌های زنده برای زمانی که کاربران فعالانه درگیر یک کار هستند عالی هستند، اما می‌خواهند به طور دوره‌ای Glass را برای اطلاعات تکمیلی بررسی کنند. به عنوان مثال، زمانی که می‌خواهند آهنگی را رد کنند یا متوقف کنند، هر چند دقیقه زمان خود را در دویدن بررسی کنند یا پخش‌کننده موسیقی را کنترل کنند.

اگر این اولین باری است که برای Glass توسعه می دهید، ابتدا راهنمای کار در حال انجام را بخوانید. این سند به نحوه ساخت یک Glassware کامل با یک کارت زنده، با پیروی از بهترین شیوه‌های طراحی ما می‌پردازد.

چگونه کار می کنند

کارت‌های زنده راهی برای ماندگاری کارت‌ها در بخش حاضر جدول زمانی تا زمانی که مرتبط هستند، فراهم می‌کنند. برخلاف کارت‌های استاتیک، کارت‌های زنده در تایم لاین باقی نمی‌مانند و کاربران به صراحت پس از اتمام کار با آن‌ها، آن‌ها را حذف می‌کنند.

کاربران معمولاً کارت‌های زنده را با گفتن یک فرمان صوتی در منوی اصلی شروع می‌کنند که یک سرویس پس‌زمینه را شروع می‌کند که کارت را ارائه می‌کند. سپس می‌توانند روی کارت ضربه بزنند تا موارد منو را نشان دهند که می‌توانند روی کارت عمل کنند، مانند حذف آن از جدول زمانی.

چه زمانی از آنها استفاده کنید

کارت‌های زنده برای کارهای جاری طراحی شده‌اند که کاربران می‌توانند مکرراً از آن‌ها استفاده کنند، مانند نمایشگری که وضعیت اجرای یک عمل، نقشه متحرک در حین پیمایش یا پخش‌کننده موسیقی را نشان می‌دهد.

یکی دیگر از مزایای کارت‌های زنده این است که برای رابط‌های کاربری که نیاز به تعامل بی‌درنگ با کاربران و به‌روزرسانی بی‌درنگ رابط کاربری دارند، مناسب هستند.

هنگام استفاده از کارت‌های زنده، خط زمانی همچنان بر تجربه کاربر کنترل دارد، بنابراین با کشیدن انگشت به جلو یا عقب روی کارت زنده، خط زمانی را به جای اینکه بر روی خود کارت زنده عمل کند، هدایت می‌کند. علاوه بر این، صفحه نمایش بر اساس نحوه رفتار سیستم روشن و خاموش می شود (بعد از 5 ثانیه بدون تعامل کاربر یا در حین حرکت سر به بالا).

با این حال، کارت‌های زنده به بسیاری از ویژگی‌های غوطه‌وری دسترسی دارند، مانند داده‌های سنسور یا GPS. این به شما امکان می‌دهد همچنان تجربیات قانع‌کننده‌ای ایجاد کنید و در عین حال به کاربران اجازه می‌دهد در تجربه خط زمانی باقی بمانند تا کارهای دیگری مانند چک کردن پیام‌ها را انجام دهند.

معماری

کارت‌های زنده به یک زمینه طولانی در حال اجرا نیاز دارند تا در تمام مدت زمانی که قابل مشاهده هستند، آنها را در اختیار داشته باشید، بنابراین آنها را در یک سرویس پس‌زمینه مدیریت کنید.

سپس می‌توانید به محض شروع سرویس یا در پاسخ به رویدادهای دیگری که سرویس نظارت می‌کند، یک کارت زنده منتشر کرده و ارائه دهید. می‌توانید کارت‌های زنده را با فرکانس پایین (هر چند ثانیه یک بار) یا فرکانس بالا (تا هر تعداد دفعاتی که سیستم می‌تواند بازخوانی کند) ارائه دهید.

وقتی کارت زنده دیگر مرتبط نیست، سرویس را از بین ببرید تا رندر متوقف شود.

رندر با فرکانس پایین

رندر با فرکانس پایین به مجموعه کوچکی از نماهای اندروید محدود می شود و فقط هر چند ثانیه یک بار می تواند نمایشگر را به روز کند.

این یک راه ساده برای ایجاد کارت های زنده با محتوای ساده است که نیازی به رندر مداوم یا به روز رسانی مکرر ندارد.

رندر فرکانس بالا

رندر با فرکانس بالا به شما امکان می دهد از گزینه های موجود در چارچوب گرافیکی اندروید بیشتر استفاده کنید.

این سیستم به شما سطح پشتیبان واقعی کارت زنده را می‌دهد که مستقیماً با استفاده از نماها و طرح‌بندی‌های دوبعدی یا حتی گرافیک‌های سه بعدی پیچیده با OpenGL روی آن می‌کشید.

ایجاد کارت های زنده با فرکانس پایین

رندر فرکانس پایین به یک رابط کاربری نیاز دارد که توسط یک شی RemoteViews ارائه شده باشد که از زیرمجموعه طرح‌بندی‌ها و نماهای Android زیر پشتیبانی می‌کند:

هنگامی که:

  • شما فقط به APIهای نمای Android استاندارد فهرست شده قبلی نیاز دارید.
  • شما فقط به به‌روزرسانی‌های نسبتاً نادری نیاز دارید (چند ثانیه بین تازه‌سازی).

یادت باشه:

  • کارتهای زنده همیشه باید دارای یک PendingIntent با setAction() برای جدول زمانی انتشار کارت باشند.
  • برای ایجاد تغییرات در کارت پس از انتشار، قبل از انتشار مجدد، setViews() روی کارت را با شی RemoteViews به روز شده فراخوانی کنید.

برای ایجاد کارت های زنده با فرکانس پایین:

  1. طرح یا نمای مورد نظر برای ارائه را ایجاد کنید. مثال زیر طرحی برای یک بازی بسکتبال خیالی را نشان می دهد:

     <TextView
         android:id="@+id/home_team_name_text_view"
         android:layout_width="249px"
         android:layout_height="wrap_content"
         android:layout_alignParentRight="true"
         android:gravity="center"
         android:textSize="40px" />
    
     <TextView
         android:id="@+id/away_team_name_text_view"
         android:layout_width="249px"
         android:layout_height="wrap_content"
         android:layout_alignParentLeft="true"
         android:gravity="center"
         android:textSize="40px" />
    
     <TextView
         android:id="@+id/away_score_text_view"
         android:layout_width="249px"
         android:layout_height="wrap_content"
         android:layout_alignLeft="@+id/away_team_name_text_view"
         android:layout_below="@+id/away_team_name_text_view"
         android:gravity="center"
         android:textSize="70px" />
    
     <TextView
         android:id="@+id/home_score_text_view"
         android:layout_width="249px"
         android:layout_height="wrap_content"
         android:layout_alignLeft="@+id/home_team_name_text_view"
         android:layout_below="@+id/home_team_name_text_view"
         android:gravity="center"
         android:textSize="70px" />
    
     <TextView
         android:id="@+id/footer_text"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_alignParentBottom="true"
         android:layout_alignParentLeft="true"
         android:layout_marginBottom="33px"
         android:textSize="26px" />
    

  2. سرویسی ایجاد کنید که کارت زنده را مدیریت کند و طرح‌بندی یا نمای شما را ارائه کند. این سرویس نمونه امتیاز یک بازی بسکتبال خیالی را هر 30 ثانیه به روز می کند.

    import java.util.Random;
    
    import com.google.android.glass.timeline.LiveCard;
    import com.google.android.glass.timeline.LiveCard.PublishMode;
    
    import android.app.PendingIntent;
    import android.app.Service;
    import android.content.Intent;
    import android.os.Handler;
    import android.os.IBinder;
    import android.widget.RemoteViews;
    
    public class LiveCardService extends Service {
    
        private static final String LIVE_CARD_TAG = "LiveCardDemo";
    
        private LiveCard mLiveCard;
        private RemoteViews mLiveCardView;
    
        private int homeScore, awayScore;
        private Random mPointsGenerator;
    
        private final Handler mHandler = new Handler();
        private final UpdateLiveCardRunnable mUpdateLiveCardRunnable =
            new UpdateLiveCardRunnable();
        private static final long DELAY_MILLIS = 30000;
    
        @Override
        public void onCreate() {
            super.onCreate();
            mPointsGenerator = new Random();
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            if (mLiveCard == null) {
    
                // Get an instance of a live card
                mLiveCard = new LiveCard(this, LIVE_CARD_TAG);
    
                // Inflate a layout into a remote view
                mLiveCardView = new RemoteViews(getPackageName(),
                        R.layout.main_layout);
    
                // Set up initial RemoteViews values
                homeScore = 0;
                awayScore = 0;
                mLiveCardView.setTextViewText(R.id.home_team_name_text_view,
                        getString(R.string.home_team));
                mLiveCardView.setTextViewText(R.id.away_team_name_text_view,
                        getString(R.string.away_team));
                mLiveCardView.setTextViewText(R.id.footer_text,
                        getString(R.string.game_quarter));
    
                // Set up the live card's action with a pending intent
                // to show a menu when tapped
                Intent menuIntent = new Intent(this, MenuActivity.class);
                menuIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                        Intent.FLAG_ACTIVITY_CLEAR_TASK);
                mLiveCard.setAction(PendingIntent.getActivity(
                        this, 0, menuIntent, 0));
    
                // Publish the live card
                mLiveCard.publish(PublishMode.REVEAL);
    
                // Queue the update text runnable
                mHandler.post(mUpdateLiveCardRunnable);
            }
            return START_STICKY;
        }
    
        @Override
        public void onDestroy() {
            if (mLiveCard != null && mLiveCard.isPublished()) {
                //Stop the handler from queuing more Runnable jobs
                mUpdateLiveCardRunnable.setStop(true);
    
                mLiveCard.unpublish();
                mLiveCard = null;
            }
            super.onDestroy();
        }
    
        /**
         * Runnable that updates live card contents
         */
        private class UpdateLiveCardRunnable implements Runnable{
    
            private boolean mIsStopped = false;
    
            /*
             * Updates the card with a fake score every 30 seconds as a demonstration.
             * You also probably want to display something useful in your live card.
             *
             * If you are executing a long running task to get data to update a
             * live card(e.g, making a web call), do this in another thread or
             * AsyncTask.
             */
            public void run(){
                if(!isStopped()){
                    // Generate fake points.
                    homeScore += mPointsGenerator.nextInt(3);
                    awayScore += mPointsGenerator.nextInt(3);
    
                    // Update the remote view with the new scores.
                    mLiveCardView.setTextViewText(R.id.home_score_text_view,
                            String.valueOf(homeScore));
                    mLiveCardView.setTextViewText(R.id.away_score_text_view,
                            String.valueOf(awayScore));
    
                    // Always call setViews() to update the live card's RemoteViews.
                    mLiveCard.setViews(mLiveCardView);
    
                    // Queue another score update in 30 seconds.
                    mHandler.postDelayed(mUpdateLiveCardRunnable, DELAY_MILLIS);
                }
            }
    
            public boolean isStopped() {
                return mIsStopped;
            }
    
            public void setStop(boolean isStopped) {
                this.mIsStopped = isStopped;
            }
        }
    
        @Override
        public IBinder onBind(Intent intent) {
          /*
           * If you need to set up interprocess communication
           * (activity to a service, for instance), return a binder object
           * so that the client can receive and modify data in this service.
           *
           * A typical use is to give a menu activity access to a binder object
           * if it is trying to change a setting that is managed by the live card
           * service. The menu activity in this sample does not require any
           * of these capabilities, so this just returns null.
           */
           return null;
        }
    }
    

ایجاد کارت های زنده با فرکانس بالا

رندر فرکانس بالا به شما امکان می دهد مستقیماً روی سطح پشتی کارت زنده بکشید.

از رندر فرکانس بالا در موارد زیر استفاده کنید:

  • شما باید کارت زنده را به طور مکرر به روز کنید (در هر ثانیه چندین بار).
  • شما در آنچه می توانید ارائه دهید به انعطاف پذیری نیاز دارید. رندر با فرکانس بالا به شما امکان می دهد از نماها و طرح بندی های اندروید برای گرافیک های پیچیده OpenGL استفاده کنید.

یادت باشه:

  • همیشه باید یک سرویس پس‌زمینه برای رندر روی سطح کارت زنده ایجاد کنید.
  • کارتهای زنده همیشه باید دارای یک PendingIntent اعلام شده با setAction() باشند.
  • اگر OpenGL و DirectRenderingCallback برای همه موارد دیگر رندر می کنید از GLRenderer استفاده کنید.

با استفاده از DirectRenderingCallback

برای ایجاد کارت های زنده با نمای استاندارد اندروید و منطق ترسیم:

  1. کلاسی ایجاد کنید که DirectRenderingCallback را پیاده‌سازی کند، پیاده‌سازی تماس‌های برگشتی در این رابط‌ها به شما امکان می‌دهد اقداماتی را در طول رویدادهای مهم چرخه حیات سطح کارت زنده انجام دهید.

    مثال زیر یک رشته پس‌زمینه برای رندر دوره‌ای ایجاد می‌کند، اما می‌توانید کارت را در پاسخ به رویدادهای خارجی (مثلاً به‌روزرسانی‌های حسگر یا مکان) به‌روزرسانی کنید.

    public class LiveCardRenderer implements DirectRenderingCallback {
    
        // About 30 FPS.
        private static final long FRAME_TIME_MILLIS = 33;
    
        private SurfaceHolder mHolder;
        private boolean mPaused;
        private RenderThread mRenderThread;
    
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format,
                int width, int height) {
            // Update your views accordingly.
        }
    
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            mPaused = false;
            mHolder = holder;
            updateRendering();
        }
    
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            mHolder = null;
            updateRendering();
        }
    
        @Override
        public void renderingPaused(SurfaceHolder holder, boolean paused) {
            mPaused = paused;
            updateRendering();
        }
    
        /**
         * Start or stop rendering according to the timeline state.
         */
        private void updateRendering() {
            boolean shouldRender = (mHolder != null) && !mPaused;
            boolean rendering = mRenderThread != null;
    
            if (shouldRender != rendering) {
                if (shouldRender) {
                    mRenderThread = new RenderThread();
                    mRenderThread.start();
                } else {
                    mRenderThread.quit();
                    mRenderThread = null;
                }
            }
        }
    
        /**
         * Draws the view in the SurfaceHolder's canvas.
         */
        private void draw() {
            Canvas canvas;
            try {
                canvas = mHolder.lockCanvas();
            } catch (Exception e) {
                return;
            }
            if (canvas != null) {
                // Draw on the canvas.
                mHolder.unlockCanvasAndPost(canvas);
            }
        }
    
        /**
         * Redraws in the background.
         */
        private class RenderThread extends Thread {
            private boolean mShouldRun;
    
            /**
             * Initializes the background rendering thread.
             */
            public RenderThread() {
                mShouldRun = true;
            }
    
            /**
             * Returns true if the rendering thread should continue to run.
             *
             * @return true if the rendering thread should continue to run
             */
            private synchronized boolean shouldRun() {
                return mShouldRun;
            }
    
            /**
             * Requests that the rendering thread exit at the next
             opportunity.
             */
            public synchronized void quit() {
                mShouldRun = false;
            }
    
            @Override
            public void run() {
                while (shouldRun()) {
                    draw();
                    SystemClock.sleep(FRAME_TIME_MILLIS);
                }
            }
        }
    }
    
  2. یک نمونه از DirectRenderingCallback خود را به عنوان پاسخ تماس LiveCard SurfaceHolder تنظیم کنید. این به کارت زنده اجازه می دهد تا بداند از چه منطقی برای ارائه خود استفاده کند.

    // Tag used to identify the LiveCard in debugging logs.
    private static final String LIVE_CARD_TAG = "my_card";
    
    // Cached instance of the LiveCard created by the publishCard() method.
    private LiveCard mLiveCard;
    
    private void publishCard(Context context) {
        if (mLiveCard == null) {
            mLiveCard = new LiveCard(this, LIVE_CARD_TAG);
    
            // Enable direct rendering.
            mLiveCard.setDirectRenderingEnabled(true);
            mLiveCard.getSurfaceHolder().addCallback(
                    new LiveCardRenderer());
    
            Intent intent = new Intent(context, MenuActivity.class);
            mLiveCard.setAction(PendingIntent.getActivity(context, 0,
                    intent, 0));
            mLiveCard.publish(LiveCard.PublishMode.SILENT);
        } else {
            // Card is already published.
            return;
        }
    }
    
    private void unpublishCard(Context context) {
        if (mLiveCard != null) {
            mLiveCard.unpublish();
            mLiveCard = null;
        }
    }
    

با استفاده از OpenGL

  1. کلاسی ایجاد کنید که GlRenderer پیاده سازی کند. پیاده‌سازی تماس‌های برگشتی در این رابط به شما امکان می‌دهد اقداماتی را در طول رویدادهای مهم چرخه حیات سطح کارت زنده انجام دهید. این مثال یک مکعب رنگی و چرخان را ترسیم می کند.

    import com.google.android.glass.timeline.GlRenderer;
    
    import android.opengl.GLES20;
    import android.opengl.Matrix;
    import android.os.SystemClock;
    
    import java.util.concurrent.TimeUnit;
    import javax.microedition.khronos.egl.EGLConfig;
    
    /**
     * Renders a 3D OpenGL Cube on a {@link LiveCard}.
     */
    public class CubeRenderer implements GlRenderer {
    
        /** Rotation increment per frame. */
        private static final float CUBE_ROTATION_INCREMENT = 0.6f;
    
        /** The refresh rate, in frames per second. */
        private static final int REFRESH_RATE_FPS = 60;
    
        /** The duration, in milliseconds, of one frame. */
        private static final float FRAME_TIME_MILLIS = TimeUnit.SECONDS.toMillis(1) / REFRESH_RATE_FPS;
    
        private final float[] mMVPMatrix;
        private final float[] mProjectionMatrix;
        private final float[] mViewMatrix;
        private final float[] mRotationMatrix;
        private final float[] mFinalMVPMatrix;
    
        private Cube mCube;
        private float mCubeRotation;
        private long mLastUpdateMillis;
    
        public CubeRenderer() {
            mMVPMatrix = new float[16];
            mProjectionMatrix = new float[16];
            mViewMatrix = new float[16];
            mRotationMatrix = new float[16];
            mFinalMVPMatrix = new float[16];
    
            // Set the fixed camera position (View matrix).
            Matrix.setLookAtM(mViewMatrix, 0, 0.0f, 0.0f, -4.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
        }
    
        @Override
        public void onSurfaceCreated(EGLConfig config) {
            // Set the background frame color
            GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
            GLES20.glClearDepthf(1.0f);
            GLES20.glEnable(GLES20.GL_DEPTH_TEST);
            GLES20.glDepthFunc(GLES20.GL_LEQUAL);
            mCube = new Cube();
        }
    
        @Override
        public void onSurfaceChanged(int width, int height) {
            float ratio = (float) width / height;
    
            GLES20.glViewport(0, 0, width, height);
            // This projection matrix is applied to object coordinates in the onDrawFrame() method.
            Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1.0f, 1.0f, 3.0f, 7.0f);
            // modelView = projection x view
            Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
        }
    
        @Override
        public void onDrawFrame() {
            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
    
            // Apply the rotation.
            Matrix.setRotateM(mRotationMatrix, 0, mCubeRotation, 1.0f, 1.0f, 1.0f);
            // Combine the rotation matrix with the projection and camera view
            Matrix.multiplyMM(mFinalMVPMatrix, 0, mMVPMatrix, 0, mRotationMatrix, 0);
    
            // Draw cube.
            mCube.draw(mFinalMVPMatrix);
            updateCubeRotation();
        }
    
        /** Updates the cube rotation. */
        private void updateCubeRotation() {
            if (mLastUpdateMillis != 0) {
                float factor = (SystemClock.elapsedRealtime() - mLastUpdateMillis) / FRAME_TIME_MILLIS;
                mCubeRotation += CUBE_ROTATION_INCREMENT * factor;
            }
            mLastUpdateMillis = SystemClock.elapsedRealtime();
        }
    }
    
  2. سرویسی ایجاد کنید که کارت زنده را مدیریت کند و کلاس CubeRenderer را به عنوان رندر کارت زنده تنظیم کند.

    import com.google.android.glass.timeline.LiveCard;
    import com.google.android.glass.timeline.LiveCard.PublishMode;
    
    import android.app.PendingIntent;
    import android.app.Service;
    import android.content.Intent;
    import android.os.IBinder;
    
    /**
     * Creates a {@link LiveCard} rendering a rotating 3D cube with OpenGL.
     */
    public class OpenGlService extends Service {
    
        private static final String LIVE_CARD_TAG = "opengl";
    
        private LiveCard mLiveCard;
    
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            if (mLiveCard == null) {
                mLiveCard = new LiveCard(this, LIVE_CARD_TAG);
                mLiveCard.setRenderer(new CubeRenderer());
                mLiveCard.setAction(
                        PendingIntent.getActivity(this, 0, new Intent(this, MenuActivity.class), 0));
                mLiveCard.publish(PublishMode.REVEAL);
            } else {
                mLiveCard.navigate();
            }
    
            return START_STICKY;
        }
    
        @Override
        public void onDestroy() {
            if (mLiveCard != null && mLiveCard.isPublished()) {
                mLiveCard.unpublish();
                mLiveCard = null;
            }
            super.onDestroy();
        }
    }
    
import android.opengl.GLES20;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

/**
 * Renders a 3D Cube using OpenGL ES 2.0.
 *
 * For more information on how to use OpenGL ES 2.0 on Android, see the
 * <a href="//developer.android.com/training/graphics/opengl/index.html">
 * Displaying Graphics with OpenGL ES</a> developer guide.
 */
public class Cube {

    /** Cube vertices */
    private static final float VERTICES[] = {
        -0.5f, -0.5f, -0.5f,
        0.5f, -0.5f, -0.5f,
        0.5f, 0.5f, -0.5f,
        -0.5f, 0.5f, -0.5f,
        -0.5f, -0.5f, 0.5f,
        0.5f, -0.5f, 0.5f,
        0.5f, 0.5f, 0.5f,
        -0.5f, 0.5f, 0.5f
    };

    /** Vertex colors. */
    private static final float COLORS[] = {
        0.0f, 1.0f, 1.0f, 1.0f,
        1.0f, 0.0f, 0.0f, 1.0f,
        1.0f, 1.0f, 0.0f, 1.0f,
        0.0f, 1.0f, 0.0f, 1.0f,
        0.0f, 0.0f, 1.0f, 1.0f,
        1.0f, 0.0f, 1.0f, 1.0f,
        1.0f, 1.0f, 1.0f, 1.0f,
        0.0f, 1.0f, 1.0f, 1.0f,
    };


    /** Order to draw vertices as triangles. */
    private static final byte INDICES[] = {
        0, 1, 3, 3, 1, 2, // Front face.
        0, 1, 4, 4, 5, 1, // Bottom face.
        1, 2, 5, 5, 6, 2, // Right face.
        2, 3, 6, 6, 7, 3, // Top face.
        3, 7, 4, 4, 3, 0, // Left face.
        4, 5, 7, 7, 6, 5, // Rear face.
    };

    /** Number of coordinates per vertex in {@link VERTICES}. */
    private static final int COORDS_PER_VERTEX = 3;

    /** Number of values per colors in {@link COLORS}. */
    private static final int VALUES_PER_COLOR = 4;

    /** Vertex size in bytes. */
    private final int VERTEX_STRIDE = COORDS_PER_VERTEX * 4;

    /** Color size in bytes. */
    private final int COLOR_STRIDE = VALUES_PER_COLOR * 4;

    /** Shader code for the vertex. */
    private static final String VERTEX_SHADER_CODE =
            "uniform mat4 uMVPMatrix;" +
            "attribute vec4 vPosition;" +
            "attribute vec4 vColor;" +
            "varying vec4 _vColor;" +
            "void main() {" +
            "  _vColor = vColor;" +
            "  gl_Position = uMVPMatrix * vPosition;" +
            "}";

    /** Shader code for the fragment. */
    private static final String FRAGMENT_SHADER_CODE =
            "precision mediump float;" +
            "varying vec4 _vColor;" +
            "void main() {" +
            "  gl_FragColor = _vColor;" +
            "}";


    private final FloatBuffer mVertexBuffer;
    private final FloatBuffer mColorBuffer;
    private final ByteBuffer mIndexBuffer;
    private final int mProgram;
    private final int mPositionHandle;
    private final int mColorHandle;
    private final int mMVPMatrixHandle;

    public Cube() {
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(VERTICES.length * 4);

        byteBuffer.order(ByteOrder.nativeOrder());
        mVertexBuffer = byteBuffer.asFloatBuffer();
        mVertexBuffer.put(VERTICES);
        mVertexBuffer.position(0);

        byteBuffer = ByteBuffer.allocateDirect(COLORS.length * 4);
        byteBuffer.order(ByteOrder.nativeOrder());
        mColorBuffer = byteBuffer.asFloatBuffer();
        mColorBuffer.put(COLORS);
        mColorBuffer.position(0);

        mIndexBuffer = ByteBuffer.allocateDirect(INDICES.length);
        mIndexBuffer.put(INDICES);
        mIndexBuffer.position(0);

        mProgram = GLES20.glCreateProgram();
        GLES20.glAttachShader(mProgram, loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_CODE));
        GLES20.glAttachShader(
                mProgram, loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_CODE));
        GLES20.glLinkProgram(mProgram);

        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
        mColorHandle = GLES20.glGetAttribLocation(mProgram, "vColor");
        mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
    }

    /**
     * Encapsulates the OpenGL ES instructions for drawing this shape.
     *
     * @param mvpMatrix The Model View Project matrix in which to draw this shape
     */
    public void draw(float[] mvpMatrix) {
        // Add program to OpenGL environment.
        GLES20.glUseProgram(mProgram);

        // Prepare the cube coordinate data.
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        GLES20.glVertexAttribPointer(
                mPositionHandle, 3, GLES20.GL_FLOAT, false, VERTEX_STRIDE, mVertexBuffer);

        // Prepare the cube color data.
        GLES20.glEnableVertexAttribArray(mColorHandle);
        GLES20.glVertexAttribPointer(
                mColorHandle, 4, GLES20.GL_FLOAT, false, COLOR_STRIDE, mColorBuffer);

        // Apply the projection and view transformation.
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);

        // Draw the cube.
        GLES20.glDrawElements(
                GLES20.GL_TRIANGLES, INDICES.length, GLES20.GL_UNSIGNED_BYTE, mIndexBuffer);

        // Disable vertex arrays.
        GLES20.glDisableVertexAttribArray(mPositionHandle);
        GLES20.glDisableVertexAttribArray(mColorHandle);
    }

    /** Loads the provided shader in the program. */
    private static int loadShader(int type, String shaderCode){
        int shader = GLES20.glCreateShader(type);

        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        return shader;
    }
}

دادن تمرکز کارت زنده

هنگامی که یک کارت زنده را با LiveCard.publish() منتشر می کنید، پارامتری را برای کنترل فوکوس بودن یا نبودن آن به آن ارسال می کنید.

برای اینکه جدول زمانی بلافاصله پس از انتشار به کارت منتقل شود، از LiveCard.PublishMode.REVEAL استفاده کنید. برای انتشار بی‌صدا کارت و وادار کردن کاربران به حرکت به سمت کارت، از LiveCard.PublishMode.SILENT استفاده کنید.

علاوه بر این، متد LiveCard.navigate() به شما امکان می دهد پس از انتشار کارت به سمت آن بپرید. به عنوان مثال، اگر کاربران سعی می کنند کارت زنده شما را از منوی صوتی اصلی شروع کنند و از قبل شروع شده است، می توانید با این روش به کارت زنده بروید.

ایجاد و نمایش منو

کارت‌های زنده نمی‌توانند سیستم منوی خود را نشان دهند، بنابراین باید یک فعالیت ایجاد کنید تا منوی کارت زنده نمایش داده شود.

سپس فعالیت منو می تواند مواردی برای توقف کارت زنده، شروع غوطه وری یا هر اقدام دیگری که می خواهید انجام دهید، داشته باشد. همچنین می‌توانید فعالیت‌های تنظیمات سیستم، مانند کنترل صدا، را به عنوان آیتم منو اضافه کنید. برای اطلاعات بیشتر، تنظیمات شروع را ببینید.

ایجاد منابع منو

ایجاد منابع منو مانند پلتفرم اندروید است، اما این دستورالعمل ها را برای Glass دنبال کنید:

  • برای هر آیتم منو، یک نماد آیتم منو 50 × 50 پیکسل ارائه دهید. نماد منو باید به رنگ سفید در پس زمینه شفاف باشد. برای مثال به نمادهای آیتم های منوی شیشه ای یا دانلود آنها برای استفاده خودتان مراجعه کنید.
  • از نام کوتاهی استفاده کنید که عمل را توصیف کند و در حروف عنوان باشد. یک فعل امری به خوبی کار می کند (به عنوان مثال، اشتراک گذاری یا پاسخ به همه ).
  • Glass کارتهای زنده را بدون آیتم منو نمایش نمی دهد. حداقل، یک آیتم منوی توقف ارائه دهید تا کاربران بتوانند کارت زنده را از جدول زمانی حذف کنند.
  • ویجت CheckBox پشتیبانی نمی شود.

    <menu xmlns:android="http://schemas.android.com/apk/res/android">
        <item
            android:id="@+id/menu_item_1"
            android:title="@string/Menu_Item_1"       <!-- must have "Stop" menu item -->
            android:icon="@drawable/menu_item_1_icon" />   <!-- white on transparent icon -->
    </menu>
    

ایجاد یک فعالیت برای رسیدگی به تماس های منو

شما باید یک فعالیت منو را تعریف کنید که کارت زنده شما زمانی که کاربران روی آن ضربه می زنند، آن را فراخوانی می کند.

برای ایجاد، نمایش و رد کردن منوها در فعالیت منو، روش‌های پاسخ به تماس Activity زیر را نادیده بگیرید:

  1. onCreateOptionsMenu() منبع منوی XML را باد می کند.
  2. onAttachedToWindow() منو را هنگامی که فعالیت در فوکوس است نشان می دهد.
  3. onPrepareOptionsMenu() آیتم های منو را در صورت نیاز نشان می دهد یا پنهان می کند. به عنوان مثال، می توانید آیتم های مختلف منو را بر اساس کاری که کاربران انجام می دهند نشان دهید. برای مثال، می‌توانید آیتم‌های مختلف منو را بر اساس برخی داده‌های متنی نشان دهید.
  4. onOptionsItemSelected() انتخاب کاربر را کنترل می کند.
  5. onOptionsMenuClosed() برای پایان دادن به فعالیت، به طوری که دیگر روی کارت زنده ظاهر نشود.

باید فعالیت را در اینجا به پایان برسانید تا زمانی که منو با یک انتخاب یا با کشیدن انگشت به پایین بسته می شود، به درستی تمام شود.

/**
 * Activity showing the options menu.
 */
public class MenuActivity extends Activity {

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        openOptionsMenu();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.stopwatch, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle item selection.
        switch (item.getItemId()) {
            case R.id.stop:
                stopService(new Intent(this, StopwatchService.class));
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public void onOptionsMenuClosed(Menu menu) {
        // Nothing else to do, closing the activity.
        finish();
    }
}

شفاف کردن فعالیت منو

برای سازگاری با سبک Glass، فعالیت منو را شفاف کنید تا کارت زنده همچنان در زیر منو قابل مشاهده باشد:

  1. یک فایل res/values/styles.xml ایجاد کنید و سبکی را اعلام کنید که پس‌زمینه فعالیت را شفاف می‌کند:

    <resources>
        <style name="MenuTheme" parent="@android:style/Theme.DeviceDefault">
            <item name="android:windowBackground">@android:color/transparent</item>
            <item name="android:colorBackgroundCacheHint">@null</item>
            <item name="android:windowIsTranslucent">true</item>
            <item name="android:windowAnimationStyle">@null</item>
        </style>
    </resources>
    
  2. در فایل AndroidManifest.xml خود، موضوع را به فعالیت منو اختصاص دهید:

    <?xml version="1.0" encoding="utf-8"?>
        <manifest ... >
          ...
            <application ... >
                ...
                <activity
                    android:name=".MenuActivity"
                    android:theme="@style/MenuTheme"
                    ...>
                </activity>
            </application>
    
        </manifest>
    

نمایش منو

با استفاده از setAction() یک PendingIntent برای عملکرد کارت ارائه کنید. هنگامی که کاربران روی کارت ضربه می زنند، از قصد در انتظار برای شروع فعالیت منو استفاده می شود:

Intent menuIntent = new Intent(this, MenuActivity.class);
mLiveCard.setAction(PendingIntent.getActivity(this, 0, menuIntent, 0));
mLiveCard.publish(LiveCard.PublishMode.REVEAL); // or SILENT

پشتیبانی از دستورات صوتی متنی

  1. مشخص کنید که MenuActivity شما از دستورات صوتی متنی پشتیبانی می کند:

    // Initialize your LiveCard as usual.
    mLiveCard.setVoiceActionEnabled(true);
    mLiveCard.publish(LiveCard.PublishMode.REVEAL); // or SILENT
    
  2. MenuActivity خود را برای پشتیبانی از فراخوانی از طریق جریان صوتی تغییر دهید:

    /**
     * Activity showing the options menu.
     */
    public class MenuActivity extends Activity {
    
        private boolean mFromLiveCardVoice;
        private boolean mIsFinishing;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mFromLiveCardVoice =
                    getIntent().getBooleanExtra(LiveCard.EXTRA_FROM_LIVECARD_VOICE, false);
            if (mFromLiveCardVoice) {
                // When activated by voice from a live card, enable voice commands. The menu
                // will automatically "jump" ahead to the items (skipping the guard phrase
                // that was already said at the live card).
                getWindow().requestFeature(WindowUtils.FEATURE_VOICE_COMMANDS);
            }
        }
    
        @Override
        public void onAttachedToWindow() {
            super.onAttachedToWindow();
            if (!mFromLiveCardVoice) {
                openOptionsMenu();
            }
        }
    
        @Override
        public boolean onCreatePanelMenu(int featureId, Menu menu) {
            if (isMyMenu(featureId)) {
                getMenuInflater().inflate(R.menu.stopwatch, menu);
                return true;
            }
            return super.onCreatePanelMenu(featureId, menu);
        }
    
        @Override
        public boolean onPreparePanel(int featureId, View view, Menu menu) {
            if (isMyMenu(featureId)) {
                // Don't reopen menu once we are finishing. This is necessary
                // since voice menus reopen themselves while in focus.
                return !mIsFinishing;
            }
            return super.onPreparePanel(featureId, view, menu);
        }
    
        @Override
        public boolean onMenuItemSelected(int featureId, MenuItem item) {
            if (isMyMenu(featureId)) {
                // Handle item selection.
                switch (item.getItemId()) {
                    case R.id.stop_this:
                        stopService(new Intent(this, StopwatchService.class));
                        return true;
                }
            }
            return super.onMenuItemSelected(featureId, item);
        }
    
        @Override
        public void onPanelClosed(int featureId, Menu menu) {
            super.onPanelClosed(featureId, menu);
            if (isMyMenu(featureId)) {
                // When the menu panel closes, either an item is selected from the menu or the
                // menu is dismissed by swiping down. Either way, we end the activity.
                isFinishing = true;
                finish();
            }
        }
    
        /**
         * Returns {@code true} when the {@code featureId} belongs to the options menu or voice
         * menu that are controlled by this menu activity.
         */
        private boolean isMyMenu(int featureId) {
            return featureId == Window.FEATURE_OPTIONS_PANEL ||
                   featureId == WindowUtils.FEATURE_VOICE_COMMANDS;
        }
    }
    

برای اطلاعات بیشتر، راهنمای دستورات صوتی متنی را بررسی کنید.

چند روش کمکی برای تغییر ظاهر و رفتار منوها موجود است. برای اطلاعات بیشتر به MenuUtils مراجعه کنید.