2016-04-04 2 views
2

Я довольно новичок в Android Wear и делаю часы. На днях я смог сделать наручные часы. У меня есть вопрос: как добавить кнопку на холст (поскольку он не использует файлы макета). Я предполагаю, что мне нужно будет каким-то образом наложить его на холст, но я не уверен, как это сделать.Android: Как добавить кнопку над CanvasWatchFace?

Для справки, вот это класс я использую:

public class WatchFace extends CanvasWatchFaceService { 
private static final String TAG = "AnalogWatchFaceService"; 

/* 
* Update rate in milliseconds for interactive mode. We update once a second to advance the 
* second hand. 
*/ 
private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1); 

@Override 
public Engine onCreateEngine() { 
    return new Engine(); 
} 

private class Engine extends CanvasWatchFaceService.Engine { 
    private static final int MSG_UPDATE_TIME = 0; 

    private static final float HOUR_STROKE_WIDTH = 5f; 
    private static final float MINUTE_STROKE_WIDTH = 3f; 
    private static final float SECOND_TICK_STROKE_WIDTH = 2f; 

    private static final float CENTER_GAP_AND_CIRCLE_RADIUS = 4f; 

    private static final int SHADOW_RADIUS = 6; 

    private Calendar mCalendar; 
    private boolean mRegisteredTimeZoneReceiver = false; 
    private boolean mMuteMode; 

    private float mCenterX; 
    private float mCenterY; 

    private float mSecondHandLength; 
    private float sMinuteHandLength; 
    private float sHourHandLength; 

    /* Colors for all hands (hour, minute, seconds, ticks) based on photo loaded. */ 
    private int mWatchHandColor; 
    private int mWatchHandHighlightColor; 
    private int mWatchHandShadowColor; 

    private Paint mHourPaint; 
    private Paint mMinutePaint; 
    private Paint mSecondPaint; 
    private Paint mTickAndCirclePaint; 

    private Paint mBackgroundPaint; 
    private Bitmap mBackgroundBitmap; 
    private Bitmap mGrayBackgroundBitmap; 

    private boolean mAmbient; 
    private boolean mLowBitAmbient; 
    private boolean mBurnInProtection; 

    private Rect mPeekCardBounds = new Rect(); 

    private final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() { 
     @Override 
     public void onReceive(Context context, Intent intent) { 
      mCalendar.setTimeZone(TimeZone.getDefault()); 
      invalidate(); 
     } 
    }; 

    /* Handler to update the time once a second in interactive mode. */ 
    private final Handler mUpdateTimeHandler = new Handler() { 
     @Override 
     public void handleMessage(Message message) { 

      if (Log.isLoggable(TAG, Log.DEBUG)) { 
       Log.d(TAG, "updating time"); 
      } 
      invalidate(); 
      if (shouldTimerBeRunning()) { 
       long timeMs = System.currentTimeMillis(); 
       long delayMs = INTERACTIVE_UPDATE_RATE_MS 
         - (timeMs % INTERACTIVE_UPDATE_RATE_MS); 
       mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs); 
      } 

     } 
    }; 

    @Override 
    public void onCreate(SurfaceHolder holder) { 
     if (Log.isLoggable(TAG, Log.DEBUG)) { 
      Log.d(TAG, "onCreate"); 
     } 
     super.onCreate(holder); 

     setWatchFaceStyle(new WatchFaceStyle.Builder(AnalogWatchFaceService.this) 
       .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT) 
       .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE) 
       .setShowSystemUiTime(false) 
       .build()); 

     mBackgroundPaint = new Paint(); 
     mBackgroundPaint.setColor(Color.BLACK); 
     mBackgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.black_square); 

     /* Set defaults for colors */ 
     mWatchHandColor = Color.WHITE; 
     mWatchHandHighlightColor = Color.RED; 
     mWatchHandShadowColor = Color.BLACK; 

     mHourPaint = new Paint(); 
     mHourPaint.setColor(mWatchHandColor); 
     mHourPaint.setStrokeWidth(HOUR_STROKE_WIDTH); 
     mHourPaint.setAntiAlias(true); 
     mHourPaint.setStrokeCap(Paint.Cap.ROUND); 
     mHourPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor); 

     mMinutePaint = new Paint(); 
     mMinutePaint.setColor(mWatchHandColor); 
     mMinutePaint.setStrokeWidth(MINUTE_STROKE_WIDTH); 
     mMinutePaint.setAntiAlias(true); 
     mMinutePaint.setStrokeCap(Paint.Cap.ROUND); 
     mMinutePaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor); 

     mSecondPaint = new Paint(); 
     mSecondPaint.setColor(mWatchHandHighlightColor); 
     mSecondPaint.setStrokeWidth(SECOND_TICK_STROKE_WIDTH); 
     mSecondPaint.setAntiAlias(true); 
     mSecondPaint.setStrokeCap(Paint.Cap.ROUND); 
     mSecondPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor); 

     mTickAndCirclePaint = new Paint(); 
     mTickAndCirclePaint.setColor(mWatchHandColor); 
     mTickAndCirclePaint.setStrokeWidth(SECOND_TICK_STROKE_WIDTH); 
     mTickAndCirclePaint.setAntiAlias(true); 
     mTickAndCirclePaint.setStyle(Paint.Style.STROKE); 
     mTickAndCirclePaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor); 

     /* Extract colors from background image to improve watchface style. */ 
     Palette.generateAsync(
       mBackgroundBitmap, 
       new Palette.PaletteAsyncListener() { 
        @Override 
        public void onGenerated(Palette palette) { 
         if (palette != null) { 
          if (Log.isLoggable(TAG, Log.DEBUG)) { 
           Log.d(TAG, "Palette: " + palette); 
          } 

          mWatchHandHighlightColor = palette.getVibrantColor(Color.RED); 
          mWatchHandColor = palette.getLightVibrantColor(Color.WHITE); 
          mWatchHandShadowColor = palette.getDarkMutedColor(Color.BLACK); 
          updateWatchHandStyle(); 
         } 
        } 
       }); 

     mCalendar = Calendar.getInstance(); 
    } 

    @Override 
    public void onDestroy() { 
     mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME); 
     super.onDestroy(); 
    } 

    @Override 
    public void onPropertiesChanged(Bundle properties) { 
     super.onPropertiesChanged(properties); 
     if (Log.isLoggable(TAG, Log.DEBUG)) { 
      Log.d(TAG, "onPropertiesChanged: low-bit ambient = " + mLowBitAmbient); 
     } 

     mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false); 
     mBurnInProtection = properties.getBoolean(PROPERTY_BURN_IN_PROTECTION, false); 
    } 

    @Override 
    public void onTimeTick() { 
     super.onTimeTick(); 
     invalidate(); 
    } 

    @Override 
    public void onAmbientModeChanged(boolean inAmbientMode) { 
     super.onAmbientModeChanged(inAmbientMode); 
     if (Log.isLoggable(TAG, Log.DEBUG)) { 
      Log.d(TAG, "onAmbientModeChanged: " + inAmbientMode); 
     } 
     mAmbient = inAmbientMode; 

     updateWatchHandStyle(); 

     /* Check and trigger whether or not timer should be running (only in active mode). */ 
     updateTimer(); 
    } 

    private void updateWatchHandStyle(){ 
     if (mAmbient){ 
      mHourPaint.setColor(Color.WHITE); 
      mMinutePaint.setColor(Color.WHITE); 
      mSecondPaint.setColor(Color.WHITE); 
      mTickAndCirclePaint.setColor(Color.WHITE); 

      mHourPaint.setAntiAlias(false); 
      mMinutePaint.setAntiAlias(false); 
      mSecondPaint.setAntiAlias(false); 
      mTickAndCirclePaint.setAntiAlias(false); 

      mHourPaint.clearShadowLayer(); 
      mMinutePaint.clearShadowLayer(); 
      mSecondPaint.clearShadowLayer(); 
      mTickAndCirclePaint.clearShadowLayer(); 

     } else { 
      mHourPaint.setColor(mWatchHandColor); 
      mMinutePaint.setColor(mWatchHandColor); 
      mSecondPaint.setColor(mWatchHandHighlightColor); 
      mTickAndCirclePaint.setColor(mWatchHandColor); 

      mHourPaint.setAntiAlias(true); 
      mMinutePaint.setAntiAlias(true); 
      mSecondPaint.setAntiAlias(true); 
      mTickAndCirclePaint.setAntiAlias(true); 

      mHourPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor); 
      mMinutePaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor); 
      mSecondPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor); 
      mTickAndCirclePaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor); 
     } 
    } 

    @Override 
    public void onInterruptionFilterChanged(int interruptionFilter) { 
     super.onInterruptionFilterChanged(interruptionFilter); 
     boolean inMuteMode = (interruptionFilter == WatchFaceService.INTERRUPTION_FILTER_NONE); 

     /* Dim display in mute mode. */ 
     if (mMuteMode != inMuteMode) { 
      mMuteMode = inMuteMode; 
      mHourPaint.setAlpha(inMuteMode ? 100 : 255); 
      mMinutePaint.setAlpha(inMuteMode ? 100 : 255); 
      mSecondPaint.setAlpha(inMuteMode ? 80 : 255); 
      invalidate(); 
     } 
    } 

    @Override 
    public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { 
     super.onSurfaceChanged(holder, format, width, height); 

     /* 
     * Find the coordinates of the center point on the screen, and ignore the window 
     * insets, so that, on round watches with a "chin", the watch face is centered on the 
     * entire screen, not just the usable portion. 
     */ 
     mCenterX = width/2f; 
     mCenterY = height/2f; 

     /* 
     * Calculate lengths of different hands based on watch screen size. 
     */ 
     mSecondHandLength = (float) (mCenterX * 0.875); 
     sMinuteHandLength = (float) (mCenterX * 0.75); 
     sHourHandLength = (float) (mCenterX * 0.5); 


     /* Scale loaded background image (more efficient) if surface dimensions change. */ 
     float scale = ((float) width)/(float) mBackgroundBitmap.getWidth(); 

     mBackgroundBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap, 
       (int) (mBackgroundBitmap.getWidth() * scale), 
       (int) (mBackgroundBitmap.getHeight() * scale), true); 

     /* 
     * Create a gray version of the image only if it will look nice on the device in 
     * ambient mode. That means we don't want devices that support burn-in 
     * protection (slight movements in pixels, not great for images going all the way to 
     * edges) and low ambient mode (degrades image quality). 
     * 
     * Also, if your watch face will know about all images ahead of time (users aren't 
     * selecting their own photos for the watch face), it will be more 
     * efficient to create a black/white version (png, etc.) and load that when you need it. 
     */ 
     if (!mBurnInProtection && !mLowBitAmbient) { 
      initGrayBackgroundBitmap(); 
     } 
    } 

    private void initGrayBackgroundBitmap() { 
     mGrayBackgroundBitmap = Bitmap.createBitmap(
       mBackgroundBitmap.getWidth(), 
       mBackgroundBitmap.getHeight(), 
       Bitmap.Config.ARGB_8888); 
     Canvas canvas = new Canvas(mGrayBackgroundBitmap); 
     Paint grayPaint = new Paint(); 
     ColorMatrix colorMatrix = new ColorMatrix(); 
     colorMatrix.setSaturation(0); 
     ColorMatrixColorFilter filter = new ColorMatrixColorFilter(colorMatrix); 
     grayPaint.setColorFilter(filter); 
     canvas.drawBitmap(mBackgroundBitmap, 0, 0, grayPaint); 
    } 

    @Override 
    public void onDraw(Canvas canvas, Rect bounds) { 
     if (Log.isLoggable(TAG, Log.VERBOSE)) { 
      Log.v(TAG, "onDraw"); 
     } 
     long now = System.currentTimeMillis(); 
     mCalendar.setTimeInMillis(now); 

     if (mAmbient && (mLowBitAmbient || mBurnInProtection)) { 
      canvas.drawColor(Color.BLACK); 
     } else if (mAmbient) { 
      canvas.drawBitmap(mGrayBackgroundBitmap, 0, 0, mBackgroundPaint); 
     } else { 
      canvas.drawBitmap(mBackgroundBitmap, 0, 0, mBackgroundPaint); 
     } 

     /* 
     * Draw ticks. Usually you will want to bake this directly into the photo, but in 
     * cases where you want to allow users to select their own photos, this dynamically 
     * creates them on top of the photo. 
     */ 
     float innerTickRadius = mCenterX - 10; 
     float outerTickRadius = mCenterX; 
     for (int tickIndex = 0; tickIndex < 12; tickIndex++) { 
      float tickRot = (float) (tickIndex * Math.PI * 2/12); 
      float innerX = (float) Math.sin(tickRot) * innerTickRadius; 
      float innerY = (float) -Math.cos(tickRot) * innerTickRadius; 
      float outerX = (float) Math.sin(tickRot) * outerTickRadius; 
      float outerY = (float) -Math.cos(tickRot) * outerTickRadius; 
      canvas.drawLine(mCenterX + innerX, mCenterY + innerY, 
        mCenterX + outerX, mCenterY + outerY, mTickAndCirclePaint); 
     } 

     /* 
     * These calculations reflect the rotation in degrees per unit of time, e.g., 
     * 360/60 = 6 and 360/12 = 30. 
     */ 
     final float seconds = 
       (mCalendar.get(Calendar.SECOND) + mCalendar.get(Calendar.MILLISECOND)/1000f); 
     final float secondsRotation = seconds * 6f; 

     final float minutesRotation = mCalendar.get(Calendar.MINUTE) * 6f; 

     final float hourHandOffset = mCalendar.get(Calendar.MINUTE)/2f; 
     final float hoursRotation = (mCalendar.get(Calendar.HOUR) * 30) + hourHandOffset; 

     /* 
     * Save the canvas state before we can begin to rotate it. 
     */ 
     canvas.save(); 

     canvas.rotate(hoursRotation, mCenterX, mCenterY); 
     canvas.drawLine(
       mCenterX, 
       mCenterY - CENTER_GAP_AND_CIRCLE_RADIUS, 
       mCenterX, 
       mCenterY - sHourHandLength, 
       mHourPaint); 

     canvas.rotate(minutesRotation - hoursRotation, mCenterX, mCenterY); 
     canvas.drawLine(
       mCenterX, 
       mCenterY - CENTER_GAP_AND_CIRCLE_RADIUS, 
       mCenterX, 
       mCenterY - sMinuteHandLength, 
       mMinutePaint); 

     /* 
     * Ensure the "seconds" hand is drawn only when we are in interactive mode. 
     * Otherwise, we only update the watch face once a minute. 
     */ 
     if (!mAmbient) { 
      canvas.rotate(secondsRotation - minutesRotation, mCenterX, mCenterY); 
      canvas.drawLine(
        mCenterX, 
        mCenterY - CENTER_GAP_AND_CIRCLE_RADIUS, 
        mCenterX, 
        mCenterY - mSecondHandLength, 
        mSecondPaint); 

     } 
     canvas.drawCircle(
       mCenterX, 
       mCenterY, 
       CENTER_GAP_AND_CIRCLE_RADIUS, 
       mTickAndCirclePaint); 

     /* Restore the canvas' original orientation. */ 
     canvas.restore(); 

     /* Draw rectangle behind peek card in ambient mode to improve readability. */ 
     if (mAmbient) { 
      canvas.drawRect(mPeekCardBounds, mBackgroundPaint); 
     } 
    } 

    @Override 
    public void onVisibilityChanged(boolean visible) { 
     super.onVisibilityChanged(visible); 

     if (visible) { 
      registerReceiver(); 
      /* Update time zone in case it changed while we weren't visible. */ 
      mCalendar.setTimeZone(TimeZone.getDefault()); 
      invalidate(); 
     } else { 
      unregisterReceiver(); 
     } 

     /* Check and trigger whether or not timer should be running (only in active mode). */ 
     updateTimer(); 
    } 

    @Override 
    public void onPeekCardPositionUpdate(Rect rect) { 
     super.onPeekCardPositionUpdate(rect); 
     mPeekCardBounds.set(rect); 
    } 

    private void registerReceiver() { 
     if (mRegisteredTimeZoneReceiver) { 
      return; 
     } 
     mRegisteredTimeZoneReceiver = true; 
     IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED); 
     AnalogWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter); 
    } 

    private void unregisterReceiver() { 
     if (!mRegisteredTimeZoneReceiver) { 
      return; 
     } 
     mRegisteredTimeZoneReceiver = false; 
     AnalogWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver); 
    } 

    /** 
    * Starts/stops the {@link #mUpdateTimeHandler} timer based on the state of the watch face. 
    */ 
    private void updateTimer() { 
     if (Log.isLoggable(TAG, Log.DEBUG)) { 
      Log.d(TAG, "updateTimer"); 
     } 
     mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME); 
     if (shouldTimerBeRunning()) { 
      mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME); 
     } 
    } 

    /** 
    * Returns whether the {@link #mUpdateTimeHandler} timer should be running. The timer 
    * should only run in active mode. 
    */ 
    private boolean shouldTimerBeRunning() { 
     return isVisible() && !mAmbient; 
    } 
} 

ответ

0

Вы должны импортировать Часы Face API 1.3 или выше.

Затем в коде allowe вашего WatchFaceStyle принимает кран события:

setWatchFaceStyle(new WatchFaceStyle.Builder(WatchFaceService.this) 
       .setAcceptsTapEvents(true) 
       // other settings 
       .build()); 

после этого overrive onTapCommand для Engine

@Override 
public void onTapCommand(
    @TapType int tapType, int x, int y, long eventTime) { 
    switch (tapType) { 
     case WatchFaceService.TAP_TYPE_TAP: 
     //make magic 
     break; 
     case WatchFaceService.TAP_TYPE_TOUCH_CANCEL: 
     //make magic 
     break; 
    // and ect. 

проверки Handling Tap Events для получения дополнительной информации