Live-Karten erscheinen im aktuellen Abschnitt der Zeitachse und werden Informationen angezeigt, die zum jeweiligen Zeitpunkt relevant sind.
Live-Karten eignen sich hervorragend für Nutzer, an einer Aufgabe beteiligt sind, aber Glass regelmäßig überprüfen möchten, um zusätzliche Informationen zu erhalten. Zum Beispiel, um nachzusehen, oder einen Musikplayer steuern, einen Song überspringen oder pausieren möchten.
Wenn Sie zum ersten Mal für Glass entwickeln, Lesen Sie den Leitfaden für laufende Aufgaben. . In diesem Dokument erfahren Sie, wie Sie Glassware mit einer Live-Karte gemäß unseren Best Practices für das Design
Funktionsweise
Mit Live-Infokarten bleiben Infokarten auch in der Gegenwart erhalten. der Zeitachse enthalten, solange sie relevant sind. Im Gegensatz zu statischen Karten Live-Karten bleiben nicht in der Zeitachse erhalten und die Nutzer wenn sie nicht mehr benötigt werden.
Nutzer starten Live-Karten in der Regel per Sprachbefehl. im Hauptmenü, das einen Hintergrunddienst startet, der die Karte rendert. Sie können dann auf die Karte tippen, um Menüoptionen aufrufen, die Aktionen auslösen können auf der Karte angezeigt, z. B. durch Schließen aus der Zeitachse.
Verwendung
Live-Karten wurden für laufende Aufgaben entwickelt, die Nutzer aufrufen können und weniger häufig, wie z. B. eine Anzeige mit dem Status einer Aktion, einer animierten Karte während der Navigation oder eines Musikplayers.
Ein weiterer Vorteil von Live-Karten besteht darin, dass sie sich gut für Benutzeroberflächen, die eine Echtzeitinteraktion mit Nutzern erfordern und Echtzeitaktualisierungen der Benutzeroberfläche.
Auch bei Verwendung von Live-Karten hat die Zeitachse Kontrolle über den Nutzer. Wischen Sie auf einer Live-Karte vorwärts oder rückwärts. navigiert in der Zeitachse und wirkt sich nicht auf die Live-Karte selbst aus. Außerdem schaltet sich der Bildschirm je nach Systemverhalten ein und aus (nach 5 Sekunden ohne Nutzerinteraktion oder während einer Erinnerung nach oben).
Live-Karten bieten jedoch Zugriff auf viele der Funktionen immersion, z. B. „Sensor“ oder GPS-Daten. So können Sie trotzdem ansprechende Erlebnisse schaffen, und die Nutzenden können in der Zeitachse bleiben, um andere Nachrichten prüfen.
Architektur
Für Live-Karten ist ein lang andauernder Kontext erforderlich, damit sie während der gesamten Zeit und sie sind sichtbar. Verwalten Sie sie daher in einem Hintergrunddienst.
Sie können eine Live-Karte veröffentlichen und rendern, sobald der Dienst oder als Reaktion auf andere vom Dienst überwachte Ereignisse. Du kannst Live-Karten mit geringer Häufigkeit (einmal alle paar Sekunden) rendern. oder hohe Frequenz (bis zu so oft, wie das System aktualisiert werden kann).
Wenn die Live-Karte nicht mehr relevant ist, zerstören Sie den Dienst, um halten Sie das Rendering an.
Low-Frequency-Rendering
Das Low-Frequency-Rendering ist auf eine kleine Anzahl von Android-Geräten beschränkt Aufrufe und kann nur einmal pro Monat aktualisiert werden. ein paar Sekunden.
Es ist eine einfache Möglichkeit, Live-Karten mit einfache Inhalte, die kein ständiges Rendern oder regelmäßig aktualisiert werden.
Hochfrequenz-Rendering
Mit dem Hochfrequenz-Rendering können Sie mehr Optionen nutzen die im Android-Grafik-Framework verfügbar sind.
Das System gibt Ihnen als Hintergrund Live-Karte, auf die Sie mit 2D direkt zeichnen Ansichten und Layouts oder sogar komplexe 3D-Grafiken mit OpenGL erstellen.
Niedrige Live-Karten erstellen
Für das Rendering mit niedriger Häufigkeit ist eine UI erforderlich, die von einem RemoteViews , das die folgenden Android-Layouts und -Ansichten unterstützt:
In folgenden Fällen sollten Sie das Rendering mit niedriger Häufigkeit verwenden:
- Sie benötigen nur die oben aufgeführten standardmäßigen Android View APIs.
- Es sind nur relativ seltene Updates erforderlich, die nur wenige Sekunden zwischen Aktualisierungsvorgänge).
Hinweise:
- Live-Karten müssen immer
PendingIntent
deklariert mitsetAction()
um die Karte zu veröffentlichen. - Wenn Sie eine Karte nach der Veröffentlichung ändern möchten, rufen Sie folgenden Link auf:
setViews()
auf der Karte mit den aktualisierten RemoteViews -Objekt vor der erneuten Veröffentlichung.
So erstellst du Live-Karten mit geringer Häufigkeit:
Erstellen Sie das Layout oder die Ansicht, das bzw. die Sie rendern möchten. Im folgenden Beispiel zeigt ein Layout für ein imaginäres Basketballspiel:
<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" />
Erstellen Sie einen Dienst, der die Live-Karte verwaltet und Ihr Layout oder Ihre Ansicht rendert. Dieser Beispieldienst aktualisiert das Ergebnis eines imaginären Basketballspiels alle 30 Sekunden
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; } }
Häufig verwendete Live-Karten erstellen
Mit dem Hochfrequenz-Rendering können Sie direkt zeichnen auf der Hintergrundfläche der Live-Karte.
In folgenden Fällen sollten Sie Hochfrequenz-Rendering verwenden:
- Du musst die Live-Karte häufig (mehrmals pro Sekunde) aktualisieren.
- Was Sie rendern können, ist flexibel. Mit dem Hochfrequenz-Rendering können Sie Android-Ansichten und -Layouts für komplexe OpenGL-Grafiken nutzen
Hinweise:
- Du solltest immer einen Hintergrunddienst erstellen, der auf der Oberfläche der Live-Karte gerendert wird.
- Live-Karten müssen immer
PendingIntent
deklariert mitsetAction()
- Verwenden Sie
GLRenderer
in folgenden Fällen: und rendern OpenGL undDirectRenderingCallback
für alle anderen Fälle.
DirectRenderingCallback verwenden
So erstellen Sie Live-Karten mit Android-Standardansichten und Zeichenlogik:
Erstellen Sie eine Klasse, die
DirectRenderingCallback
, Durch die Implementierung der Callbacks in diesen Schnittstellen kannst du Aktionen während wichtiger Ereignisse im Lebenszyklus der Live-Karte ausführen.Im folgenden Beispiel wird ein Hintergrundthread erstellt, der regelmäßig gerendert wird. Sie die Karte als Reaktion auf externe Ereignisse (z. B. Sensor- oder Standortaktualisierungen).
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); } } } }
Instanz von
DirectRenderingCallback
festlegen alsLiveCard
Rückruf vonSurfaceHolder
Dieses informiert die Livekarte darüber, welche Logik zum Rendern selbst verwendet werden soll.// 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 verwenden
Erstellen Sie eine Klasse, die
GlRenderer
Durch die Implementierung der Callbacks in dieser Oberfläche kannst du Aktionen bei wichtigen Ereignissen ausführen. des Lebenszyklus der Live-Karte. In diesem Beispiel wird ein farbiger, rotierender Würfel gezeichnet.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(); } }
Dienst erstellen, der die Live-Karte verwaltet und festlegt die
CubeRenderer
-Klasse als Renderer der Livekarte.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;
}
}
Fokus auf Live-Karten legen
Wenn Sie eine Live-Karte mit LiveCard.publish()
veröffentlichen, übergeben Sie einen Parameter
um zu steuern, ob es sofort im Fokus ist.
Wenn die Zeitachse sofort nach der Veröffentlichung zur Karte springen soll, verwenden Sie
LiveCard.PublishMode.REVEAL
Wenn Sie die Karte ohne Meldung veröffentlichen und die Nutzer dazu bringen möchten, sie selbst aufzurufen, verwenden Sie
LiveCard.PublishMode.SILENT
Darüber hinaus enthält die LiveCard.navigate()
können Sie nach der Veröffentlichung zur Karte springen. Wenn Nutzer beispielsweise versuchen, Ihre
Live-Karte aus dem Hauptmenü heraus, die bereits gestartet wurde, können Sie direkt zur Live-
mit dieser Methode verwenden.
Menü erstellen und anzeigen
Für Live-Karten kann kein eigenes Menüsystem angezeigt werden. Sie müssen also eine Aktivität erstellen, um ein Menü anzeigen zu können. für die Live-Karte.
Die Menüaktivität kann dann Elemente zum Anhalten der Live-Karte enthalten, beginnend mit ein Eintauchen oder eine andere Aktion durchführen. Sie können auch Systemeinstellungsaktivitäten wie die Lautstärkeregelung als Menüpunkt. Weitere Informationen finden Sie unter Starteinstellungen:
Menüressourcen erstellen
Das Erstellen von Menüressourcen erfolgt auf die gleiche Weise wie auf der Android-Plattform. Beachten Sie jedoch Folgendes: Richtlinien für Glass:
- Stellen Sie für jeden Menüpunkt ein 50 × 50 Pixel großes Menüsymbol bereit. Das Menü Das Symbol muss weiß auf transparentem Hintergrund sein. Weitere Informationen finden Sie in der Glass-Menüpunkte für oder sie für Ihre eigene Verwendung herunterladen.
- Verwende einen Kurznamen, der die Aktion beschreibt und die Groß-/Kleinschreibung des Titels verwendet. Ein imperatives Verb funktioniert (zum Beispiel Teilen oder Allen antworten).
- Glass zeigt keine Live-Karten ohne Menüpunkt an. Zumindest sollten einen Stopp-Menüpunkt bereitstellen, damit Nutzer die Live-Karte aus der Zeitleiste.
Die CheckBox-Widget wird nicht unterstützt.
<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>
Aktivität für Menü-Callbacks erstellen
Sie müssen eine Menüaktivität definieren, die auf Ihrer Livekarte ausgelöst wird, wenn Nutzer darauf tippen.
Folgendes überschreiben
Activity
-Callback-Methoden
wie Sie Menüs in Ihrer Menüaktivität richtig erstellen, anzeigen und schließen:
onCreateOptionsMenu()
bläht die XML-Menüressource auf.onAttachedToWindow()
zeigt das Menü an, wenn die Aktivität im Fokus ist.onPrepareOptionsMenu()
Blendet Menüpunkte bei Bedarf ein oder aus. Sie können beispielsweise unterschiedliche Menüpunkte je nachdem, was Nutzende tun. Sie können beispielsweise basierend auf einige Kontextdaten.onOptionsItemSelected()
übernimmt die Nutzerauswahl.onOptionsMenuClosed()
um die Aktivität abzuschließen, damit sie nicht mehr über der Live-Karte angezeigt wird.
Du musst die Aktivität hier abschließen, damit sie richtig abgeschlossen wird, wenn das Menü durch eine Auswahl oder durch Wischen nach unten geschlossen wird.
/**
* 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üaktivität transparent gestalten
Passen Sie die Menüaktivität an, um dem Glass-Stil zu entsprechen. durchscheinend, sodass die Live-Karte weiterhin darunter zu sehen ist Menü:
res/values/styles.xml
-Datei erstellen und einen Stil deklarieren Dadurch wird der Hintergrund der Aktivität transparent:<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>
Weisen Sie in der Datei
AndroidManifest.xml
das Design der Menüaktivität zu:<?xml version="1.0" encoding="utf-8"?> <manifest ... > ... <application ... > ... <activity android:name=".MenuActivity" android:theme="@style/MenuTheme" ...> </activity> </application> </manifest>
Menü aufrufen
Geben Sie
PendingIntent
für die Aktion der Karte mit setAction()
. Der ausstehende Intent wird zum Starten
die Menüaktivität, wenn Nutzer auf die Karte tippen:
Intent menuIntent = new Intent(this, MenuActivity.class);
mLiveCard.setAction(PendingIntent.getActivity(this, 0, menuIntent, 0));
mLiveCard.publish(LiveCard.PublishMode.REVEAL); // or SILENT
Kontextbezogene Sprachbefehle unterstützen
Gib an, dass dein
MenuActivity
Folgendes unterstützt: kontextbezogene Sprachbefehle:// Initialize your LiveCard as usual. mLiveCard.setVoiceActionEnabled(true); mLiveCard.publish(LiveCard.PublishMode.REVEAL); // or SILENT
Ändere deinen
MenuActivity
so, dass er den Aufruf über den Sprachablauf unterstützt:/** * 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; } }
In der kontextbezogene Sprachbefehle für weitere Informationen.
Menü-Dienstprogramme
Es stehen einige Hilfsmethoden zur Verfügung, mit denen Sie das Aussehen und Verhalten von Menüs ändern können. Weitere Informationen finden Sie unter MenuUtils
.