Os cards dinâmicos aparecem na seção "Presente" da linha do tempo e exibe as informações relevantes no momento.
Os cards dinâmicos são ótimos para quando os usuários envolvidos em uma tarefa, mas querem verificar o Google Glass periodicamente para informações complementares. Por exemplo, verificar o horário em intervalos de poucos minutos ou controlando um player de música quando para pular ou pausar uma música.
Se esta é a primeira vez que você desenvolve para o Google Glass, Leia o guia de tarefas em andamento. primeiro. Esse documento explica como criar um Glassware com um cartão ativo, seguindo nossas práticas recomendadas de design.
Como funcionam
Os cards dinâmicos são uma forma de persistir no presente do cronograma, enquanto forem relevantes. Ao contrário dos cards estáticos, os cards ativos não permanecem na linha do tempo, e os usuários removê-los após terminar de usá-los.
Os usuários geralmente iniciam cards dinâmicos falando um comando de voz no menu principal, que inicia um serviço em segundo plano que renderiza o cartão. Eles podem tocar no card para mostrar itens do menu que podem agir no card, por exemplo, dispensando-a da linha do tempo.
Quando usar as extensões
Os cards dinâmicos são projetados para tarefas contínuas que podem ser acessadas pelos usuários e com frequência, como uma tela que mostra o status de execução de uma ação, um mapa animado durante a navegação ou um player de música.
Outro benefício dos cards ao vivo é que eles são adequados para IUs que exigem interação em tempo real com os usuários e atualizações em tempo real na interface.
Ao usar cards ao vivo, a linha do tempo ainda tem controle sobre o usuário ou seja, ao deslizar para frente ou para trás em um card dinâmico, navega pela linha do tempo em vez de atuar no próprio card dinâmico. Além disso, a tela liga e desliga com base no comportamento do sistema (depois cinco segundos sem interação do usuário ou durante um alerta de cabeça).
No entanto, os cards ativos têm acesso a muitos dos mesmos recursos que você imersion faz, como os sensores ou Dados de GPS. Assim, você ainda pode criar experiências interessantes permitindo que os usuários permaneçam na experiência da linha do tempo para fazer outras como conferir mensagens.
Arquitetura
Os cards dinâmicos precisam de um contexto de longa duração para serem possuídos durante todo o tempo que eles ficam visíveis, portanto, gerencie-os em um serviço em segundo plano.
Você pode publicar e renderizar um card ativo assim que o serviço ou em resposta a outros eventos que o serviço monitora. É possível renderizar cards ativos com baixa frequência (uma vez a cada poucos segundos), ou alta frequência (até quantas vezes o sistema conseguir atualizar).
Quando o card ativo não for mais relevante, destrua o serviço para parar a renderização.
Renderização de baixa frequência
A renderização de baixa frequência é limitada a um pequeno conjunto de dispositivos visualizações e só pode atualizar a tela uma vez a cada alguns segundos.
É uma maneira simples de criar cards dinâmicos com conteúdo simples que não exija renderização constante ou atualizações frequentes.
Renderização de alta frequência
A renderização de alta frequência permite usar mais opções disponíveis no framework gráfico do Android.
O sistema oferece a superfície de apoio real cartão ao vivo no qual você desenha diretamente usando 2D visualizações, layouts e até mesmo gráficos 3D complexos com o OpenGL.
Como criar cards dinâmicos de baixa frequência
A renderização de baixa frequência requer uma interface fornecida por um RemoteViews , que oferece suporte ao seguinte subconjunto de layouts e visualizações do Android:
Use a renderização de baixa frequência quando:
- Você só precisa das APIs de visualização padrão do Android listadas anteriormente.
- Você só precisa de atualizações relativamente infrequentes (alguns segundos entre é atualizado).
Vale lembrar:
- Os cards ativos sempre precisam ter uma
PendingIntent
declarado comsetAction()
para o cronograma de publicação do card. - Para fazer alterações em um card após a publicação, chame
setViews()
no cartão com os RemoteViews atualizados antes de publicar novamente.
Para criar cards dinâmicos de baixa frequência:
Crie o layout ou a visualização que você quer renderizar. O exemplo a seguir mostra um layout para um jogo de basquete imaginário:
<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" />
Crie um serviço que gerencie o card dinâmico e renderize o layout ou a visualização. Este serviço de exemplo atualiza a pontuação de um jogo de basquete imaginário a cada 30 segundos.
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; } }
Como criar cards ao vivo de alta frequência
A renderização de alta frequência permite desenhar diretamente na Superfície de apoio do card publicado.
Use a renderização de alta frequência quando:
- Você precisa atualizar o cartão ativo com frequência (muitas vezes por segundo).
- Você precisa de flexibilidade no que pode ser renderizado. A renderização de alta frequência permite usar visualizações e layouts do Android para gráficos OpenGL complexos.
Vale lembrar:
- Sempre crie um serviço em segundo plano para renderizar na superfície do cartão ativo.
- Os cards ativos sempre precisam ter uma
PendingIntent
declarado comsetAction()
- Use
GLRenderer
se você estiver renderização de OpenGL eDirectRenderingCallback
para todos os outros casos.
Como usar DirectRenderingCallback
Para criar cards dinâmicos com visualizações padrão do Android e lógica de desenho:
Crie uma classe que implemente
DirectRenderingCallback
, A implementação dos callbacks nessas interfaces permite realizar ações durante eventos importantes do ciclo de vida da superfície do card ativo.O exemplo a seguir cria uma linha de execução em segundo plano para renderizar periodicamente, mas você pode atualizar o cartão em resposta a eventos externos (por exemplo, do sensor ou atualizações de localização).
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); } } } }
Definir uma instância do
DirectRenderingCallback
comoLiveCard
Callback deSurfaceHolder
. Isso informa ao card ativo qual lógica usar para se renderizar.// 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; } }
Como usar o OpenGL
Crie uma classe que implemente
GlRenderer
A implementação de callbacks nessa interface permite realizar ações durante eventos importantes do ciclo de vida da superfície do card ativo. O exemplo abaixo desenha um cubo colorido em rotação.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(); } }
Criar um serviço que gerencia o card ativo e define a classe
CubeRenderer
como o renderizador do card ativo.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;
}
}
Foco em um card dinâmico
Ao publicar um card ativo com o LiveCard.publish()
, você transmite um parâmetro a ele.
para controlar se ele tem foco imediatamente.
Para que a linha do tempo vá para o card imediatamente após a publicação, use
LiveCard.PublishMode.REVEAL
Para publicar o card silenciosamente e fazer com que os usuários naveguem até ele por conta própria, use
LiveCard.PublishMode.SILENT
Além disso, o LiveCard.navigate()
permite pular para o card depois que ele é publicado. Por exemplo, se os usuários tentarem iniciar
ao vivo no menu de voz principal e ele já tiver sido iniciado, você pode pular para a
com esse método.
Como criar e exibir um menu
Os cards dinâmicos não podem mostrar um sistema de menus próprio. Por isso, é necessário criar uma atividade para mostrar um menu. para o card dinâmico.
A atividade do menu pode ter itens para interromper o card ativo, começando uma imersão ou qualquer outra ação que queira realizar. Você também pode adicionar atividades de configurações do sistema, por exemplo, o controle de volume, como um item de menu. Para mais informações, consulte Configurações iniciais.
Como criar recursos de menu
A criação de recursos de menu é a mesma usada na plataforma Android, mas siga estas Diretrizes para o Google Glass:
- Para cada item de menu, forneça um ícone de 50 x 50 pixels. O menu precisa ser branco em um fundo transparente. Consulte a Ícones de itens de menu do Google Glass para um exemplo ou fazer download delas para uso próprio.
- Use um nome curto que descreva a ação e apareça com a primeira letra maiúscula. Um verbo imperativo funciona bem (por exemplo, Compartilhar ou Responder a todos).
- O Google Glass não exibe cards ativos sem um item de menu. No mínimo, forneça um item de menu Parar para que os usuários possam remover o card ativo da na linha do tempo.
A Widget CheckBox não é compatível.
<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>
Criar uma atividade para processar callbacks de menu
É necessário definir uma atividade de menu que o card dinâmico invoca quando os usuários tocam nele.
Substitua o seguinte
Métodos de callback Activity
para criar, mostrar e dispensar menus corretamente na atividade de menus:
onCreateOptionsMenu()
infla o recurso de menu XML.onAttachedToWindow()
mostra o menu quando a atividade está em foco.onPrepareOptionsMenu()
mostra ou oculta itens de menu se necessário. Por exemplo, você pode mostrar diferentes itens de menu com base no que os usuários estão fazendo. Por exemplo, você pode mostrar diferentes itens de menu com base em alguns dados contextuais.- O
onOptionsItemSelected()
processa a seleção do usuário. onOptionsMenuClosed()
para concluir a atividade, para que ela não apareça mais sobre o card ativo.
Você deve finalizar a atividade aqui para que ela seja corretamente finalizada quando o menu for fechado por uma seleção ou deslizando para baixo.
/**
* 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();
}
}
Como tornar a atividade do menu transparente
Para manter a consistência com o estilo do Glass, deixe a atividade do menu translúcido para que o cartão ativo ainda fique visível abaixo o menu:
Criar um arquivo
res/values/styles.xml
e declarar um estilo que torna o plano de fundo da atividade transparente:<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>
No arquivo
AndroidManifest.xml
, atribua o tema à atividade do menu:<?xml version="1.0" encoding="utf-8"?> <manifest ... > ... <application ... > ... <activity android:name=".MenuActivity" android:theme="@style/MenuTheme" ...> </activity> </application> </manifest>
Como mostrar o menu
Forneça um
PendingIntent
para a ação do card usando setAction()
. A intent pendente é usada para iniciar
a atividade do menu quando os usuários tocam no card:
Intent menuIntent = new Intent(this, MenuActivity.class);
mLiveCard.setAction(PendingIntent.getActivity(this, 0, menuIntent, 0));
mLiveCard.publish(LiveCard.PublishMode.REVEAL); // or SILENT
Suporte a comandos de voz contextuais
Indicar que o
MenuActivity
oferece suporte a comandos de voz contextuais:// Initialize your LiveCard as usual. mLiveCard.setVoiceActionEnabled(true); mLiveCard.publish(LiveCard.PublishMode.REVEAL); // or SILENT
Modifique o
MenuActivity
para oferecer suporte à invocação usando o fluxo de voz:/** * 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; } }
Consulte o comandos de voz contextuais para mais informações.
Utilitários de menu
Há alguns métodos auxiliares disponíveis para modificar a aparência e o comportamento dos menus. Veja
mais informações em
MenuUtils
.