Merge "Account for parent scroll position" into nyc-dev
diff --git a/bridge/src/android/view/BridgeInflater.java b/bridge/src/android/view/BridgeInflater.java
index 723e827..bdddfd8 100644
--- a/bridge/src/android/view/BridgeInflater.java
+++ b/bridge/src/android/view/BridgeInflater.java
@@ -265,12 +265,15 @@
if (viewKey != null) {
bc.addViewKey(view, viewKey);
}
- String scrollPos = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollY");
- if (scrollPos != null) {
- if (scrollPos.endsWith("px")) {
- int value = Integer.parseInt(scrollPos.substring(0, scrollPos.length() - 2));
- bc.setScrollYPos(view, value);
- }
+ String scrollPosX = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollX");
+ if (scrollPosX != null && scrollPosX.endsWith("px")) {
+ int value = Integer.parseInt(scrollPosX.substring(0, scrollPosX.length() - 2));
+ bc.setScrollXPos(view, value);
+ }
+ String scrollPosY = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollY");
+ if (scrollPosY != null && scrollPosY.endsWith("px")) {
+ int value = Integer.parseInt(scrollPosY.substring(0, scrollPosY.length() - 2));
+ bc.setScrollYPos(view, value);
}
if (ReflectionUtils.isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) {
Integer resourceId = null;
diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index b3f1ee1..4161307 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -127,7 +127,8 @@
private final LayoutlibCallback mLayoutlibCallback;
private final WindowManager mWindowManager;
private final DisplayManager mDisplayManager;
- private final HashMap<View, Integer> mScrollYPos = new HashMap<View, Integer>();
+ private final HashMap<View, Integer> mScrollYPos = new HashMap<>();
+ private final HashMap<View, Integer> mScrollXPos = new HashMap<>();
private Resources.Theme mTheme;
@@ -1837,6 +1838,15 @@
return pos != null ? pos : 0;
}
+ public void setScrollXPos(@NonNull View view, int scrollPos) {
+ mScrollXPos.put(view, scrollPos);
+ }
+
+ public int getScrollXPos(@NonNull View view) {
+ Integer pos = mScrollXPos.get(view);
+ return pos != null ? pos : 0;
+ }
+
@Override
public Context createDeviceProtectedStorageContext() {
// pass
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index 53f1912..016825a 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -1057,25 +1057,30 @@
}
/**
- * Set the vertical scroll position on all the components with the "scrollY" attribute. If the
- * component supports nested scrolling attempt that first, then use the unconsumed scroll part
- * to scroll the content in the component.
+ * Set the scroll position on all the components with the "scrollX" and "scrollY" attribute. If
+ * the component supports nested scrolling attempt that first, then use the unconsumed scroll
+ * part to scroll the content in the component.
*/
private void handleScrolling(View view) {
BridgeContext context = getContext();
- int scrollPos = context.getScrollYPos(view);
- if (scrollPos != 0) {
+ int scrollPosX = context.getScrollXPos(view);
+ int scrollPosY = context.getScrollYPos(view);
+ if (scrollPosX != 0 || scrollPosY != 0) {
if (view.isNestedScrollingEnabled()) {
int[] consumed = new int[2];
- if (view.startNestedScroll(DesignLibUtil.SCROLL_AXIS_VERTICAL)) {
- view.dispatchNestedPreScroll(0, scrollPos, consumed, null);
- view.dispatchNestedScroll(consumed[0], consumed[1], 0, scrollPos, null);
+ int axis = scrollPosX != 0 ? View.SCROLL_AXIS_HORIZONTAL : 0;
+ axis |= scrollPosY != 0 ? View.SCROLL_AXIS_VERTICAL : 0;
+ if (view.startNestedScroll(axis)) {
+ view.dispatchNestedPreScroll(scrollPosX, scrollPosY, consumed, null);
+ view.dispatchNestedScroll(consumed[0], consumed[1], scrollPosX, scrollPosY,
+ null);
view.stopNestedScroll();
- scrollPos -= consumed[1];
+ scrollPosX -= consumed[0];
+ scrollPosY -= consumed[1];
}
}
- if (scrollPos != 0) {
- view.scrollBy(0, scrollPos);
+ if (scrollPosX != 0 || scrollPosY != 0) {
+ view.scrollBy(scrollPosX, scrollPosY);
}
}
@@ -1276,14 +1281,20 @@
return null;
}
+ ViewParent parent = view.getParent();
ViewInfo result;
if (isContentFrame) {
+ // Account for parent scroll values when calculating the bounding box
+ int scrollX = parent != null ? ((View)parent).getScrollX() : 0;
+ int scrollY = parent != null ? ((View)parent).getScrollY() : 0;
+
// The view is part of the layout added by the user. Hence,
// the ViewCookie may be obtained only through the Context.
result = new ViewInfo(view.getClass().getName(),
getContext().getViewKey(view),
- view.getLeft(), view.getTop() + offset, view.getRight(),
- view.getBottom() + offset, view, view.getLayoutParams());
+ -scrollX + view.getLeft(), -scrollY + view.getTop() + offset,
+ -scrollX + view.getRight(), -scrollY + view.getBottom() + offset,
+ view, view.getLayoutParams());
} else {
// We are part of the system decor.
SystemViewInfo r = new SystemViewInfo(view.getClass().getName(),
@@ -1311,7 +1322,6 @@
// its parent is of type ActionMenuView. We can also check if the view is
// instanceof ActionMenuItemView but that will fail for menus using
// actionProviderClass.
- ViewParent parent = view.getParent();
while (parent != mViewRoot && parent instanceof ViewGroup) {
if (parent instanceof ActionMenuView) {
r.setViewType(ViewType.ACTION_BAR_MENU);
diff --git a/bridge/tests/res/testApp/MyApplication/golden/scrolled.png b/bridge/tests/res/testApp/MyApplication/golden/scrolled.png
new file mode 100644
index 0000000..87bd502
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/scrolled.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/layout/scrolled.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/layout/scrolled.xml
new file mode 100644
index 0000000..a5ebc2e
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/layout/scrolled.xml
@@ -0,0 +1,57 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:scrollX="10px"
+ android:scrollY="30px">
+ <LinearLayout
+ android:layout_width="60dp"
+ android:layout_height="60dp"
+ android:background="#FF0000" />
+ <LinearLayout
+ android:layout_width="60dp"
+ android:layout_height="30dp"
+ android:background="#00FF00" />
+ <LinearLayout
+ android:layout_width="60dp"
+ android:layout_height="60dp"
+ android:background="#0000FF" />
+ <LinearLayout
+ android:layout_width="60dp"
+ android:layout_height="30dp"
+ android:background="#FF00FF" />
+ <LinearLayout
+ android:layout_width="60dp"
+ android:layout_height="60dp"
+ android:background="#00FFFF" />
+
+ <LinearLayout
+ android:layout_width="200dp"
+ android:layout_height="400dp"
+ android:orientation="vertical"
+ android:scrollX="-30px"
+ android:scrollY="150px">
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="60dp"
+ android:background="#FF0000" />
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="30dp"
+ android:background="#00FF00" />
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="60dp"
+ android:background="#0000FF" />
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="30dp"
+ android:background="#FF00FF" />
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="60dp"
+ android:background="#00FFFF" />
+ </LinearLayout>
+
+
+</LinearLayout>
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
index 2726042..c2f06e8 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
@@ -21,6 +21,7 @@
import com.android.ide.common.rendering.api.Result;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
+import com.android.ide.common.rendering.api.ViewInfo;
import com.android.ide.common.resources.FrameworkResources;
import com.android.ide.common.resources.ResourceItem;
import com.android.ide.common.resources.ResourceRepository;
@@ -48,9 +49,11 @@
import java.net.URL;
import java.util.Arrays;
import java.util.Comparator;
-import java.util.Map;
import java.util.concurrent.TimeUnit;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
@@ -398,6 +401,40 @@
renderAndVerify(params, "vector_drawable.png", TimeUnit.SECONDS.toNanos(2));
}
+ /** Test activity.xml */
+ @Test
+ public void testScrolling() throws ClassNotFoundException {
+ // Create the layout pull parser.
+ LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/" +
+ "scrolled.xml");
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger());
+ layoutLibCallback.initResources();
+
+ SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+ layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+ RenderingMode.V_SCROLL, 22);
+ params.setForceNoDecor();
+ params.setExtendedViewInfoMode(true);
+
+ RenderResult result = renderAndVerify(params, "scrolled.png");
+ assertNotNull(result);
+ assertTrue(result.getResult().isSuccess());
+
+ ViewInfo rootLayout = result.getRootViews().get(0);
+ // Check the first box in the main LinearLayout
+ assertEquals(-90, rootLayout.getChildren().get(0).getTop());
+ assertEquals(-30, rootLayout.getChildren().get(0).getLeft());
+ assertEquals(90, rootLayout.getChildren().get(0).getBottom());
+ assertEquals(150, rootLayout.getChildren().get(0).getRight());
+
+ // Check the first box within the nested LinearLayout
+ assertEquals(-450, rootLayout.getChildren().get(5).getChildren().get(0).getTop());
+ assertEquals(90, rootLayout.getChildren().get(5).getChildren().get(0).getLeft());
+ assertEquals(-270, rootLayout.getChildren().get(5).getChildren().get(0).getBottom());
+ assertEquals(690, rootLayout.getChildren().get(5).getChildren().get(0).getRight());
+ }
+
/**
* Create a new rendering session and test that rendering the given layout doesn't throw any
* exceptions and matches the provided image.
@@ -405,7 +442,8 @@
* If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time indicates
* how far in the future is.
*/
- private void renderAndVerify(SessionParams params, String goldenFileName, long frameTimeNanos)
+ @Nullable
+ private RenderResult renderAndVerify(SessionParams params, String goldenFileName, long frameTimeNanos)
throws ClassNotFoundException {
// TODO: Set up action bar handler properly to test menu rendering.
// Create session params.
@@ -428,36 +466,43 @@
try {
String goldenImagePath = APP_TEST_DIR + "/golden/" + goldenFileName;
ImageUtils.requireSimilar(goldenImagePath, session.getImage());
+
+ return RenderResult.getFromSession(session);
} catch (IOException e) {
getLogger().error(e, e.getMessage());
} finally {
session.dispose();
}
+
+ return null;
}
/**
* Create a new rendering session and test that rendering the given layout doesn't throw any
* exceptions and matches the provided image.
*/
- private void renderAndVerify(SessionParams params, String goldenFileName)
+ @Nullable
+ private RenderResult renderAndVerify(SessionParams params, String goldenFileName)
throws ClassNotFoundException {
- renderAndVerify(params, goldenFileName, -1);
+ return renderAndVerify(params, goldenFileName, -1);
}
/**
* Create a new rendering session and test that rendering the given layout on nexus 5
* doesn't throw any exceptions and matches the provided image.
*/
- private void renderAndVerify(String layoutFileName, String goldenFileName)
+ @Nullable
+ private RenderResult renderAndVerify(String layoutFileName, String goldenFileName)
throws ClassNotFoundException {
- renderAndVerify(layoutFileName, goldenFileName, ConfigGenerator.NEXUS_5);
+ return renderAndVerify(layoutFileName, goldenFileName, ConfigGenerator.NEXUS_5);
}
/**
* Create a new rendering session and test that rendering the given layout on given device
* doesn't throw any exceptions and matches the provided image.
*/
- private void renderAndVerify(String layoutFileName, String goldenFileName,
+ @Nullable
+ private RenderResult renderAndVerify(String layoutFileName, String goldenFileName,
ConfigGenerator deviceConfig)
throws ClassNotFoundException {
// Create the layout pull parser.
@@ -469,7 +514,7 @@
// Create session params.
SessionParams params = getSessionParams(parser, deviceConfig,
layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
- renderAndVerify(params, goldenFileName);
+ return renderAndVerify(params, goldenFileName);
}
/**
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderResult.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderResult.java
new file mode 100644
index 0000000..17b20f7
--- /dev/null
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderResult.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.intensive;
+
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.ViewInfo;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+class RenderResult {
+ private final List<ViewInfo> mRootViews;
+ private final List<ViewInfo> mSystemViews;
+ private final Result mRenderResult;
+
+ private RenderResult(@Nullable Result result, @Nullable List<ViewInfo> systemViewInfoList,
+ @Nullable List<ViewInfo> rootViewInfoList) {
+ mSystemViews = systemViewInfoList == null ? Collections.emptyList() : systemViewInfoList;
+ mRootViews = rootViewInfoList == null ? Collections.emptyList() : rootViewInfoList;
+ mRenderResult = result;
+ }
+
+ @NonNull
+ static RenderResult getFromSession(@NonNull RenderSession session) {
+ return new RenderResult(session.getResult(),
+ new ArrayList<>(session.getSystemRootViews()),
+ new ArrayList<>(session.getRootViews()));
+ }
+
+ @Nullable
+ Result getResult() {
+ return mRenderResult;
+ }
+
+ @NonNull
+ public List<ViewInfo> getRootViews() {
+ return mRootViews;
+ }
+
+ @NonNull
+ public List<ViewInfo> getSystemViews() {
+ return mSystemViews;
+ }
+}