пятница, 17 февраля 2012 г.

Android Training-Проектирование для различных экранов-Поддержка различных размеров экрана

Этот урок покажет вам как поддерживать различные размеры экрана следующим образом:
  • обеспечение возможности изменения разметки в зависимости от размеров экрана.
  • предоставление соотвествующей размеру экрана разметки UI
  • обеспечение применения правильной разметки к правильному экрану
  • предоставление растровых изображений, которые могут быть масштабированы корректно 
Использование "wrap_content" и "match_parent".
Чтобы убедиться в том, что ваша разметка является гибкой и легко адаптируется к экранам различных размеров используйте свойства "wrap_content" и "match_parent" для высоты и ширины некоторых компонентов вида(view). Если вы используете "wrap_content" ширина или высота view-компонента становится минимального размера, необходимого для отображения содержимого, помещаемого в этот компонент, при установке же "match_parent"("fill_parent" до API версии 8) размер компонента изменяется так, чтобы полностью подходить под размер родительского компонента.
При установке размеров с использованием свойств "wrap_content" и "match_parent" вместо жестко заданых значений ваши виды используют только необходимое для их отображения место либо же изменяют свой размер для заполнения всего свободного пространства соответственно.
Пример:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout android:layout_width="match_parent" 
                  android:id="@+id/linearLayout1"  
                  android:gravity="center"
                  android:layout_height="50dp">
        <ImageView android:id="@+id/imageView1" 
                   android:layout_height="wrap_content"
                   android:layout_width="wrap_content"
                   android:src="@drawable/logo"
                   android:paddingRight="30dp"
                   android:layout_gravity="left"
                   android:layout_weight="0" />
        <View android:layout_height="wrap_content" 
              android:id="@+id/view1"
              android:layout_width="wrap_content"
              android:layout_weight="1" />
        <Button android:id="@+id/categorybutton"
                android:background="@drawable/button_bg"
                android:layout_height="match_parent"
                android:layout_weight="0"
                android:layout_width="120dp"
                style="@style/CategoryButtonStyle"/>
    </LinearLayout>

    <fragment android:id="@+id/headlines" 
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="match_parent" />
</LinearLayout>
Обратите внимание как  пример использует свойства "match_content" и "fill_parent" вместо указания конкретных размеров для установки размеров компонентов. Это позволяет разметке корректно адаптироваться под различные размеры и ориентации экрана.
Вот так например эта разметка выглядит в альбомном и портретном режиме. Обратите внимание,что размеры компонентов автоматически адаптируются под длину и высоту.
Рисунок 1. Приложение The News Reader в портретном и альбомном режиме.
Использование RelativeLayout
Вы можете создавать достаточно сложные разметки, используя вложенные экземпляры  LinearLayout  и комбинации "match_content" и "fill_parent". Тем не менее, LinearLayout не позволяет вам точно контролировать пространственные отношения между компонентами вида-детьми(child view), виды в LinearLayout просто выстраиваются в линию бок-к-боку. Если же вам необходимо построение, отличное от прямой линии лучшим решением будет использование   RelativeLayout , это позволит писать разметку в терминах пространственных отношений между различными компонентами. Например, один компонент можно выровнять по левому краю экрана, другой-по правую.
Пример:

<?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>

Рисунок 2. Скриншот QVGA экрана.
Рисунок 3. Скриншот WSVGA экрана.
Обратите внимание, несмотря на то, что размер компонентов изменился, их пространственные отношения сохранились, как указано в RelativeLayout.LayoutParams.
Использование квалификаторов размеров.
Все что вы можете получить от RelativeLayout или LinearLayout описано выше. Несмотря на то, что эти виды разметок адаптируются под размеры экрана растягивая пространство внутри в вокруг компонентов они не обеспечивают идеального взаимодействия с UI для каждого размера экрана. Таким образом, ваше приложение должно не только использовать гибкие разметки, но и предоставлять несколько различных разметок под конкретные размеры экрана. Это достигается при помощи  configuration qualifiers, средства, позволяющего среде выполнения автоматически выбирать соотвествующий ресурс, основанный на текущей конфигурации устройства(как например различные разметки для различных размеров экрана).
Например, многие приложения используют двухпанельный(2 pane) паттерн для больших экранов(приложение может показывать список элементов на одной панеле и содержание на второй).У планшетов и телевизоров экраны позволяют показывать обе панели одновременно, в то время как на телефоне они будут показываться по отдельности. Таким образом, реализация этих разметок может выглядеть так::

  • res/layout/main.xml, разметка с одной панелью
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
      <fragment android:id="@+id/headlines"
              android:layout_height="fill_parent" 
              android:name="com.example.android.newsreader.HeadlinesFragment"          android:layout_width="match_parent" />
    </LinearLayout>
  • res/layout-xlarge/main.xml, разметка с двумя панелями
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="horizontal">
        <fragment android:id="@+id/headlines"
                  android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
                  android:layout_width="400dp"
                  android:layout_marginRight="10dp"/>
        <fragment android:id="@+id/article"
                  android:layout_height="fill_parent"
                android:name="com.example.android.newsreader.ArticleFragment"
                  android:layout_width="fill_parent" />
    </LinearLayout>
Обратите внимание на xlarge-квалификатор в имени директории второй разметки. Эта разметка будет выбрана на устройствах с экранами, классифицируемыми как extra-large-например 10" планшетах. Другая раскладка(без квалификаторов) будет использоваться для меньших устройств.
Использование квалификатора наименьшей ширины.
Одной из трудностей, с которой разработчики сталкивались, работая с устройствами управляемыми Android версии ниже 3.2 был размер экрана "large" который представлял собой экраны Dell Streak, оригинальный Samsung Galaxy Tab и другие 7-дюймовые планшеты. Тем не менее в достаточном количестве приложений требуется возможность иметь различные разметки для различных устройств (например для 5- и 7-дюймовых устройств) имеющих один тип экрана "large". Для этих целей в Android 3.2 был представлен квалификатор  "Smallest-width" -наименьшая ширина.
Этот квалификатор позволяет вам нацеливаться на экраны с минимальной шириной указанной в dp. Например, ширина типичного 7-дюймового планшета обычно минимум 600dp, и если вы хотите двухпанельный UI на этих экранах, и однопанельный на меньших, вы можете использовать 2 разметки из предыдущей секции, но заменить квалификатор xlarge на sw600dp для 2панельной разметки. Это будет означать, что устройства, имеющие ширину большую или равную 600dp будет использовать 2х панельную разметку, устройства с меньшей шириной экрана-простую однопанельную. Обратите внимание, что эта возможность появилась только в
Android версии 3.2, поэтому для устройств с более старой версией ОС вам придется иметь копию данной разметки но с квалификаторм xlarge вместо sw600dp. В следующей секции мы покажем как избежать дублирования файлов разметок.
Использование псевдонимов(aliase) разметок.
Квалификатор наименьшей ширины доступен только в версиях Android 3.2 и выше. Таким образом вы по прежнему должны использовать абстрактные размеры   (small, normal, large и xlarge) для совместимости с  ранними версиями. Для примера, если вам нужно сделать UI, который будет показан как однопанельный на телефонах и многопанельный на 7-дюймовых планшетах и бОльших устройствах вам необходимо включить следующие файлы:

res/layout/main.xml: одно-панельная разметка
res/layout-xlarge: много-панельная разметка
res/layout-sw600dp: много-панельная разметка
Последние 2 файла одинаковы, один из них подходит для устройств с Android 3.2 и другой для планшетов с ранними версиями Android. Чтобы избежать дублирования одинаковых файлов и головной боли, связанной с ними вы можете использовать псевдонимы файлов. Для примера, вы можете определить следующие разметки:
res/layout/main.xml, однопанельная разметка
res/layout/main_twopanes.xml, двупанельная разметка
И добавить эти 2 файла:
  • 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>
Эти 2 файла имеют похожее содержание, но они не являются разметками. Они определяют main как псевдоним для разметки main_twopanes. Так как у этих файлов есть квалификаторы xlarge и sw600dp они будут применяться к планшетам в зависимости от версии Android.

Использование квалификаторов ориентации.

Некоторые разметки работают отлично, как в портретной, так и в альбомной ориентации но большинство из них может получить преимущества используя подстройку под ориентацию. В нашем приложении-примере News Reader, разметки подстроены под размер и ориентацию экрана следующим образом:
  • маленький экран, портретный режим: однопанельная разметка с логотипом
  • маленький экран, альбомный режим: однопанельная разметка с логотипом 
  • 7" планшет, портретный режим: однопанельная разметка с панелью кнопок
  • 7" планшет, альбомный режим: двупанельная разметка, широкая, с панелью кнопок 
  • 10" планшет, портретный режим: двупанельная разметка,узкая, с панелью кнопок 
  • 10" планшет, альбомный режим: двупанельная разметка, широкая, с панелью кнопок 
Таким образом каждая из этих разметок определена в XML файле в папке res/layout/. Для дальнейшего назначения каждой разметки различным конфигурация экрана приложение использует псевдонимы разметок:
res/layout/onepane.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment android:id="@+id/headlines"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="match_parent" />
</LinearLayout>
res/layout/onepane_with_bar.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout android:layout_width="match_parent" 
                  android:id="@+id/linearLayout1"  
                  android:gravity="center"
                  android:layout_height="50dp">
        <ImageView android:id="@+id/imageView1" 
                   android:layout_height="wrap_content"
                   android:layout_width="wrap_content"
                   android:src="@drawable/logo"
                   android:paddingRight="30dp"
                   android:layout_gravity="left"
                   android:layout_weight="0" />
        <View android:layout_height="wrap_content" 
              android:id="@+id/view1"
              android:layout_width="wrap_content"
              android:layout_weight="1" />
        <Button android:id="@+id/categorybutton"
                android:background="@drawable/button_bg"
                android:layout_height="match_parent"
                android:layout_weight="0"
                android:layout_width="120dp"
                style="@style/CategoryButtonStyle"/>
    </LinearLayout>

    <fragment android:id="@+id/headlines" 
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="match_parent" />
</LinearLayout>
res/layout/twopanes.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal">
    <fragment android:id="@+id/headlines"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="400dp"
              android:layout_marginRight="10dp"/>
    <fragment android:id="@+id/article"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.ArticleFragment"
              android:layout_width="fill_parent" />
</LinearLayout>
res/layout/twopanes_narrow.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal">
    <fragment android:id="@+id/headlines"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="200dp"
              android:layout_marginRight="10dp"/>
    <fragment android:id="@+id/article"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.ArticleFragment"
              android:layout_width="fill_parent" />
</LinearLayout>
Тепер, после определения всех возможных разметок остается правильно сопоставить корректную разметку каждой конфигурации использую квалификаторы конфигурации. Вы сейчас можете сделать это используя технику псевдонимов разметок:
res/values/layouts.xml:
<resources>
    <item name="main_layout" type="layout">@layout/onepane_with_bar</item>
    <bool name="has_two_panes">false</bool>
</resources>
res/values-sw600dp-land/layouts.xml:
<resources>
    <item name="main_layout" type="layout">@layout/twopanes</item>
    <bool name="has_two_panes">true</bool>
</resources>
res/values-sw600dp-port/layouts.xml:
<resources>
    <item name="main_layout" type="layout">@layout/onepane</item>
    <bool name="has_two_panes">false</bool>
</resources>
res/values-xlarge-land/layouts.xml:
<resources>
    <item name="main_layout" type="layout">@layout/twopanes</item>
    <bool name="has_two_panes">true</bool>
</resources>
res/values-xlarge-port/layouts.xml:
<resources>
    <item name="main_layout" type="layout">@layout/twopanes_narrow</item>
    <bool name="has_two_panes">true</bool>
</resources>
Использование nine-patch растровых изображений.


Использование различных размеров экрана обычно означает что ваши ресурсы-изображения также должны адаптироваться к различным размерам. Например фоновое изображение для кнопки должно подстраиваться под размер кнопки.

Если вы будете использовать обычные изображения для компонентов, которые могут менять свой размер, результат не произведет на вас впечатление, т.к. среда выполнения будет сжимать или растягивать изображение равномерно. Решение заключается в использовании nine-patch bitmap изображений, которые представляют собой специально форматированные PNG файлы с областями, которые указаны как такие, что не могут быть растянуты.

Таким образом, при создании изображений которые будут использоваться на компонентах с переменным размером всегда используйте nine-patch. Для преобразования bitmap в a nine-patch, начнем с обычной картинки(на рисунке использутеся 4х кратное увеличение для показа деталей):





Рисунок 4. button.png.
Теперь запускаем утилиту draw9patch поставляемую в комплекте с SDK ( папка tools/ ), в которой выделяем область, разрешеную к растягиванию и область, которая должна сохранять содержание:







Рисунок 5. button.9.png

Обратите внимание на черные пиксели на границах. Те, что сверху слева показывают места, в которых изображение может быть растянуто, те что справа и внизу показывают границы где содержимое должно быть размещено.

Также обратите внимание на .9.png расширение. Его необходимо использовать для различия nine-patch изображений от обычных PNG.

Когда вы применяете это фоновое изображение к компоненту( установкой android:background="@drawable/button"), среда растягивает изображение корректно, приспосабливая его к размеру кнопки как показано на рисунке 6.




Рисунок 6. Кнопка, использующая nine-patch изображение в различных размерах.
Оригинальный материал доступен по следующему адресу.

Комментариев нет:

Отправить комментарий