Kartu Live

Kartu live akan muncul di bagian linimasa saat ini dan menampilkan informasi yang relevan pada saat ini.

Kartu live sangat bagus digunakan saat pengguna aktif sedang melakukan tugas, tetapi ingin memeriksa Glass secara berkala untuk informasi tambahan. Misalnya, memeriksa waktu berlari setiap beberapa menit atau mengendalikan pemutar musik ketika mereka ingin melewati atau menjeda lagu.

Jika ini adalah pertama kalinya Anda mengembangkan aplikasi untuk Glass, baca panduan Tugas Berkelanjutan terlebih dahulu. Dokumen itu membahas cara membuat Gelas dengan kartu aktif, mengikuti praktik terbaik desain kami.

Cara kerjanya

Kartu live menyediakan cara agar kartu tetap ada di masa kini dari jangka waktu tertentu selama hal itu relevan. Tidak seperti kartu statis, kartu aktif tidak selalu ada dalam linimasa, dan pengguna secara eksplisit menghapusnya setelah selesai.

Pengguna biasanya memulai kartu langsung dengan mengucapkan perintah suara di menu utama, yang memulai layanan latar belakang yang merender kartu. Pengguna kemudian dapat mengetuk kartu untuk menampilkan item menu yang dapat melakukan tindakan di kartu, seperti menutupnya dari linimasa.

Kapan menggunakannya

Kartu live dirancang untuk tugas berkelanjutan yang dapat diikuti oleh pengguna dan tidak sering, seperti tampilan yang menunjukkan status lari dari aksi, peta animasi selama navigasi, atau pemutar musik.

Manfaat lain dari kartu {i>live<i} adalah mereka sangat cocok untuk UI yang memerlukan interaksi real-time dengan pengguna dan pembaruan real-time ke UI.

Saat menggunakan kartu live, linimasa masih memiliki kontrol atas pengguna pengalaman yang sama, jadi menggeser maju atau mundur pada kartu langsung menavigasi linimasa, bukan bertindak pada kartu live itu sendiri. Selain itu, layar menyala dan mati berdasarkan perilaku sistem (setelah 5 detik tanpa interaksi pengguna atau selama dorongan maju).

Namun, kartu live memiliki akses ke banyak fitur yang sama imersi, seperti sensor atau Data GPS. Hal ini memungkinkan Anda tetap menciptakan pengalaman yang menarik sembari memungkinkan pengguna tetap berada dalam pengalaman {i>timeline<i} untuk melakukan hal lain seperti memeriksa pesan.

Arsitektur

Kartu live memerlukan konteks jangka panjang untuk memilikinya selama mereka terlihat, jadi kelola mereka di layanan latar belakang.

Anda kemudian dapat memublikasikan dan merender kartu aktif segera setelah layanan tersebut dimulai atau sebagai respons terhadap peristiwa lain yang dipantau layanan. Anda dapat merender kartu live dengan frekuensi rendah (setiap beberapa detik sekali), atau frekuensi tinggi (hingga frekuensi yang dapat dimuat ulang sistem).

Jika kartu live tidak lagi relevan, hancurkan layanan untuk menghentikan rendering.

Rendering Frekuensi Rendah

Rendering frekuensi rendah terbatas untuk sekumpulan kecil Android dan hanya dapat memperbarui tampilan sekali setiap beberapa detik.

Ini adalah cara sederhana untuk membuat kartu {i>live<i} dengan konten sederhana yang tidak memerlukan rendering atau pembaruan berkala.

Rendering Frekuensi Tinggi

Rendering frekuensi tinggi memungkinkan Anda menggunakan lebih banyak opsi yang tersedia dalam framework grafis Android.

Sistem ini memberi Anda permukaan pendukung sebenarnya dari kartu live yang dapat langsung Anda gambar menggunakan 2D tampilan dan tata letak atau bahkan grafik 3D yang kompleks dengan OpenGL.

 

Membuat kartu live frekuensi rendah

Rendering frekuensi rendah memerlukan UI yang disediakan oleh RemoteViews , yang mendukung subset tata letak dan tampilan Android berikut:

Gunakan rendering frekuensi rendah saat:

  • Anda hanya memerlukan Android View API standar yang tercantum sebelumnya.
  • Anda hanya memerlukan pembaruan yang relatif jarang (beberapa detik antara memperbarui).

Perhatikan:

  • Kartu live harus selalu memiliki PendingIntent dideklarasikan dengan setAction() linimasa untuk memublikasikan kartu.
  • Untuk membuat perubahan pada kartu setelah publikasi, panggil setViews() pada kartu dengan RemoteViews yang diperbarui sebelum mempublikasikannya kembali.

Untuk membuat kartu live frekuensi rendah:

  1. Buat tata letak atau tampilan yang ingin Anda render. Contoh berikut menunjukkan tata letak untuk permainan bola basket imajiner:

     <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. Buat layanan yang mengelola kartu aktif dan merender tata letak atau tampilan Anda. Layanan contoh ini memperbarui skor pertandingan bola basket imajiner setiap 30 detik.

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

Membuat kartu live frekuensi tinggi

Rendering frekuensi tinggi memungkinkan Anda menggambar secara langsung di Surface pendukung pada kartu live.

Gunakan rendering frekuensi tinggi saat:

  • Anda harus sering memperbarui kartu yang tayang (berkali-kali dalam satu detik).
  • Anda memerlukan fleksibilitas dalam merender. Rendering frekuensi tinggi memungkinkan Anda menggunakan tampilan dan tata letak Android hingga grafis OpenGL yang kompleks.

Perhatikan:

Menggunakan DirectRenderingCallback

Untuk membuat kartu live dengan tampilan Android standar dan logika menggambar:

  1. Membuat class yang mengimplementasikan DirectRenderingCallback, Mengimplementasikan callback dalam antarmuka ini memungkinkan Anda melakukan tindakan selama peristiwa penting pada siklus proses platform kartu aktif.

    Contoh berikut membuat thread latar belakang untuk merender secara berkala, tetapi Anda dapat memperbarui kartu sebagai respons terhadap peristiwa eksternal (misalnya, pembaruan sensor atau lokasi).

    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. Tetapkan instance DirectRenderingCallback sebagai LiveCard Callback SurfaceHolder. Ini memungkinkan kartu aktif mengetahui logika apa yang digunakan untuk me-render dirinya sendiri.

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

Menggunakan OpenGL

  1. Membuat class yang mengimplementasikan GlRenderer Mengimplementasikan callback dalam antarmuka ini memungkinkan Anda melakukan tindakan selama peristiwa penting dari siklus proses platform kartu yang aktif. Contoh ini menggambar kubus berwarna yang berputar.

    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. Buat layanan yang mengelola kartu aktif dan set class CubeRenderer sebagai perender kartu yang aktif.

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

Memberikan fokus kartu live

Saat memublikasikan kartu live dengan LiveCard.publish(), Anda meneruskan parameter ke kartu tersebut untuk mengontrol apakah model akan segera mendapatkan fokus.

Agar lini masa langsung muncul di kartu setelah dipublikasikan, gunakan LiveCard.PublishMode.REVEAL Untuk memublikasikan kartu secara diam-diam dan membuat pengguna membuka kartu sendiri, gunakan LiveCard.PublishMode.SILENT

Selain itu, LiveCard.navigate() memungkinkan Anda langsung membuka kartu setelah dipublikasikan. Misalnya, jika pengguna mencoba memulai kartu live dari menu suara utama dan kartu sudah dimulai, Anda dapat langsung membuka live streaming kartu dengan metode ini.

Membuat dan menampilkan menu

Kartu live tidak dapat menampilkan sistem menunya sendiri, jadi Anda perlu membuat aktivitas untuk menampilkan menu untuk kartu aktif.

Aktivitas menu kemudian bisa memiliki item untuk menghentikan kartu aktif, yang dimulai pengalaman, atau tindakan lain yang ingin Anda lakukan. Anda juga dapat menambahkan aktivitas setelan sistem Anda, seperti kontrol volume, sebagai item menu. Untuk selengkapnya informasi, lihat Memulai setelan.

Membuat resource menu

Membuat sumber daya menu sama seperti pada platform Android, tetapi ikuti langkah-langkah pedoman untuk Glass:

  • Untuk setiap item menu, sediakan ikon item menu berukuran 50 × 50 piksel. Menu ikon harus berwarna putih dengan latar belakang transparan. Lihat Ikon item menu kaca untuk contoh atau mengunduhnya untuk Anda gunakan sendiri.
  • Gunakan nama pendek yang menjelaskan tindakan dan dalam kapitalisasi judul. Sebuah kata kerja imperatif berfungsi baik (misalnya, Bagikan atau Balas ke semua).
  • Glass tidak menampilkan kartu aktif tanpa item menu. Paling tidak, menyediakan item menu Berhenti, sehingga pengguna dapat menghapus kartu aktif dari {i>timeline<i} (rentang waktu).
  • Tujuan Widget CheckBox tidak didukung.

    <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>
    

Membuat aktivitas untuk menangani callback menu

Anda harus menentukan aktivitas menu yang dipanggil kartu live Anda saat pengguna mengetuknya.

Ganti hal berikut Metode callback Activity untuk membuat, menampilkan, dan menutup menu dengan benar di aktivitas menu Anda:

  1. onCreateOptionsMenu() meng-inflate sumber daya menu XML.
  2. onAttachedToWindow() menampilkan menu ketika aktivitas sedang difokuskan.
  3. onPrepareOptionsMenu() menampilkan atau menyembunyikan item menu jika diperlukan. Misalnya, Anda dapat menampilkan berbagai item menu berdasarkan apa yang dilakukan pengguna. Misalnya, Anda dapat menampilkan item menu yang berbeda berdasarkan berdasarkan beberapa data kontekstual.
  4. onOptionsItemSelected() menangani pemilihan pengguna.
  5. onOptionsMenuClosed() untuk menyelesaikan aktivitas, sehingga aktivitas tidak lagi muncul di atas kartu yang aktif.

Anda harus menyelesaikan aktivitas di sini agar bisa diselesaikan dengan benar saat menu ditutup dengan pilihan atau dengan menggeser ke bawah.

/**
 * 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();
    }
}

Membuat aktivitas menu transparan

Agar konsisten dengan gaya Glass, buat aktivitas menu transparan, sehingga kartu aktif tetap terlihat di bawah menu:

  1. Membuat file res/values/styles.xml dan mendeklarasikan gaya yang membuat latar belakang aktivitas transparan:

    <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. Di file AndroidManifest.xml Anda, tetapkan tema ke aktivitas menu:

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

Menampilkan menu

Berikan PendingIntent untuk tindakan kartu menggunakan setAction(). Intent tertunda digunakan untuk memulai aktivitas menu saat pengguna mengetuk kartu:

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

Mendukung perintah suara kontekstual

  1. Tunjukkan bahwa MenuActivity Anda mendukung perintah suara kontekstual:

    // Initialize your LiveCard as usual.
    mLiveCard.setVoiceActionEnabled(true);
    mLiveCard.publish(LiveCard.PublishMode.REVEAL); // or SILENT
    
  2. Ubah MenuActivity untuk mendukung panggilan melalui alur suara:

    /**
     * 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;
        }
    }
    

Lihat perintah suara kontekstual kami untuk informasi selengkapnya.

Beberapa metode bantuan tersedia untuk memodifikasi tampilan dan perilaku menu. Lihat MenuUtils untuk mengetahui informasi selengkapnya.