如何为Android手表Face

时间:2015-07-31 11:08:39

标签: android wear-os android-wear-data-api

这是一个星期我试图创建一款适合Android的手表Face。作为一个开始,我按照谷歌官方文档找到了这些Android official watch face app tutorial with source code

所以我目前的问题是,在Google文档中,他们使用 canvas 来创建模拟监视面。表针使用 paint

生成

以下是创建拨号指针的代码示例

    public class AnalogWatchFaceService 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 {
    static final int MSG_UPDATE_TIME = 0;

    static final float TWO_PI = (float) Math.PI * 2f;

    Paint mHourPaint;
    Paint mMinutePaint;
    Paint mSecondPaint;
    Paint mTickPaint;
    boolean mMute;
    Calendar mCalendar;

    /** Handler to update the time once a second in interactive mode. */
    final Handler mUpdateTimeHandler = new Handler() {
        @Override
        public void handleMessage(Message message) {
            switch (message.what) {
                case MSG_UPDATE_TIME:
                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
                        Log.v(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);
                    }
                    break;
            }
        }
    };

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

    /**
     * Whether the display supports fewer bits for each color in ambient mode. When true, we
     * disable anti-aliasing in ambient mode.
     */
    boolean mLowBitAmbient;

    Bitmap mBackgroundBitmap;
    Bitmap mBackgroundScaledBitmap;

    @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());

        Resources resources = AnalogWatchFaceService.this.getResources();
        Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg, null /* theme */);
        mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();

        mHourPaint = new Paint();
        mHourPaint.setARGB(255, 200, 200, 200);
        mHourPaint.setStrokeWidth(5.f);
        mHourPaint.setAntiAlias(true);
        mHourPaint.setStrokeCap(Paint.Cap.ROUND);

        mMinutePaint = new Paint();
        mMinutePaint.setARGB(255, 200, 200, 200);
        mMinutePaint.setStrokeWidth(3.f);
        mMinutePaint.setAntiAlias(true);
        mMinutePaint.setStrokeCap(Paint.Cap.ROUND);

        mSecondPaint = new Paint();
        mSecondPaint.setARGB(255, 255, 0, 0);
        mSecondPaint.setStrokeWidth(2.f);
        mSecondPaint.setAntiAlias(true);
        mSecondPaint.setStrokeCap(Paint.Cap.ROUND);

        mTickPaint = new Paint();
        mTickPaint.setARGB(100, 255, 255, 255);
        mTickPaint.setStrokeWidth(2.f);
        mTickPaint.setAntiAlias(true);

        mCalendar = Calendar.getInstance();
    }

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

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

    @Override
    public void onTimeTick() {
        super.onTimeTick();
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "onTimeTick: ambient = " + isInAmbientMode());
        }
        invalidate();
    }

    @Override
    public void onAmbientModeChanged(boolean inAmbientMode) {
        super.onAmbientModeChanged(inAmbientMode);
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "onAmbientModeChanged: " + inAmbientMode);
        }
        if (mLowBitAmbient) {
            boolean antiAlias = !inAmbientMode;
            mHourPaint.setAntiAlias(antiAlias);
            mMinutePaint.setAntiAlias(antiAlias);
            mSecondPaint.setAntiAlias(antiAlias);
            mTickPaint.setAntiAlias(antiAlias);
        }
        invalidate();

        // Whether the timer should be running depends on whether we're in ambient mode (as well
        // as whether we're visible), so we may need to start or stop the timer.
        updateTimer();
    }

    @Override
    public void onInterruptionFilterChanged(int interruptionFilter) {
        super.onInterruptionFilterChanged(interruptionFilter);
        boolean inMuteMode = (interruptionFilter == WatchFaceService.INTERRUPTION_FILTER_NONE);
        if (mMute != inMuteMode) {
            mMute = 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) {
        if (mBackgroundScaledBitmap == null
                || mBackgroundScaledBitmap.getWidth() != width
                || mBackgroundScaledBitmap.getHeight() != height) {
            mBackgroundScaledBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap,
                    width, height, true /* filter */);
        }
        super.onSurfaceChanged(holder, format, width, height);
    }

    @Override
    public void onDraw(Canvas canvas, Rect bounds) {
        mCalendar.setTimeInMillis(System.currentTimeMillis());

        int width = bounds.width();
        int height = bounds.height();

        // Draw the background, scaled to fit.
        canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null);

        // Find the center. 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.
        float centerX = width / 2f;
        float centerY = height / 2f;

        // Draw the ticks.
        float innerTickRadius = centerX - 10;
        float outerTickRadius = centerX;
        for (int tickIndex = 0; tickIndex < 12; tickIndex++) {
            float tickRot = tickIndex * TWO_PI / 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(centerX + innerX, centerY + innerY,
                    centerX + outerX, centerY + outerY, mTickPaint);
        }

        float seconds =
                mCalendar.get(Calendar.SECOND) + mCalendar.get(Calendar.MILLISECOND) / 1000f;
        float secRot = seconds / 60f * TWO_PI;
        float minutes = mCalendar.get(Calendar.MINUTE) + seconds / 60f;
        float minRot = minutes / 60f * TWO_PI;
        float hours = mCalendar.get(Calendar.HOUR) + minutes / 60f;
        float hrRot = hours / 12f * TWO_PI;

        float secLength = centerX - 20;
        float minLength = centerX - 40;
        float hrLength = centerX - 80;

        if (!isInAmbientMode()) {
            float secX = (float) Math.sin(secRot) * secLength;
            float secY = (float) -Math.cos(secRot) * secLength;
            canvas.drawLine(centerX, centerY, centerX + secX, centerY + secY, mSecondPaint);
        }

        float minX = (float) Math.sin(minRot) * minLength;
        float minY = (float) -Math.cos(minRot) * minLength;
        canvas.drawLine(centerX, centerY, centerX + minX, centerY + minY, mMinutePaint);

        float hrX = (float) Math.sin(hrRot) * hrLength;
        float hrY = (float) -Math.cos(hrRot) * hrLength;
        canvas.drawLine(centerX, centerY, centerX + hrX, centerY + hrY, mHourPaint);
    }

}

整个代码可以在官方示例应用程序中找到。您可以在下面找到我使用Google官方教程制作的应用程序的屏幕截图。 enter image description here

如果有人知道如何用可绘图像替换时钟指针? 。任何帮助,将不胜感激 。

3 个答案:

答案 0 :(得分:2)

创建可绘制资源的位图:

Bitmap hourHand = BitmapFactory.decodeResource(context.getResources(), R.drawable.hour_hand);

对画布执行所需的任何转换并绘制位图:

canvas.save();
canvas.rotate(degrees, px, py);
canvas.translate(dx, dy);
canvas.drawBitmap(hourHand, centerX, centerY, null); // Or use a Paint if you need it
canvas.restore();

答案 1 :(得分:1)

使用以下方法从画布旋转位图

 //My viewController

 import UIKit
 import MediaPlayer
 import MobileCoreServices
 import AVFoundation


 class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIGestureRecognizerDelegate {

let captureSession = AVCaptureSession()
var previewLayer : AVCaptureVideoPreviewLayer?
var captureDevice : AVCaptureDevice?

override func viewDidAppear(animated: Bool) {

    if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera) {


        println("captureVideoPressed and camera available.")

        var imagePicker = UIImagePickerController()

        imagePicker.delegate = self
        imagePicker.sourceType = .Camera;
        imagePicker.mediaTypes = [kUTTypeMovie!]
        imagePicker.allowsEditing = false

        imagePicker.showsCameraControls = true


        self.presentViewController(imagePicker, animated: true, completion: nil)

    }

    else {
        println("Camera not available.")
    }
}

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}


func imagePickerController(picker: UIImagePickerController!,  info: NSDictionary!) {

    let tempImage = info[UIImagePickerControllerMediaURL] as! NSURL!
    let pathString = tempImage.relativePath
    self.dismissViewControllerAnimated(true, completion: {})

    UISaveVideoAtPathToSavedPhotosAlbum(pathString, self, nil, nil)

}

}

答案 2 :(得分:0)

我的答案有点晚了,但也许对其他人有帮助

canvas.save()

val antialias = Paint()
antialias.isAntiAlias = true
antialias.isFilterBitmap = true
antialias.isDither = true

canvas.rotate(secondsRotation - minutesRotation, centerX, centerY)
canvas.drawBitmap(
     secondsHandBitmap,
     centerX - 10,
     centerY - 160,
     antialias
)
canvas.restore()

这是我的公开Git Repo,您可以检查源代码