【问题标题】:How to use custom created drawable images for Android watch Face如何为 Android 表盘使用自定义创建的可绘制图像
【发布时间】:2015-07-31 11:08:39
【问题描述】:

我已经花了一周时间尝试为 Android 磨损创建表盘。作为一个开始,我按照谷歌官方文档找到了这些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);
    }

}

完整的代码可以在官方示例应用中找到。下面你可以找到我使用谷歌官方教程制作的应用程序的屏幕截图。

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

【问题讨论】:

  • 读取Canvas API,即drawBitmap()rotate() / concat() 或只是drawBitmap()Matrix 参数
  • 如果您能更具体一些,将会很有帮助。
  • 我很具体:我不是指阅读整个课堂文档,我指出了学习的方法......
  • 我刚刚尝试了你所指出的,问题是每个 canvas.drawBitmap() 都必须包含一个 paint 对象,我真正需要在其中添加 可绘制而不是绘制
  • "public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) ... paint 用于绘制位图的paint(可能为null)”阅读文档仔细

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


【解决方案1】:

创建可绘制资源的位图:

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

【讨论】:

  • 我试过你的代码,但我面临的问题是位图没有显示在中心位置。它的显示指日可待
  • 然后您需要更改调用 translate 和/或 rotate 方法的值。您可以先旋转位图,然后再移动它,或者反过来。请记住您用于旋转的中心点。通常,如果您有一个代表一只手的资产,您可能希望将其围绕底部而不是中心旋转。如果您首先将其移动到屏幕中心,则很容易想象旋转将如何影响它。
  • 在旋转位图之前,您可能需要将位图平移一半宽度并略低于高度(假设手表指针的图像是垂直的)。然后,将其绘制在屏幕中心或将画布平移到中心并在 0,0 处绘制。
【解决方案2】:

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

/**
 * To rotate bitmap on canvas
 *
 * @param canvas         : canvas on which you are drawing
 * @param handBitmap     : bitmap of hand
 * @param centerPoint    : center for rotation
 * @param rotation       : rotation angle in form of seconds
 * @param offset         : offset of bitmap from center point (If not needed then keep it 0)
 */

public void rotateBitmap(Canvas canvas, Bitmap handBitmap, PointF centerPoint, float rotation, float offset) {
    canvas.save();
    canvas.rotate(secondRotation - 90, centerPoint.x, centerPoint.y);
    canvas.drawBitmap(handBitmap, centerPoint.x - offset, centerPoint.y - handBitmap.getHeight() / Constants.INTEGER_TWO, new Paint(Paint.FILTER_BITMAP_FLAG));
    canvas.restore();
}

【讨论】:

    【解决方案3】:

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

    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你可以查看源代码

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-11
      • 2016-12-10
      • 2017-05-10
      • 2016-05-29
      • 2019-03-10
      • 1970-01-01
      相关资源
      最近更新 更多