以前的布局方向下在相同位置重新绘制多个路径

8

根据我之前的问题 "如何在camera2 Android api中创建一个作为StickyBottomCaptureLayout的BottomBar?",我创建了一个带有StickyBar(SB)的布局,它始终锁定在系统栏的上方/附近。我在 onLayout() 中设置了 SB 和其他布局的默认位置和坐标(正好像 我的答案 一样)。

上部布局是一个简单的自定义DrawView,其中有一个用户绘制的Path数组列表。当设备旋转时,它会重新调用onDraw()并多次调用canvas.drawPath()。然而,Path以前所在的坐标被重新绘制到不同的位置和布局大小。这些屏幕截图展示了实际行为:

portrait landscape

左侧:纵向 - 右侧:横向

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

portrait lanscape

左侧:与上方相同的肖像 - 右侧:横向带有“纵向”坐标。使用android:orientation="portrait"锁定我的活动不是预期的解决方案。我使用android:configChanges="orientation"OrientationListener来检测旋转并防止完全重新创建Activity
  • I tried to set other different positions in onLayout() but obviously, this is not the right way.
  • I previously tried to transform the multiple Paths like this:

    for (Path path : mPathList) {
        Matrix matrix = new Matrix();
        RectF bounds = new RectF();
        path.computeBounds(bounds, true);
    
        // center points to rotate
        final float px = bounds.centerX();
        final float py = bounds.centerY();
        // distance points to move 
        final float dx; // ?
        final float dy; // ?
        /** I tried many calculations without success, it's 
            not worth to paste these dumb calculations here... **/
    
        matrix.postRotate(rotation, px, py); // rotation is 90°, -90° or 0
        matrix.postTranslate(dx, dy); // ?
        path.transform(matrix);
    }
    
  • I also tried to rotate the canvas as follows:

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.save();
        canvas.rotate(rotation); // rotation is 90°, -90° or 0
    
        canvas.drawColor(mDrawHelper.getBackgroundViewColor());
        for (int i=0; i < mPathList.size(); i++) {
           canvas.drawPath(mPathList.get(i), mPaintList.get(i));
        }
        if (mPath != null && mPaint != null)
           canvas.drawPath(mPath, mPaint);
    
        canvas.restore();
    }  
    
无论如何,我尝试了许多操作,但在这种特定情况下似乎没有任何作用。是否有人有光辉而神奇的想法可以分享,以引导我朝正确的方向前进?
感谢您提前的帮助。

如果用户在横屏模式下在极右角画一条线,然后将设备旋转到竖屏模式,会发生什么? - Anurag Singh
我的问题是,在纵向模式下,您希望逻辑是该行是否可见。同样的用例是什么? - Anurag Singh
抱歉,@AnuragSingh,我误解了。如果用户在横屏模式下在极右侧绘制一条线,则我希望在纵向模式下将此线显示在设备顶部(就像我在帖子中的第二个示例中所示)。 - Blo
如果在横向模式下有一条线位于极右上角,则在纵向模式下应显示在极左上角。如果它在横向模式下的极右下角,则在纵向模式下它将位于极右上角。@AnuragSingh,希望这样更清楚明白。 - Blo
3个回答

2

更新:方法已经简化并且更易于理解,示例应用程序已经更新。

我认为我理解了你想要做的事情。你希望图形与您定义的StickyCaptureLayout保持关联。我喜欢使用PathMatrix变换的方法。

在确定设备旋转后,创建一个Matrix来进行适当的旋转,并围绕图形中心旋转。

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

这里的oldBounds是移动前图形的边界。我们将使用它来确定旋转图形的边距。继续进行旋转。

mPath.transform(mMatrix)

图形已经旋转,但位置不正确。它在旧位置上但是被旋转了。创建一个翻译MatrixPath移动到适当的位置。实际计算取决于旋转。对于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是旋转后的边界。需要注意的是,getWidth()将给出“旧”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;
        }
    }
}

肖像模式

enter image description here

横向模式

enter image description here


不错!我刚刚对你的解决方案进行了一些更改:不在onDraw中处理旋转,而是在调用它之前进行处理,并在循环中为每个路径设置rotationtranslation(因为我想将它们分开)。但看起来它确实非常好用。还要感谢你解释计算过程! - Blo

1
你的解决方案#2几乎是正确的。你需要适当地翻译你的画布。
假设“rotation”被声明为“int”,并且只能是“90”、“-90”或“0”,你需要替换这行代码:
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);
}

这个会起作用的。如果需要,我可以设置一个演示项目。


简单。然而,我更喜欢在我的视图中不使用全局的“Canvas”,并且不在“onDraw”中做更多的事情(只是绘制路径,没有其他)。 - Blo
这不是全局Canvas,它只是方法的参数,你可以随心所欲地使用它。 translaterotate方法做的事情是一样的-它们都修改变换矩阵。没有比这更快的了。 - Oleksii K.

1

不必实现一个非常特定于您问题的解决方案,您可以实现一个更通用的解决方案。例如,一种会旋转其内部所有内容的布局,我认为这更加优雅。

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来测量自身(和子元素)之后,它会旋转并使用视图属性进行平移,以适应新位置。使用视图属性的额外好处是触摸事件可以正常工作,这个按钮也可以像往常一样被按下。

在纵向方向上:

enter image description here

向左旋转:

enter image description here

onConfigurationChanged中出现问题

虽然此布局始终会以正确的方向绘制自己,但必须有某个事件会导致重新绘制。如果您仅依赖于 onConfigurationChanged,那么这可能会成为一个问题。在您的情况下,Activity 可以对从横向到纵向和从纵向到横向的更改做出反应。但是,在以下直接切换时不会发送任何事件:

  • 从纵向方向切换到翻转的纵向方向(如果您在 AndroidManifest 中启用了反向纵向)- 用蓝色标记。
  • 从横向方向切换到反向横向方向 - 用红色标记

enter image description here

请记住,在普通设备上进行直接方向切换是正常的交互方式,这不是你只能在模拟器上才能做到的“人为”的操作。
没有标准事件会导致视图重新绘制 - 没有触发 onConfigurationChanged, onMeasure, onLayout, onDraw 等方法。系统只是为您旋转了一切(甚至没有重新绘制),这将导致视图 RotatedLayout 旋转错误,而且没有任何更正。所以请注意您必须处理这种情况。
你可以在 Dianne Hackborn 的 answered 中看到这一点。

这只是一个简单的方向变化,并不是配置更改。由于对应用程序环境来说是不可见的,因此平台没有提供任何通知。

要解决这个问题,您需要使用SensorManager并注册OrientationEventListener来确定何时刷新视图,而不是依赖于onConfigurationChanged方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接