Reverse horizontal translation set on preview in RTL layouts
When correcting or applying a scale type to the preview in PreviewView, a horizontal translation might be applied to it, which works fine in left-to-right layouts. In the context of a right-to-left layout, the horizontal tarnslation should be reversed, otherwise it may cause the preview is be offset.
Bug: 154944939
Test: ./gradlew camera:camera-view:test
Change-Id: I7d59750304bb24c58b5c7e8a4a086aaf6248830e
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java b/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
index 01a939f..3b9890a 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
@@ -261,28 +261,38 @@
public enum ScaleType {
/**
* Scale the preview, maintaining the source aspect ratio, so it fills the entire
- * {@link PreviewView}, and align it to the top left corner of the view.
+ * {@link PreviewView}, and align it to the start of the view, which is the top left
+ * corner in a left-to-right (LTR) layout, or the top right corner in a right-to-left
+ * (RTL) layout.
+ * <p>
* This may cause the preview to be cropped if the camera preview aspect ratio does not
* match that of its container {@link PreviewView}.
*/
FILL_START(0),
/**
* Scale the preview, maintaining the source aspect ratio, so it fills the entire
- * {@link PreviewView}, and center it inside the view.
+ * {@link PreviewView}, and center it in the view.
+ * <p>
* This may cause the preview to be cropped if the camera preview aspect ratio does not
* match that of its container {@link PreviewView}.
*/
FILL_CENTER(1),
/**
* Scale the preview, maintaining the source aspect ratio, so it fills the entire
- * {@link PreviewView}, and align it to the bottom right corner of the view.
+ * {@link PreviewView}, and align it to the end of the view, which is the bottom right
+ * corner in a left-to-right (LTR) layout, or the bottom left corner in a right-to-left
+ * (RTL) layout.
+ * <p>
* This may cause the preview to be cropped if the camera preview aspect ratio does not
* match that of its container {@link PreviewView}.
*/
FILL_END(2),
/**
* Scale the preview, maintaining the source aspect ratio, so it is entirely contained
- * within the {@link PreviewView}, and align it to the top left corner of the view.
+ * within the {@link PreviewView}, and align it to the start of the view, which is the
+ * top left corner in a left-to-right (LTR) layout, or the top right corner in a
+ * right-to-left (RTL) layout.
+ * <p>
* Both dimensions of the preview will be equal or less than the corresponding dimensions
* of its container {@link PreviewView}.
*/
@@ -290,13 +300,17 @@
/**
* Scale the preview, maintaining the source aspect ratio, so it is entirely contained
* within the {@link PreviewView}, and center it inside the view.
+ * <p>
* Both dimensions of the preview will be equal or less than the corresponding dimensions
* of its container {@link PreviewView}.
*/
FIT_CENTER(4),
/**
* Scale the preview, maintaining the source aspect ratio, so it is entirely contained
- * within the {@link PreviewView}, and align it to the bottom right corner of the view.
+ * within the {@link PreviewView}, and align it to the end of the view, which is the
+ * bottom right corner in a left-to-right (LTR) layout, or the bottom left corner in a
+ * right-to-left (RTL) layout.
+ * <p>
* Both dimensions of the preview will be equal or less than the corresponding dimensions
* of its container {@link PreviewView}.
*/
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/TranslationTransform.java b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/TranslationTransform.java
index b7a3788..2809dc2 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/TranslationTransform.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/TranslationTransform.java
@@ -23,8 +23,14 @@
import androidx.camera.view.preview.transform.transformation.TranslationTransformation;
/**
- * Computes the x and y coordinates of the top left corner of the preview in order to position it
- * at the start (top left), center or end (bottom right) of its parent.
+ * Computes the horizontal and vertical translations by which the preview needs to be translated
+ * to position it at the start, center or end of its parent.
+ * <p>
+ * The start represents the top left corner in a left-to-right (LTR) layout, or the top right
+ * corner in a right-to-left (RTL) layout.
+ * <p>
+ * The end represents the bottom right corner in a left-to-right (LTR) layout, or the bottom left
+ * corner in a right-to-left (RTL) layout.
*/
final class TranslationTransform {
@@ -32,8 +38,11 @@
}
/**
- * Computes the x and y coordinates of the top left corner of {@code view} so that it's
- * aligned to the top left corner of its parent {@code container}.
+ * Computes the horizontal and vertical translations to set on {@code view} to align it to the
+ * start of its parent {@code container}.
+ * <p>
+ * The start represents the top left corner in a left-to-right (LTR) layout, or the top right
+ * corner in a right-to-left (RTL) layout.
*/
static TranslationTransformation start(@NonNull final View view,
@NonNull final Pair<Float, Float> scaleXY) {
@@ -63,14 +72,14 @@
final int currentCenterX = view.getWidth() / 2;
final int currentCenterY = view.getHeight() / 2;
- final int transX = targetCenterX - currentCenterX;
+ final int transX = reverseIfRTLLayout(view, targetCenterX - currentCenterX);
final int transY = targetCenterY - currentCenterY;
return new TranslationTransformation(transX, transY);
}
/**
- * Computes the x and y coordinates of the top left corner of {@code view} so that it's
- * centered in its parent {@code container}.
+ * Computes the horizontal and vertical translations to set on {@code view} to center it in its
+ * parent {@code container}.
*/
static TranslationTransformation center(@NonNull final View container,
@NonNull final View view) {
@@ -86,14 +95,17 @@
final int currentCenterX = view.getWidth() / 2;
final int currentCenterY = view.getHeight() / 2;
- final int transX = targetCenterX - currentCenterX;
+ final int transX = reverseIfRTLLayout(view, targetCenterX - currentCenterX);
final int transY = targetCenterY - currentCenterY;
return new TranslationTransformation(transX, transY);
}
/**
- * Computes the x and y coordinates of the top left corner of {@code view} so that it's
- * aligned to the bottom right corner of its parent {@code container}.
+ * Computes the horizontal and vertical translations to set on {@code view} to align it to the
+ * end of its parent {@code container}.
+ * <p>
+ * The end represents the bottom right corner in a left-to-right (LTR) layout, or the bottom
+ * left corner in a right-to-left (RTL) layout.
*/
static TranslationTransformation end(@NonNull final View container, @NonNull final View view,
@NonNull final Pair<Float, Float> scaleXY) {
@@ -127,8 +139,19 @@
final int currentCenterX = view.getWidth() / 2;
final int currentCenterY = view.getHeight() / 2;
- final int transX = targetCenterX - currentCenterX;
+ final int transX = reverseIfRTLLayout(view, targetCenterX - currentCenterX);
final int transY = targetCenterY - currentCenterY;
return new TranslationTransformation(transX, transY);
}
+
+ /**
+ * Reverses a horizontal translation if the {@code view} is in a right-to-left (RTL) layout.
+ *
+ * @return The passed in horizontal translation if the layout is left-to-right (LTR), or its
+ * reverse if the layout is right-to-left (RTL).
+ */
+ private static int reverseIfRTLLayout(@NonNull final View view, int transX) {
+ final boolean isRTLDirection = view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ return isRTLDirection ? -transX : transX;
+ }
}
diff --git a/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/TranslationTransformTest.java b/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/TranslationTransformTest.java
index 699f6db..05ba62f 100644
--- a/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/TranslationTransformTest.java
+++ b/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/TranslationTransformTest.java
@@ -16,6 +16,9 @@
package androidx.camera.view.preview.transform;
+import static android.view.View.LAYOUT_DIRECTION_LTR;
+import static android.view.View.LAYOUT_DIRECTION_RTL;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
@@ -89,6 +92,16 @@
}
@Test
+ public void start_rtlLayout() {
+ final View view = setUpView(Surface.ROTATION_0, LAYOUT_DIRECTION_RTL);
+ final Pair<Float, Float> scaleXY = new Pair<>(2F, 0.5F);
+ final TranslationTransformation transformation = TranslationTransform.start(view, scaleXY);
+
+ assertThat(transformation.getTransX()).isEqualTo(-100);
+ assertThat(transformation.getTransY()).isEqualTo(-75);
+ }
+
+ @Test
public void center_viewNotScaled_rotation0() {
final View container = setUpContainer();
final View view = setUpView(Surface.ROTATION_0);
@@ -137,6 +150,18 @@
}
@Test
+ public void center_rtlLayout() {
+ final View container = setUpContainer();
+ final View view = setUpView(Surface.ROTATION_0, LAYOUT_DIRECTION_RTL);
+ final Pair<Float, Float> scaleXY = new Pair<>(1F, 1F);
+ final TranslationTransformation transformation = TranslationTransform.center(container,
+ view);
+
+ assertThat(transformation.getTransX()).isEqualTo(-150);
+ assertThat(transformation.getTransY()).isEqualTo(250);
+ }
+
+ @Test
public void end_viewNotScaled_rotation0() {
final View container = setUpContainer();
final View view = setUpView(Surface.ROTATION_0);
@@ -184,11 +209,29 @@
assertThat(transformation.getTransY()).isEqualTo(450);
}
+ @Test
+ public void end_rtlLayout() {
+ final View container = setUpContainer();
+ final View view = setUpView(Surface.ROTATION_0, LAYOUT_DIRECTION_RTL);
+ final Pair<Float, Float> scaleXY = new Pair<>(1F, 1F);
+ final TranslationTransformation transformation = TranslationTransform.end(container, view,
+ scaleXY);
+
+ assertThat(transformation.getTransX()).isEqualTo(-300);
+ assertThat(transformation.getTransY()).isEqualTo(500);
+ }
+
@NonNull
private View setUpView(final int rotation) {
+ return setUpView(rotation, LAYOUT_DIRECTION_LTR);
+ }
+
+ @NonNull
+ private View setUpView(final int rotation, final int layoutDirection) {
final View view = mock(View.class);
when(view.getWidth()).thenReturn(VIEW_WIDTH);
when(view.getHeight()).thenReturn(VIEW_HEIGHT);
+ when(view.getLayoutDirection()).thenReturn(layoutDirection);
final Display display = mock(Display.class);
when(view.getDisplay()).thenReturn(display);
diff --git a/camera/integration-tests/viewtestapp/src/main/AndroidManifest.xml b/camera/integration-tests/viewtestapp/src/main/AndroidManifest.xml
index aed04bf..62ab73b 100644
--- a/camera/integration-tests/viewtestapp/src/main/AndroidManifest.xml
+++ b/camera/integration-tests/viewtestapp/src/main/AndroidManifest.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -24,6 +23,7 @@
<application
android:label="@string/app_name"
android:largeHeap="true"
+ android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity