В этом уроке:
- изучаем подклассы Shader
Используя подклассы класса Shader мы получаем возможность "рисовать рисунком". Для этого необходимо передать объект Shader в метод кисти setShader и кисть будет использовать рисунок шейдера для рисования объектов. Рассмотрим существующих наследников класса Shader.
Примеры я буду делать в Activity. Вся графика будет реализована в классе DrawView.
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new DrawView(this)); } class DrawView extends View { public DrawView(Context context) { super(context); } @Override protected void onDraw(Canvas canvas) { canvas.drawARGB(80, 102, 204, 255); } } }
Для создания этого шейдера необходимо передать ему Bitmap и указать вертикальный и горизонтальный TileMode. Подробно про режимы TileMode можно посмотреть в Уроке 163.
Перепишем класс DrawView:
class DrawView extends View { Paint paint; Bitmap bitmap; Bitmap scaledBitmap; public DrawView(Context context) { super(context); Shader shader = createShader(); paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setShader(shader); } @Override protected void onDraw(Canvas canvas) { canvas.drawARGB(80, 102, 204, 255); canvas.drawRect(100, 100, 400, 300, paint); canvas.drawCircle(300, 400, 100, paint); } private Shader createShader() { bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher); BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); return shader; } }
Читаем ic_launcher в Bitmap, создаем шейдер и передаем этот шейдер в метод кисти setShader. И используя эту кисть рисуем квадрат и круг.
Мы использовали TileMode.REPEAT, поэтому изображение повторяется по всему доступному пространству.
К шейдеру можно применить преобразования, используя матрицу (о матрицах был Урок 144).
Перепишем метод createShader:
private Shader createShader() { bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher); BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); Matrix matrix = new Matrix(); matrix.postScale(2, 1.5f); matrix.postRotate(45); shader.setLocalMatrix(matrix); return shader; }
В матрице настраиваем растяжение и поворот и передаем ее в шейдер методом setLocalMatrix. Смотрим результат
Можно растянуть Bitmap на всю канву
private Shader createShader() { bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher); bitmap = Bitmap.createScaledBitmap(bitmap, 480, 680, true); BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); return shader; }
Методом createScaledBitmap создаем копию bitmap размером с канву.
Этот шейдер позволяет нам получить градиент. У его класса есть два конструктора. Оба просят от нас указать им TileMode и координаты линии, которая будет задавать одновременно направление, начало и размер градиента.
Разница заключается в способе указания цветов для градиента. Один конструктор просит от нас указать ему два цвета. По ним он и нарисует градиент.
private Shader createShader() { LinearGradient shader = new LinearGradient(0, 0, 100, 20, Color.RED, Color.GREEN, Shader.TileMode.MIRROR); return shader; }
Указываем красный и зеленый цвета. Линию указываем (0,0)-(100,20). Градиент будет идти в направлении линии и будет размером с длину этой линии. В качестве TileMode передаем MIRROR.
Другой конструктор позволяет задать массив цветов и их положений в градиенте.
private Shader createShader() { LinearGradient shader = new LinearGradient(0, 0, 100, 20, new int[] { Color.RED, Color.BLUE, Color.GREEN }, null, Shader.TileMode.MIRROR); return shader; }
Передаем массив цветов, а вместо массива позиций передаем null – градиент сам равномерно распределит цвета..
Теперь попробуем задать позиции вручную.
private Shader createShader() { LinearGradient shader = new LinearGradient(120, 0, 380, 0, new int[] { Color.RED, Color.BLUE, Color.GREEN }, new float[] { 0f, 0.5f, 1f }, Shader.TileMode.REPEAT); return shader; }
Обратите внимание, что я здесь использовал TileMode REPEAT. В таком режиме лучше будет видно распределение цветов по градиенту. Также, я немного изменил координаты линии, чтобы градиент стал побольше и чисто горизонтальным.
В массиве позиций передаем три позиции (соответственно количеству цветов). Чтобы понять, что означают эти позиции, давайте примем за единицу размер градиента (в нашем случае это ширина). А позиции будут обозначать в каком месте градиента будет максимальное насыщение соответствующим цветом.
В нашем примере три цвета: красный синий, зеленый. И три позиции: 0, 0.5, 1. Т.е. красный будет располагаться в начале градиента (0), синий – в середине (0.5), а зеленый – в конце (1). Пространство между цветами будет заполнено соответствующим градиентом.
Немного изменим позиции и сместим синий правее, тем самым увеличивая зону красно-синего градиента и уменьшая зону сине-зеленого.
private Shader createShader() { LinearGradient shader = new LinearGradient(120, 0, 380, 0, new int[] { Color.RED, Color.BLUE, Color.GREEN }, new float[] { 0f, 0.7f, 1f }, Shader.TileMode.REPEAT); return shader; }
Для синего позиция стала равной 0.7 и он ушел правее.
Для класса RadialGradient указываем центр и радиус и градиент пойдет от центра к краям. А SweepGradient – градиент, который идет по кругу вокруг центра. У этих двух классов, аналогично LinearGradient, есть простой конструктор, где можно указать два цвета, а есть посложнее - с возможностью указания массива цветов и их соотношений.
Примеры использования xml-вариантов RadialGradient и SweepGradient можно посмотреть в уроке 162.
Присоединяйтесь к нам в Telegram:
- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня