Если вам нужно показывать в приложении статические изображения, используйте класс Drawable и его подклассы для рисования фигур и изображений. Drawable – это общее абстрактное понятие, обозначающее объект, который можно нарисовать. Различные подклассы помогают в определенных сценариях работы с изображениями, и вы можете расширить их, чтобы определить собственные объекты Drawable, которые будут вести себя уникальным образом.
Помимо конструкторов классов, определить и создать экземпляр Drawable можно двумя способами:
- Развернуть ресурс изображения (файл растрового изображения), сохраненный в проекте.
- Разверните XML-ресурс, определяющий свойства объекта drawable.
Примечание. Вместо этого можно использовать класс VectorDrawable, который определяет изображение с помощью набора точек, линий и кривых, а также связанной с ними информации о цвете. Это позволяет масштабировать векторные изображения без потери качества. Подробнее о векторных изображениях…
Как создавать объекты Drawable на основе изображений из ресурсов
Вы можете добавить в приложение графику, указав ссылку на файл изображения в ресурсах проекта. Поддерживаемые форматы файлов: PNG (предпочтительный), JPG (допустимый) и GIF (не рекомендуется). Этот метод хорошо подходит для значков приложений, логотипов и других изображений, например используемых в играх.
Чтобы использовать ресурс изображения, добавьте файл в каталог res/drawable/ проекта. После этого вы сможете ссылаться на ресурс изображения в коде или XML-макете. В любом случае к нему можно обратиться по идентификатору ресурса – названию файла без расширения. Например, my_image.png нужно отформатировать как my_image.
Примечание. Ресурсы изображений, размещенные в каталоге res/drawable/, могут быть автоматически оптимизированы с помощью сжатия без потерь инструментом aapt во время сборки. Например, полноцветный PNG-файл, для которого достаточно 256 цветов, может быть преобразован в 8-битный PNG-файл с цветовой палитрой. В результате получается изображение того же качества, но занимающее меньше места в памяти. В результате двоичные файлы изображений, помещенные в этот каталог, могут измениться во время сборки. Если вы планируете читать изображение как поток битов, чтобы преобразовать его в растровое изображение, поместите его в папку res/raw/, где инструмент aapt не будет его изменять.
В приведенном ниже фрагменте кода показано, как создать объект ImageView, который использует изображение, созданное на основе ресурса drawable, и добавить его в макет.
Kotlin
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, как показано в следующем примере:
Kotlin
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" />
Подробнее о том, как использовать ресурсы проекта…
Примечание. Если вы используете ресурсы изображений в качестве источника объектов, убедитесь, что изображения имеют подходящий размер для разных плотностей пикселей. Если изображения не соответствуют требованиям, они будут масштабированы, что может привести к появлению артефактов. Подробнее о том, как поддерживать экраны с разной плотностью пикселей…
Как создавать объекты drawable из 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:
Kotlin
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(), как показано в следующем примере кода:
Kotlin
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 в приведенном выше примере кода можно использовать так же, как и любое другое специальное представление. Например, вы можете добавить его в действие в своем приложении программным способом, как показано в следующем примере:
Kotlin
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. Подробнее о фигурах в разделе Типы ресурсов для изображений…
Изображения NinePatch
Графический объект NinePatchDrawable представляет собой растягиваемое растровое изображение, которое можно использовать в качестве фона представления. Android автоматически изменяет размер графики, чтобы она соответствовала содержимому представления. Пример использования изображения NinePatch – фон стандартных кнопок Android. Кнопки должны растягиваться, чтобы вмещать строки разной длины. Графика NinePatch – это стандартное изображение PNG с дополнительной границей шириной в один пиксель.
Его нужно сохранить с расширением 9.png в каталоге res/drawable/ проекта.
Используйте рамку, чтобы определить растягиваемые и статические области изображения. Чтобы указать растягиваемую область, нарисуйте одну или несколько черных линий шириной в один пиксель в левой и верхней части границы (остальные пиксели границы должны быть полностью прозрачными или белыми). Количество растягиваемых разделов не ограничено. Относительный размер растягиваемых частей не меняется, поэтому самая большая часть всегда остается самой большой.
Вы также можете определить необязательную область изображения, в которой можно рисовать (фактически, линии отступов), нарисовав линию справа и линию внизу. Если объект View задает графический элемент NinePatch в качестве фона, а затем указывает текст представления, он растягивается так, чтобы весь текст занимал только область, ограниченную правой и нижней линиями (если они есть).
Если линии отступов не указаны, Android использует левую и верхнюю линии, чтобы определить область отрисовки.
Чтобы понять разницу между этими линиями, представьте, что левая и верхняя линии определяют, какие пиксели изображения можно копировать, чтобы растянуть изображение. Нижняя и правая линии определяют относительную область изображения, которую может занимать контент представления.
На рисунке 1 показан пример графического объекта NinePatch, используемого для определения кнопки:
Рис. 1. Пример графического объекта NinePatch, определяющего кнопку
На этом изображении NinePatch определена одна растягиваемая область (левая и верхняя линии) и одна область для рисования (нижняя и правая линии). На верхнем изображении пунктирными серыми линиями обозначены области, которые были скопированы, чтобы растянуть изображение. Розовый прямоугольник на нижнем изображении обозначает область, в которой разрешено показывать содержимое. Если контент не помещается в этой области, изображение растягивается.
Инструмент Draw 9-patch позволяет создавать изображения 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
Чтобы создать собственные рисунки, можно расширить класс Drawable или любой из его подклассов.
Самый важный метод – draw(Canvas), поскольку он предоставляет объект Canvas, который необходимо использовать для передачи инструкций по рисованию.
В приведенном ниже фрагменте кода показан простой подкласс Drawable, который рисует круг:
Kotlin
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; } }
После этого вы можете добавить изображение в любое место, например в элемент ImageView, как показано ниже.
Kotlin
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) и более поздних версиях вы также можете определять экземпляры собственного объекта Drawable с помощью 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, который позволяет извлекать основные цвета из изображения.
Вы можете загрузить объекты Drawable как Bitmap и передать их в Palette, чтобы получить доступ к их цветам.
Подробнее о том, как выбирать цвета с помощью Palette API…