На этом уроке:
- создаем Drawable
В прошлых уроках мы рассмотрели несколько системных Drawable, теперь предлагаю вам научиться создавать их самим. Drawable – это абстрактный класс, и его наследникам необходимо реализовать 4 следующих метода:
1) public abstract void draw(Canvas canvas) – это, пожалуй, самый главный метод, т.к. здесь нам дается канва и нам необходимо на ней нарисовать то, что должен отображать наш кастомный Drawable
2) public abstract int getOpacity() – насколько я понял хелп, в этом методе нам следует вернуть значение прозрачности нашего Drawable. Всего есть 4 константы:
UNKNOWN – прозрачность неизвестна
TRANSPARENT – Drawable будет полностью прозрачным
TRANSLUCENT – Drawable будет состоять из прозрачных и непрозрачных участков
OPAQUE – Drawable будет полностью непрозрачным
Т.е. если кто-то, например, надумает из вашего Drawable сделать bitmap, то он может методом getOpacity запросить прозрачность и, если вы вернете константу OPAQUE, то это будет означать, что можно использовать конфиг RGB_565 вместо ARGB_8888, т.е. не тратить биты памяти на прозрачность. (Подробнее про конфиги, биты и память читайте в уроке 157).
3) public abstract void setAlpha (int alpha) – тут нам дают значение прозрачности и нам надо каким то образом применить это к итоговому изображению
4) public abstract void setColorFilter (ColorFilter colorFilter) – аналогичен предыдущему, только на вход идет не альфа, а ColorFilter.
Шестиугольник
Давайте создадим свой Drawable и на этом примере станет понятней как реализовать вышеописанные 4 метода. Для примера создадим Drawable, который будет выводить шестиугольник. Класс назовем HexagonDrawable и не забываем указать, что он наследник класса Drawable:
import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; public class HexagonDrawable extends Drawable { private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Path mPath = new Path(); @Override public void draw(Canvas canvas) { canvas.drawPath(mPath, mPaint); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } @Override public void setAlpha(int alpha) { mPaint.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter colorFilter) { mPaint.setColorFilter(colorFilter); } @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); int width = bounds.width(); int height = bounds.height(); mPath.reset(); mPath.moveTo(0, height/2); mPath.lineTo(width/4, 0); mPath.lineTo(width*3/4, 0); mPath.lineTo(width, height/2); mPath.lineTo(width*3/4, height); mPath.lineTo(width/4, height); mPath.close(); } }
В методе draw просто выводим mPath (который будет сформирован в другом методе) на канву, используя кисть mPaint.
В методе getOpacity возвращаем TRANSLUCENT, т.к. у нас будет непрозрачный шестиугольник, а оставшееся пространство Drawable будет прозрачным. Хелп, кстати, рекомендует использовать именно TRANSLUCENT, если точно не знаете, что указать.
Методы setAlpha и setColorFilter я просто переадресую кисти mPaint. Тут все просто, т.к. кисть у меня всего одна, и она при рисовании сама учтет переданные ей эти данные.
Вообще, методы setAlpha и setColorFilter можно не реализовывать и оставить пустыми, ваш Drawable будет работать и без них. Но если вдруг кто-то (или вы сами) будет использовать ваш Drawable и захочет сделать его полупрозрачным или применить ColorFilter, то он не получит ожидаемого результата, т.к. методы не реализованы.
Кроме 4 обязательных методов пришлось еще реализовать метод onBoundsChange. Этот метод вызывается когда меняется размер Drawable. А т.к. нам нужно нарисовать 6-тиугольник размером с Drawable, мы должны знать его размер. Здесь мы получаем ширину и высоту Drawable и используем их для создания path-фигуры 6-тиугольника.
Осталось в layout повесить View, задать ему размер, например 200x200 dp и в коде задать ему наш HexagonDrawable в качестве background
View view = findViewById(R.id.view); Drawable dr = new HexagonDrawable(); view.setBackgroundDrawable(dr);
Результат:
Мы создали Drawable, который просто рисует 6-иугольник дефолтным черным цветом. Но это выглядит немного скучновато, поэтому давайте «добавим красок» и реализуем возможность указания любого цвета для заливки шестиугольника.
Цветной шестиугольник
Функционал уже созданного класса мы менять не будем, пусть он так и отображает черный шестиугольник. Мы создадим новый класс, который будет наследником HexagonDrawable.
Но сначала в класс HexagonDrawable все же придется добавить такой метод
protected Paint getPaint() { return mPaint; }
Он позволит наследникам HexagonDrawable получать доступ к кисти, которая рисует шестиугольник. И, соответственно, меняя параметры кисти мы будем получать изменения рисунка.
Теперь создаем класс ColorHexagonDrawable:
public class ColorHexagonDrawable extends HexagonDrawable { public ColorHexagonDrawable(int color) { getPaint().setColor(color); } }
Он наследует класс HexagonDrawable, который мы создали ранее, т.е. он тоже будет рисовать 6-тиугольник, но в конструкторе мы добавили возможность указания цвета. Используя добавленный в HexagonDrawable метод getPaint мы получаем кисть и задаем ей требуемый цвет.
Создаем объект ColorHexagonDrawable с указанием зеленого цвета, и давайте заодно проверим, работает ли прозрачность:
View view = findViewById(R.id.view); Drawable dr = new ColorHexagonDrawable(Color.GREEN); dr.setAlpha(50); view.setBackgroundDrawable(dr);
Результат:
Мы указали зеленый цвет и значение прозрачности. Оба этих значения были переданы кисти, что мы и видим в итоге на экране - зеленый полупрозрачный шестиугольник.
Таким образом у нас теперь есть целых два собственных Drawable: один рисует черный шестиугольник, а второй еще и позволяет указать цвет. Сделаем третий, который возьмет Bitmap и сделает из него шестиугольник.
Шестиугольник из картинки
Создаем класс BitmapHexagonDrawable, наследуя HexagonDrawable
import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Rect; import android.graphics.Shader; public class BitmapHexagonDrawable extends HexagonDrawable { Bitmap mOriginBitmap; public BitmapHexagonDrawable(Bitmap bitmap) { mOriginBitmap = bitmap; } @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); Bitmap bitmap = Bitmap.createScaledBitmap(mOriginBitmap, bounds.width(), bounds.height(), true); BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); getPaint().setShader(shader); } }
В конструктор передаем Bitmap, а в onBoundsChange берем размеры Drawable, создаем Bitmap этого же размера, создаем на его основе шейдер и передаем его в кисть. Про шейдеры подробнее можно почитать в Уроке 165.
Для примера я возьму вот эту картинку
Кладем ее в папку res под именем picture.png. Теперь создаем с нее Bitmap и передаем его в конструктор BitmapHexagonDrawable.
View view = findViewById(R.id.view); Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.picture); Drawable dr = new BitmapHexagonDrawable(bitmap); view.setBackgroundDrawable(dr);
Результат:
Картинка стала шестиугольной формы.
Присоединяйтесь к нам в Telegram:
- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня