Как использовать созданные пользователем изображения для Android Watch Face

Уже неделю я пытаюсь создать циферблат для часов Android. Для начала я просмотрел официальную документацию Google и нашел это официальное руководство по приложению Android Watch Face с исходным кодом. код

Итак, моя текущая проблема: в документации Google они используют холст для создания аналоговых циферблатов. Стрелки часов созданы с помощью 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. введите здесь описание изображения

Если у кого-нибудь есть идеи, как заменить стрелки часов на рисуемые изображения? . Любая помощь будет оценена по достоинству.


person Nikhil    schedule 31.07.2015    source источник
comment
читать Canvas API, а именно drawBitmap(), rotate()/concat() или просто drawBitmap() с параметром Matrix   -  person pskink    schedule 31.07.2015
comment
будет полезно, если вы будете более конкретными, пожалуйста.   -  person Nikhil    schedule 31.07.2015
comment
я конкретен: я не имею в виду чтение всей документации класса, я указал, какие методы изучать...   -  person pskink    schedule 31.07.2015
comment
я только что попробовал то, что вам было указано, проблема в том, что каждый canvas.drawBitmap() должен включать объект paint, где мне действительно нужно добавить drawable вместо краски   -  person Nikhil    schedule 31.07.2015
comment
public void drawBitmap (Bitmap bitmap, float left, float top, Paint paint) ... paint Краска, используемая для рисования растрового изображения (может быть null), внимательно прочитайте документацию   -  person pskink    schedule 31.07.2015
comment
да я уже сделал. Позвольте мне углубиться в это, и я скоро вернусь   -  person Nikhil    schedule 31.07.2015
comment
посмотри, что?   -  person pskink    schedule 03.08.2015
comment
Извините, я только что обновил код. пожалуйста, взгляните   -  person Nikhil    schedule 03.08.2015
comment
Вместо canvas.drawLine(centerX, centerY, centerX + hrX, centerY + hrY, mHourPaint); я попробовал canvas.drawBitmap(bitmap,mCenterX,mCenterY,null); .Даже несмотря на то, что он работает, к сожалению, он не может работать как рука, он просто идет параллельно экрану.   -  person Nikhil    schedule 03.08.2015
comment
просто используйте canvas.drawBitmap(bitmap, matrix, null), конечно, вам нужно настроить Matrix, но сначала попробуйте с немодифицированным Matrix, затем попробуйте postTranslate матрицу, чтобы опорная точка находилась в месте [0, 0], например, на изображении: pasteboard.co/2rPyWVa5.png точка опоры — это маленькая красная точка, если точка опоры находится в точке [0, 0 ] вызовите postRotate(degrees) и, наконец, postTranslate еще раз, чтобы расположить его там, где вы хотите   -  person pskink    schedule 03.08.2015


Ответы (3)


Создайте растровое изображение вашего доступного ресурса:

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();
person TofferJ    schedule 31.07.2015
comment
Я попробовал ваш код, но проблема, с которой я столкнулся, заключается в том, что растровое изображение не отображается в центральном положении. это видно за углом - person Nikhil; 03.08.2015
comment
Затем вам нужно изменить значения для вызова метода translate и/или rotate. Вы можете либо сначала повернуть растровое изображение, а затем переместить его, либо наоборот. Просто имейте в виду, какую центральную точку вы используете для вращения. Как правило, если у вас есть ассет, представляющий руку, вам нужно вращать его вокруг нижней части, а не по центру. Если вы начнете с перемещения его в центр экрана, то легко представить, как на него повлияет вращение. - person TofferJ; 03.08.2015
comment
Вероятно, вам нужно перевести растровое изображение на половину его ширины и немного меньше его высоты, прежде чем вращать его (при условии, что изображение стрелки часов вертикально). Затем нарисуйте его в центре экрана или переместите холст в центр и нарисуйте его в 0,0. - person joshbodily; 03.09.2015

Используйте следующий метод для поворота растрового изображения с холста,

/**
 * 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();
}
person Umang Kothari    schedule 03.08.2015

Я немного опоздал с ответом, но, возможно, это может быть полезно для других

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 вы можете проверить исходный код

person Viktor Apoyan    schedule 19.06.2019