Canvas drawbitmap android kotlin

Работаем с графикой. Основы

Цель нашего урока — понять основы графики. Мы напишем простую рисовалку — хотя это слишком громко сказано. Пока мы сами рисовать ничего не будем — за нас это будет делать глупая машина, т.е. Android. Но тем не менее некоторые полезные вещи мы узнаем, а значит повысим свой профессиональный уровень. Продолжить своё обучение можно в разделе Котошоп.

Создадим новый проект SimplePaint. Создадим новый класс Draw2D, который будет наследоваться от View. Именно в этом классе мы и будем проводить графические опыты. Щёлкаем правой кнопкой мыши на имени пакета и выбираем в меню New | Kotlin Class/File или New | Java Class. В открывшемся диалоговом окне устанавливаем имя для класса Draw2D.

 // Kotlin package ru.alexanderklimov.simplepaint import android.content.Context import android.graphics.Canvas import android.view.View class Draw2D(context: Context?) : View(context) < override fun onDraw(canvas: Canvas?) < super.onDraw(canvas) >> 
// Java package ru.alexanderklimov.simplepaint; public class Draw2D extends View < public Draw2D(Context context) < super(context); >@Override protected void onDraw(Canvas canvas) < super.onDraw(canvas); >>

В данном коде мы наследуемся от класса android.view.View и переопределяем метод класса onDraw().

Далее необходимо загрузить созданный класс при старте программы. Открываем основной файл активности MainActivity и заменяем строчку после super.onCreate(savedInstanceState):

 // Kotlin setContentView(R.layout.activity_main) val draw2D = Draw2D(this) setContentView(draw2D) 
// Java // эта строчка нам не нужна setContentView(R.layout.activity_main); Draw2D draw2D = new Draw2D(this); setContentView(draw2D);

В нашем случае мы говорим системе, что не нужно загружать разметку в экран активности. Вместо неё мы загрузим свой класс, у которого есть свой холст для рисования.

Читайте также:  Css высота ширина div

Подготовительные работы закончены. Перейдём к графике. Весь дальнейший код мы будем писать в классе Draw2D. Совсем коротко о теории рисования. Для графики используется холст Canvas — некая графическая поверхность для рисования. Прежде чем что-то рисовать, нужно определить некоторые параметры — цвет, толщина, фигура. Представьте себе, что вы рисуете на бумаге и в вашем распоряжении есть цветные карандаши, фломастеры, кисть, циркуль, ластик и т.п. Например, вы берёте толстый красный фломастер и рисуете жирную линию, затем берёте циркуль с жёлтым карандашом и рисуете окружность. Улавливаете аналогию? Теория закончена.

Вся работа с графикой происходит в методе onDraw() класса Draw2D. Создадим виртуальную кисть в классе. В методе укажем, что будем закрашивать всю поверхность белым цветом:

 // Kotlin private val paint: Paint = Paint() override fun onDraw(canvas: Canvas?) < super.onDraw(canvas) paint.apply < style = Paint.Style.FILL // стиль Заливка color = Color.WHITE // закрашиваем холст белым цветом >canvas?.drawPaint(paint) > 
// Java private Paint mPaint = new Paint(); @Override protected void onDraw(Canvas canvas) < super.onDraw(canvas); // стиль Заливка mPaint.setStyle(Paint.Style.FILL); // закрашиваем холст белым цветом mPaint.setColor(Color.WHITE); canvas.drawPaint(mPaint); >

Итак, холст готов. Далее начинается собственно рисование. Следуя описанному выше принципу мы задаём перед каждым рисованием свои настройки и вызываем нужный метод. Например, для того, чтобы нарисовать жёлтый, круг мы включаем режим сглаживания, устанавливаем жёлтый цвет и вызываем метод drawCircle() с нужными координатами и заливаем окружность выбранным цветом. Получилось симпатичное солнышко.

 // Kotlin paint.apply < isAntiAlias = true color = Color.YELLOW >canvas?.drawCircle(950F, 30F, 25F, paint) 
// Java // Рисуем жёлтый круг mPaint.setAntiAlias(true); mPaint.setColor(Color.YELLOW); canvas.drawCircle(950, 30, 25, mPaint);

Всегда соблюдайте очерёдность рисования. Если вы поместите данный код до заливки холста белым цветом, то ничего не увидите. У вас получится, что вы сначала нарисовали на стене солнце, а потом заклеили рисунок обоями.

Для рисования зелёного прямоугольника мы также задаём координаты и цвет. У нас получится красивая лужайка.

 // Kotlin paint.color = Color.GREEN canvas?.drawRect(20F, 650F, 950F, 680F, paint) 
// Java mPaint.setColor(Color.GREEN); canvas.drawRect(20, 650, 950, 680, mPaint);

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

 // Kotlin paint.apply < color = Color.BLUE style = Paint.Style.FILL isAntiAlias = true textSize = 32F >canvas?.drawText("Лужайка только для котов", 30F, 648F, paint) 
// Рисуем текст mPaint.setColor(Color.BLUE); mPaint.setStyle(Paint.Style.FILL); mPaint.setAntiAlias(true); mPaint.setTextSize(32); canvas.drawText("Лужайка только для котов", 30, 648, mPaint);

При желании можно вывести текст под углом. Пусть это будет лучик солнца.

 // Kotlin private val rect: Rect = Rect() val x = 810F val y = 190F paint.apply < color = Color.GRAY style = Paint.Style.FILL textSize = 27F >val str2rotate = "Лучик солнца!" canvas?.save() canvas?.rotate(-45F, x + rect.exactCenterX(), y + rect.exactCenterY()) canvas?.drawText(str2rotate, x, y, paint) canvas?.restore() 
// Java // до метода onDraw() private Rect mRect = new Rect(); // Текст под углом int x = 810; int y = 190; mPaint.setColor(Color.GRAY); mPaint.setTextSize(27); String str2rotate = "Лучик солнца!"; canvas.save(); // Создаём ограничивающий прямоугольник для наклонного текста // поворачиваем холст по центру текста canvas.rotate(-45, x + mRect.exactCenterX(), y + mRect.exactCenterY()); // Рисуем текст mPaint.setStyle(Paint.Style.FILL); canvas.drawText(str2rotate, x, y, mPaint); // восстанавливаем холст canvas.restore();

И завершим нашу композицию выводом рисунка из ресурсов.

 // Выводим изображение canvas.drawBitmap(mBitmap, 450, 530, mPaint); 

В данном примере я вручную подбирал размеры и координаты фигур для экрана свого телефона. В реальных приложениях необходимо сначала вычислить размеры экрана у пользователя, а потом уже выводить фигуры в соответствии с полученными результатами. Иначе получится так, что некоторые элементы композиции просто не попадут на экран при вращении устройства. Допустим, в альбомном режиме вы установите у точки X значение 800, но в портретном режиме ширина экрана будет, скажем, 480, и точка окажется вне поле зрения. Поэтому следует позаботиться о вычислениях размеров экрана и плясать от этой печки. Ниже представлен немного переделанный вариант для общего понимания.

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

Основы графики

Основы графики

Лужайка только для котов

Исходный код класса

 // Kotlin package ru.alexanderklimov.simplepaint import android.content.Context import android.content.res.Resources import android.graphics.* import android.view.View import android.graphics.BitmapFactory class Draw2D(context: Context?) : View(context) < private val paint: Paint = Paint() private val rect: Rect = Rect() val res: Resources = this.resources private var bitmap: Bitmap = BitmapFactory.decodeResource(res, R.drawable.cat) override fun onDraw(canvas: Canvas?) < super.onDraw(canvas) paint.apply < style = Paint.Style.FILL // стиль Заливка color = Color.WHITE // закрашиваем холст белым цветом >canvas?.drawPaint(paint) // Солнце paint.apply < isAntiAlias = true color = Color.YELLOW >canvas?.drawCircle(width - 30F, 30F, 25F, paint) // Лужайка paint.color = Color.GREEN canvas?.drawRect(0F, height - 30F, width.toFloat(), height.toFloat(), paint) // Текст над лужайкой paint.apply < color = Color.BLUE style = Paint.Style.FILL isAntiAlias = true textSize = 32F >canvas?.drawText("Лужайка только для котов", 30F, height - 32F, paint) // Лучик солнца val x = width - 170F val y = 190F paint.apply < color = Color.GRAY style = Paint.Style.FILL textSize = 27F >val beam = "Лучик солнца!" canvas?.save() canvas?.rotate(-45F, x + rect.exactCenterX(), y + rect.exactCenterY()) canvas?.drawText(beam, x, y, paint) canvas?.restore() canvas?.drawBitmap( bitmap, (width - bitmap.width).toFloat(), (height - bitmap.height - 10).toFloat(), paint ) > > 
//Java // Если этот код работает, его написал Александр Климов, // а если нет, то не знаю, кто его писал. package ru.alexanderklimov.simplepaint; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.view.View; public class Draw2D extends View < private Paint mPaint = new Paint(); private Rect mRect = new Rect(); private Bitmap mBitmap; public Draw2D(Context context) < super(context); // Выводим значок из ресурсов Resources res = this.getResources(); mBitmap = BitmapFactory.decodeResource(res, R.drawable.cat_bottom); >@Override protected void onDraw(Canvas canvas) < super.onDraw(canvas); int width = canvas.getWidth(); int height = canvas.getHeight(); // стиль Заливка mPaint.setStyle(Paint.Style.FILL); // закрашиваем холст белым цветом mPaint.setColor(Color.WHITE); canvas.drawPaint(mPaint); // Рисуем жёлтый круг mPaint.setAntiAlias(true); mPaint.setColor(Color.YELLOW); // canvas.drawCircle(950, 30, 25, mPaint); canvas.drawCircle(width - 30, 30, 25, mPaint); // Рисуем зелёный прямоугольник mPaint.setColor(Color.GREEN); // canvas.drawRect(20, 650, 950, 680, mPaint); canvas.drawRect(0, canvas.getHeight() - 30, width, height, mPaint); // Рисуем текст mPaint.setColor(Color.BLUE); mPaint.setStyle(Paint.Style.FILL); mPaint.setAntiAlias(true); mPaint.setTextSize(32); // canvas.drawText("Лужайка только для котов", 30, 648, mPaint); canvas.drawText("Лужайка только для котов", 30, height - 32, mPaint); // Текст под углом // int x = 810; int x = width - 170; int y = 190; mPaint.setColor(Color.GRAY); mPaint.setTextSize(27); String beam = "Лучик солнца!"; canvas.save(); // Создаём ограничивающий прямоугольник для наклонного текста // поворачиваем холст по центру текста canvas.rotate(-45, x + mRect.exactCenterX(), y + mRect.exactCenterY()); // Рисуем текст mPaint.setStyle(Paint.Style.FILL); canvas.drawText(beam, x, y, mPaint); // восстанавливаем холст canvas.restore(); // Выводим изображение // canvas.drawBitmap(mBitmap, 450, 530, mPaint); canvas.drawBitmap(mBitmap, width - mBitmap.getWidth(), height - mBitmap.getHeight() - 10, mPaint); >>

Источник

Некоторые фишки в Android разработке

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

Надеюсь, что моя статейка окажется полезной и ее не закидают плохими словами злые программисты.

Да именно злые, зло плодится, а вы не знали? Шучу конечно 🙂

Получение цвета темы

Недавно я писал свою кастомную вьюху и мне нужно было установить цвет по умолчанию primaryColor и поэтому я создал небольшую Kotlin функцию, которая получает его из темы приложения:

// Kotlin расширение для получения цвета fun Context.themeColor(@AttrRes attrRes: Int): Int < val typedValue = TypedValue() theme.resolveAttribute (attrRes, typedValue, true) return typedValue.data >// получаем primaryColor из темы прилы context.themeColor(android.R.attr.colorPrimary)

Перевести dp в пиксели

Иногда нам нужно прописать оступ в коде и чтобы мы могли использовать независимые от плотности пиксели я довольно часто пишу следующее Kotlin расширения для своих кастомных вьюшках:

// Kotlin расширение, определенное внутри кастомной вьюшки private fun Int.dp() = (context.resources.displayMetrics.density * this) .toInt() // также можно реализовать через get private fun Int.dp get() = (context.resources.displayMetrics.density * this).toInt() // устанавливаем padding в 10 dp setPadding(10.dp(), 10.dp(), 10.dp(), 10.dp()) 

Создание круглого изображения

С этой проблемой я сталкивался ни один раз и поэтому решений довольно много, но я решил показать одно из самых неочевидных по моему мнению, создание кастомной вьюшки:

// отображает круглое изображение class RoundedImageView @JvmOverloads constructor( ctx: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : AppCompatImageView(ctx, attrs, defStyleAttr) < private val Int.dp get() = context.resources.displayMetrics.density * this private val roundedBitmapPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply < color = 0xff424242.toInt() >override fun onDraw(canvas: Canvas) < // получаем наш bitmap val drawable = drawable ?: return if (width == 0 || height == 0) < return >val bitmap = (drawable as BitmapDrawable).bitmap // делаем его круглым val roundedBitmap = rounded(bitmap, 100.dp) // и отрисовываем canvas.drawBitmap(roundedBitmap, 0f, 0f, null) > // cоздаем круглую bitmap'у private fun rounded(bitmap: Bitmap, radius: Float) : Bitmap < val result = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888) val canvas = Canvas(result) canvas.drawARGB(0, 0, 0, 0) val rectF = RectF(0f, 0f, bitmap.width / 1f, bitmap.height / 1f) canvas.drawRoundRect(rectF, radius, radius, roundedBitmapPaint) roundedBitmapPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN) val rect = Rect(0, 0, bitmap.width, bitmap.height) canvas.drawBitmap(bitmap, rect, rect, roundedBitmapPaint) return result >>

Анимация в кастомных вьюшках

В основном, я использую ValueAnimator :

 override fun onTouchEvent(event: MotionEvent): Boolean < return when (event.action) < MotionEvent.ACTION_DOWN -> < // запускаем анимацию, когда произошло прикосновение к экрану val animator = ValueAnimator.ofFloat(0f, width.toFloat()) animator.addUpdateListener < // обновляем ширину и перерисовываем View animWidth = it.animatedValue as Float invalidate() >// задержка анимации animator.duration = 400L animator.doOnEnd < // возвращает к исходному состоянию animWidth = 0f invalidate() >animator.start() true > MotionEvent.ACTION_UP -> < listener.firstOrNull()?.invoke() true >else -> super.onTouchEvent(event) > > override fun dispatchDraw(canvas: Canvas) < canvas.drawRect(8f, 8f, width - 8f, height - 8f, borderPaint) // значение animWidth меняется с каждым кадром анимации canvas.drawRect(0f, 0f, animWidth, height.toFloat(), bgPaint) super.dispatchDraw(canvas) >

Здесь я привел простенький пример без отмены предыдущей анимации.

Небольшая оберточка для SharedPreferences

В одном из приложений для простоты использования я реализовал простенькую обертку вокруг SharedPreferences и до сих пор ее юзаю:

// сохранение и чтение Int значений interface IntPrefs < fun saveInt(key: String, value: Int) fun int(key: String, default: Int) : Int >// сохранение и чтение String значений interface StringPrefs < fun saveStr(key: String, value: String) fun str(key: String, default: String) : String >// обертка вокруг SharedPreferences class LocalPrefsDataSource(ctx: Context) : IntPrefs, StringPrefs < private val prefsName = "app_prefs" private val sharedPrefs = ctx.getSharedPreferences(prefsName, Context.MODE_PRIVATE) override fun saveInt(key: String, value: Int) < sharedPrefs.edit().putInt(key, value).apply() >override fun saveStr(key: String, value: String) < sharedPrefs.edit().putString(key, value).apply() >override fun str(key: String, default: String) : String < return sharedPrefs.getString(key, default) ?: default >override fun int(key: String, default: Int) : Int < return sharedPrefs.getInt(key, default) >>

Добавление навигации по кнопки назад в WebView

Если вы юзали WebView то вы знаете, что по умолчанию по нажатию на кнопку назад мы просто выйдем из приложения (если конечно backstack состоит только из одной активити или одного фрагмента).

Для организации навигации в WebView есть простое решение:

class MainActivity : AppCompatActivity() < private lateinit var webView: WebView override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) webView = findViewById(R.id.ali_express_web_view) val progressPageLoading = findViewById(R.id.progress_page_loading) webView.webViewClient = object: WebViewClient() < override fun onPageFinished(view: WebView?, url: String?) < super.onPageFinished(view, url) progressPageLoading.visibility = View.GONE >> webView.settings.javaScriptEnabled = true // грузим наш любимый AliExpress :) webView.loadUrl("https://best.aliexpress.ru/") > // переопределяем onKeyDown для корректной навигации по сайтам в WebView override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean < if (event?.action == KeyEvent.ACTION_DOWN) < if (keyCode == KeyEvent.KEYCODE_BACK) < if (webView.canGoBack()) < webView.goBack() >else < finish() >return true > > return super.onKeyDown(keyCode, event) > >

Создание кастомного фрагмента для Google Maps

В документации по Google Maps рекомендуется использовать предопределенный фрагмент SupportMapFragment .

Но бывают ситуации, когда нам нужно кастомизировать View фрагмента, добавить поверх карты какие-либо элементы и поэтому нужно реализовать свой GoogleMapFragment :

class GoogleMapFragment : Fragment() < private var mapView : MapView? = null private var googleMap: GoogleMap? = null override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View < val binding = GoogleMapFragmentBinding.inflate(inflater, container, false) this.mapView = binding.mapView // MapView имеет методы жизненного цикла которые нужно вызывать binding.mapView.onCreate(savedInstanceState) binding.mapView.getMapAsync < googleMap ->// можем юзать Google Maps API this.googleMap = googleMap > return binding.root > override fun onStart() < super.onStart() mapView?.onStart() >override fun onResume() < super.onResume() mapView?.onResume() >override fun onPause() < super.onPause() mapView?.onPause() >override fun onStop() < super.onStop() mapView?.onStop() >override fun onDestroy() < super.onDestroy() mapView?.onDestroy() >override fun onSaveInstanceState(outState: Bundle) < super.onSaveInstanceState(outState) mapView?.onSaveInstanceState(outState) >override fun onLowMemory() < super.onLowMemory() mapView?.onLowMemory() >> 

Разметка нашего фрагмента ( google_map_fragment.layout ):

Заключение

Ну и напоследок я хотел бы поделиться некоторыми моими репозиторчиками:

  • Kotlin-Algorithms-and-Design-Patterns — я почти каждый день добавляю новые алгоритмы, структуры данных и паттерны проектирования.
  • LearningApps — репозиторий с мини приложениями, в каждом из которых проработана одна или несколько тем по Android разработке.

Как сказал, один из индийских разработчиков, изучайте и делитесь знаниями.

Источник

Оцените статью