Thẻ trực tiếp xuất hiện trong mục hiện tại của dòng thời gian và hiển thị thông tin có liên quan tại thời điểm hiện tại.
Thẻ trực tiếp rất phù hợp khi người dùng chủ động tham gia vào một nhiệm vụ, nhưng muốn kiểm tra định kỳ Glass để biết thông tin bổ sung. Ví dụ: kiểm tra thời gian chạy bộ vài phút một lần hoặc điều khiển trình phát nhạc khi họ muốn bỏ qua hoặc tạm dừng một bài hát.
Nếu đây là lần đầu tiên bạn phát triển ứng dụng Glass, trước hết hãy đọc Hướng dẫn về nhiệm vụ đang diễn ra. Tài liệu đó trình bày cách tạo một Glassware hoàn chỉnh bằng thẻ trực tiếp, theo các phương pháp hay nhất về thiết kế của chúng tôi.
Cách hoạt động
Thẻ trực tiếp cung cấp cách để thẻ tiếp tục tồn tại trong phần hiện tại của dòng thời gian, miễn là các thẻ đó có liên quan. Không giống như thẻ tĩnh, thẻ trực tiếp không tồn tại trong tiến trình và người dùng loại bỏ các thẻ đó một cách rõ ràng sau khi thực hiện xong.
Người dùng thường bắt đầu thẻ trực tiếp bằng cách nói một lệnh thoại trong trình đơn chính. Trình đơn này sẽ bắt đầu một dịch vụ nền để kết xuất thẻ. Sau đó, họ có thể nhấn vào thẻ để hiển thị các mục trong trình đơn có thể hoạt động trên thẻ, chẳng hạn như loại bỏ thẻ khỏi dòng thời gian.
Thời điểm nên sử dụng thành phần địa điểm của đơn vị liên kết
Thẻ trực tiếp được thiết kế cho các thao tác đang diễn ra mà người dùng có thể thực hiện thường xuyên, chẳng hạn như màn hình cho thấy trạng thái chạy của một thao tác, bản đồ động trong khi di chuyển hoặc trình phát nhạc.
Một lợi ích khác của thẻ trực tiếp là thẻ rất phù hợp với các giao diện người dùng yêu cầu tương tác theo thời gian thực với người dùng và việc cập nhật giao diện người dùng theo thời gian thực.
Khi sử dụng thẻ trực tiếp, tiến trình vẫn có quyền kiểm soát trải nghiệm người dùng, vì vậy, thao tác vuốt về phía trước hoặc phía sau trên thẻ trực tiếp sẽ di chuyển trong tiến trình thay vì thao tác trên thẻ trực tiếp. Ngoài ra, màn hình bật và tắt dựa trên cách hoạt động của hệ thống (sau 5 giây mà không có sự tương tác của người dùng hoặc trong một cử chỉ đầu lên trên).
Tuy nhiên, thẻ trực tiếp có quyền truy cập vào nhiều tính năng giống như độ nhúng, chẳng hạn như cảm biến hoặc dữ liệu GPS. Điều này cho phép bạn vẫn tạo ra trải nghiệm hấp dẫn trong khi cho phép người dùng ở lại trong trải nghiệm dòng thời gian để làm những việc khác, chẳng hạn như kiểm tra thông báo.
Kiến trúc
Thẻ trực tiếp yêu cầu ngữ cảnh chạy lâu để sở hữu chúng trong toàn bộ thời gian hiển thị, vì vậy, hãy quản lý chúng trong dịch vụ nền.
Sau đó, bạn có thể phát hành và kết xuất thẻ trực tiếp ngay khi dịch vụ bắt đầu hoặc để phản hồi các sự kiện khác mà dịch vụ đó theo dõi. Bạn có thể kết xuất các thẻ trực tiếp với tần suất thấp (cứ vài giây một lần) hoặc tần suất cao (tối đa số lần hệ thống có thể làm mới).
Khi thẻ trực tiếp không còn phù hợp, hãy huỷ dịch vụ để ngừng kết xuất.
Kết xuất tần số thấp
Khả năng hiển thị tần số thấp bị giới hạn trong một nhóm nhỏ các thành phần hiển thị Android và chỉ có thể cập nhật màn hình một lần trong vài giây.
Đây là một cách đơn giản để tạo thẻ trực tiếp với nội dung đơn giản mà không yêu cầu kết xuất liên tục hoặc cập nhật thường xuyên.
Hiển thị tần số cao
Tính năng hiển thị tần suất cao cho phép bạn sử dụng nhiều tuỳ chọn hơn trong khung đồ hoạ Android.
Hệ thống này cung cấp cho bạn giao diện sao lưu thực tế của thẻ trực tiếp mà bạn vẽ trực tiếp bằng khung nhìn và bố cục 2D, hoặc thậm chí là đồ hoạ 3D phức tạp với OpenGL.
Tạo thẻ trực tiếp tần suất thấp
Hiển thị tần suất thấp yêu cầu giao diện người dùng do đối tượng RemoteViews cung cấp, hỗ trợ tập hợp con bố cục và chế độ xem Android sau:
Sử dụng hiển thị tần số thấp khi:
- Bạn chỉ yêu cầu API chế độ xem Android chuẩn được liệt kê trước đó.
- Bạn chỉ yêu cầu cập nhật tương đối không thường xuyên (vài giây giữa các lần làm mới).
Lưu ý:
- Thẻ trực tiếp phải luôn khai báo
PendingIntent
bằngsetAction()
để tiến trình phát hành thẻ được diễn ra. - Để thực hiện thay đổi đối với một thẻ sau khi phát hành, hãy gọi
setViews()
trên thẻ đó bằng đối tượng RemoteViews đã cập nhật trước khi xuất bản lại.
Cách tạo thẻ trực tiếp tần suất thấp:
Tạo bố cục hoặc chế độ xem mà bạn muốn hiển thị. Ví dụ sau cho thấy bố cục của một trò chơi bóng rổ ảo:
<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" />
Tạo một dịch vụ quản lý thẻ trực tiếp và hiển thị bố cục hoặc chế độ xem của bạn. Dịch vụ mẫu này cập nhật điểm số của một trò chơi bóng rổ tưởng tượng mỗi 30 giây.
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; } }
Tạo thẻ trực tiếp với tần suất cao
Tính năng hiển thị tần suất cao cho phép bạn vẽ trực tiếp trên Bề mặt của thẻ trực tiếp.
Sử dụng hiển thị tần số cao khi:
- Bạn cần cập nhật thẻ trực tiếp thường xuyên (nhiều lần một giây).
- Bạn cần có sự linh hoạt để có thể hiển thị. Tính năng hiển thị tần suất cao cho phép bạn sử dụng các chế độ xem và bố cục của Android cho đồ hoạ OpenGL phức tạp.
Lưu ý:
- Bạn phải luôn tạo một dịch vụ nền để hiển thị trên giao diện của thẻ trực tiếp.
- Thẻ trực tiếp phải luôn được
PendingIntent
khai báo bằngsetAction()
. - Hãy sử dụng
GLRenderer
nếu bạn đang kết xuất OpenGL vàDirectRenderingCallback
cho tất cả các trường hợp khác.
Sử dụng DirectRenderingCallback
Để tạo thẻ trực tiếp với chế độ xem Android chuẩn và logic vẽ:
Tạo một lớp triển khai
DirectRenderingCallback
, Việc triển khai các lệnh gọi lại trong các giao diện này cho phép bạn thực hiện các hành động trong vòng sự kiện quan trọng trong vòng đời của thẻ trực tiếp.Ví dụ sau đây sẽ tạo một luồng trong nền để hiển thị định kỳ, nhưng bạn có thể cập nhật thẻ này để phản hồi các sự kiện bên ngoài (ví dụ: cảm biến hoặc cập nhật vị trí).
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); } } } }
Thiết lập một bản sao của
DirectRenderingCallback
làm lệnh gọi lại củaLiveCard
SurfaceHolder
. Điều này cho phép thẻ trực tiếp biết cần dùng logic nào để tự kết xuất.// 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; } }
Sử dụng OpenGL
Tạo một lớp triển khai
GlRenderer
. Việc triển khai các lệnh gọi lại trong giao diện này cho phép bạn thực hiện các thao tác trong những sự kiện quan trọng trong vòng đời của thẻ trực tiếp. Ví dụ này vẽ một khối lập phương xoay vòng được tô màu.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(); } }
Tạo một dịch vụ quản lý thẻ trực tiếp và đặt lớp
CubeRenderer
làm trình kết xuất của thẻ trực tiếp.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;
}
}
Tập trung vào thẻ trực tiếp
Khi phát hành một thẻ đang hoạt động với LiveCard.publish()
, bạn sẽ truyền cho thẻ đó một thông số để kiểm soát việc thẻ có quyền phát ngay hay không.
Để tiến trình chuyển sang thẻ ngay sau khi xuất bản, hãy sử dụng
LiveCard.PublishMode.REVEAL
.
Để tự xuất bản thẻ và khiến người dùng tự chuyển đến thẻ, hãy sử dụng
LiveCard.PublishMode.SILENT
.
Ngoài ra, phương thức LiveCard.navigate()
cho phép bạn chuyển đến thẻ sau khi thẻ được xuất bản. Ví dụ: nếu người dùng cố gắng bắt đầu thẻ trực tiếp của bạn từ trình đơn thoại chính và thẻ đã bắt đầu, thì bạn có thể chuyển sang thẻ phát trực tiếp bằng phương thức này.
Tạo và hiển thị trình đơn
Thẻ trực tiếp không thể hiển thị hệ thống trình đơn riêng, vì vậy, bạn cần tạo một hoạt động để hiển thị trình đơn cho thẻ trực tiếp.
Sau đó, hoạt động trình đơn có thể có các mục để dừng thẻ trực tiếp, bắt đầu nhúng hoặc bất kỳ hành động nào khác mà bạn muốn thực hiện. Bạn cũng có thể thêm các hoạt động cài đặt hệ thống, chẳng hạn như điều khiển âm lượng, dưới dạng một mục trong trình đơn. Để biết thêm thông tin, hãy xem phần Cài đặt bắt đầu.
Tạo tài nguyên trình đơn
Việc tạo tài nguyên trình đơn giống như trên nền tảng Android, nhưng tuân theo các nguyên tắc sau cho Glass:
- Đối với mỗi mục trong trình đơn, hãy cung cấp biểu tượng mục trong trình đơn có kích thước 50 × 50 pixel. Biểu tượng trình đơn phải có màu trắng trên nền trong suốt. Xem biểu tượng mục trong trình đơn Glass để biết ví dụ hoặc để tải xuống cho mục đích sử dụng của riêng bạn.
- Sử dụng tên ngắn mô tả hành động và nằm trong trường hợp viết tiêu đề. Động từ bắt buộc hoạt động tốt (ví dụ: Chia sẻ hoặc Trả lời tất cả).
- Glass không hiển thị thẻ trực tiếp nếu không có mục trong trình đơn. Ít nhất, bạn cần cung cấp mục trong trình đơn Dừng để người dùng có thể xoá thẻ đang hoạt động khỏi dòng thời gian.
Tiện ích CheckBox không được hỗ trợ.
<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>
Tạo một hoạt động để xử lý các lệnh gọi lại trình đơn
Bạn phải xác định hoạt động trình đơn mà thẻ trực tiếp của bạn gọi khi người dùng nhấn vào.
Ghi đè phương thức gọi lại Activity
sau đây để tạo, hiển thị và loại bỏ đúng cách các trình đơn trong hoạt động của trình đơn:
onCreateOptionsMenu()
tăng cường tài nguyên của trình đơn XML.onAttachedToWindow()
hiển thị trình đơn khi hoạt động được lấy tiêu điểm.onPrepareOptionsMenu()
hiển thị hoặc ẩn các mục trong trình đơn nếu cần. Ví dụ: bạn có thể hiển thị nhiều mục trong trình đơn dựa trên hành động của người dùng. Ví dụ: bạn có thể hiển thị nhiều mục trên trình đơn dựa trên một số dữ liệu ngữ cảnh.onOptionsItemSelected()
sẽ xử lý lựa chọn của người dùng.onOptionsMenuClosed()
để hoàn tất hoạt động để thẻ không còn xuất hiện trên thẻ đang hoạt động nữa.
Bạn phải hoàn tất hoạt động tại đây để hoạt động này được hoàn tất đúng cách khi trình đơn được chọn bằng một lựa chọn hoặc vuốt xuống.
/**
* 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();
}
}
Minh bạch hoạt động của trình đơn
Để nhất quán với kiểu Glass, hãy làm cho hoạt động trình đơn trong suốt, để thẻ trực tiếp vẫn xuất hiện bên dưới trình đơn:
Tạo tệp
res/values/styles.xml
và khai báo một kiểu giúp nền của hoạt động trở nên minh bạch:<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>
Trong tệp
AndroidManifest.xml
, hãy gán giao diện cho hoạt động trình đơn:<?xml version="1.0" encoding="utf-8"?> <manifest ... > ... <application ... > ... <activity android:name=".MenuActivity" android:theme="@style/MenuTheme" ...> </activity> </application> </manifest>
Hiện trình đơn
Cung cấp một PendingIntent
cho thao tác của thẻ bằng cách sử dụng setAction()
. Ý định đang chờ xử lý được dùng để bắt đầu hoạt động trình đơn khi người dùng nhấn vào thẻ:
Intent menuIntent = new Intent(this, MenuActivity.class);
mLiveCard.setAction(PendingIntent.getActivity(this, 0, menuIntent, 0));
mLiveCard.publish(LiveCard.PublishMode.REVEAL); // or SILENT
Hỗ trợ lệnh thoại theo ngữ cảnh
Cho biết rằng
MenuActivity
của bạn hỗ trợ các lệnh thoại theo ngữ cảnh:// Initialize your LiveCard as usual. mLiveCard.setVoiceActionEnabled(true); mLiveCard.publish(LiveCard.PublishMode.REVEAL); // or SILENT
Sửa đổi
MenuActivity
của bạn để hỗ trợ lệnh gọi thông qua luồng thoại:/** * 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; } }
Hãy xem hướng dẫn về lệnh thoại theo ngữ cảnh để biết thêm thông tin.
Tiện ích menu
Có một số phương thức trợ giúp để sửa đổi giao diện và hành vi của trình đơn. Xem
MenuUtils
để biết thêm thông tin.