{"id":1080,"date":"2020-11-24T12:13:30","date_gmt":"2020-11-24T20:13:30","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/surface-duo\/?p=1080"},"modified":"2020-11-24T12:13:30","modified_gmt":"2020-11-24T20:13:30","slug":"custom-zipper-layout-jetpack-window-manager","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/surface-duo\/custom-zipper-layout-jetpack-window-manager\/","title":{"rendered":"Build a dual-screen custom layout with Jetpack Window Manager"},"content":{"rendered":"<p>\n  Hello Android developers,\n<\/p>\n<p>\n  The Microsoft Surface Duo SDK offers a variety of custom controls to help enhance your dual-screen apps, from layouts, tabs, and navigation controls for Kotlin and Java developers to the TwoPaneView for Xamarin, React Native, and Uno Platform developers. However, you might have an application-specific requirement that is not offered in our SDK, so this blog post shows how to build a simple dual-screen aware layout control. \n<\/p>\n<p>\n  This custom control might be useful in your apps, or you can use the source code as the basis for your own dual-screen control ideas!\n<\/p>\n<h2>Introducing ZipperLayout<\/h2>\n<p>\n  ZipperLayout is based on the LinearLayout, and on a single screen it behaves exactly like a linear layout:\n<\/p>\n<p>\n<a href=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/11\/blog-singles.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/11\/blog-singles-1024x613.png\" alt=\"ZipperLayout in single screen portrait and landscape\" width=\"640\" height=\"383\" class=\"alignnone size-large wp-image-1097\" srcset=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/11\/blog-singles-1024x613.png 1024w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/11\/blog-singles-300x180.png 300w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/11\/blog-singles-768x460.png 768w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/11\/blog-singles-1536x920.png 1536w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/11\/blog-singles-2048x1227.png 2048w\" sizes=\"(max-width: 640px) 100vw, 640px\" \/><\/a><br\/><i>Figure 1: ZipperLayout in single-screen portrait and landscape orientations<\/i>\n<\/p>\n<p>\n  However, when the app is spanned, the elements of the layout are rendered on either side of the hinge without appearing underneath. The developer chooses which elements appear on the left and right screens:\n<\/p>\n<p>\n  <a href=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/11\/blog-spanned.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/11\/blog-spanned-1024x800.png\" alt=\"Surface Duo with ZipperLayout in spanned mode\" width=\"640\" height=\"500\" class=\"alignnone size-large wp-image-1095\" srcset=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/11\/blog-spanned-1024x800.png 1024w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/11\/blog-spanned-300x234.png 300w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/11\/blog-spanned-768x600.png 768w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/11\/blog-spanned-1536x1201.png 1536w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/11\/blog-spanned-2048x1601.png 2048w\" sizes=\"(max-width: 640px) 100vw, 640px\" \/><\/a><br\/><i>Figure 2: ZipperLayout spanned across both screens<\/i>\n<\/p>\n<p>\n  Here is an example of the layout XML (with some items removed for clarity) \u2013 notice the <code>app:layout_rightSpanned<\/code> attributes which control which screen the element is rendered on:\n<\/p>\n<pre>&lt;com.example.myapplication.ZipperLayout...\r\n    android:layout_width=\"match_parent\"\r\n    android:layout_height=\"match_parent\"\r\n    android:orientation=\"vertical\">\r\n\r\n    &lt;TextView\r\n        app:layout_rightSpanned=\"true\"\r\n        android:id=\"@+id\/txtMain\" ... \/>\r\n\r\n    &lt;Button\r\n        android:id=\"@+id\/button\" ...\r\n        android:text=\"Button1\" \/>\r\n\r\n    &lt;Button\r\n        app:layout_rightSpanned=\"true\"\r\n        android:id=\"@+id\/button2\" ...\r\n        android:text=\"Button2\" \/>\r\n\r\n    &lt;Button\r\n        android:id=\"@+id\/button3\" ...\r\n        android:text=\"Button3\" \/>\r\n\r\n    &lt;CheckBox\r\n        app:layout_rightSpanned=\"true\"\r\n        android:id=\"@+id\/checkBox\" ...\r\n        android:text=\"CheckBox\" \/><\/pre>\n<h2>Implementing a dual-screen layout<\/h2>\n<p>\n  The custom control is implemented in the <strong>ZipperLayout.java<\/strong> file and extends the built-in <code>LinearLayout<\/code> control:\n<\/p>\n<pre>public class ZipperLayout \r\nextends LinearLayout {<\/pre>\n<p>\n  To facilitate the custom child attribute <code>layout_rightSpanned<\/code>, add this XML to the <strong>values\/attrs.xml<\/strong> file:\n<\/p>\n<pre>&lt;declare-styleable name=\"ZippperLayout_LayoutParams\">\r\n    &lt;attr name=\"layout_rightSpanned\" format=\"boolean\"\/>\r\n    &lt;attr name=\"layout_weight\" format=\"float\"\/>\r\n    &lt;attr name=\"android:layout_gravity\"\/>\r\n&lt;\/declare-styleable><\/pre>\n<p>\n  Within the ZipperLayout class declaration, extend <code>LayoutParams<\/code> and override the methods required to use the subclass (such as <code>generateLayoutParams<\/code>). In the subclass, create a local variable <code>rightSpanned<\/code> for the value and parse it via the <code>LayoutParams<\/code> constructor using:\n<\/p>\n<pre>TypedArray a = context.obtainStyledAttributes(attributeSet, R.styleable.ZipperLayout_LayoutParams);\r\nrightSpanned = a.getBoolean(R.styleable.ZipperLayout_LayoutParams_layout_rightSpanned, false);\r\n...<\/pre>\n<p>\n  This custom attribute is used later when laying out the child controls.\n<\/p>\n<h2>Jetpack Window Manager<\/h2>\n<p>\n  Dual-screen awareness for this example is provided by the <code>WindowBackend<\/code> interface described in the <a href=\"https:\/\/developer.android.com\/reference\/androidx\/window\/package-summary\">AndroidX API documentation<\/a>, which is added to the project by including a dependency in the <strong>build.gradle<\/strong> file\n<\/p>\n<pre>implementation \"androidx.window:window:1.0.0-alpha01\"\r\n<\/pre>\n<p>\n  To use the window manager, it is instantiated in <code>onAttachedToWindow<\/code> (and set to null in <code>onDetachedFromWindow<\/code>). \n<\/p>\n<pre>protected void onAttachedToWindow() {\r\n    super.onAttachedToWindow();\r\n    wm = new WindowManager(getContext(),null);\r\n}<\/pre>\n<p>\n  The window manager is then available during the layout process to determine if the app is spanned and the dimensions of the control to distribute the layout elements. Window manager is used in two important overrides: <code>onMeasure<\/code> and <code>onLayout<\/code>.\n<\/p>\n<p><code>onMeasure<\/code> is called first, and the window manager is used to determine if there is a display feature (which is the API terminology for the hinge). If the hinge is detected, and it is detected to be vertical in the middle of the screen, the layout\u2019s <code>widthMeasureSpec<\/code> is modified to use only the width of one screen, rather than the entire spanned screen width.\n<\/p>\n<pre>windowLayoutInfo = wm.getWindowLayoutInfo();\r\nList<DisplayFeature> displayFeatures = windowLayoutInfo.getDisplayFeatures();\r\nif(displayFeatures.size() > 0)\r\n{ \r\n...<\/pre>\n<p>\n  This will affect the width of the child controls when they are ready to be positioned on the screen.\n<\/p>\n<p>\n  When <code>onLayout<\/code> is called, it also checks for the presence of the hinge via <code>getDisplayFeatures<\/code> and determines the x coordinate for the child elements to be displayed on the right screen. The method then lays out the child controls in a loop, checking if the app is split across two screens, and whether the child element should be placed on the right screen:\n<\/p>\n<pre>for(int i=0;i&lt;getChildCount();i++)\r\n{\r\n    final View childAt = this.getChildAt(i);\r\n    final LayoutParams lp = (LayoutParams) childAt.getLayoutParams();\r\n    if (lp.isRightSpanned() && split) { \r\n...<\/pre>\n<p>\n  The loop keeps track of the height of each element as it stacks them down each screen until all the controls are positioned. In double-landscape mode, the hinge is ignored and the control behaves like a regular <code>LinearLayout<\/code>.\n<\/p>\n<p>\n  You can download the complete sample from <a href=\"https:\/\/github.com\/conceptdev\/kotlin-samples\/blob\/main\/ZipperLayout\/README.md\">GitHub<\/a>.\n<\/p>\n<h2>November Surface Duo emulator update<\/h2>\n<p>\n  A new version of the Surface Duo emulator image was released last week. Follow the instructions to <a href=\"https:\/\/docs.microsoft.com\/dual-screen\/android\/emulator\/surface-duo-download\/?WT.mc_id=docs-surfaceduoblog-conceptdev\">download and install the new version<\/a>, and check the <a href=\"https:\/\/docs.microsoft.com\/dual-screen\/android\/emulator\/release-notes\/?WT.mc_id=docs-surfaceduoblog-conceptdev\">release notes<\/a> for the version history.\n<\/p>\n<h2>Coming soon \u2013 droidcon APAC 2020<\/h2>\n<p>\n  The Surface Duo Developer Experience team was a presenter at droidcon EMEA and droidcon AMERICAS (see <a href=\"https:\/\/devblogs.microsoft.com\/surface-duo\/droidcon-webinar-recap\/\">this recap<\/a>). We\u2019ll also be presenting at <a href=\"https:\/\/www.online.droidcon.com\/apac-2020\">droidcon APAC 2020<\/a> on December 14th-15th, 2020. Register now and join us for 90+ tech talks, hackathons, roundtables, 1:1 meetings, and more.\n<\/p>\n<p>\n  <a href=\"https:\/\/www.online.droidcon.com\/apac-2020\"><img decoding=\"async\" width=\"800\" height=\"213\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/11\/word-image-15.png\" class=\"wp-image-1084\" alt=\"droidcon asia pacific banner 14-15 December 2020\" srcset=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/11\/word-image-15.png 800w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/11\/word-image-15-300x80.png 300w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/11\/word-image-15-768x204.png 768w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><\/a>\n<\/p>\n<h2>Feedback<\/h2>\n<p>\n  I hope this has been a good introduction to building custom controls for dual-screen devices. There are many additional features that could be added to this control and the possibilities for other custom layouts is only limited by your imagination.\n<\/p>\n<p>\n  We would love to hear from you about your experiences using the controls in our dual-screen library, as well as any custom controls you build.\n<\/p>\n<p>\n  Please reach out using the\u00a0<a href=\"http:\/\/aka.ms\/SurfaceDuoSDK-Feedback\">feedback forum<\/a>\u00a0or find me on\u00a0<a href=\"https:\/\/twitter.com\/conceptdev\">Twitter<\/a> or the team <a href=\"https:\/\/twitter.com\/surfaceduodev\">@surfaceduodev<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hello Android developers, The Microsoft Surface Duo SDK offers a variety of custom controls to help enhance your dual-screen apps, from layouts, tabs, and navigation controls for Kotlin and Java developers to the TwoPaneView for Xamarin, React Native, and Uno Platform developers. However, you might have an application-specific requirement that is not offered in our [&hellip;]<\/p>\n","protected":false},"author":570,"featured_media":1095,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[706,473,46],"class_list":["post-1080","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-surface-duo-sdk","tag-jetpack-window-manager","tag-kotlin","tag-surface-duo"],"acf":[],"blog_post_summary":"<p>Hello Android developers, The Microsoft Surface Duo SDK offers a variety of custom controls to help enhance your dual-screen apps, from layouts, tabs, and navigation controls for Kotlin and Java developers to the TwoPaneView for Xamarin, React Native, and Uno Platform developers. However, you might have an application-specific requirement that is not offered in our [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/posts\/1080","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/users\/570"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/comments?post=1080"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/posts\/1080\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/media\/1095"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/media?parent=1080"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/categories?post=1080"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/tags?post=1080"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}