Canlı Kartlar

Canlı kartlar, zaman çizelgesinin şimdiki bölümünde görünür ve o ayla ilgili bilgileri görüntüler.

Canlı kartlar, kullanıcıların bir işe aktif olarak dahil olduğu ancak ek bilgi için Glass'ı düzenli olarak kontrol etmek istedikleri durumlarda idealdir. Örneğin, koşuda geçirdikleri süreyi birkaç dakikada bir kontrol etmek veya bir şarkıyı atlamak ya da duraklatmak istediklerinde bir müzik çaları kontrol etmek.

Glass için ilk kez geliştirme yapıyorsanız önce Devam Eden Görev kılavuzunu okuyun. Bu dokümanda, tasarımla ilgili en iyi uygulamalarımızı izleyerek canlı bir kartla eksiksiz bir Cam yazılımının nasıl oluşturulacağını ele alıyoruz.

İşleyiş şekli

Canlı kartlar, kartların alakalı oldukları sürece zaman çizelgesinin mevcut bölümünde kalması için bir yol sunar. Statik kartlardan farklı olarak canlı kartlar zaman çizelgesinde durmaz ve kullanıcılar işlem bittikten sonra bunları açıkça kaldırır.

Kullanıcılar genellikle ana menüde bir sesli komut söyleyerek canlı kartları başlatır. Bu komut, kartı oluşturan bir arka plan hizmeti başlatır. Daha sonra, kart üzerinde işlem yapabilen menü öğelerini (ör. kartı zaman çizelgesinden kapatmak) göstermek için karta dokunabilirler.

Ne zaman kullanılır?

Canlı kartlar, kullanıcıların sık sık atlayıp çıkabilecekleri, bir işlemin çalışma durumunu gösteren bir ekran, navigasyon sırasında animasyonlu bir harita veya bir müzik çalar gibi devam eden görevler için tasarlanmıştır.

Canlı kartların bir diğer avantajı, kullanıcılarla gerçek zamanlı etkileşim ve kullanıcı arayüzünde gerçek zamanlı güncellemeler gerektiren kullanıcı arayüzlerine uygun olmalarıdır.

Canlı kartlar kullanılırken zaman çizelgesi kullanıcı deneyimini kontrol etmeye devam eder. Bu nedenle, canlı bir kartta ileri veya geri kaydırmak yerine canlı karta işlem yapmak yerine zaman çizelgesinde gezinmenizi sağlar. Ayrıca, ekran, sistemin davranışına göre (kullanıcı etkileşimi olmadan 5 saniye sonra veya baş gösterme sırasında) açılıp kapanır.

Bununla birlikte, canlı kartlar sayesinde, sensör veya GPS verileri gibi yoğunlaştırma özelliğinin birçok özelliğine erişebilirsiniz. Bu sayede, ilgi çekici deneyimler oluşturmaya devam ederken kullanıcıların, mesajları kontrol etme gibi diğer işlemler için zaman çizelgesi deneyiminden sapmamasını sağlayabilirsiniz.

Mimari

Canlı kartlar, göründükleri süre boyunca sahiplenebilmeleri için uzun bir bağlam gerektirir. Bu nedenle, bu kartları bir arka plan hizmetinde yönetin.

Daha sonra, canlı bir kartı hizmet başlar başlamaz veya hizmetin izlediği diğer etkinliklere yanıt olarak yayınlayabilir ve oluşturabilirsiniz. Canlı kartları düşük sıklıkta (birkaç saniyede bir) veya yüksek sıklıkta (sistemin yenileme sıklığı kadar) oluşturabilirsiniz.

Canlı kart artık alakalı olmadığında oluşturmayı durdurmak için hizmeti kaldırın.

Düşük Frekanslı Oluşturma

Düşük frekanslı oluşturma işlemi küçük bir Android görünümü grubuyla sınırlıdır ve ekranı yalnızca birkaç saniyede bir güncelleyebilir.

Sürekli oluşturma veya sık güncelleme gerektirmeyen, basit içeriğe sahip canlı kartlar oluşturmanın basit bir yoludur.

Yüksek Frekanslı Oluşturma

Yüksek frekanslı oluşturma, Android grafik çerçevesinde mevcut olan daha fazla seçeneği kullanabilmenizi sağlar.

Sistem, 2D görünüm ve düzenler ve hatta OpenGL ile karmaşık 3D grafikler kullanarak doğrudan üzerine çizdiğiniz canlı kartın gerçek arka yüzeyini sağlar.

 

Düşük frekanslı canlı kartlar oluşturma

Düşük frekanslı oluşturma işlemi için RemoteViews nesnesi tarafından sağlanan kullanıcı arayüzü gerekir. Bu, Android düzenleri ve görünümlerinin aşağıdaki alt kümesini destekler:

Düşük frekanslı oluşturma işlemini şu durumlarda kullanın:

  • Yalnızca daha önce listelenen standart Android görünümü API'lerini kullanmanız gerekir.
  • Yalnızca nispeten seyrek aralıklarla (yenilemeler arasında birkaç saniye) güncelleme yapmanız gerekir.

Unutmayın:

  • Kartı yayınlama zaman çizelgesinde, canlı kartlarda her zaman setAction() ile birlikte bir PendingIntent belirtilmelidir.
  • Yayınladıktan sonra bir kartta değişiklik yapmak için tekrar yayınlamadan önce güncellenmiş RemoteViews nesnesiyle kartta setViews() yöntemini çağırın.

Düşük frekanslı canlı kartlar oluşturmak için:

  1. Oluşturmak istediğiniz düzeni veya görünümü oluşturun. Aşağıdaki örnekte hayali bir basketbol maçının düzeni gösterilmektedir:

     <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. Yayındaki kartı yöneten ve düzeninizi veya görünümünüzü oluşturan bir hizmet oluşturun. Bu örnek hizmet, hayali bir basketbol maçının skorunu 30 saniyede bir günceller.

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

Yüksek frekanslı canlı kartlar oluşturma

Yüksek frekanslı oluşturma, doğrudan yayındaki kartın arka yüzünde çizim yapmanızı sağlar.

Yüksek frekanslı oluşturma işlemini şu durumlarda kullanın:

  • Yayındaki kartı sık sık (saniyede birkaç kez) güncellemeniz gerekiyor.
  • Oluşturabileceğiniz öğeler konusunda esnekliğe ihtiyacınız var. Yüksek frekanslı oluşturma, karmaşık OpenGL grafiklerinin Android görünümlerini ve düzenlerini kullanmanıza olanak tanır.

Unutmayın:

  • Yayındaki kartın yüzeyinde oluşturmak için her zaman bir arka plan hizmeti oluşturmanız gerekir.
  • Canlı kartlarda her zaman setAction() ile belirtilmiş bir PendingIntent olmalıdır.
  • OpenGL ve diğer tüm durumlarda DirectRenderingCallback oluşturuyorsanız GLRenderer seçeneğini kullanın.

DirectRenderingCallback Kullanma

Standart Android görünümleri ve çizim mantığıyla canlı kartlar oluşturmak için:

  1. DirectRenderingCallback uygulayan bir sınıf oluşturun. Geri çağırmaları bu arayüzlerde uygulamak, canlı kartın yüzey yaşam döngüsündeki önemli etkinlikler sırasında işlem yapmanızı sağlar.

    Aşağıdaki örnek, düzenli olarak oluşturulacak bir arka plan iş parçacığı oluşturur ancak harici etkinliklere (örneğin, sensör veya konum güncellemeleri) yanıt olarak kartı güncelleyebilirsiniz.

    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 öğenizin bir örneğini LiveCard SurfaceHolder geri çağırması olarak ayarlayın. Bu, canlı kartın kendini oluşturmak için hangi mantığı kullanacağını bilmesini sağlar.

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

  1. GlRenderer uygulayan bir sınıf oluşturun. Bu arayüzde geri çağırmayı uygulamak, canlı kartın yüzey yaşam döngüsündeki önemli olaylar sırasında işlem yapmanızı sağlar. Bu örnekte renkli, dönen bir küp çiziliyor.

    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. Canlı kartı yöneten ve CubeRenderer sınıfını canlı kartın oluşturucusu olarak ayarlayan bir hizmet oluşturun.

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

Canlı karta odaklanma

LiveCard.publish() ile canlı bir kart yayınladığınızda, ilgili karta hemen odaklanılıp odaklanmayacağını kontrol etmek için bir parametre iletirsiniz.

Zaman çizelgesi yayınlandıktan hemen sonra karta atlamak için LiveCard.PublishMode.REVEAL işlevini kullanın. Kartı sessiz bir şekilde yayınlamak ve kullanıcıların karta kendi başlarına gitmelerini sağlamak için LiveCard.PublishMode.SILENT kullanın.

Ayrıca LiveCard.navigate() yöntemi, yayınlandıktan sonra karta geçmenizi sağlar. Örneğin, kullanıcılar canlı kartınızı ana ses menüsünden başlatmaya çalışırsa ve kart zaten başlatılmışsa bu yöntemi kullanarak canlı karta atlayabilirsiniz.

Menü oluşturma ve görüntüleme

Canlı kartlar kendi menü sistemlerini gösteremez. Bu nedenle, canlı kart için menü görüntülemek üzere bir etkinlik oluşturmanız gerekir.

Daha sonra menü etkinliğinde canlı kartı durduracak, yoğun ilgiyi başlatacak veya gerçekleştirmek istediğiniz başka işlemler yapılacak öğeler bulunabilir. Ayrıca ses denetimi gibi sistem ayarları etkinliklerini menü öğesi olarak ekleyebilirsiniz. Daha fazla bilgi için Başlangıç ayarları bölümüne bakın.

Menü kaynakları oluşturma

Menü kaynakları oluşturma işlemi Android platformunda olduğu gibidir, ancak Glass için aşağıdaki yönergeleri izleyin:

  • Her menü öğesi için 50 × 50 piksel boyutunda bir menü öğesi simgesi sağlayın. Menü simgesi, şeffaf bir arka plan üzerinde beyaz renkli olmalıdır. Örnek görmek veya kendi kullanımınız için indirmek için Cam menü öğesi simgelerine bakın.
  • İşlemi açıklayan ve yalnızca ilk harfler büyük olacak şekilde kısa bir ad kullanın. Zorunlu fiiller işe yarar (örneğin, Paylaş veya Tümünü yanıtla).
  • Glass, menü öğesi olmayan canlı kartları göstermiyor. Kullanıcıların yayındaki kartı zaman çizelgesinden kaldırabilmesi için en azından bir Durdur menü öğesi sağlayın.
  • CheckBox widget'ı desteklenmez.

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

Menü geri çağırmalarını işlemek için etkinlik oluşturma

Kullanıcılar karta dokunduğunda canlı kartınızın çağrıştıracağı bir menü etkinliği tanımlamanız gerekir.

Menü etkinliğinizde menüleri düzgün şekilde oluşturmak, göstermek ve kapatmak için aşağıdaki Activity geri çağırma yöntemlerini geçersiz kılın:

  1. onCreateOptionsMenu(), XML menü kaynağını şişirir.
  2. onAttachedToWindow() Etkinlik odaktayken menüyü gösterir.
  3. onPrepareOptionsMenu() gerekirse menü öğelerini gösterir veya gizler. Örneğin, kullanıcıların yaptıklarına bağlı olarak farklı menü öğeleri gösterebilirsiniz. Örneğin, bazı bağlam verilerine göre farklı menü öğeleri gösterebilirsiniz.
  4. onOptionsItemSelected(), kullanıcı seçimini işler.
  5. onOptionsMenuClosed() simgesini tıklayın.

Menü bir seçimle veya hızlıca aşağı kaydırılarak kapatıldığında etkinliğin düzgün bir şekilde tamamlanması için burada bitirmeniz gerekir.

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

Menü etkinliğini şeffaf hale getirmek

Glass stiliyle tutarlı olmak için menü etkinliğini yarı saydam hale getirin. Böylece canlı kart menünün altında görünmeye devam eder:

  1. Bir res/values/styles.xml dosyası oluşturun ve etkinliğin arka planını şeffaf hale getiren bir stil bildirin:

    <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 dosyanızda, temayı menü etkinliğine atayın:

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

Menüyü gösterme

setAction() kullanarak kart işlemi için bir PendingIntent sağlayın. Bekleyen intent, kullanıcılar karta dokunduğunda menü etkinliğini başlatmak için kullanılır:

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

Bağlamsal sesli komutları destekleme

  1. MenuActivity cihazınızın bağlama dayalı sesli komutları desteklediğini belirtin:

    // Initialize your LiveCard as usual.
    mLiveCard.setVoiceActionEnabled(true);
    mLiveCard.publish(LiveCard.PublishMode.REVEAL); // or SILENT
    
  2. MenuActivity öğenizi, ses akışı üzerinden çağrıyı destekleyecek şekilde değiştirin:

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

Daha fazla bilgi için bağlama dayalı sesli komutlar rehberine göz atın.

Menülerin görünümünü ve davranışını değiştirmek için birkaç yardımcı yöntem vardır. Daha fazla bilgi için MenuUtils sayfasını inceleyin.