Обзор чертежей

Попробуйте способ создания композиций.
Jetpack Compose — это рекомендуемый набор инструментов для создания пользовательского интерфейса для Android. Узнайте, как отображать графику в Compose.

Когда вам нужно отображать статические изображения в вашем приложении, вы можете использовать класс Drawable и его подклассы для рисования фигур и изображений. Drawable — это общая абстракция для чего-либо, что можно нарисовать . Различные подклассы помогают в конкретных сценариях работы с изображениями, и вы можете расширять их, чтобы определять свои собственные объекты Drawable, которые ведут себя уникальным образом.

Помимо использования конструкторов классов, существует два способа определения и создания экземпляра Drawable :

  • Создайте файл изображения (растровый файл), сохраненный в вашем проекте.
  • Создайте XML-ресурс, определяющий свойства, позволяющие отображать данные.

Примечание: Возможно, вам больше подойдет векторный рисунок, который определяет изображение с помощью набора точек, линий и кривых, а также соответствующей информации о цвете. Это позволяет масштабировать векторные рисунки для разных размеров без потери качества. Для получения дополнительной информации см. Обзор векторных рисунков .

Создавайте изображения, которые можно накладывать на ресурсы, используя изображения из исходных файлов.

Вы можете добавить графику в свое приложение, используя в качестве ссылки файл изображения из ресурсов проекта. Поддерживаемые типы файлов: PNG (предпочтительно), JPG (допустимо) и GIF (не рекомендуется). Значки приложений, логотипы и другая графика, например, используемая в играх, хорошо подходят для этого метода.

Чтобы использовать графический ресурс, добавьте файл в каталог res/drawable/ вашего проекта. После добавления файла в проект вы сможете ссылаться на него из кода или XML-макета. В любом случае, ссылка осуществляется с помощью идентификатора ресурса (resource ID), который представляет собой имя файла без расширения типа файла. Например, для ссылки на my_image.png используйте my_image .

Примечание: Изображения, размещенные в каталоге res/drawable/ могут быть автоматически оптимизированы с помощью сжатия без потерь инструментом aapt в процессе сборки. Например, полноцветный PNG-файл, не требующий более 256 цветов, может быть преобразован в 8-битный PNG-файл с цветовой палитрой. Это приводит к получению изображения того же качества, но требующего меньше памяти. В результате, бинарные файлы изображений, размещенные в этом каталоге, могут изменяться во время сборки. Если вы планируете считывать изображение как битовый поток для преобразования его в растровое изображение, поместите изображения в папку res/raw/ , где инструмент aapt их не будет изменять.

Приведённый ниже фрагмент кода демонстрирует, как создать ImageView , который использует изображение, созданное из ресурса drawable, и добавляет его в макет:

Котлин

private lateinit var constraintLayout: ConstraintLayout

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // Instantiate an ImageView and define its properties
    val i = ImageView(this).apply {
        setImageResource(R.drawable.my_image)
        contentDescription = resources.getString(R.string.my_image_desc)

        // set the ImageView bounds to match the Drawable's dimensions
        adjustViewBounds = true
        layoutParams = ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT)
    }

    // Create a ConstraintLayout in which to add the ImageView
    constraintLayout = ConstraintLayout(this).apply {

        // Add the ImageView to the layout.
        addView(i)
    }

    // Set the layout as the content view.
    setContentView(constraintLayout)
}

Java

ConstraintLayout constraintLayout;

protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

  // Create a ConstraintLayout in which to add the ImageView
  constraintLayout = new ConstraintLayout(this);

  // Instantiate an ImageView and define its properties
  ImageView i = new ImageView(this);
  i.setImageResource(R.drawable.my_image);
  i.setContentDescription(getResources().getString(R.string.my_image_desc));

  // set the ImageView bounds to match the Drawable's dimensions
  i.setAdjustViewBounds(true);
  i.setLayoutParams(new ViewGroup.LayoutParams(
          ViewGroup.LayoutParams.WRAP_CONTENT,
          ViewGroup.LayoutParams.WRAP_CONTENT));

  // Add the ImageView to the layout and set the layout as the content view.
  constraintLayout.addView(i);
  setContentView(constraintLayout);
}

В других случаях может потребоваться обрабатывать изображение как объект Drawable , как показано в следующем примере:

Котлин

val myImage: Drawable = ResourcesCompat.getDrawable(context.resources, R.drawable.my_image, null)

Java

Resources res = context.getResources();
Drawable myImage = ResourcesCompat.getDrawable(res, R.drawable.my_image, null);

Внимание: каждый уникальный ресурс в вашем проекте может поддерживать только одно состояние, независимо от того, сколько различных объектов вы для него создаёте. Например, если вы создадите два объекта Drawable из одного и того же ресурса изображения и измените свойство (например, альфа-канал) для одного объекта, это также повлияет на другой. При работе с несколькими экземплярами ресурса изображения вместо прямого преобразования объекта Drawable следует выполнять анимацию с помощью твина .

Приведённый ниже фрагмент XML-кода показывает, как добавить ресурс drawable в ImageView в XML-макете:

<ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/my_image"
        android:contentDescription="@string/my_image_desc" />

Для получения более подробной информации об использовании ресурсов проекта см. раздел «Ресурсы и активы» .

Примечание: При использовании графических ресурсов в качестве источника изображений убедитесь, что изображения имеют соответствующий размер для различных плотностей пикселей. Если изображения некорректны, они будут масштабированы для соответствия размеру, что может привести к появлению артефактов в ваших изображениях. Для получения дополнительной информации см. раздел «Поддержка различных плотностей пикселей» .

Создание графических элементов на основе XML-ресурсов

Если вам нужно создать объект Drawable , который изначально не зависит от переменных, определенных вашим кодом или взаимодействием с пользователем, то определение объекта Drawable в XML — хороший вариант. Даже если вы ожидаете, что свойства вашего Drawable будут изменяться во время взаимодействия пользователя с вашим приложением, вам следует рассмотреть возможность определения объекта в XML, поскольку вы можете изменять свойства после того, как объект будет создан.

После того, как вы определили свой Drawable в XML, сохраните файл в каталоге res/drawable/ вашего проекта. В следующем примере показан XML-код, определяющий ресурс TransitionDrawable , который наследует от Drawable :

<!-- res/drawable/expand_collapse.xml -->
<transition xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/image_expand"/>
    <item android:drawable="@drawable/image_collapse"/>
</transition>

Затем получите и создайте экземпляр объекта, вызвав Resources#getDrawable() и передав идентификатор ресурса из вашего XML-файла. Любой подкласс Drawable , поддерживающий метод inflate() может быть определен в XML и создан вашим приложением.

Каждый класс drawable, поддерживающий XML-инфляцию, использует определенные XML-атрибуты, которые помогают определить свойства объекта. Следующий код создает экземпляр TransitionDrawable и устанавливает его в качестве содержимого объекта ImageView :

Котлин

val transition= ResourcesCompat.getDrawable(
        context.resources,
        R.drawable.expand_collapse,
        null
) as TransitionDrawable

val image: ImageView = findViewById(R.id.toggle_image)
image.setImageDrawable(transition)

// Description of the initial state that the drawable represents.
image.contentDescription = resources.getString(R.string.collapsed)

// Then you can call the TransitionDrawable object's methods.
transition.startTransition(1000)

// After the transition is complete, change the image's content description
// to reflect the new state.

Java

Resources res = context.getResources();
TransitionDrawable transition =
    (TransitionDrawable) ResourcesCompat.getDrawable(res, R.drawable.expand_collapse, null);

ImageView image = (ImageView) findViewById(R.id.toggle_image);
image.setImageDrawable(transition);

// Description of the initial state that the drawable represents.
image.setContentDescription(getResources().getString(R.string.collapsed));

// Then you can call the TransitionDrawable object's methods.
transition.startTransition(1000);

// After the transition is complete, change the image's content description
// to reflect the new state.

Для получения более подробной информации о поддерживаемых XML-атрибутах обратитесь к классам, перечисленным выше.

Рисунки в форме

Объект ShapeDrawable может быть хорошим вариантом, если вам нужно динамически рисовать двухмерную графику. Вы можете программно рисовать примитивные фигуры на объекте ShapeDrawable и применять стили, необходимые вашему приложению.

ShapeDrawable является подклассом Drawable . Поэтому вы можете использовать ShapeDrawable везде, где ожидается Drawable . Например, вы можете использовать объект ShapeDrawable для установки фона представления, передав его методу setBackgroundDrawable() этого представления. Вы также можете нарисовать свою фигуру как собственное пользовательское представление и добавить его в макет вашего приложения.

Поскольку ShapeDrawable имеет собственный метод draw() , вы можете создать подкласс View , который будет рисовать объект ShapeDrawable во время события onDraw() , как показано в следующем примере кода:

Котлин

class CustomDrawableView(context: Context) : View(context) {
    private val drawable: ShapeDrawable = run {
        val x = 10
        val y = 10
        val width = 300
        val height = 50
        contentDescription = context.resources.getString(R.string.my_view_desc)

        ShapeDrawable(OvalShape()).apply {
            // If the color isn't set, the shape uses black as the default.
            paint.color = 0xff74AC23.toInt()
            // If the bounds aren't set, the shape can't be drawn.
            setBounds(x, y, x + width, y + height)
        }
    }

    override fun onDraw(canvas: Canvas) {
        drawable.draw(canvas)
    }
}

Java

public class CustomDrawableView extends View {
  private ShapeDrawable drawable;

  public CustomDrawableView(Context context) {
    super(context);

    int x = 10;
    int y = 10;
    int width = 300;
    int height = 50;
    setContentDescription(context.getResources().getString(
            R.string.my_view_desc));

    drawable = new ShapeDrawable(new OvalShape());
    // If the color isn't set, the shape uses black as the default.
    drawable.getPaint().setColor(0xff74AC23);
    // If the bounds aren't set, the shape can't be drawn.
    drawable.setBounds(x, y, x + width, y + height);
  }

  protected void onDraw(Canvas canvas) {
    drawable.draw(canvas);
  }
}

В приведенном выше примере кода вы можете использовать класс CustomDrawableView так же, как и любой другой пользовательский вид. Например, вы можете программно добавить его в активность вашего приложения, как показано в следующем примере:

Котлин

private lateinit var customDrawableView: CustomDrawableView

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    customDrawableView = CustomDrawableView(this)

    setContentView(customDrawableView)
}

Java

CustomDrawableView customDrawableView;

protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  customDrawableView = new CustomDrawableView(this);

  setContentView(customDrawableView);
}

Если вы хотите использовать пользовательское представление в XML-макете, то класс CustomDrawableView должен переопределить конструктор View(Context, AttributeSet) , который вызывается при создании класса из XML. В следующем примере показано, как объявить CustomDrawableView в XML-макете:

<com.example.shapedrawable.CustomDrawableView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        />

Класс ShapeDrawable , как и многие другие типы данных из пакета android.graphics.drawable , позволяет определять различные свойства объекта с помощью открытых методов. В качестве примеров свойств, которые вы можете захотеть настроить, можно привести альфа-канал, цветовой фильтр, дизеринг, непрозрачность и цвет.

Также можно определять примитивные фигуры, отображаемые в виде изображений, используя XML-ресурсы. Дополнительную информацию см. в разделе « Фигура, отображаемая в виде изображений» в описании типов ресурсов Drawable .

NinePatch drawables

Графический объект NinePatchDrawable — это растягиваемое растровое изображение, которое можно использовать в качестве фона для представления. Android автоматически изменяет размер изображения, чтобы оно соответствовало содержимому представления. Примером использования изображения NinePatch является фон стандартных кнопок Android — кнопки должны растягиваться, чтобы вмещать строки различной длины. Графический объект NinePatch — это стандартное изображение PNG, которое включает дополнительную рамку в 1 пиксель. Его необходимо сохранить с расширением 9.png в каталоге res/drawable/ вашего проекта.

Используйте рамку для определения растягиваемых и статических областей изображения. Растягиваемая область обозначается одной (или несколькими) черными линиями шириной в 1 пиксель в левой и верхней части рамки (остальные пиксели рамки должны быть полностью прозрачными или белыми). Вы можете создать любое количество растягиваемых областей. Относительный размер растягиваемых областей остается неизменным, поэтому самая большая область всегда остается самой большой.

Также можно определить необязательную область изображения, доступную для отрисовки (фактически, отступы), нарисовав линию справа и линию снизу. Если объект View устанавливает графический элемент NinePatch в качестве фона, а затем указывает текст для этого объекта, он растягивается таким образом, чтобы весь текст занимал только область, обозначенную правой и нижней линиями (если они присутствуют). Если отступы не включены, Android использует левую и верхнюю линии для определения этой области, доступной для отрисовки.

Для наглядности, левая и верхняя линии определяют, какие пиксели изображения разрешено дублировать для растягивания изображения. Нижняя и правая линии определяют относительную площадь внутри изображения, которую может занимать содержимое области просмотра.

На рисунке 1 показан пример графического элемента NinePatch, используемого для определения кнопки:

Изображение растягивающейся области и коробки с подкладкой

Рисунок 1: Пример графического элемента NinePatch, определяющего кнопку.

Эта графическая схема NinePatch определяет одну растягиваемую область с помощью левой и верхней линий, и область для рисования с помощью нижней и правой линий. На верхнем изображении пунктирные серые линии обозначают области изображения, которые дублируются для растягивания изображения. Розовый прямоугольник на нижнем изображении обозначает область, в которой разрешено размещать содержимое. Если содержимое не помещается в эту область, изображение растягивается, чтобы оно поместилось.

Инструмент «Нарисовать 9-патч» предлагает чрезвычайно удобный способ создания изображений NinePatch с помощью графического редактора WYSIWYG. Он даже выдает предупреждения, если область, определенная для растягиваемой области, подвержена риску появления артефактов отрисовки в результате дублирования пикселей.

Приведенный ниже пример XML-макета демонстрирует, как добавить графическое изображение NinePatch к нескольким кнопкам. Изображение NinePatch сохраняется в res/drawable/my_button_background.9.png .

<Button android:id="@+id/tiny"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerInParent="true"
        android:text="Tiny"
        android:textSize="8sp"
        android:background="@drawable/my_button_background"/>

<Button android:id="@+id/big"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerInParent="true"
        android:text="Biiiiiiig text!"
        android:textSize="30sp"
        android:background="@drawable/my_button_background"/>

Обратите внимание, что атрибуты layout_width и layout_height установлены в wrap_content , чтобы кнопка аккуратно облегала текст.

На рисунке 2 показаны две кнопки, созданные на основе XML-файла и изображения NinePatch, представленных выше. Обратите внимание, как ширина и высота кнопки меняются в зависимости от текста, а фоновое изображение растягивается, чтобы вместить его.

Изображение крошечных и кнопок обычного размера

Рисунок 2: Кнопки, отображаемые с использованием XML-ресурса и графического элемента NinePatch.

Пользовательские графические элементы

Если вы хотите создать собственные рисунки, вы можете сделать это, расширив класс Drawable (или любой из его подклассов).

Наиболее важный метод для реализации — это draw(Canvas) поскольку он предоставляет объект Canvas , который необходимо использовать для передачи инструкций рисования.

Следующий код демонстрирует простой подкласс класса Drawable , который рисует круг:

Котлин

class MyDrawable : Drawable() {
    private val redPaint: Paint = Paint().apply { setARGB(255, 255, 0, 0) }

    override fun draw(canvas: Canvas) {
        // Get the drawable's bounds
        val width: Int = bounds.width()
        val height: Int = bounds.height()
        val radius: Float = Math.min(width, height).toFloat() / 2f

        // Draw a red circle in the center
        canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, redPaint)
    }

    override fun setAlpha(alpha: Int) {
        // This method is required
    }

    override fun setColorFilter(colorFilter: ColorFilter?) {
        // This method is required
    }

    override fun getOpacity(): Int =
        // Must be PixelFormat.UNKNOWN, TRANSLUCENT, TRANSPARENT, or OPAQUE
        PixelFormat.OPAQUE
}

Java

public class MyDrawable extends Drawable {
    private final Paint redPaint;

    public MyDrawable() {
        // Set up color and text size
        redPaint = new Paint();
        redPaint.setARGB(255, 255, 0, 0);
    }

    @Override
    public void draw(Canvas canvas) {
        // Get the drawable's bounds
        int width = getBounds().width();
        int height = getBounds().height();
        float radius = Math.min(width, height) / 2;

        // Draw a red circle in the center
        canvas.drawCircle(width/2, height/2, radius, redPaint);
    }

    @Override
    public void setAlpha(int alpha) {
        // This method is required
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        // This method is required
    }

    @Override
    public int getOpacity() {
        // Must be PixelFormat.UNKNOWN, TRANSLUCENT, TRANSPARENT, or OPAQUE
        return PixelFormat.OPAQUE;
    }
}

Затем вы можете добавить свой ресурс drawable куда угодно, например, в ImageView , как показано здесь:

Котлин

val myDrawing = MyDrawable()
val image: ImageView = findViewById(R.id.imageView)
image.setImageDrawable(myDrawing)
image.contentDescription = resources.getString(R.string.my_image_desc)

Java

MyDrawable mydrawing = new MyDrawable();
ImageView image = findViewById(R.id.imageView);
image.setImageDrawable(mydrawing);
image.setContentDescription(getResources().getString(R.string.my_image_desc));

В Android 7.0 (уровень API 24) и выше вы также можете определять экземпляры вашего пользовательского изображения с помощью XML следующими способами:

  • В качестве имени XML-элемента используется полное имя класса. При таком подходе пользовательский класс drawable должен быть открытым классом верхнего уровня:
    <com.myapp.MyDrawable xmlns:android="http://schemas.android.com/apk/res/android"
        android:color="#ffff0000" />
  • Использование drawable в качестве имени XML-тега и указание полного имени класса из атрибута `class`. Этот подход может использоваться как для открытых классов верхнего уровня, так и для открытых статических внутренних классов:
    <drawable xmlns:android="http://schemas.android.com/apk/res/android"
        class="com.myapp.MyTopLevelClass$MyDrawable"
        android:color="#ffff0000" />

Добавить оттенок к изображениям

Начиная с Android 5.0 (уровень API 21) и выше, вы можете изменять цвет растровых изображений и девятицветных патчей, определенных как альфа-маски. Вы можете изменять их цвет с помощью цветовых ресурсов или атрибутов темы, которые преобразуются в цветовые ресурсы (например, ?android:attr/colorPrimary ). Обычно вы создаете эти ресурсы только один раз, и они автоматически окрашиваются в соответствии с вашей темой.

Вы можете применить оттенок к объектам BitmapDrawable , NinePatchDrawable или VectorDrawable с помощью метода setTint() . Вы также можете установить цвет и режим оттенка в своих макетах с помощью атрибутов android:tint и android:tintMode .

Извлечение основных цветов из изображения

Библиотека поддержки Android включает класс Palette , который позволяет извлекать основные цвета из изображения. Вы можете загружать свои изображения в виде Bitmap и передавать его в Palette для доступа к их цветам. Для получения дополнительной информации см. раздел « Выбор цветов с помощью API Palette» .