从先前的布局方向在相同位置重绘多个路径

Redraw multiple Paths at same positions from previous layout orientation

基于我之前的问题“", I created a layout with a StickyBar (SB) which is always locked above/near the system bar. I set the default positions and coordinates of the SB and the other layout in onLayout() (exactly ”。

上面的布局是一个简单的自定义 DrawView,它有一个由用户绘制的 Path 的 ArrayList。当设备旋转时,它会调用onDraw()并调用几次canvas.drawPath()。但是,Path 将使用与以前相同的坐标重新绘制,但位置和布局大小不同。这些屏幕截图展示了实际行为:

左:纵向 - 右:横向

但是我想在方向改变时保持相同的坐标和位置,像这样:

左:与上图相同 - 右:风景 "portrait" 坐标

android:orientation="portrait" 锁定我的 activity 不是预期的解决方案。我使用 android:configChanges="orientation"OrientationListener 来检测旋转并防止 Activity.

的完全重新创建

无论如何,我尝试了很多操作,但在这种特定情况下似乎没有任何效果。是否有人有一个聪明绝妙的想法可以分享,可以引导我朝着正确的方向前进?
在此先感谢您的帮助。

更新: 方法论已得到简化,更易于遵循。示例应用已更新。

我想我明白你想做什么。您希望图形保持与您定义的 StickyCaptureLayout 的关系。我喜欢使用 PathMatrix 转换的方法。

确定设备经过的旋转后,创建一个Matrix做相应的旋转,绕图形中心旋转。

mMatrix.postRotate(rotationDegrees, oldBounds.centerX(), oldBounds.centerY());

这里oldBounds是location前图形的bounds。我们将使用它来确定旋转图形的边距。继续旋转

mPath.transform(mMatrix)

图形已旋转但位置不正确。它处于旧位置但已旋转。创建翻译 Matrix 以将 Path 移动到适当的位置。实际计算取决于旋转。对于 90 度旋转,计算结果为

transY = -newBounds.bottom; // move bottom of graphic to top of View
transY += getHeight(); // move to bottom of rotated view
transY -= (getHeight() - oldBounds.right); // finally move up by old right margin
transX = -newBounds.left; // Pull graphic to left of container
transX += getWidth() - oldBounds.bottom; // and pull right for margin

其中 transY 是 Y 平移,transX 是 X 平移。 oldBounds 是预旋转边界,newBounds 是 post-旋转边界。这里要注意的重要一点是 getWidth() 会给你 "old" View 高度,getHeight() 会给你旧的 View 宽度。

这是一个示例程序,可以完成我上面描述的内容。下面的几个图形显示了使用此示例应用程序进行的 90 度旋转。

演示应用程序

package com.example.rotatetranslatedemo;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.os.Bundle;
import android.view.Display;
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;

public class MainActivity extends Activity {

    private DrawingView dv;
    private Paint mPaint;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        dv = new DrawingView(this);
        setContentView(dv);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setColor(Color.GREEN);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(12);
    }

    public class DrawingView extends View {

        private Bitmap mBitmap;
        private Path mPath;
        private Paint mBitmapPaint;
        Context context;
        private Paint paint;
        Matrix mMatrix = new Matrix();
        RectF oldBounds = new RectF();
        RectF newBounds = new RectF();

        public DrawingView(Context c) {
            super(c);
            context = c;
            mBitmapPaint = new Paint(Paint.DITHER_FLAG);
            paint = new Paint();
            paint.setAntiAlias(true);
            paint.setColor(Color.BLUE);
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeJoin(Paint.Join.MITER);
            paint.setStrokeWidth(4f);
        }

        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);

            mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        }

        @Override
        protected void onDraw(Canvas canvas) {
            Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
                    .getDefaultDisplay();
            int rotationDegrees = 0;
            float transX = 0;
            float transY = 0;

            super.onDraw(canvas);

            canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);

            // Determine the rotation of the screen.
            switch (display.getRotation()) {
                case Surface.ROTATION_0:
                    break;

                case Surface.ROTATION_90:
                    rotationDegrees = 270;
                    break;

                case Surface.ROTATION_180:
                    rotationDegrees = 180;
                    break;

                case Surface.ROTATION_270:
                    rotationDegrees = 90;
                    break;

                default:
                    rotationDegrees = 0;
                    break;
            }

            if (mPath == null) { // Just define what we are drawing/moving
                mPath = setupGraphic();
            }

            // Reposition the graphic taking into account the current rotation.
            if (rotationDegrees != 0) {
                mMatrix.reset();
                // Rotate the graphic by its center and in place.
                mPath.computeBounds(oldBounds, true);
                mMatrix.postRotate(rotationDegrees, oldBounds.centerX(), oldBounds.centerY());
                mPath.transform(mMatrix);
                // Get the bounds of the rotated graphic
                mPath.computeBounds(newBounds, true);
                mMatrix.reset();
                if (rotationDegrees == 90) {
                    transY = -newBounds.bottom; // move bottom of graphic to top of View
                    transY += getHeight(); // move to bottom of rotated view
                    transY -= (getHeight() - oldBounds.right); // finally move up by old right margin
                    transX = -newBounds.left; // Pull graphic to left of container
                    transX += getWidth() - oldBounds.bottom; // and pull right for margin
                } else if (rotationDegrees == 270) {
                    transY = -newBounds.top; // pull top of graphic to the top of View
                    transY += getHeight() - oldBounds.right; // move down for old right margin
                    transX = getWidth() - newBounds.right; // Pull to right side of View
                    transX -= getHeight() - oldBounds.right; // Reestablish right margin
                }
                mMatrix.postTranslate(transX, transY);
                mPath.transform(mMatrix);
            }
            canvas.drawPath(mPath, mPaint);
        }

        // Define the graphix that we will draw and move.
        private Path setupGraphic() {
            int startX;
            int startY;
            final int border = 20;
            Path path;

            if (getHeight() > getWidth()) {
                startX = getWidth() - border - 1;
                startY = getHeight() - border - 1;
            } else {
                startX = getHeight() - border - 1;
                startY = getWidth() - border - 1;
            }
            startX = startX - 200;

            Pt[] myLines = {
                    new Pt(startX, startY),
                    new Pt(startX, startY - 500),

                    new Pt(startX, startY),
                    new Pt(startX - 100, startY),

                    new Pt(startX, startY - 500),
                    new Pt(startX - 50, startY - 400),

                    new Pt(startX, startY - 500),
                    new Pt(startX + 50, startY - 400),

                    new Pt(startX + 200, startY),
                    new Pt(startX + 200, startY - 500)
            };

            // Create the final Path
            path = new Path();
            for (int i = 0; i < myLines.length; i = i + 2) {
                path.moveTo(myLines[i].x, myLines[i].y);
                path.lineTo(myLines[i + 1].x, myLines[i + 1].y);
            }

            return path;
        }

        private static final String TAG = "DrawingView";

    }

    // Class to hold ordered pair
    private class Pt {
        float x, y;

        Pt(float _x, float _y) {
            x = _x;
            y = _y;
        }
    }
}

纵向

横向

您的解决方案 #2 几乎是正确的。您需要做的就是适当地翻译您的 canvas。

假设rotation被声明为int并且可能只是90-900,你需要替换这一行:

canvas.rotate(rotation); // rotation is 90°, -90° or 0

通过以下代码:

if (rotation == 90) {
    canvas.translate(canvas.getWidth(), 0);
    canvas.rotate(90);
} else if (rotation == -90) {
    canvas.translate(0, canvas.getHeight());
    canvas.rotate(-90);
}

这会奏效。如果需要,我可以设置一个演示项目。

您可以只实施更通用的解决方案,而不是针对您的问题实施非常具体的解决方案。例如,将旋转内部所有内容的布局,我认为这要优雅得多。

public class RotatedLayout extends FrameLayout {

    public RotatedLayout(Context context) {
        super(context);
    }

    ...

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int rotation = 0;
        boolean swapDimensions = false;
        int translationX = 0;
        int translationY = 0;

        final Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
        switch (display.getRotation()) {
            case Surface.ROTATION_0:
                rotation = 0;
                break;
            case Surface.ROTATION_90:
                rotation = -90;
                swapDimensions = true;
                break;
            case Surface.ROTATION_180:
                rotation = 180;
                break;
            case Surface.ROTATION_270:
                rotation = 90;
                swapDimensions = true;
                break;
        }

        if (swapDimensions) {
            final int width = MeasureSpec.getSize(widthMeasureSpec);
            final int height = MeasureSpec.getSize(heightMeasureSpec);
            translationX = (width - height) / 2;
            translationY = (height - width) / 2;
            final int tmpMeasureSpec = heightMeasureSpec;
            heightMeasureSpec = widthMeasureSpec;
            widthMeasureSpec = tmpMeasureSpec;
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setTranslationX(translationX);
        setTranslationY(translationY);
        setRotation(rotation);
    }
}

这个布局相当简单。如果以横向模式显示,它会强制使用交换尺寸来测量自己。它不关心里面是什么,所以你可以把所有东西都放在那里,还有一个普通的界面。在使用交换的 MeasureSpecs 测量自身(和子级)后,它会旋转自身并使用视图属性进行平移以适应新位置。作为使用视图属性的奖励 - 触摸事件工作正常,可以像往常一样按下此按钮。

纵向:

向左旋转:

onConfigurationChanged有问题

虽然这个布局总是会以正确的方向绘制自己,但一定有一些事件会导致它被重新绘制。如果您仅依赖 onConfigurationChanged,这可能是个问题。在您的情况下,Activity 可以对从横向到纵向以及从纵向到横向的变化做出反应。但是直接从:

切换时没有发送事件
  • 纵向到还原纵向(如果您在 AndroidManifest 中启用了反向纵向)- 标记为蓝色。
  • 横向到颠倒的横向 - 标记为红色

请记住,这种直接方向交换到反向方向是常规设备上的正常交互,它不是 artificial 您只能在模拟器上执行的操作。

没有发送会导致视图重绘自身的标准事件 - 没有调用 onConfigurationChangedonMeasureonLayoutonDraw 等。 系统只是为你旋转一切(甚至没有重新绘制),这将导致视图旋转错误 RotatedLayout 没有更改来更正它。所以请注意,您必须处理这种情况。

您可以在 Dianne Hackborn 的 answered 中看到它。

This is simply not a configuration change. There is no notification the platform provides for when it does this, because it is invisible to the environment the app is in.

要解决此问题,您必须使用 SensorManager and register OrientationEventListener 来确定何时刷新视图,而不是依赖于 onConfigurationChanged 方法。