从先前布局方向

时间:2017-03-23 20:55:33

标签: android android-layout android-canvas draw android-orientation

根据我之前关于" How to create a BottomBar as StickyBottomCaptureLayout in camera2 Android api?"的问题,我创建了一个带有StickyBar(SB)的布局,该布局始终锁定在系统栏上方/附近。我在onLayout()中设置了SB和其他布局的默认位置和坐标(正好是as my answer)。

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

portrait landscape

left:portrait - right:landscape

但是我想在方向改变时保持相同的坐标和位置,如下所示:

portrait lanscape

左:与上面相同的肖像 - 右:与"肖像"坐标

使用android:orientation="portrait"锁定我的活动不是预期的解决方案。我使用android:configChanges="orientation"OrientationListener来检测轮换并阻止Activity的完全重新创建。

  • 我试图在onLayout()中设置其他不同的位置,但很明显,这不是正确的方法。
  • 我之前曾试图像这样转换多个Path

    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);
    }
    
  • 我还尝试按如下方式旋转画布:

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

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

3 个答案:

答案 0 :(得分:2)

更新:方法已经简化并且更易于遵循。示例应用已更新。

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

确定设备经历的旋转后,创建Matrix以进行适当的旋转并围绕图形的中心旋转。

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

此处oldBounds是位置前图形的边界。我们将使用它来确定旋转图形上的边距。继续进行轮换

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-translation,transX是X-translation。 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

答案 1 :(得分:1)

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

假设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);
}

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

答案 2 :(得分: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

请注意,这种直接方向交换为反向方向是常规设备上的正常交互,而不是{em}只能在模拟器上进行的操作。

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

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

  

这根本不是配置更改。没有通知   平台提供何时执行此操作,因为它是不可见的   应用程序所处的环境。

要解决此问题,您必须使用SensorManager并注册OrientationEventListener以确定何时刷新视图,而不是依赖RotatedLayout方法。< / p>