根据我之前关于" 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
重绘的坐标与之前相同,但位置和布局大小不同。这些屏幕截图演示了实际行为:
left:portrait - right:landscape
但是我想在方向改变时保持相同的坐标和位置,如下所示:
左:与上面相同的肖像 - 右:与"肖像"坐标
使用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();
}
无论如何,我尝试了很多操作,但在这个特定情况下似乎没有任何效果。有人有一个明亮而神话般的想法,可以引导我朝着正确的方向前进吗? 在此先感谢您的帮助。
答案 0 :(得分:2)
更新:方法已经简化并且更易于遵循。示例应用已更新。
我想我明白你要做什么。您希望图形与您定义的StickyCaptureLayout
保持关系。我喜欢使用Path
和Matrix
转换的方法。
确定设备经历的旋转后,创建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;
}
}
}
<强>肖像强>
<强>风景强>
答案 1 :(得分: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);
}
这会奏效。如果需要,我可以设置一个演示项目。
答案 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
测量自身(和孩子)后,它会自行旋转并使用视图属性进行翻译以适应新位置。作为使用视图属性的好处 - 触摸事件可以正常工作,并且可以像往常一样按下此按钮。
纵向:
向左旋转:
onConfigurationChanged
虽然此布局总是以正确的方向绘制,但必须有一些事件会导致它被重新绘制。如果您仅依赖onConfigurationChanged
,这可能会成为问题。在您的情况下,Activity
可以对从横向到纵向和从纵向到横向的变化做出反应。但直接切换时没有发送事件:
请注意,这种直接方向交换为反向方向是常规设备上的正常交互,而不是{em}只能在模拟器上进行的操作。
没有发送会导致视图重绘的标准事件 - 不会调用artificial
,onConfigurationChanged
,onMeasure
,onLayout
等。
系统只是为你旋转一切(甚至没有重绘它),它将导致视图的错误旋转onDraw
没有更改来纠正它。所以请注意,你必须处理这种情况。
你可以在Dianne Hackborn的answered中看到它。
这根本不是配置更改。没有通知 平台提供何时执行此操作,因为它是不可见的 应用程序所处的环境。
要解决此问题,您必须使用SensorManager并注册OrientationEventListener以确定何时刷新视图,而不是依赖RotatedLayout
方法。< / p>