Android:如何在CanvasWatchFace上添加按钮?

时间:2016-04-04 03:57:58

标签: android

我是Android Wear的新手并制作表盘。前几天我成功地制作了一张表盘。我的问题是,如何向画布添加按钮(因为它不使用布局文件)。我想我需要在画布上以某种方式覆盖它,但我不确定该怎么做。

供参考,这是我正在使用的课程:

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

1 个答案:

答案 0 :(得分:0)

您需要导入Watch Face API 1.3或更高版本。

然后在您的代码允许中WatchFaceStyle接受点击事件:

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

之后,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以获取更多信息