From 801fda548c719a8618e7f4cd64cad8404b0970b9 Mon Sep 17 00:00:00 2001
From: Scott Main NewsReader.zip Depending on the layout that your application is currently showing, the UI
+flow may be different. For example, if your application is in the dual-pane
+mode, clicking on an item on the left pane will simply display the content on
+the right pane; if it is in single-pane mode, the content should be displayed
+on its own (in a different activity). Since your implementation of each layout will be a little different, one of
+the first things you will probably have to do is determine what layout the user is currently
+viewing. For example, you might want to know whether the user is in "single
+pane" mode or "dual pane" mode. You can do that by querying if a given view
+exists and is visible: Notice that this code queries whether the "article" pane is available or not,
+which is much more flexible than hard-coding a query for a specific layout. Another example of how you can adapt to the existence of different
+components is to check whether they are available before performing an operation on
+them. For example, in the News Reader sample app, there is a button that opens a
+menu, but that button only exists when running on versions older than Android 3.0 (because it's
+function is taken over by the {@link android.app.ActionBar} on API level 11+). So, to add the event
+listener for this button, you can do: Some actions may have a different result depending on the current layout.
+For example, in the News Reader sample, clicking on a headline from the
+headlines list opens the article in the right hand-side pane if the UI
+is in dual pane mode, but will launch a separate activity if the UI is in
+single-pane mode: Likewise, if the app is in dual-pane mode, it should set up the action bar
+with tabs for navigation, whereas if the app is in single-pane mode, it should set
+up navigation with a spinner widget. So your code should also check which case is
+appropriate: A recurring pattern in designing for multiple screens is having a portion of
+your interface that's implemented as a pane on some screen configurations and
+as a separate activity on other configurations. For example, in the News Reader
+sample, the news article text is presented in the right side pane on
+large screens, but is a separate activity on smaller screens. In cases like this, you can usually avoid code duplication by reusing the
+same {@link android.app.Fragment} subclass in several activities. For example,
+ And reused (without a layout) in the activity layout for smaller screens
+( Naturally, this has the same effect as declaring the fragment in an XML
+layout, but in this case an XML layout is unnecessary work because the article fragment
+is the only component of this activity. One very important point to keep in mind when designing your fragments is
+to not create a strong coupling to a specific activity. You can usually do that
+by defining an interface that abstracts all the ways in which the fragment
+needs to interact with its host activity, and then the host activity
+implements that interface: For example, the News Reader app's Then, when the user selects a headline, the fragment notifies the listener specified by the host
+activity (as opposed to notifying a specific hard-coded activity): This technique is discussed further in the guide to Supporting Tablets and Handsets. If you are using separate activities to implement separate parts of your interface,
+you have to keep in mind that it may be necessary to react to certain
+configuration changes (such as a rotation change) in order to keep your
+interface consistent. For example, on a typical 7" tablet running Android 3.0 or higher, the News Reader sample uses a
+separate activity to display the news article when running in portrait mode,
+but uses a two-pane layout when in landscape mode. This means that when the user is in portrait mode and the activity for viewing an article is
+onscreen, you need to detect that the orientation changed to landscape and
+react appropriately by ending the activity and return to the main activity so the content can
+display in the two-pane layout: NewsReader.zip Android powers hundreds of device types with several different screen sizes,
+ranging from small phones to large TV sets. Therefore, it’s important
+that you design your application to be compatible with all screen sizes so it’s available to as many
+users as possible. But being compatible with different device types is not enough. Each screen
+size offers different possibilities and challenges for user interaction, so in
+order to truly satisfy and impress your users, your application must go beyond merely
+supporting multiple screens: it must optimize the user
+experience for each screen configuration. This class shows you how to implement a user interface that's
+optimized for several screen configurations. The code in each lesson comes from a sample application that demonstrates best practices in
+optimizing for multiple screens. You can download the sample (to the right) and use it as a source
+of reusable code for your own application. Note: This class and the associated sample use the support library in order to use the {@link
+android.app.Fragment} APIs on versions lower than Android 3.0. You must download and add the
+library to your application in order to use all APIs in this class. NewsReader.zip This lesson shows you how to support different screen densities
+by providing different resources and using resolution-independent units of
+measurements. One common pitfall you must avoid when designing your layouts is using
+absolute pixels to define distances or sizes. Defining layout dimensions with
+pixels is a problem because different screens have different pixel densities,
+so the same number of pixels may correspond to different physical sizes on
+different devices. Therefore, when specifying dimensions, always use either
+ For example, when you specify spacing between two views, use When specifying text size, always use Since Android runs in devices with a wide variety of screen densities,
+you should always provide your bitmap resources tailored to each of
+the generalized density buckets: low, medium, high and extra-high density.
+This will help you achieve good graphical quality and performance on all
+screen densities. To generate these images, you should start with your raw resource in
+vector format and generate the images for each density using the following
+size scale:This lesson teaches you to
+
+
+
+
+You should also read
+
+
+
+Try it out
+
+Determine the Current Layout
+
+
+public class NewsReaderActivity extends FragmentActivity {
+ boolean mIsDualPane;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main_layout);
+
+ View articleView = findViewById(R.id.article);
+ mIsDualPane = articleView != null &&
+ articleView.getVisibility() == View.VISIBLE;
+ }
+}
+
+
+
+Button catButton = (Button) findViewById(R.id.categorybutton);
+OnClickListener listener = /* create your listener here */;
+if (catButton != null) {
+ catButton.setOnClickListener(listener);
+}
+
+
+
+React According to Current Layout
+
+
+@Override
+public void onHeadlineSelected(int index) {
+ mArtIndex = index;
+ if (mIsDualPane) {
+ /* display article on the right pane */
+ mArticleFragment.displayArticle(mCurrentCat.getArticle(index));
+ } else {
+ /* start a separate activity */
+ Intent intent = new Intent(this, ArticleActivity.class);
+ intent.putExtra("catIndex", mCatIndex);
+ intent.putExtra("artIndex", index);
+ startActivity(intent);
+ }
+}
+
+
+
+final String CATEGORIES[] = { "Top Stories", "Politics", "Economy", "Technology" };
+
+public void onCreate(Bundle savedInstanceState) {
+ ....
+ if (mIsDualPane) {
+ /* use tabs for navigation */
+ actionBar.setNavigationMode(android.app.ActionBar.NAVIGATION_MODE_TABS);
+ int i;
+ for (i = 0; i < CATEGORIES.length; i++) {
+ actionBar.addTab(actionBar.newTab().setText(
+ CATEGORIES[i]).setTabListener(handler));
+ }
+ actionBar.setSelectedNavigationItem(selTab);
+ }
+ else {
+ /* use list navigation (spinner) */
+ actionBar.setNavigationMode(android.app.ActionBar.NAVIGATION_MODE_LIST);
+ SpinnerAdapter adap = new ArrayAdapter
+
+
+Reuse Fragments in Other Activities
+
+ArticleFragment
+is used in the dual-pane layout:ArticleActivity
):
+ArticleFragment frag = new ArticleFragment();
+getSupportFragmentManager().beginTransaction().add(android.R.id.content, frag).commit();
+
+
+HeadlinesFragment
does precisely that:
+public class HeadlinesFragment extends ListFragment {
+ ...
+ OnHeadlineSelectedListener mHeadlineSelectedListener = null;
+
+ /* Must be implemented by host activity */
+ public interface OnHeadlineSelectedListener {
+ public void onHeadlineSelected(int index);
+ }
+ ...
+
+ public void setOnHeadlineSelectedListener(OnHeadlineSelectedListener listener) {
+ mHeadlineSelectedListener = listener;
+ }
+}
+
+
+
+public class HeadlinesFragment extends ListFragment {
+ ...
+ @Override
+ public void onItemClick(AdapterView<?> parent,
+ View view, int position, long id) {
+ if (null != mHeadlineSelectedListener) {
+ mHeadlineSelectedListener.onHeadlineSelected(position);
+ }
+ }
+ ...
+}
+
+
+Handle Screen Configuration Changes
+
+
+public class ArticleActivity extends FragmentActivity {
+ int mCatIndex, mArtIndex;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mCatIndex = getIntent().getExtras().getInt("catIndex", 0);
+ mArtIndex = getIntent().getExtras().getInt("artIndex", 0);
+
+ // If should be in two-pane mode, finish to return to main activity
+ if (getResources().getBoolean(R.bool.has_two_panes)) {
+ finish();
+ return;
+ }
+ ...
+}
+
+
+
diff --git a/docs/html/training/multiscreen/index.jd b/docs/html/training/multiscreen/index.jd
new file mode 100644
index 0000000..a986cef
--- /dev/null
+++ b/docs/html/training/multiscreen/index.jd
@@ -0,0 +1,87 @@
+page.title=Designing for Multiple Screens
+
+trainingnavtop=true
+startpage=true
+next.title=Supporting Different Screen Sizes
+next.link=screensizes.html
+
+@jd:body
+
+Dependencies and prerequisites
+
+
+
+
+You should also read
+
+
+
+Try it out
+
+Lessons
+
+
+
diff --git a/docs/html/training/multiscreen/screendensities.jd b/docs/html/training/multiscreen/screendensities.jd
new file mode 100644
index 0000000..7d6ff44
--- /dev/null
+++ b/docs/html/training/multiscreen/screendensities.jd
@@ -0,0 +1,127 @@
+page.title=Supporting Different Densities
+parent.title=Designing for Multiple Screens
+parent.link=index.html
+
+trainingnavtop=true
+previous.title=Supporting Different Screen Sizes
+previous.link=screensizes.html
+next.title=Implementing Adaptative UI Flows
+next.link=adaptui.html
+
+@jd:body
+
+
+
+This lesson teaches you to
+
+
+You should also read
+
+
+
+Try it out
+
+Use Density-independent Pixels
+
+dp
or sp
units. A dp
is a density-independent pixel
+that corresponds to the physical size of a pixel at 160 dpi. An sp
is the same
+base unit, but is scaled by the user's preferred text size (it’s a
+scale-independent pixel), so you should use this measurement unit when defining
+text size (but never for layout sizes).dp
+rather than px
:
+<Button android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/clickme"
+ android:layout_marginTop="20dp" />
+
+
+sp
:
+<TextView android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="20sp" />
+
+
+
+Provide Alternative Bitmaps
+
+
+
xhdpi
: 2.0
+ hdpi
: 1.5
+ mdpi
: 1.0 (baseline)
+ ldpi
: 0.75
+
This means that if you generate a 200x200 image for xhdpi
+devices, you should generate the same resource in 150x150 for hdpi
,
+100x100 for mdpi
and finally a 75x75 image for ldpi
+devices.
Then, place the generated image files in the appropriate subdirectory
+under res/
and the system will pick the correct one automatically
+based on the screen density of the device your application is running on:
+MyProject/ + res/ + drawable-xhdpi/ + awesomeimage.png + drawable-hdpi/ + awesomeimage.png + drawable-mdpi/ + awesomeimage.png + drawable-ldpi/ + awesomeimage.png ++ +
Then, any time you reference @drawable/awesomeimage
, the system selects the
+appropriate bitmap based on the screen's dpi.
For more tips and guidelines for creating icon assets for your application, see the Icon Design +Guidelines.
+ diff --git a/docs/html/training/multiscreen/screensizes.jd b/docs/html/training/multiscreen/screensizes.jd new file mode 100644 index 0000000..2db0b67 --- /dev/null +++ b/docs/html/training/multiscreen/screensizes.jd @@ -0,0 +1,376 @@ +page.title=Supporting Different Screen Sizes +parent.title=Designing for Multiple Screens +parent.link=index.html + +trainingnavtop=true +next.title=Supporting Different Screen Densities +next.link=screendensities.html + +@jd:body + + + +NewsReader.zip
+This lesson shows you how to support different screen sizes by:
+To ensure that your layout is flexible and adapts to different screen sizes,
+you should use "wrap_content"
and "match_parent"
for the width
+and height of some view components. If you use "wrap_content"
, the width
+or height of the view is set to the minimum size necessary to fit the content
+within that view, while "match_parent"
(also known as
+"fill_parent"
before API level 8) makes the component expand to match the size of its
+parent view.
By using the "wrap_content"
and "match_parent"
size values instead of
+hard-coded sizes, your views either use only the space required for that
+view or expand to fill the available space, respectively. For example:
Notice how the sample uses "wrap_content"
and "match_parent"
+for component sizes rather than specific dimensions. This allows the layout
+to adapt correctly to different screen sizes and orientations.
For example, this is what this layout looks like in portrait and landscape +mode. Notice that the sizes of the components adapt automatically to the +width and height:
+ +Figure 1. The News Reader sample app in portrait (left) +and landscape (right).
+ + +You can construct fairly complex layouts using nested instances of {@link
+android.widget.LinearLayout} and
+combinations of "wrap_content"
and "match_parent"
sizes.
+However, {@link android.widget.LinearLayout} does not allow you to precisely control the
+spacial relationships of child views; views in a {@link android.widget.LinearLayout} simply line up
+side-by-side. If you need child views to be oriented in variations other than a straight line, a
+better solution is often to use a {@link android.widget.RelativeLayout}, which allows
+you to specify your layout in terms of the spacial relationships between
+components. For instance, you can align one child view on the left side and another view on
+the right side of the screen.
For example:
+ ++<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <TextView + android:id="@+id/label" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Type here:"/> + <EditText + android:id="@+id/entry" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/label"/> + <Button + android:id="@+id/ok" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/entry" + android:layout_alignParentRight="true" + android:layout_marginLeft="10dp" + android:text="OK" /> + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toLeftOf="@id/ok" + android:layout_alignTop="@id/ok" + android:text="Cancel" /> +</RelativeLayout> ++ +
Figure 2 shows how this layout appears on a QVGA screen.
+ +Figure 2. Screenshot on a QVGA screen (small screen).
+ +Figure 3 shows how it appears on a larger screen.
+ +Figure 3. Screenshot on a WSVGA screen (large screen).
+ +Notice that although the size of the components changed, their +spatial relationships are preserved as specified by the {@link +android.widget.RelativeLayout.LayoutParams}.
+ + +There's only so much mileage you can get from a flexible layout or relative layout +like the one in the previous sections. While those layouts adapt to +different screens by stretching the space within and around components, they +may not provide the best user experience for each screen size. Therefore, your +application should not only implement flexible layouts, but should also provide +several alternative layouts to target different screen configurations. You do +so by using configuration qualifiers, which allows the runtime +to automatically select the appropriate resource based on the current device’s +configuration (such as a different layout design for different screen sizes).
+ +For example, many applications implement the "two pane" pattern for large +screens (the app might show a list of items on one pane and the content on +another pane). Tablets and TVs are large enough for both panes to fit +simultaneously on screen, but phone screens have to show them separately. So, +to implement these layouts, you could have the following files:
+ +res/layout/main.xml
, single-pane (default) layout:
+
+{@sample development/samples/training/multiscreen/newsreader/res/layout/onepane.xml all}
+res/layout-xlarge/main.xml
, two-pane layout:
+
+{@sample development/samples/training/multiscreen/newsreader/res/layout/twopanes.xml all}
+Notice the xlarge
qualifier in the directory name of the second layout. This layout
+will be selected on devices with screens classified as extra-large (for example, 10" tablets). The
+other layout (without qualifiers) will be selected for smaller devices.
One of the difficulties developers had in pre-3.2 Android devices was the +"large" screen size bin, which encompasses the Dell Streak, the original Galaxy +Tab, and 7" tablets in general. However, many applications may want to show +different layouts for different devices in this category (such as for 5" and 7" devices), even +though they are all considered to be "large" screens. That's why Android introduced the +"Smallest-width" qualifier (amongst others) in Android 3.2.
+ +The Smallest-width qualifier allows you to target screens that have a certain minimum
+width given in dp. For example, the typical 7" tablet has a minimum width of
+600 dp, so if you want your UI to have two panes on those screens (but a single
+list on smaller screens), you can use the same two layouts from the previous section for single
+and two-pane layouts, but instead of the xlarge
size qualifier, use
+sw600dp
to indicate the two-pane layout is for screens on which the smallest-width
+is 600 dp:
res/layout/main.xml
, single-pane (default) layout:
+
+{@sample development/samples/training/multiscreen/newsreader/res/layout/onepane.xml all}
+res/layout-sw600dp/main.xml
, two-pane layout:
+
+{@sample development/samples/training/multiscreen/newsreader/res/layout/twopanes.xml all}
+This means that devices whose smallest width is greater than or equal to
+600dp will select the layout-sw600dp/main.xml
(two-pane) layout,
+while smaller screens will select the layout/main.xml
(single-pane)
+layout.
However, this won't work well on pre-3.2 devices, because they don't
+recognize sw600dp
as a size qualifier, so you still have to use the xlarge
+qualifier as well. So, you should have a file named
+res/layout-xlarge/main.xml
+which is identical to res/layout-sw600dp/main.xml
. In the next section
+you'll see a technique that allows you to avoid duplicating the layout files this way.
The smallest-width qualifier is available only on Android 3.2 and above. +Therefore, you should also still use the abstract size bins (small, normal, +large and xlarge) to be compatible with earlier versions. For example, if you +want to design your UI so that it shows a single-pane UI on phones but a +multi-pane UI on 7" tablets and larger devices, you'd have to supply these +files:
+ +res/layout/main.xml:
single-pane layoutres/layout-xlarge:
multi-pane layoutres/layout-sw600dp:
multi-pane layoutThe last two files are identical, because one of them will be matched by +Android 3.2 devices, and the other one is for the benefit of tablets with +earlier versions of Android.
+ +To avoid this duplication of the same file for tablets (and the maintenance +headache resulting from it), you can use alias files. For example, you can define the following +layouts:
+ +res/layout/main.xml
, single-pane layoutres/layout/main_twopanes.xml
, two-pane layoutAnd add these two files:
+ +res/values-xlarge/layout.xml
:
++<resources> + <item name="main" type="layout">@layout/main_twopanes</item> +</resources> ++
res/values-sw600dp/layout.xml
:
++<resources> + <item name="main" type="layout">@layout/main_twopanes</item> +</resources> ++ +
These latter two files have identical content, but they don’t actually define
+the layout. They merely set up {@code main} to be an alias to {@code main_twopanes}. Since
+these files have xlarge
and sw600dp
selectors, they are
+applied to tablets regardless of Android version (pre-3.2 tablets match
+{@code xlarge}, and post-3.2 will match sw600dp
).
Some layouts work well in both landscape and portrait orientations, but most of them can +benefit from adjustments. In the News Reader sample app, here is how the layout +behaves in each screen size and orientation:
+ +So each of these layouts is defined in an XML file in the
+res/layout/
directory. To then assign each layout to the various screen
+configurations, the app uses layout aliases to match them to
+each configuration:
res/layout/onepane.xml:
res/layout/onepane_with_bar.xml:
res/layout/twopanes.xml
:
res/layout/twopanes_narrow.xml
:
Now that all possible layouts are defined, it's just a matter of mapping the correct layout to +each configuration using the configuration qualifiers. You can now do it using the layout alias +technique:
+ +res/values/layouts.xml
:
res/values-sw600dp-land/layouts.xml
:
res/values-sw600dp-port/layouts.xml
:
res/values-xlarge-land/layouts.xml
:
res/values-xlarge-port/layouts.xml
:
Supporting different screen sizes usually means that your image resources +must also be capable of adapting to different sizes. For example, a button +background must fit whichever button shape it is applied to.
+ +If you use simple images on components that can change size, you will +quickly notice that the results are somewhat less than impressive, since the +runtime will stretch or shrink your images uniformly. The solution is using nine-patch bitmaps, +which are specially +formatted PNG files that indicate which areas can and cannot be stretched.
+ +Therefore, when designing bitmaps that will be used on components with +variable size, always use nine-patches. To convert a bitmap into a nine-patch, +you can start with a regular image (figure 4, shown with in 4x zoom for clarity).
+ +Figure 4. button.png
And then run it through the draw9patch
utility of the
+SDK (which is located in the tools/
directory), in which you can mark the areas that
+should be stretched by drawing pixels along the left and top borders. You can also mark the area
+that should hold the content by drawing pixels along the right and bottom borders, resulting in
+figure 5.
Figure 5. button.9.png
Notice the black pixels along the borders. The ones on the top and left +borders indicate the places where the image can be stretched, and the ones on +the right and bottom borders indicate where the content should be +placed.
+ +Also, notice the .9.png
extension. You must use this
+extension, since this is how the framework detects that this is a nine-patch
+image, as opposed to a regular PNG image.
When you apply this background to a component (by setting
+android:background="@drawable/button"
), the framework stretches
+the image correctly to accommodate the size of the button, as shown in various sizes in figure
+6.
Figure 6. A button using the button.9.png
+nine-patch in various sizes.