Rotating a bitmap around a pivot on a canvas

2019-08-07 02:06发布

I am trying to implement a watch face where a bitmap image of a hand rotates about the center of the face on a canvas.

  1. Basically in the onDraw() method, I want to be able to put an image resource onto the canvas then rotate it rotate it every second.
  2. I have logic in place to trigger the rotation every second, I am not sure how to obtain the image resource and then rotate it.

Any help would be appriciated!

3条回答
唯我独甜
2楼-- · 2019-08-07 02:24

I recently needed to solve the bitmap rotation problem for an Android Wear watchface. The following onDraw code worked for me. Be sure your bitmaps are scaled before onDraw gets called:

        // first draw the background
        if (isInAmbientMode()) {
            canvas.drawColor(Color.BLACK);
        } else {
            if (mBackgroundBitmapScaled == null) {
                canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mBackgroundPaint);
            }
            else {
                canvas.drawBitmap(mBackgroundBitmapScaled, 0, 0, null);
            }
        }

        // now setup to draw the hands..
        float centerX = bounds.width() / 2f;
        float centerY = bounds.height() / 2f;

        float secRot = mCalendar.get(Calendar.SECOND) / 30f * (float) Math.PI;
        int minutes = mCalendar.get(Calendar.MINUTE);
        float minRot = minutes / 30f * (float) Math.PI;
        float hrRot = ((mCalendar.get(Calendar.HOUR) + (minutes / 60f)) / 6f) * (float) Math.PI;

        if (isInAmbientMode()) {
            mHandPaint.clearShadowLayer();
            float minLength = centerX - 40;
            float hrLength = centerX - 80;

            // hour hand
            float hrX = (float) Math.sin(hrRot) * hrLength;
            float hrY = (float) -Math.cos(hrRot) * hrLength;
            canvas.drawLine(centerX, centerY, centerX + hrX, centerY + hrY, mHandPaint);

            // minute hand
            float minX = (float) Math.sin(minRot) * minLength;
            float minY = (float) -Math.cos(minRot) * minLength;
            canvas.drawLine(centerX, centerY, centerX + minX, centerY + minY, mHandPaint);
        }
        else {
            // hour hand
            Matrix matrix = new Matrix();
            matrix.setRotate (hrRot / (float) Math.PI * 180, mHourHandBitmapScaled.getWidth()/2, mHourHandBitmapScaled.getHeight()/2);
            canvas.drawBitmap(mHourHandBitmapScaled, matrix, mHandPaint);

            // minute hand
            matrix = new Matrix();
            matrix.setRotate (minRot / (float) Math.PI * 180, mHourHandBitmapScaled.getWidth()/2, mHourHandBitmapScaled.getHeight()/2);
            canvas.drawBitmap(mMinuteHandBitmapScaled, matrix, mHandPaint);
        }

        if (!mAmbient) {
            // second hand
            float secLength = centerX - 20;
            float secX = (float) Math.sin(secRot) * secLength;
            float secY = (float) -Math.cos(secRot) * secLength;
            canvas.drawLine(centerX, centerY, centerX + secX, centerY + secY, mHandPaint);
        }

and for scaling:

    private void scaleWatchFace(int width, int height) {
        Log.v(TAG, "scaleWatchFace");
        mBackgroundBitmapScaled = Bitmap.createScaledBitmap(mBackgroundBitmap, width, height, true /* filter */);
        float ratio = (float) width / mBackgroundBitmap.getWidth();
        mMinuteHandBitmapScaled = Bitmap.createScaledBitmap(mMinuteHandBitmap,
                (int) (mMinuteHandBitmap.getWidth() * ratio),
                (int) (mMinuteHandBitmap.getHeight() * ratio), true);
        mHourHandBitmapScaled = Bitmap.createScaledBitmap(mHourHandBitmap,
                (int) (mHourHandBitmap.getWidth() * ratio),
                (int) (mHourHandBitmap.getHeight() * ratio), true);
    }

Note that the image sizes of my background and watch hand resources are all 480x480. That eliminates any need for translation to center, and (possibly) is easier on the battery.

I hope this is useful.

查看更多
孤傲高冷的网名
3楼-- · 2019-08-07 02:30

Basic steps:

  1. Decode your bitmap from, for example, Resources, but there are other ways. (You would want to do this once when initializing the class, not on every draw cycle.):

    Bitmap = mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.watch_face);

  2. Set up a Matrix inside onDraw for the rotation. Something like:

    Matrix mMatrix = new Matrix(); mMatrix.setRotate (degreeOfRotation, mBitmap.getWidth()/2, mBitmap.getHeight()/2);

  3. Draw the Bitmap using a method that takes in your Matrix: canvas.drawBitmap(mBitmap, mMatrix, mPaint);

There are other details. Setting up Paint. You may find you need to use the Matrix's postRotate method instead. Etc. Etc. But this should be enough to you started Googling.

查看更多
混吃等死
4楼-- · 2019-08-07 02:40

In this certain situation (I guess you're trying to rotate bitmaps with Google watch face api) you should have in mind rules described in this brilliant post, and avoid using large bitmaps for watch hands. This means you'll have to specify vertical and horizontal margins. I used the solution posted by Jave here.

yourCanvas.save(Canvas.MATRIX_SAVE_FLAG); //Save the canvas
yourCanvas.rotate(currentRotation, centerX, centerY);
int marginX = (width - yourBitmap.getWidth()) / 2; //Calculate horizontal margin
yourCanvas.drawBitmap(yourBitmap, marginX, 0, yourPaint);
yourCanvas.restore();

I gave up cropping the bitmap vertically, since it's much easier to rotate it this way, but it should be taken in consideration if you want to be in full compliance to the rules. It would be even better not to calculate marginX here. It may be done in onSurfaceChanged, as described in the post mentioned above.

查看更多
登录 后发表回答